fix(init): --help should not mutate config or scan filesystem

`gbrain init --help` (and `-h`) currently fall through to the smart-detection
branch in runInit(), which scans cwd for .md files and on a directory with
1000+ files prints "Found ~1500 .md files. For a brain this size, Supabase
gives faster search..." then defaults to PGLite — calling saveConfig() and
overwriting any existing Postgres config with `engine: 'pglite' +
database_path: ~/.gbrain/brain.pglite`.

Confirmed in the wild: ran `gbrain init --help` from $HOME on a machine where
~/.gbrain/config.json pointed at a Supabase Postgres brain with 10K+ pages.
The config was silently flipped to PGLite. The Supabase data was intact, but
gbrain stopped pointing at it until the config was manually restored.

Root cause: cli.ts:62-69 only routes --help → printOpHelp() for shared-op
commands; CLI_ONLY commands (init, embed, etc.) fall through to their handler
with --help still in argv. None of them check for it.

Fix: add a --help/-h guard at the top of runInit() that prints help text and
returns. Help should never mutate state — Postel's robustness principle for
CLI tools.

Help text covers all flags (engine selection, AI provider options, thin-client
mode) so users running `--help` get the canonical list rather than having to
read the source.

A wider architectural fix — adding --help routing for all CLI_ONLY commands in
cli.ts — is plausible follow-up, but each CLI_ONLY command would still need
its own help text. This per-command pattern matches how shared ops handle it
via printOpHelp(). Init is the highest-stakes case because it's the only
CLI_ONLY command that calls saveConfig().

Smoke test: from a directory with 1500 .md files, with GBRAIN_HOME pointed at
a fresh tempdir:
  - Before fix: ~/.gbrain/config.json materialized with engine: 'pglite'
  - After fix: help text printed, no config dir created

`bun run typecheck` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Lipman
2026-05-08 20:21:50 -04:00
co-authored by Claude Opus 4.7
parent dffb607ef7
commit ed11fdd58c
+59
View File
@@ -11,6 +11,20 @@ import { createEngine } from '../core/engine-factory.ts';
import { discoverOAuth, mintClientCredentialsToken, smokeTestMcp } from '../core/remote-mcp-probe.ts';
export async function runInit(args: string[]) {
// Help guard: cli.ts only routes --help to printOpHelp() for shared-op
// commands; CLI_ONLY commands (init, embed, etc.) fall through to their
// handler with --help in argv. Without this guard, `gbrain init --help`
// proceeds into the smart-detection branch below, scans cwd for .md files,
// and on a directory with 1000+ files (e.g. $HOME for someone whose brain
// and notes share a root) silently overwrites the existing Supabase config
// with a fresh PGLite brain at ~/.gbrain/brain.pglite. Confirmed in the
// wild — flipped a working `engine: postgres` config to `engine: pglite`
// on a brain with 10K+ pages. Help should never mutate state.
if (args.includes('--help') || args.includes('-h')) {
printInitHelp();
return;
}
const isSupabase = args.includes('--supabase');
const isPGLite = args.includes('--pglite');
const isMcpOnly = args.includes('--mcp-only');
@@ -729,3 +743,48 @@ export function reportModStatus(): void {
console.log('Soul audit: run `gbrain soul-audit` to customize agent identity');
console.log('');
}
function printInitHelp() {
console.log(`
gbrain init — initialize a brain (PGLite or Supabase Postgres)
USAGE
gbrain init [flags]
ENGINE SELECTION (mutually exclusive)
--pglite Use embedded PGLite (zero-config, default for <1000 .md files)
--supabase Use Supabase Postgres (recommended for 1000+ files)
--url <URL> Use a manual Postgres connection string
--mcp-only Thin-client mode: connect to a remote gbrain MCP, no local engine
OPTIONS
--force Overwrite an existing config (gated by default)
--non-interactive Don't prompt; use defaults
--migrate-only Apply pending schema migrations against the configured engine
without re-saving config (used by post-upgrade and orchestrators)
--json JSON output for status reporting
--path <DIR> Override default brain path (PGLite only)
--key <APIKEY> Provide an API key non-interactively (Supabase only)
--embedding-model <PROVIDER:MODEL>
e.g. openai:text-embedding-3-large, voyage:voyage-multimodal-3
--model <PROVIDER> Shorthand: pick recipe default for a provider
--embedding-dimensions <N>
Embedding dimensions (must match the model)
--expansion-model <PROVIDER:MODEL>
Model for query expansion (default: anthropic:claude-haiku)
--chat-model <PROVIDER:MODEL>
Default subagent driver (v0.27+)
EXAMPLES
gbrain init --pglite # Local-only, no API keys
gbrain init --supabase # Interactive Supabase setup
gbrain init --url postgresql://... # Use a custom Postgres
gbrain init --mcp-only --url https://... # Thin-client mode
NOTES
- Bare \`gbrain init\` in a directory with 1000+ .md files defaults to Supabase
interactive setup. With <1000 files (or with --pglite explicitly), defaults
to PGLite at ~/.gbrain/brain.pglite.
- Existing config is preserved unless --force is passed.
`.trim());
}