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
12 changes: 11 additions & 1 deletion .github/pages/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ def is_table_divider(cells):
return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell or "") for cell in cells)


def is_command_table(body):
"""True when every row leads with a code span -- a command/effect listing."""
if not body:
return False
return all(
len(row) >= 2 and row[0].startswith("`") and row[0].endswith("`") and len(row[0]) > 2
for row in body
)


def render_table(rows):
"""GFM table. An all-empty header row is dropped -- the README uses those
purely to get a two-column layout, and a blank <thead> is just a gap."""
Expand All @@ -125,7 +135,7 @@ def render_table(rows):
head, body = rows[0], rows[2:]
if not any(cell for cell in head):
head = None
parts = ['<table>']
parts = ['<table class="cmd">' if is_command_table(body) else "<table>"]
if head:
parts.append("<thead><tr>%s</tr></thead>" % "".join(
"<th>%s</th>" % inline(cell) for cell in head))
Expand Down
12 changes: 12 additions & 0 deletions .github/pages/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,18 @@
.doc td + td { color: var(--ink-2); }
.doc td p { color: var(--ink-2); }
.doc td :last-child { margin-bottom: 0; }
/* command listings: the command needs room, and must wrap rather than scroll */
.doc table.cmd td:first-child { width: 50%; font-weight: 400; }
.doc table.cmd td:first-child code {
background: none; padding: 0; font-size: .8em;
color: var(--accent-ink);
/* break between words only: hyphens are line-break opportunities in
Unicode, which would split "--object-type" after the dashes.
break-word stays as the fallback for a token wider than the column. */
word-break: keep-all; overflow-wrap: break-word;
}
.doc table.cmd td + td { color: var(--ink-2); }

/* the README's two-column feature table is layout, not data */
.doc td[width="50%"] { width: 50%; font-weight: 400; padding: 20px 22px; }
.doc td[width="50%"] p { color: var(--ink-2); max-width: 46ch; }
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,090 tests, stdlib unittest — works with or without pytest)
# Run the full test suite (1,094 tests, stdlib unittest — works with or without pytest)
python3 -m pytest tests/
python3 -m unittest discover tests/

Expand Down
44 changes: 21 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,31 @@ python3 install.py && glean # installed, with a macOS Spotlight app
```

```text
/login acme # browser SSO — or /login --token <bearer_token>
/login acme
/search "quarterly planning"
/chat "summarise the Q2 plan"
```

No login? You're still up and running — mock mode serves ranked results from a real corpus.
`/login acme` opens browser SSO; `/login --token <bearer_token>` takes a Glean-issued token instead. No login at all? You're still up and running — mock mode serves ranked results from a real corpus.

**→ [Install guide](docs/INSTALL.md)** · **[Configuration](docs/CONFIGURATION.md)** · **[Command index](docs/COMMAND_INDEX.md)**

<br>

## What you can do

```text
?login into acme and search for "Q2 plan" ← natural language, planned + confirmed
/search "checkout incident" --datasource jira ← search every connected source
/chat "what changed in the pricing doc?" ← threaded Glean Assistant chat
/agents.run <agent-id> "draft the release notes" ← run agents, call tools
/insights --all --export insights.csv ← usage metrics straight to CSV
/debug.user gdrive alice@example.com ← why can't Alice see that doc?
/index.bulk-documents --path ./docs/ --datasource custom1 --object-type Article --dry-run
← index a folder, inspect the payload first
/metadata.attach --doc-id <id> --group tickets --values owner=alice
← enrich docs without re-uploading
/personal index ~/Documents --label docs ← index your own files, locally
/flow show ← draw the investigations you ran
```
| Command | What it does |
| --- | --- |
| `?login into acme and search for "Q2 plan"` | Natural language, planned and confirmed before anything runs |
| `/search "checkout incident" --datasource jira` | Search every connected source |
| `/chat "what changed in the pricing doc?"` | Threaded Glean Assistant chat |
| `/agents.run <agent-id> "draft the release notes"` | Run agents, call tools |
| `/insights --all --export insights.csv` | Usage metrics straight to CSV |
| `/debug.user gdrive alice@example.com` | Why can't Alice see that doc? |
| `/index.bulk-documents --path ./docs/ --datasource custom1 --object-type Article --dry-run` | Index a folder, inspecting the payload first |
| `/metadata.attach --doc-id <id> --group tickets --values owner=alice` | Enrich docs without re-uploading them |
| `/personal index ~/Documents --label docs` | Index your own files, locally |
| `/flow show` | Draw the investigations you ran |

<table>
<tr><td width="50%" valign="top">
Expand Down Expand Up @@ -138,12 +136,12 @@ Sessions run down a rail in the order you worked; each connection branches off i

Mock mode proves every command works offline against a fictional corpus. `/personal` points the same machinery at content that is actually yours.

```text
/personal index ~/Documents --label docs
/personal search "salary bands" --explain # which terms hit, which missed, and the bm25 score
/personal link && /personal related roadmap # a phrase graph, with shared phrases as evidence
/mode local # /search and /chat now answer from your files
```
| Command | What it does |
| --- | --- |
| `/personal index ~/Documents --label docs` | Build the index from a folder |
| `/personal search "salary bands" --explain` | Which terms hit, which missed, and the bm25 score |
| `/personal link && /personal related roadmap` | A phrase graph, with shared phrases as evidence |
| `/mode local` | `/search` and `/chat` now answer from your files |

SQLite FTS5, incremental on a content hash, `.docx`/`.xlsx`/`.pptx` read straight out of their ZIP-XML with the stdlib. **No server, no daemon, no Docker, no network, no credentials** — the whole index is one file you can copy between machines. Answers are labelled `[LOCAL INDEX]` and quote your passages verbatim; the REPL has no model in-process and will not invent prose. Four MCP tools expose the same index to an agent that does.

Expand Down Expand Up @@ -178,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,090-test suite and what it covers |
| ✅ **[Testing](docs/TESTING.md)** | Running the 1,094-test suite and what it covers |
| 🛟 **[Support](SUPPORT.md)** · **[Changelog](CHANGELOG.md)** | How to report a bug · release history |

> [!NOTE]
Expand Down
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,090 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,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).

## Development notes

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

All 1,090 tests pass. Here's what was added across the development passes:
All 1,094 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
31 changes: 31 additions & 0 deletions tests/test_pages_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ def test_table_with_a_real_header_keeps_it(self):
self.assertIn("<thead><tr><th>Flag</th><th>Effect</th></tr></thead>", out)
self.assertIn("<code>--dev</code>", out)

def test_command_table_is_tagged(self):
out = build.render(
"| Command | What it does |\n| --- | --- |\n"
"| `/search \"x\"` | Search |\n| `/flow show` | Draw |")
self.assertIn('<table class="cmd">', out)

def test_a_prose_table_is_not_a_command_table(self):
out = build.render("| | |\n| --- | --- |\n| ⚡ **Fast** | it is quick |")
self.assertIn("<table>", out)
self.assertNotIn('class="cmd"', out)

def test_command_table_needs_every_row_to_lead_with_code(self):
body = [["`/search`", "Search"], ["plain text", "Nope"]]
self.assertFalse(build.is_command_table(body))
self.assertTrue(build.is_command_table([["`/a`", "x"], ["`/b`", "y"]]))

def test_divider_detection(self):
self.assertTrue(build.is_table_divider(["---", ":---:"]))
self.assertFalse(build.is_table_divider(["Flag", "Effect"]))
Expand Down Expand Up @@ -222,6 +238,21 @@ def test_build_replaces_a_previous_output_dir(self):
build.build(out_dir=out_dir)
self.assertFalse(stale.exists())

def test_no_code_block_is_wide_enough_to_overflow(self):
"""Long space-aligned listings used to force a horizontal scroll inside
the card. Command listings belong in tables, which wrap; code blocks
stay narrow enough to fit the content column."""
import html as html_mod
import re as re_mod

with tempfile.TemporaryDirectory() as tmp:
page = (build.build(out_dir=pathlib.Path(tmp) / "_site") / "index.html").read_text()
widest = 0
for block in re_mod.findall(r"<pre><code[^>]*>(.*?)</code></pre>", page, re_mod.S):
for line in html_mod.unescape(block).split("\n"):
widest = max(widest, len(line))
self.assertLessEqual(widest, 80, "a code block is %d chars wide" % widest)

def test_no_unresolved_repo_relative_links_remain(self):
with tempfile.TemporaryDirectory() as tmp:
page = (build.build(out_dir=pathlib.Path(tmp) / "_site") / "index.html").read_text()
Expand Down
Loading