The brain HTTP REST /query endpoint accepts POST with JSON body
{query, limit}, not GET with URL query string. Surfaced empirically
during Plan 4 Task 7 smoke testing. Spec text now matches the
implementation.
8.8 KiB
Spec: hyperguild CLI
Plan 4 of 7 — Hyperguild Skill Migration. Loaded after
feature-specskill.
Problem Statement
Three needs converge on a single small Go binary:
- Tier probing as MCP is overkill. The supervisor's
tierMCP runs onkoala:30320and answers a one-shot question (which models are reachable right now?). Pulling Claude Code through MCP startup, tool listing, and a JSON-RPC call for a 2-second probe is wasteful and adds a network hop the answer doesn't need. - Brain access from shell scripts has no good front door. The brain's HTTP REST API exists (Plan 1) at
koala:3300for non-MCP clients, but every shell script that wants to query or write to the brain re-implements the curl invocation. A CLI gives shell pipelines, ad-hoc agent prompts, and quick-debug scenarios a stable interface. - Mode bootstrap is manual. Each new project that wants to operate in a chosen mode (cloud / client-local / sovereign) needs a
.mcp.jsonwritten by hand. Without automation, mode adoption is gated on remembering the right MCP server URLs.
Why now: Plans 1–3 are merged. The CLI is the next building block in shrinking the supervisor pod toward a thin Mode-2 routing layer. Plans 5 and 6 build on the CLI's tier and brain helpers.
Success Criteria
hyperguild tierreturns the sametier.Infothatinternal/tier.Detectproduces for the same probe URLs, in < 3 s under all three tier conditions, with both human-readable and--jsonoutput.hyperguild brain query <topic>returns BM25 results from the brain HTTP REST/queryendpoint, exit 0 on success and non-zero on transport failure.hyperguild brain write <type> <slug>reads markdown content from stdin, posts to/writewith the type and slug, and createsbrain/knowledge/<slug>.md. A round-trip (hyperguild brain query <slug>immediately after) finds the entry.hyperguild mode <cloud|client-local|sovereign>writes a parseable JSON file at the target path with the per-modemcpServersentries;jq -e .mcpServerssucceeds on the output.- All commands print usage on
--help, exit 2 on unknown flags, exit non-zero on operational errors. task checkpasses (lint + test + vet) on each task and on the merged branch.
Constraints
- Stdlib only. No
cobra,urfave/cli,viper, etc. CLI router and flag parsing useflag.NewFlagSet. - Go 1.26.1, project default.
- Module:
github.com/mathiasbq/supervisor, peer tocmd/supervisor/. New code atcmd/hyperguild/. The module name keeps its historicalsupervisorvalue — renaming the module is out of scope and would touch every import. - Reuse
internal/tierunchanged. The CLI is a thin wrapper aroundtier.Detect. - Brain endpoint configurable via
BRAIN_URLenv var (defaulthttp://koala:30330— Tailscale-exposed NodePort, both MCP at/mcpand HTTP REST at/query,/write, etc., share the port). No hostname literals embedded in the CLI body — sourced from env per the existing "logical-addresses-in-instructions" memory. - Test discipline: table-driven, testify, fakes for HTTP and tier probing. No live network in tests.
- Errors: wrapped via
fmt.Errorf("op: %w", err). No naked returns. Stderr for errors, stdout for results.
Out of Scope
- The Mode 6 routing pod itself —
mode client-localwrites a placeholder entry pointing at the future routing URL with a_routing_pendingannotation; the CLI does not provision the pod. - Pass-rate logging (Plan 5) — the CLI's
brain writedoes not emitsession_logevents. - Skill worker CLIs (
hyperguild tdd_red,hyperguild review, etc.) — those stay on the supervisor MCP until Plan 7. - Brain HTTP server changes — the REST endpoints already exist.
- Authentication / TLS — Tailscale provides network isolation; no auth currently.
- Windows/Linux binaries — macOS-only per the user's setup.
go buildis portable but no cross-compilation in CI. - A
crushconfig writer for Mode 3 — Mode 3 (sovereign) writes a Claude-Code-compatible.mcp.jsonwith brain-only MCP, on the assumption that even Crush-primary users may fall back to Claude Code with brain access. Crush's own config is owned by the user manually. - A unified
--configfile for the CLI — env var + flags is enough today.
Technical Approach
-
Single binary, inline subcommand router.
cmd/hyperguild/main.godispatches onos.Args[1]to per-subcommand functions, each owning its ownflag.NewFlagSet. Rationale: 4 top-level subcommands (tier,brain,mode, plus--help) and one nested level (brain query,brain write); ~80 lines of routing plumbing in stdlib beats pulling cobra's ~3 KLOC of dependencies for a tiny CLI. The router is testable by injectingargs []stringinstead of readingos.Argsdirectly. -
tiersubcommand reusesinternal/tier.Detectverbatim. Probe URLs (https://api.anthropic.comand the LiteLLM base URL) come from environment:ANTHROPIC_PROBE_URL(default the literal Anthropic URL) andLITELLM_BASE_URL(no default — error if--mode-needs-llmand unset). Rationale: matching the supervisor's existing wiring means the CLI cannot disagree with the supervisor about tier; a single source of truth. -
brainsubcommand calls the HTTP REST API. Two nested subcommands:brain query <topic>issuesPOST /querywith JSON body{query, limit}(default--limit 5), prints results in human-readable form by default and with--jsonfor machine consumption.brain write <type> <slug>reads stdin, postsPOST /writewith JSON body{type, slug, content}, prints the resulting path on success. Rationale: HTTP REST is simpler than MCP framing for a CLI. Per CLAUDE.md, the REST endpoints are documented as the official non-MCP interface.
-
mode <name>writes a per-mode.mcp.jsontemplate. Defaults to writing./.mcp.json(cwd); accepts--out <path>. Per-mode bodies:cloud—mcpServerscontains onlybrainathttp://koala:30330/mcp.client-local—mcpServerscontainsbrainathttp://koala:30330/mcpand aroutingplaceholder entry withurlset to a marker (http://koala:30310/mcp) and an extra field"_routing_pending": "Plan 6 — routing pod not deployed yet". Rationale: keeping strict-JSON parseable means using a placeholder field rather than a JSON comment, which the spec parser would reject.sovereign—mcpServerscontains onlybrain, plus a top-level"_mode_note": "Sovereign mode primarily uses Crush + LiteLLM. This .mcp.json is provided as Claude Code fallback.". All three are valid JSON and all three round-trip throughjqfor verification. Rationale: a single subcommand with three clearly-different outputs is easier to evolve than three nearly-duplicate subcommands. The placeholder fields are intentional documentation in the file itself, which the user actually opens and edits.
-
No global state. Each subcommand is a function
(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error, allowing table-driven tests to exercise full subcommand flows withoutos.Exitor fd capture. -
HTTP client injection. A package-level
http.Clientwith 5s timeout forbraincalls, overridable in tests via a constructor. Real client formain,httptest.Serverfor tests.
Risks
-
.mcp.jsonschema may evolve. Claude Code's MCP config format is defined by the harness, and Anthropic could change it. Mitigation: document the format in the CLI's--helptext and in the spec; if it breaks, the fix is local to one template function. -
Brain endpoint hostname drift. If the brain moves off
koala, the env-var override avoids breaking the CLI but themodetemplate's hardcodedkoala:30330becomes stale. Mitigation: source the URL in themodetemplate from the same env var (BRAIN_URL) so all three subcommands stay in lockstep with the user's actual environment. -
tierprobe URL gap. The CLI inherits the supervisor's hardcodedhttps://api.anthropic.comprobe URL viainternal/tier. If Anthropic changes the URL, both supervisor and CLI break together. Mitigation: env-var overrideANTHROPIC_PROBE_URL; default unchanged. -
No HTTP retry logic. The CLI returns first-error to the user. For ad-hoc shell use this is fine; for automation a future
--retryflag may be needed. Out of scope for this iteration. -
Tests don't cover live network. Pure-fake tests catch regression but not "does the brain pod actually answer." Mitigation: add a smoke-test
task hyperguild:smokein a follow-up that runs against the real brain — separate concern, not in Plan 4. -
Mode 3 sovereign output may surprise users who expect Mode 3 to skip writing a
.mcp.jsonentirely (since Crush is the primary harness). Mitigation: the_mode_notefield explains the choice; the--out /dev/nullescape hatch lets users skip the write if they want.