Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/pages/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
HERE = pathlib.Path(__file__).resolve().parent

REPO_URL = "https://github.com/barkz/glean-code-cli"
# What the browser tab says. Deliberately the command you type, not the prose
# title -- the hidden <h1> below still carries the product name.
SITE_TITLE = "glean_code_cli"
BLOB_URL = REPO_URL + "/blob/main/"

# Directories copied next to index.html so relative image paths keep working.
Expand Down Expand Up @@ -286,14 +289,14 @@ def build(out_dir=None, root=None):
markdown = (root / "README.md").read_text(encoding="utf-8")
template = (HERE / "template.html").read_text(encoding="utf-8")

title = page_title(markdown)
heading = page_title(markdown)
content = render(markdown)
if "<h1" not in content:
# The README leads with the wordmark image; keep a real heading for
# screen readers and search engines.
content = '<h1 class="sr-only">%s</h1>\n\n%s' % (title, content)
content = '<h1 class="sr-only">%s</h1>\n\n%s' % (heading, content)

page = template.replace("{{TITLE}}", title)
page = template.replace("{{TITLE}}", SITE_TITLE)
page = page.replace("{{REPO_URL}}", REPO_URL)
page = page.replace("{{CONTENT}}", content)

Expand Down
113 changes: 113 additions & 0 deletions .github/pages/make_icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Generate the site's browser icons: a shell prompt in the wordmark's cyan.

An SVG favicon covers every current browser and stays sharp at any size.
iOS home-screen icons must be raster, so a 180x180 PNG is written too --
encoded here with zlib and struct rather than Pillow, keeping the repo's
zero-dependency rule. iOS rounds and masks the corners itself, so the PNG is
drawn square while the SVG carries its own rounded plate.

Regenerate after changing the artwork:
python3 .github/pages/make_icons.py

tests/test_pages_build.py fails if the committed icons have drifted.
"""

import pathlib
import struct
import sys
import zlib

ROOT = pathlib.Path(__file__).resolve().parents[2]
SVG_OUT = ROOT / "assets" / "favicon.svg"
PNG_OUT = ROOT / "assets" / "apple-touch-icon.png"

PLATE = (0x1B, 0x1B, 0x23)
MARK = (0x1C, 0xC8, 0xF0)
PNG_SIZE = 180

# Artwork in a 32-unit grid: a ">" chevron and an "_" cursor.
CHEVRON = ((10.0, 10.0), (17.0, 16.0), (10.0, 22.0))
STROKE_HALF = 1.6
CURSOR = (18.6, 20.4, 26.0, 23.0) # x0, y0, x1, y1


def svg():
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"'
' role="img" aria-label="glean_code_cli">\n'
' <rect width="32" height="32" rx="7" fill="#%02x%02x%02x"/>\n' % PLATE
+ ' <path d="M%g %g L%g %g L%g %g" fill="none" stroke="#%02x%02x%02x"'
' stroke-width="%g" stroke-linecap="square" stroke-linejoin="miter"/>\n'
% (CHEVRON[0][0], CHEVRON[0][1], CHEVRON[1][0], CHEVRON[1][1],
CHEVRON[2][0], CHEVRON[2][1], MARK[0], MARK[1], MARK[2], STROKE_HALF * 2)
+ ' <rect x="%g" y="%g" width="%g" height="%g" fill="#%02x%02x%02x"/>\n'
% (CURSOR[0], CURSOR[1], CURSOR[2] - CURSOR[0], CURSOR[3] - CURSOR[1],
MARK[0], MARK[1], MARK[2])
+ "</svg>\n"
)


def _distance_to_segment(px, py, ax, ay, bx, by):
dx, dy = bx - ax, by - ay
span = dx * dx + dy * dy
t = 0.0 if span == 0 else ((px - ax) * dx + (py - ay) * dy) / span
t = max(0.0, min(1.0, t))
cx, cy = ax + t * dx, ay + t * dy
return ((px - cx) ** 2 + (py - cy) ** 2) ** 0.5


def _coverage(u, v, edge):
"""How much of the pixel at grid point (u, v) the mark covers, 0..1."""
inside_cursor = (CURSOR[0] <= u <= CURSOR[2]) and (CURSOR[1] <= v <= CURSOR[3])
if inside_cursor:
return 1.0
nearest = min(
_distance_to_segment(u, v, CHEVRON[0][0], CHEVRON[0][1], CHEVRON[1][0], CHEVRON[1][1]),
_distance_to_segment(u, v, CHEVRON[1][0], CHEVRON[1][1], CHEVRON[2][0], CHEVRON[2][1]),
)
# linear ramp across one pixel for a clean edge
return max(0.0, min(1.0, (STROKE_HALF - nearest) / edge + 0.5))


def png_rows(size=PNG_SIZE):
scale = size / 32.0
edge = 1.0 / scale
rows = []
for y in range(size):
v = (y + 0.5) / scale
row = bytearray()
for x in range(size):
u = (x + 0.5) / scale
alpha = _coverage(u, v, edge)
for channel in range(3):
base, mark = PLATE[channel], MARK[channel]
row.append(int(round(base + (mark - base) * alpha)))
rows.append(bytes(row))
return rows


def _chunk(kind, payload):
return (struct.pack(">I", len(payload)) + kind + payload
+ struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF))


def png(size=PNG_SIZE):
raw = b"".join(b"\x00" + row for row in png_rows(size))
return b"".join([
b"\x89PNG\r\n\x1a\n",
_chunk(b"IHDR", struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0)),
_chunk(b"IDAT", zlib.compress(raw, 9)),
_chunk(b"IEND", b""),
])


def main():
SVG_OUT.write_text(svg(), encoding="utf-8")
PNG_OUT.write_bytes(png())
print("wrote %s and %s (%d bytes)" % (SVG_OUT.name, PNG_OUT.name, PNG_OUT.stat().st_size))
return 0


if __name__ == "__main__":
sys.exit(main())
4 changes: 3 additions & 1 deletion .github/pages/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{TITLE}}</title>
<meta name="description" content="A terminal-first client for the Glean REST API. Pure Python, zero runtime dependencies, fully usable offline.">
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;450;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap">
Expand Down Expand Up @@ -251,7 +253,7 @@

<div class="topbar">
<div class="topbar-in">
<span class="mark">glean<i>_</i>code</span>
<span class="mark">glean<i>_</i>code<i>_</i>cli</span>
<nav>
<a href="#how-it-works">How it works</a>
<a href="#documentation">Docs</a>
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ python3 install.py # --cli-only, --dev, --prefix, --verify, --uninstal
# Pipe a single command (non-interactive; cli.py detects a non-tty stdin)
echo '/search "q2 plan"' | python3 -m glean_code

# Run the full test suite (1,094 tests, stdlib unittest — works with or without pytest)
# Run the full test suite (1,100 tests, stdlib unittest — works with or without pytest)
python3 -m pytest tests/
python3 -m unittest discover tests/

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ The full Glean Code REPL — slash commands, status bar, mock/live switching, se
| 🔐 **[SSO / OAuth](docs/SSO_OAUTH.md)** · **[Secure tokens](docs/SECURE_TOKENS.md)** | Browser sign-in, secure refs, the masking matrix |
| 🔌 **[MCP server](docs/MCP.md)** | Glean as native tools in Claude Code, Claude Desktop, Cursor |
| 🏛️ **[Architecture](docs/ARCHITECTURE.md)** · **[REST paths](docs/REST_PATHS.md)** | Module map, request flow, endpoints, how to add a command |
| ✅ **[Testing](docs/TESTING.md)** | Running the 1,094-test suite and what it covers |
| ✅ **[Testing](docs/TESTING.md)** | Running the 1,100-test suite and what it covers |
| 🛟 **[Support](SUPPORT.md)** · **[Changelog](CHANGELOG.md)** | How to report a bug · release history |

> [!NOTE]
Expand Down
Binary file added assets/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ files, and they outrank the `Glean Code.app` launcher in `Cmd+Space`:
export PYTHONPYCACHEPREFIX="$HOME/.cache/python"
```

1,094 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools).
1,100 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools).

## Development notes

Notes on the test suite added during development of glean-code-cli.

All 1,094 tests pass. Here's what was added across the development passes:
All 1,100 tests pass. Here's what was added across the development passes:

`tests/test_commands_extended.py` (155 new tests) — covers all previously untested commands:

Expand Down
55 changes: 54 additions & 1 deletion tests/test_pages_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import importlib.util
import pathlib
import struct
import sys
import tempfile
import unittest
Expand All @@ -26,8 +27,13 @@ def _load(name, path):
return module


ICONS_PY = REPO_ROOT / ".github" / "pages" / "make_icons.py"
FAVICON_SVG = REPO_ROOT / "assets" / "favicon.svg"
TOUCH_ICON = REPO_ROOT / "assets" / "apple-touch-icon.png"

build = _load("pages_build", BUILD_PY)
make_banner = _load("pages_make_banner", BANNER_PY)
make_icons = _load("pages_make_icons", ICONS_PY)


class TestInline(unittest.TestCase):
Expand Down Expand Up @@ -218,7 +224,9 @@ def test_build_writes_a_site(self):
self.assertTrue((out / "assets" / "glean_code_cli_example.png").is_file())

page = index.read_text(encoding="utf-8")
self.assertIn("<title>Glean Code</title>", page)
# the tab shows the command you type; the hidden h1 keeps the product name
self.assertIn("<title>glean_code_cli</title>", page)
self.assertIn('<h1 class="sr-only">Glean Code</h1>', page)
self.assertNotIn("{{CONTENT}}", page)
self.assertNotIn("{{TITLE}}", page)
self.assertNotIn("{{REPO_URL}}", page)
Expand Down Expand Up @@ -260,6 +268,51 @@ def test_no_unresolved_repo_relative_links_remain(self):
self.assertNotIn('href="LICENSE"', page)


class TestIcons(unittest.TestCase):
"""Browser icons are generated by .github/pages/make_icons.py."""

def test_committed_favicon_matches_the_generator(self):
self.assertEqual(
make_icons.svg(),
FAVICON_SVG.read_text(encoding="utf-8"),
"assets/favicon.svg is stale -- run python3 .github/pages/make_icons.py",
)

def test_committed_touch_icon_matches_the_generator(self):
self.assertEqual(
make_icons.png(),
TOUCH_ICON.read_bytes(),
"assets/apple-touch-icon.png is stale -- run python3 .github/pages/make_icons.py",
)

def test_favicon_is_well_formed_svg(self):
ET.fromstring(make_icons.svg())

def test_touch_icon_is_a_180px_png(self):
data = make_icons.png()
self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n")
# IHDR width/height live at bytes 16..24
width, height = struct.unpack(">II", data[16:24])
self.assertEqual((width, height), (180, 180))

def test_the_mark_covers_and_misses_the_right_pixels(self):
rows = make_icons.png_rows(32)
def pixel(x, y):
return tuple(rows[y][x * 3:x * 3 + 3])
# the cursor block is solid cyan; a far corner stays plate
self.assertEqual(pixel(22, 21), make_icons.MARK)
self.assertEqual(pixel(1, 1), make_icons.PLATE)

def test_page_declares_both_icons(self):
with tempfile.TemporaryDirectory() as tmp:
out = build.build(out_dir=pathlib.Path(tmp) / "_site")
page = (out / "index.html").read_text()
self.assertIn('rel="icon" type="image/svg+xml" href="assets/favicon.svg"', page)
self.assertIn('rel="apple-touch-icon" href="assets/apple-touch-icon.png"', page)
self.assertTrue((out / "assets" / "favicon.svg").is_file())
self.assertTrue((out / "assets" / "apple-touch-icon.png").is_file())


class TestBanner(unittest.TestCase):
"""The header image is generated from glean_code.ui.GLEAN_WORDMARK."""

Expand Down
Loading