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
23 changes: 22 additions & 1 deletion .github/workflows/tests.yml → .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: tests
name: release

on:
push:
Expand Down Expand Up @@ -54,3 +54,24 @@ jobs:
if [ -n "$(git status --porcelain)" ]; then
echo "::error::working tree dirty after build"; git status --short; exit 1
fi

# One leg only — actions/upload-artifact@v4 rejects a second upload under
# the same name, and the zipapp is identical across the matrix anyway.
- name: Build the downloadable zipapp
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
env:
PYTHONPYCACHEPREFIX: ${{ runner.temp }}/pycache
run: |
python -c "
import pathlib, install
out = install.build_zipapp(pathlib.Path('$RUNNER_TEMP/glean-code.pyz'))
print(f'built {out} ({out.stat().st_size / 1024:.0f} KB)')
"

- name: Upload glean-code.pyz
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
uses: actions/upload-artifact@v4
with:
name: glean-code-pyz
path: ${{ runner.temp }}/glean-code.pyz
if-no-files-found: error
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![Glean](https://img.shields.io/badge/Glean-343CED?style=for-the-badge&logoColor=white)
![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white)
![Claude](https://img.shields.io/badge/Claude-D97757?style=for-the-badge&logo=claude&logoColor=white)
[![Tests](https://img.shields.io/github/actions/workflow/status/barkz/glean-code-cli/tests.yml?style=for-the-badge&label=tests&logo=githubactions&logoColor=white)](https://github.com/barkz/glean-code-cli/actions/workflows/tests.yml)
[![Release](https://img.shields.io/github/actions/workflow/status/barkz/glean-code-cli/release.yml?style=for-the-badge&label=release&logo=githubactions&logoColor=white)](https://github.com/barkz/glean-code-cli/actions/workflows/release.yml)

A local, terminal-first client for the Glean Client REST API. Inspired by Claude Code. Built in Python with zero runtime dependencies.

Expand All @@ -13,7 +13,7 @@ A local, terminal-first client for the Glean Client REST API. Inspired by Claude

- **One-command install, and a Spotlight-launchable macOS app** — `python3 install.py` builds a
single-file zipapp (~84 KB, still zero dependencies) and puts `glean` on your PATH, plus a
registered `Glean.app` so `Cmd+Space` → "Glean" opens the REPL.
registered `Glean Code.app` so `Cmd+Space` → "Glean Code" opens the REPL.
[#11](https://github.com/barkz/glean-code-cli/pull/11) ·
[`e46d517`](https://github.com/barkz/glean-code-cli/commit/e46d517)
- **Every REST call audited against Glean's published OpenAPI spec** — the new
Expand All @@ -22,7 +22,7 @@ A local, terminal-first client for the Glean Client REST API. Inspired by Claude
[#10](https://github.com/barkz/glean-code-cli/pull/10) ·
[`f81902d`](https://github.com/barkz/glean-code-cli/commit/f81902d)
- **Repo hygiene fixes** — removed 35 tracked `__pycache__` artifacts, corrected stale docs
(the suite is 721 tests, not 604), and dropped a Spotlight ignore rule that macOS never
(the documented test count was stale by over a hundred), and dropped a Spotlight ignore rule that macOS never
honored.
[#12](https://github.com/barkz/glean-code-cli/pull/12) ·
[`17199b5`](https://github.com/barkz/glean-code-cli/commit/17199b5)
Expand Down Expand Up @@ -76,9 +76,9 @@ python3 install.py
```

This builds a single-file zipapp (~84 KB, still stdlib-only) and installs it as
`glean` in `~/.local/bin`. On macOS it also creates `~/Applications/Glean.app` and
registers it with LaunchServices, so the REPL is launchable from Spotlight —
`Cmd+Space` → "Glean" → Enter opens it in a Terminal window.
`glean` in `~/.local/bin`. On macOS it also creates `~/Applications/Glean Code.app`
and registers it with LaunchServices, so the REPL is launchable from Spotlight —
`Cmd+Space` → "Glean Code" → Enter opens it in a Terminal window.

| Flag | Effect |
| --- | --- |
Expand All @@ -87,11 +87,26 @@ registers it with LaunchServices, so the REPL is launchable from Spotlight —
| `--dev` | App launches from the working tree, so edits apply with no rebuild |
| `--prefix DIR` | Install the `glean` executable somewhere other than `~/.local/bin` |
| `--verify` | Report what is currently installed |
| `--uninstall` | Remove everything the installer created (config is left alone) |
| `--uninstall` | Remove the CLI and the app bundle this installer created (config is left alone) |

Re-run `python3 install.py` after pulling changes to refresh the snapshot, or install
once with `--dev` and skip that step entirely.

**The bundle is called `Glean Code.app`, never `Glean.app`** — the latter is Glean's own
desktop client (`com.glean.desktop`). The installer reads `CFBundleIdentifier` before it
writes anything and refuses a bundle it did not create, and `--uninstall` skips one for the
same reason, so neither can damage an app it doesn't own:

```text
x ~/Applications/Glean Code.app already exists and belongs to another app
(CFBundleIdentifier: com.glean.desktop).
Refusing to write into it. Move or rename that bundle, or run with --cli-only.
```

If you installed before this change you have a `Glean.app` bundle that we do own; the next
`python3 install.py` removes it and replaces it with `Glean Code.app`. That's why the
Spotlight entry changes name — nothing is lost.

### Or just alias it

```bash
Expand Down Expand Up @@ -221,7 +236,10 @@ Config lives at `~/.gleancode/config.json`. Change any key with `/config set <ke

`glean_mcp.py` exposes Glean as an MCP server so Claude Code, Claude Desktop, and Cursor can call Glean search, chat, and agents as native tools. It reads the same `~/.gleancode/config.json` but forces live mode, so MCP tools hit the real API by default — mock mode is opt-in via `GLEAN_MOCK` and every mock response carries a visible fabricated-data banner.

Requires Python 3.10+ and the `mcp` package. The REPL itself remains Python 3.9+ and stdlib-only.
Requires Python 3.10+ and the **v1 line** of the `mcp` package — install it as
`mcp[cli]>=1,<2`. mcp 2.0.0 renamed `FastMCP` to `MCPServer` and removed the module this
server imports, so an unpinned install breaks it ([details](docs/MCP.md#mcp-sdk-v2)). The
REPL itself remains Python 3.9+ and stdlib-only.

Setup for all three clients, the tool table, and the mock-mode rationale: **[docs/MCP.md](docs/MCP.md)**.

Expand Down Expand Up @@ -265,13 +283,13 @@ python3 -m unittest discover tests/
```

On macOS, keep bytecode caches out of the working tree — Spotlight indexes stray `.pyc`
files, and they outrank the `Glean.app` launcher in `Cmd+Space`:
files, and they outrank the `Glean Code.app` launcher in `Cmd+Space`:

```bash
export PYTHONPYCACHEPREFIX="$HOME/.cache/python"
```

721 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, and the MCP server. Development notes: [docs/TESTING.md](docs/TESTING.md).
740 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, and the MCP server. Development notes: [docs/TESTING.md](docs/TESTING.md).

## Documentation

Expand Down
10 changes: 9 additions & 1 deletion docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Notes on the test suite added during development of glean-code-cli. See [Running tests](../README.md#running-tests) for the user-facing instructions on how to run the tests.

All 699 tests pass. Here's what was added across the development passes:
All 740 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 Expand Up @@ -48,6 +48,14 @@ All 699 tests pass. Here's what was added across the development passes:
- Cross-endpoint coherence — a `/search` result URL resolves through `/getdocuments` and `/summarize` to the same document, `/chat` citations track the question, `/getdocumentpermissions` owner is the document author, `/people` reads the roster
- Custom corpus files — `mock_corpus_path` and `GLEAN_MOCK_CORPUS` overrides (config wins), bare-array form, and `CorpusError` on a missing file, invalid JSON, a document with no title, or an empty document list

`tests/test_install.py` (10 new tests) — covers app-bundle ownership in the installer:

- `bundle_identifier` / `owns_bundle` — reads `CFBundleIdentifier` out of `Info.plist`; a missing bundle counts as ours (nothing to clobber), a foreign identifier and an unreadable or binary plist do not
- Refusal — `install_macos_app` exits rather than writing into a bundle it did not create, leaving that bundle's `Info.plist` byte-identical and creating no `Contents/Resources`
- Legacy cleanup — an old `Glean.app` is removed on install when we own it, and left alone when it belongs to another app
- Uninstall — removes our own bundle, and never deletes a foreign one (the regression that would have deleted a user's Glean Desktop install)
- The default `APP_DIR` is asserted **not** to be `Glean.app`

`tests/test_mcp.py` (9 new tests) — covers mock mode on the MCP server:

- `_build_client` — forces live mode whatever `mode` the config file carries (including `auto`), and switches to mock only when `GLEAN_MOCK` is set; truthy spellings (`1`, `true`, `yes`, `on`) accepted, everything else ignored
Expand Down
Loading