Skip to content
Open
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
583 changes: 583 additions & 0 deletions work/gbrain-no-api-keys/0001-claude-code-keyless-mode.patch

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions work/gbrain-no-api-keys/ISSUE-codex-kimi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Ready-to-paste issue for https://github.com/garrytan/gbrain/issues/new

**Title:**

codex-cli and kimi-cli recipes: subscription-native keyless dispatch for Codex and Kimi users (follow-up to #94)

**Body:**

## Context

#94 asked for a Claude Code-native mode with no API keys; PR #3464 implements it: `gbrain init --mode claude-code` routes chat, query expansion, and the subagent loop through the `claude-cli` recipe (Claude Code OAuth session, subscription-billed), and picks up a local Ollama daemon for keyless embeddings when one is running.

The same friction exists for users of other agent CLIs with subscription auth. This issue proposes extending the pattern so **any** agent subscription can power gbrain keylessly:

## Proposal

**1. `codex-cli` recipe** — OpenAI Codex CLI users sign in with their ChatGPT subscription; `codex exec` provides a non-interactive mode suitable for subprocess dispatch. A `CodexCliLanguageModel` modeled on `src/core/ai/providers/claude-cli-language-model.ts` plus a pure-data recipe (`implementation: 'codex-cli'`, `auth_env.required: []`) would give Codex subscribers chat + expansion + subagent dispatch with zero API keys — the exact #334/#94 story for the other big agent population.

**2. `kimi-cli` recipe** — Moonshot's Kimi CLI is the same shape: subscription-authenticated agent CLI with a non-interactive mode. Same provider pattern, same recipe contract. (gbrain already ships a hosted `moonshot` recipe; this would be its keyless sibling, the way `claude-cli` sits alongside `anthropic`.)

**3. Possible generalization** — the two providers above will be ~90% identical to the claude-cli one (spawn binary, isolate cwd, parse JSON/text output, map tool calls). It may be worth extracting a shared `agent-cli` subprocess base where each recipe supplies binary name/env var, argv template, and an output parser — so the third agent CLI is a recipe, not a provider implementation.

**4. `--mode claude-code` naming** — with more than one agent CLI supported, init could grow `--mode codex` / `--mode kimi` (or a generic `--mode agent-cli --agent <bin>`), all sharing the keyless machinery PR #3464 introduced (`claude_code_mode` sentinel, implicit `--no-embed` import, conservative search-mode seeding, Ollama embedding probe).

## Why not in PR #3464

Shipping subprocess providers untested would be worse than proposing them: verifying the `codex exec` / Kimi CLI output protocols needs machines with those binaries logged in. The claude-cli path in #3464 was verified end-to-end; these two deserve the same bar.

## Questions for the maintainer

1. Would PRs for `codex-cli` / `kimi-cli` recipes be welcome, following the claude-cli provider pattern?
2. Preference between per-provider implementations vs. the shared `agent-cli` base (option 3)?
3. Any constraints on adding init modes per agent CLI vs. a single generic flag?

---
_Generated by [Claude Code](https://claude.ai/code)_
101 changes: 101 additions & 0 deletions work/gbrain-no-api-keys/PR-DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Upstream PR description for garrytan/gbrain

Ready to paste when opening the PR from a fork (apply
`0001-claude-code-keyless-mode.patch` on a branch off `master`).

---

**Title:** feat(init): `--mode claude-code` — keyless mode for Claude Code subscribers (#94)

**Body:**

Closes #94.

## What

`gbrain init --mode claude-code` gives a Claude Code subscriber a fully working
brain with **zero provider API keys** — no `OPENAI_API_KEY`, no
`ANTHROPIC_API_KEY`:

```bash
gbrain init --mode claude-code # keyless: PGLite, keyword + graph search
gbrain import ~/notes/
gbrain search "who works at Acme"
```

Search runs on the existing keyword + graph + title arms; chat commands route
through the existing `claude-cli` recipe (Claude Code OAuth session, billed to
the subscription).

## Why this is a small diff

Most of the machinery already existed — this PR wires the seams:

- the `claude-cli` chat recipe (#334) already runs keyless and supports the
subagent loop;
- hybrid search already degrades gracefully to keyword + graph + title when no
embedding provider is available (Codex C3 / D1 paths);
- `gbrain sync` already honors the `embedding_disabled` sentinel.

What was missing: `init` fail-louded without keys, `import` refused outright on
a no-embedding brain, and chat defaulted to `anthropic:*`.

## Changes

- **init**: new `--mode claude-code` flag. Implies `--pglite`; writes
`claude_code_mode: true` alongside `embedding_disabled: true` (every existing
skip-embed callsite keeps working); defaults `chat_model` to
`claude-cli:claude-sonnet-4-6`; seeds `search.mode = conservative` (the one
bundle with no reranker / LLM-expansion spend); best-effort check that the
`claude` binary is on PATH. Re-inits stay keyless without re-passing the flag.
- **import**: a claude-code brain imports without vectors (implicit
`--no-embed`) instead of exiting 1 with the deferred-setup error.
- **embed**: refusal message tailored to keyless mode, with the vector upgrade
recipe.
- **advisor**: `embedding_disabled` is not a setup smell when keyless by design.
- **Bug fix**: explicit `--embedding-model` / `--model` now clears a persisted
deferred/keyless sentinel. Previously the documented upgrade path
(`gbrain init --force --embedding-model …` — the exact command
`assertEmbeddingEnabled` prints) was silently ignored because the seeded
`noEmbedding` won in `initPGLite`. A resolved model also removes stale
sentinels from `config.json` (the "one or the other, never both" invariant).
- **Bug fix**: the post-init "subagent features require ANTHROPIC_API_KEY"
caveat no longer fires for `claude-cli:*` chat models — that recipe declares
`supports_subagent_loop` and drives Minions through the OAuth session.
- Docs: README quick-start subsection, CHANGELOG, `init --help`, and the
fail-loud no-provider hint now advertises the keyless path.

## Tests

- New `test/init-claude-code-mode.test.ts` (12 tests): keyless predicate,
tailored refusal messages, re-init seeding, and the claude-cli recipe's
keyless contract (no required env vars, subagent loop supported).
- All touched-area suites green (init-env-detection, init-embed-check,
init-mode-picker, init-provider-picker, sync-no-embed-sentinel, advisor-core,
import-file, embed-preflight, embedding-dim-check, config);
`bun run typecheck` clean.
- End-to-end smoke with all provider keys unset:
init → import → keyword search returns the right page → `embed --stale`
refuses with the keyless message → `init --force --pglite --embedding-model
zeroentropyai:zembed-1 --embedding-dimensions 1280` clears the sentinels and
configures the model.

## Out of scope (deliberate)

- Query expansion / reranking via Claude Code skills (the issue's stretch
ideas): expansion is already gated behind `isAvailable('expansion')` and off
in the conservative bundle; the reranker fails open. Nothing breaks keylessly;
wiring them through `claude-cli` can be a follow-up.
- Postgres/Supabase keyless installs: the flag targets the local PGLite path
per the issue; the runtime behavior (import/search) is engine-agnostic, so
extending later is trivial.

## Unrelated heads-up found while working on this

`master` (at least since faf5cdb) has an accidentally **committed
`node_modules` symlink** pointing at `/tmp/fleet/repo/node_modules` — a path
from someone's build environment. It's tracked despite `node_modules/` being in
`.gitignore` (gitignore doesn't apply to already-tracked paths), so every fresh
clone gets a dangling symlink and `bun install` behaves oddly until you
`rm node_modules && bun install`. Worth a one-line cleanup commit:
`git rm --cached node_modules`. Not fixed in this PR to keep it scoped to #94.
87 changes: 87 additions & 0 deletions work/gbrain-no-api-keys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# gbrain: Claude Code-native keyless mode (fix for garrytan/gbrain#94)

Prepared fix for [garrytan/gbrain#94](https://github.com/garrytan/gbrain/issues/94) —
"Plans for a Claude Code-native mode (no API keys, like gstack)?"

The patch adds `gbrain init --mode claude-code`: a Claude Code subscriber gets a
fully working brain with **zero provider API keys** (no `OPENAI_API_KEY`, no
`ANTHROPIC_API_KEY`).

## Applying

```bash
git clone https://github.com/garrytan/gbrain && cd gbrain
git checkout ddd66e1 # base commit the patch was built against (master, 2026-07-27)
git am path/to/0001-claude-code-keyless-mode.patch
```

## What was already there vs. what was missing

gbrain already had most of the building blocks; the blockers were at the seams:

| Piece | Status before the patch |
|---|---|
| `claude-cli` chat recipe (OAuth session, no key, subagent loop) | ✅ existed (their #334) |
| Hybrid search degrades to keyword + graph + title when no embedding provider | ✅ existed (Codex C3 / D1 paths) |
| `gbrain sync` skips embedding on the `embedding_disabled` sentinel | ✅ existed |
| `gbrain init` without keys | ❌ exits 1 unless you know `--no-embedding` |
| `gbrain import` on a no-embedding brain | ❌ refuses outright (deferred-setup error) |
| Chat/expansion defaults | ❌ point at `anthropic:*`, which needs a key |
| Upgrade path `init --force --embedding-model …` out of deferred mode | ❌ latent bug: persisted sentinel silently beat the explicit flag |

## What the patch does

- **`gbrain init --mode claude-code`** — implies `--pglite`; writes
`claude_code_mode: true` alongside `embedding_disabled: true` (so every
existing skip-embed callsite keeps working); defaults `chat_model` to
`claude-cli:claude-sonnet-4-6`; seeds `search.mode = conservative` (the only
mode bundle with no reranker / LLM-expansion spend); warns if the `claude`
binary isn't on PATH.
- **`gbrain import`** treats a claude-code brain as implicit `--no-embed` —
chunks land keyword-searchable instead of the command exiting 1.
- **`gbrain embed`** refuses with a keyless-specific message including the
vector upgrade recipe.
- **advisor** no longer flags the missing embedding provider as a setup smell
on keyless brains.
- **Bug fix**: explicit `--embedding-model` / `--model` now clears a persisted
deferred/keyless sentinel, making the documented upgrade path work; a
resolved model also removes stale sentinels from `config.json`.
- **Bug fix**: the post-init "subagent features require ANTHROPIC_API_KEY"
caveat is suppressed for `claude-cli:*` chat models (that recipe drives the
subagent loop through the Claude Code OAuth session).
- Docs: README quick-start section, CHANGELOG entry, `init --help` text, and
the fail-loud no-provider hint now advertises the keyless path.
- Tests: `test/init-claude-code-mode.test.ts` (12 tests) covering the keyless
predicate, tailored refusal messages, re-init seeding, and the claude-cli
recipe's keyless contract (no required env vars, subagent loop supported).

## Verification performed

- `bun test` on the new suite plus all touched-area suites
(init-env-detection, init-embed-check, init-mode-picker, init-provider-picker,
sync-no-embed-sentinel, advisor-core, import-file, embed-preflight,
embedding-dim-check, config): **all green**.
- `bun run typecheck` (tsc --noEmit): clean.
- End-to-end smoke with **all provider keys unset**:
`init --mode claude-code` → config sentinels + conservative search mode
written → `import` of markdown succeeds without vectors → `search` returns
keyword-arm results → `embed --stale` refuses with the keyless message →
`init --force --pglite --embedding-model zeroentropyai:zembed-1
--embedding-dimensions 1280` clears the sentinels and configures the model.

## Not in scope (deliberate)

- Query expansion / reranking via Claude Code skills (the issue's stretch
ideas): expansion is already gracefully gated behind
`isAvailable('expansion')` and off in the conservative bundle; the reranker
fails open. Nothing breaks keylessly; wiring them through `claude-cli` can be
a follow-up.
- Postgres/Supabase keyless installs: `--mode claude-code` targets the local
PGLite path per the issue; the runtime behavior (import/search) is
engine-agnostic, so extending the flag later is trivial.

## Submitting upstream

This session couldn't open a PR against `garrytan/gbrain` (repo outside the
session's GitHub scope), so the change ships here as a `git am`-ready patch.
Apply on a fork branch and open the PR referencing issue #94.
23 changes: 23 additions & 0 deletions work/gbrain-no-api-keys/open-upstream-pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Opens the upstream PR garrytan/gbrain <- mnemonik-dev:claude-code-keyless-mode.
#
# Run from any machine with a GitHub token that can see garrytan/gbrain
# (a classic PAT with `public_repo`, or a fine-grained token; the branch
# already sits on the mnemonik-dev/gbrain fork):
#
# GITHUB_TOKEN=ghp_... ./open-upstream-pr.sh
#
# Why this script exists: the Claude Code session that prepared the fix is
# repo-scoped to mnemonik-dev/* — its outbound proxy 403s every GitHub API
# call to garrytan/gbrain, and cross-owner add_repo is a v1 limitation.
set -euo pipefail
cd "$(dirname "$0")"

: "${GITHUB_TOKEN:?set GITHUB_TOKEN to a token that can access garrytan/gbrain}"

curl -sS -X POST \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/garrytan/gbrain/pulls \
-d @pr-payload.json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("html_url") or json.dumps(d, indent=2))'
7 changes: 7 additions & 0 deletions work/gbrain-no-api-keys/pr-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"title": "feat(init): --mode claude-code — keyless mode for Claude Code subscribers (#94)",
"head": "mnemonik-dev:claude-code-keyless-mode",
"base": "master",
"maintainer_can_modify": true,
"body": "Closes #94.\n\n## What\n\n`gbrain init --mode claude-code` gives a Claude Code subscriber a fully working\nbrain with **zero provider API keys** — no `OPENAI_API_KEY`, no\n`ANTHROPIC_API_KEY`:\n\n```bash\ngbrain init --mode claude-code # keyless: PGLite, keyword + graph search\ngbrain import ~/notes/\ngbrain search \"who works at Acme\"\n```\n\nSearch runs on the existing keyword + graph + title arms; chat commands route\nthrough the existing `claude-cli` recipe (Claude Code OAuth session, billed to\nthe subscription).\n\n## Why this is a small diff\n\nMost of the machinery already existed — this PR wires the seams:\n\n- the `claude-cli` chat recipe (#334) already runs keyless and supports the\n subagent loop;\n- hybrid search already degrades gracefully to keyword + graph + title when no\n embedding provider is available (Codex C3 / D1 paths);\n- `gbrain sync` already honors the `embedding_disabled` sentinel.\n\nWhat was missing: `init` fail-louded without keys, `import` refused outright on\na no-embedding brain, and chat defaulted to `anthropic:*`.\n\n## Changes\n\n- **init**: new `--mode claude-code` flag. Implies `--pglite`; writes\n `claude_code_mode: true` alongside `embedding_disabled: true` (every existing\n skip-embed callsite keeps working); defaults `chat_model` to\n `claude-cli:claude-sonnet-4-6`; seeds `search.mode = conservative` (the one\n bundle with no reranker / LLM-expansion spend); best-effort check that the\n `claude` binary is on PATH. Re-inits stay keyless without re-passing the flag.\n- **import**: a claude-code brain imports without vectors (implicit\n `--no-embed`) instead of exiting 1 with the deferred-setup error.\n- **embed**: refusal message tailored to keyless mode, with the vector upgrade\n recipe.\n- **advisor**: `embedding_disabled` is not a setup smell when keyless by design.\n- **Bug fix**: explicit `--embedding-model` / `--model` now clears a persisted\n deferred/keyless sentinel. Previously the documented upgrade path\n (`gbrain init --force --embedding-model …` — the exact command\n `assertEmbeddingEnabled` prints) was silently ignored because the seeded\n `noEmbedding` won in `initPGLite`. A resolved model also removes stale\n sentinels from `config.json` (the \"one or the other, never both\" invariant).\n- **Bug fix**: the post-init \"subagent features require ANTHROPIC_API_KEY\"\n caveat no longer fires for `claude-cli:*` chat models — that recipe declares\n `supports_subagent_loop` and drives Minions through the OAuth session.\n- Docs: README quick-start subsection, CHANGELOG, `init --help`, and the\n fail-loud no-provider hint now advertises the keyless path.\n\n## Tests\n\n- New `test/init-claude-code-mode.test.ts` (12 tests): keyless predicate,\n tailored refusal messages, re-init seeding, and the claude-cli recipe's\n keyless contract (no required env vars, subagent loop supported).\n- All touched-area suites green (init-env-detection, init-embed-check,\n init-mode-picker, init-provider-picker, sync-no-embed-sentinel, advisor-core,\n import-file, embed-preflight, embedding-dim-check, config);\n `bun run typecheck` clean.\n- End-to-end smoke with all provider keys unset:\n init → import → keyword search returns the right page → `embed --stale`\n refuses with the keyless message → `init --force --pglite --embedding-model\n zeroentropyai:zembed-1 --embedding-dimensions 1280` clears the sentinels and\n configures the model.\n\n## Out of scope (deliberate)\n\n- Query expansion / reranking via Claude Code skills (the issue's stretch\n ideas): expansion is already gated behind `isAvailable('expansion')` and off\n in the conservative bundle; the reranker fails open. Nothing breaks keylessly;\n wiring them through `claude-cli` can be a follow-up.\n- Postgres/Supabase keyless installs: the flag targets the local PGLite path\n per the issue; the runtime behavior (import/search) is engine-agnostic, so\n extending later is trivial.\n\n## Unrelated heads-up found while working on this\n\n`master` (at least since faf5cdb) has an accidentally **committed\n`node_modules` symlink** pointing at `/tmp/fleet/repo/node_modules` — a path\nfrom someone's build environment. It's tracked despite `node_modules/` being in\n`.gitignore` (gitignore doesn't apply to already-tracked paths), so every fresh\nclone gets a dangling symlink and `bun install` behaves oddly until you\n`rm node_modules && bun install`. Worth a one-line cleanup commit:\n`git rm --cached node_modules`. Not fixed in this PR to keep it scoped to #94.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nhttps://claude.ai/code/session_01WQ7za53ZWxWqL2XAfSgTux"
}