@kinqs/brainrouter-mcp-server 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api/auth/crypto.js +8 -3
- package/dist/index.js +1 -1
- package/docs/specs/0.3.7-terminal-ui-redesign.md +259 -0
- package/package.json +3 -3
package/README.md
CHANGED
package/dist/api/auth/crypto.js
CHANGED
|
@@ -37,9 +37,14 @@ export function verifyJwt(token, secret) {
|
|
|
37
37
|
if (parts.length !== 3)
|
|
38
38
|
return null;
|
|
39
39
|
const [header, claims, signature] = parts;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// Compare in base64url string space so single-character changes to padding
|
|
41
|
+
// bits (e.g. "A" → "B" at the last position of a 32-byte HMAC) are caught.
|
|
42
|
+
// Raw-byte comparison misses these because base64url decoding ignores the
|
|
43
|
+
// bottom 2 bits of the final character.
|
|
44
|
+
const expected = createHmac("sha256", secret).update(`${header}.${claims}`).digest("base64url");
|
|
45
|
+
const expBuf = Buffer.from(expected);
|
|
46
|
+
const actBuf = Buffer.from(signature);
|
|
47
|
+
if (expBuf.length !== actBuf.length || !timingSafeEqual(expBuf, actBuf))
|
|
43
48
|
return null;
|
|
44
49
|
try {
|
|
45
50
|
const payload = JSON.parse(base64UrlDecode(claims).toString("utf8"));
|
package/dist/index.js
CHANGED
|
@@ -102,7 +102,7 @@ const PORT = parseInt(parseFlag('--port') ?? '3747', 10);
|
|
|
102
102
|
function buildMcpServer(registry, options) {
|
|
103
103
|
const defaultUserId = options?.defaultUserId ?? STDIO_DEFAULT_USER_ID;
|
|
104
104
|
const isAdmin = options?.isAdmin ?? false;
|
|
105
|
-
const server = new Server({ name: 'brainrouter-mcp-server', version: '0.3.
|
|
105
|
+
const server = new Server({ name: 'brainrouter-mcp-server', version: '0.3.7' }, { capabilities: { tools: {} } });
|
|
106
106
|
// ── Tool list ──────────────────────────────────────────────────────────────
|
|
107
107
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
108
108
|
tools: [
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Spec: 0.3.7 Item 6 — Terminal UI redesign + in-terminal config wizard
|
|
2
|
+
|
|
3
|
+
> Status: drafted, awaiting implementation. See [Tasks.md](../../Tasks.md)
|
|
4
|
+
> for live progress. Pairs with [`brainrouter-roadmap/0.3.7.md`](../../brainrouter-roadmap/0.3.7.md)
|
|
5
|
+
> as the headline feature of the 0.3.7 cycle.
|
|
6
|
+
|
|
7
|
+
## Objective
|
|
8
|
+
|
|
9
|
+
Make BrainRouter's CLI feel like Claude Code / OpenCode / Codex / Grok-CLI
|
|
10
|
+
/ DeepSeek-TUI — **especially around in-terminal, picker-driven
|
|
11
|
+
configuration**. Today a first-run user has to:
|
|
12
|
+
|
|
13
|
+
1. Run `brainrouter login` outside the REPL → answers a sequence of
|
|
14
|
+
`inquirer` text prompts → exits.
|
|
15
|
+
2. Run `brainrouter config` outside the REPL → answers another sequence
|
|
16
|
+
of text prompts → exits.
|
|
17
|
+
3. Then start `brainrouter` (the chat REPL) and discover everything else
|
|
18
|
+
(`/theme`, `/personality`, `/effort`, `/mode`, statusline) via
|
|
19
|
+
`/help` or by hand-editing JSON.
|
|
20
|
+
|
|
21
|
+
Peer CLIs do this differently:
|
|
22
|
+
|
|
23
|
+
- **Claude Code** — `/config` opens a tabbed settings panel; `/theme`,
|
|
24
|
+
`/model`, `/login` are arrow-key pickers; first-run shows a welcome
|
|
25
|
+
card → onboarding wizard with a step counter.
|
|
26
|
+
- **Codex** — `Step::{Welcome, Auth, TrustDirectory}` state machine
|
|
27
|
+
drives the onboarding; one modal owns multiple sub-states (`PickMode`
|
|
28
|
+
→ `ApiKeyEntry`) via an enum.
|
|
29
|
+
- **DeepSeek-TUI** — `/config` is verb-overloaded: bare opens a picker,
|
|
30
|
+
`<key>` shows, `<key> <val>` sets session, `<key> <val> --save`
|
|
31
|
+
persists. Provider picker uses a single modal with `Stage::{List,
|
|
32
|
+
KeyEntry}`; selecting an un-configured provider transitions the same
|
|
33
|
+
modal into key entry.
|
|
34
|
+
- **Grok-CLI** — first-run is a render-time `ApiKeyModal` overlay
|
|
35
|
+
inside the REPL, not a separate `login` subcommand. Each settings
|
|
36
|
+
surface (`/models`, `/mcp`, `/sandbox`, `/wallet`) is its own modal
|
|
37
|
+
that merges through a single `saveUserSettings(partial)`.
|
|
38
|
+
|
|
39
|
+
We adopt the **render-time modal** + **state-enum** + **verb-overloaded
|
|
40
|
+
slash command** patterns, on top of our existing
|
|
41
|
+
`brainrouter-cli/src/cli/cliPrompt.ts` picker primitive.
|
|
42
|
+
|
|
43
|
+
## Non-goals
|
|
44
|
+
|
|
45
|
+
- A full Ratatui/Ink-style fullscreen renderer. That's the 0.5.0 cycle.
|
|
46
|
+
- Replacing `readline` as the REPL composer. We extend the existing
|
|
47
|
+
raw-mode picker; we do not rewrite the input loop.
|
|
48
|
+
- Multi-provider OAuth flows. v0.3.7 keeps API-key auth; OAuth /
|
|
49
|
+
device-code can be a follow-up once the wizard skeleton lands.
|
|
50
|
+
- Touching the MCP server's config (`~/.config/brainrouter/server.env`).
|
|
51
|
+
The wizard reads/writes only the CLI-owned files:
|
|
52
|
+
`~/.config/brainrouter/config.json` and
|
|
53
|
+
`<workspace>/.brainrouter/cli/preferences.json`.
|
|
54
|
+
|
|
55
|
+
## Tech stack
|
|
56
|
+
|
|
57
|
+
- Same as the rest of `brainrouter-cli/`: TypeScript on Node 22+, ESM,
|
|
58
|
+
Vitest, `chalk` for color, our own `cliPrompt.askChoice` for the
|
|
59
|
+
arrow-key picker.
|
|
60
|
+
- `inquirer` is deliberately **removed from the new flows** — its
|
|
61
|
+
competing readline interface is the bug that motivated `cliPrompt.ts`
|
|
62
|
+
in the first place. Legacy `brainrouter login` / `brainrouter config`
|
|
63
|
+
subcommands stay (for back-compat) but the docs steer users at the
|
|
64
|
+
new in-REPL flows.
|
|
65
|
+
|
|
66
|
+
## Slash command surface
|
|
67
|
+
|
|
68
|
+
Following the verb-overload pattern from DeepSeek-TUI:
|
|
69
|
+
|
|
70
|
+
| Command | Behaviour |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `/init` | Re-runs the first-run onboarding wizard (theme → provider → model → MCP → done). Idempotent; safe to re-run. **Aliased from the old `/init` which only wrote `AGENT.md`** — that one-shot is folded into the wizard's final step as an opt-in toggle so we don't regress for users who relied on it. |
|
|
73
|
+
| `/config` | Bare — opens the **settings home panel** (picker over: LLM, MCP, Theme, Statusline, Effort, Mode, Quiet, Personality, Editor mode, View raw config). |
|
|
74
|
+
| `/config <key>` | Show current value for `<key>` (e.g. `/config theme` → `theme: dark (preference)`). |
|
|
75
|
+
| `/config <key> <value>` | Set `<key>` to `<value>` and persist (writes to `~/.config/brainrouter/config.json` for LLM/MCP knobs, `<workspace>/.brainrouter/cli/preferences.json` for prefs). |
|
|
76
|
+
| `/login` | NEW slash alias for the old `brainrouter login` flow — opens the **MCP profile editor** in-REPL (transport picker → fields → reachability test → save). The standalone `brainrouter login` CLI subcommand stays for first-run-before-REPL use. |
|
|
77
|
+
| `/logout` | Already exists; unchanged. Clears API keys from the active profile. |
|
|
78
|
+
| `/theme` | Already exists. Extended with `/theme` (bare) opening the picker (live-preview the banner + prompt accent on cursor-change), confirming via ENTER, restoring on Esc. |
|
|
79
|
+
| `/model` | Already exists. Extended with bare-`/model` opening a picker over a curated short-list (gpt-4o-mini, gpt-4o, gpt-5, claude-sonnet-4, deepseek-v4, qwen3-coder, …) + "Other" for free-text. |
|
|
80
|
+
|
|
81
|
+
The bare `/config` panel is the **settings home** — it's the screen
|
|
82
|
+
that lets users discover every knob without needing to memorise the
|
|
83
|
+
slash vocabulary.
|
|
84
|
+
|
|
85
|
+
## Onboarding wizard (`/init`)
|
|
86
|
+
|
|
87
|
+
State machine — same shape as Codex `Step` and DeepSeek
|
|
88
|
+
`OnboardingState`:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Welcome → Theme → Provider → ApiKey → Model → MCP → AgentMd → Done
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **Welcome** — a single boxed card (~5 lines) introducing
|
|
95
|
+
BrainRouter, what gets configured, and where (path of
|
|
96
|
+
`~/.config/brainrouter/config.json`). ENTER continues, q quits
|
|
97
|
+
without writing.
|
|
98
|
+
- **Theme** — arrow-key picker over `dark / light / mono / auto`,
|
|
99
|
+
with **live preview**: on cursor-change, redraw the banner accent so
|
|
100
|
+
the user sees the change before confirming. Persists to
|
|
101
|
+
`preferences.theme` on confirm.
|
|
102
|
+
- **Provider** — picker over a curated provider list:
|
|
103
|
+
`OpenAI · DeepSeek · OpenRouter · Anthropic (via gateway) · Gemini
|
|
104
|
+
(OpenAI-compat) · LM Studio (local) · Ollama (local) · Custom…`.
|
|
105
|
+
Each row shows a one-line hint (endpoint, "(local)" / "(cloud)",
|
|
106
|
+
needs-key indicator). Pre-detects from env vars
|
|
107
|
+
(`OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`, etc.)
|
|
108
|
+
— pre-selects the row whose key is already present in the shell.
|
|
109
|
+
- **ApiKey** — free-text entry (re-uses the picker's `awaitingOther`
|
|
110
|
+
free-text mode). Pre-filled from env when available. Validation
|
|
111
|
+
tier `Accept{warning?}` / `Reject(reason)`: empty for local
|
|
112
|
+
endpoints is OK; non-empty with `sk-`-shape prefix or vendor-known
|
|
113
|
+
shape is OK; everything else is **accepted with a warning** (not
|
|
114
|
+
rejected — vendors invent new prefixes all the time). Mask to last
|
|
115
|
+
4 chars after entry.
|
|
116
|
+
- **Model** — picker over the provider's curated short-list +
|
|
117
|
+
"Other" for free-text. Defaults: OpenAI → `gpt-4o-mini`, DeepSeek
|
|
118
|
+
→ `deepseek-chat`, OpenRouter → `anthropic/claude-sonnet-4`,
|
|
119
|
+
LM Studio → blank (user picks from loaded models), etc.
|
|
120
|
+
- **MCP** — picker over `Local stdio (brainrouter-mcp on PATH) ·
|
|
121
|
+
Local HTTP (http://localhost:3747/mcp) · Remote HTTP… (free-text
|
|
122
|
+
URL) · Skip (offline-only)`. On confirm, run a single
|
|
123
|
+
`mcpClient.listTools()` reachability test (5s timeout); on success
|
|
124
|
+
save the profile and mark it active; on failure offer "save
|
|
125
|
+
anyway" / "try a different transport" / "skip".
|
|
126
|
+
- **AgentMd** — yes/no: "Write AGENT.md to this workspace? (helps
|
|
127
|
+
agents understand your repo conventions)". Defaults to **yes** if
|
|
128
|
+
no `AGENT.md` / `CLAUDE.md` exists yet, **no** otherwise. This is
|
|
129
|
+
the toggle that absorbs the old `/init` behaviour.
|
|
130
|
+
- **Done** — summary card showing the saved config (with API keys
|
|
131
|
+
masked) + next steps (`/help`, `/config`, `/where`). Marker file
|
|
132
|
+
written to `~/.config/brainrouter/.onboarded` so subsequent CLI
|
|
133
|
+
starts skip the wizard. Re-running `/init` is always allowed.
|
|
134
|
+
|
|
135
|
+
**Skip semantics.** At any step `Esc` backs out one state; `q`
|
|
136
|
+
aborts the whole wizard with **no changes saved** (everything is
|
|
137
|
+
journalled in-memory until the Done step commits the write).
|
|
138
|
+
|
|
139
|
+
**Auto-trigger.** When the user runs `brainrouter` (the chat command)
|
|
140
|
+
and no `~/.config/brainrouter/config.json` exists, instead of the
|
|
141
|
+
current "Run \`brainrouter login\` …" error-and-exit, we drop them
|
|
142
|
+
**straight into the wizard inside the REPL** (no separate subcommand).
|
|
143
|
+
This is the grok-cli `ApiKeyModal` pattern — the modal IS the
|
|
144
|
+
onboarding.
|
|
145
|
+
|
|
146
|
+
## Settings home panel (`/config`)
|
|
147
|
+
|
|
148
|
+
Picker over the settings categories. Each row shows the current value
|
|
149
|
+
on the right so the user can scan the state at a glance:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
┌─ ⚙️ BrainRouter Config ────────────────────────────────────────┐
|
|
153
|
+
│ │
|
|
154
|
+
│ ▶ LLM provider openai · gpt-4o-mini │
|
|
155
|
+
│ MCP profile default · http · 🟢 online │
|
|
156
|
+
│ Theme dark │
|
|
157
|
+
│ Statusline mode,branch,workflow,goal │
|
|
158
|
+
│ Reasoning effort medium │
|
|
159
|
+
│ Execution mode planning │
|
|
160
|
+
│ Review policy request │
|
|
161
|
+
│ Quiet mode off │
|
|
162
|
+
│ Personality standard │
|
|
163
|
+
│ Editor mode emacs │
|
|
164
|
+
│ │
|
|
165
|
+
│ View raw config │
|
|
166
|
+
│ Quit (Esc) │
|
|
167
|
+
│ │
|
|
168
|
+
│ ↑/↓ navigate · ENTER edit · Esc to quit │
|
|
169
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Selecting a row opens the appropriate sub-picker (provider picker,
|
|
173
|
+
theme picker, etc.). On confirm, returns to the home panel with the
|
|
174
|
+
new value visible. Esc backs out one level (sub-picker → home → exit).
|
|
175
|
+
|
|
176
|
+
The "View raw config" row prints the scrubbed JSON (today's bare
|
|
177
|
+
`/config` behaviour) so users who prefer the JSON view can still get
|
|
178
|
+
it without leaving the panel.
|
|
179
|
+
|
|
180
|
+
## Persistence contract
|
|
181
|
+
|
|
182
|
+
One central writer per file. No scattered `fs.writeFileSync` calls.
|
|
183
|
+
|
|
184
|
+
| File | Wrapper | Owns |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| `~/.config/brainrouter/config.json` | `saveConfig(partial)` in [`config/config.ts`](../../brainrouter-cli/src/config/config.ts) — existing function, called with the merged result. | LLM provider, MCP profiles, active profile. |
|
|
187
|
+
| `<workspace>/.brainrouter/cli/preferences.json` | `writePreferences(workspaceRoot, partial)` in [`state/preferencesStore.ts`](../../brainrouter-cli/src/state/preferencesStore.ts) — existing function; already merges. | Theme, statusline, effort, exec mode, review policy, quiet, personality, editor, sandbox grants. |
|
|
188
|
+
| `~/.config/brainrouter/.onboarded` | New `markOnboarded()` helper alongside `config/config.ts`. | Empty file; presence = wizard has run at least once. |
|
|
189
|
+
|
|
190
|
+
The wizard accumulates intent in an in-memory `Draft` object across
|
|
191
|
+
all steps; the commit happens **once** at the Done step. Aborting via
|
|
192
|
+
`q` discards the draft.
|
|
193
|
+
|
|
194
|
+
## Picker primitive — additions to `cliPrompt.ts`
|
|
195
|
+
|
|
196
|
+
Today's `askChoice` is sufficient for confirm-only pickers; the
|
|
197
|
+
redesign needs two new behaviours we can fold in as optional fields:
|
|
198
|
+
|
|
199
|
+
1. **`onCursorChange(index)` callback** — fired by `reducePicker` after
|
|
200
|
+
an `up`/`down` keystroke if the option list moved. Theme picker
|
|
201
|
+
uses it to repaint the banner accent live.
|
|
202
|
+
2. **`prefilledOther?: string`** — when the wizard wants to drop the
|
|
203
|
+
user straight into the `awaitingOther` free-text input pre-filled
|
|
204
|
+
with the env-var value (so ENTER accepts the env-derived default,
|
|
205
|
+
editing overrides it).
|
|
206
|
+
|
|
207
|
+
Both are additive; existing callers keep working with `undefined`.
|
|
208
|
+
|
|
209
|
+
## Boundary rules (per `spec-driven-skill`)
|
|
210
|
+
|
|
211
|
+
- **Always:** route all settings persistence through
|
|
212
|
+
`saveConfig` / `writePreferences`; mask API keys to last 4 chars on
|
|
213
|
+
every render; pause the parent `rl` while a picker is active; close
|
|
214
|
+
with the cursor visible.
|
|
215
|
+
- **Ask first:** changing `~/.config/brainrouter/config.json` shape
|
|
216
|
+
(any new top-level key) — needs explicit user sign-off because it
|
|
217
|
+
surfaces in `/debug-config`.
|
|
218
|
+
- **Never:** write API keys to disk in plain text under the workspace
|
|
219
|
+
(only under `~/.config/brainrouter/`); silently overwrite an
|
|
220
|
+
existing config without confirmation; default to option 1 in
|
|
221
|
+
non-TTY mode (mirrors the existing `NoTTYError` contract).
|
|
222
|
+
|
|
223
|
+
## Definition of Done
|
|
224
|
+
|
|
225
|
+
1. `brainrouter` started against a clean `$HOME` (no `~/.config/brainrouter/`)
|
|
226
|
+
drops into the wizard automatically — no `brainrouter login`
|
|
227
|
+
needed.
|
|
228
|
+
2. `/init` re-runs the wizard at any time inside the REPL.
|
|
229
|
+
3. `/config` (bare) opens the settings home panel; arrow-keys
|
|
230
|
+
navigate; ENTER opens a sub-picker; Esc backs out.
|
|
231
|
+
4. `/config theme dark` sets theme to dark and prints the new value
|
|
232
|
+
without opening any picker.
|
|
233
|
+
5. Theme picker live-previews on cursor-change and restores the
|
|
234
|
+
original theme if the user presses Esc.
|
|
235
|
+
6. Tests: pure-function tests for the wizard reducer (`stepReducer`,
|
|
236
|
+
`Draft` shape, validation tier), `Vitest` tests for the
|
|
237
|
+
`/config` argument parser (bare / get / set), and one
|
|
238
|
+
`cliPrompt.ts` test for the new `onCursorChange` callback.
|
|
239
|
+
7. Docs: `brainrouter-docs/cli.md` and `brainrouter-docs/configuration.md`
|
|
240
|
+
are updated end-to-end. README's "First-time setup" section
|
|
241
|
+
collapses to "run `brainrouter`; the wizard takes over."
|
|
242
|
+
8. No regressions: `npm run test --workspace brainrouter-cli` stays
|
|
243
|
+
green; the legacy `brainrouter login` / `brainrouter config`
|
|
244
|
+
subcommands still work for users who scripted around them.
|
|
245
|
+
|
|
246
|
+
## Open questions
|
|
247
|
+
|
|
248
|
+
- **Should `/init` skip the wizard if the marker file exists**, or
|
|
249
|
+
always re-run? Current answer: always re-run when invoked
|
|
250
|
+
explicitly; only **auto-trigger on REPL start** when the marker is
|
|
251
|
+
missing. This matches Codex's "explicit `--login` always honoured"
|
|
252
|
+
contract.
|
|
253
|
+
- **Should the MCP step probe both stdio and HTTP**, or just the
|
|
254
|
+
user's pick? Current answer: just the pick. Probing both adds 5s
|
|
255
|
+
to the wizard for unclear payoff. Users can re-run `/init` if they
|
|
256
|
+
want to try the other transport.
|
|
257
|
+
- **Should the wizard offer to install missing system deps** (gh,
|
|
258
|
+
jq, ripgrep)? Current answer: no. Out of scope for v0.3.7. A
|
|
259
|
+
future `/doctor --fix` could handle it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinqs/brainrouter-mcp-server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "BrainRouter MCP server — the cognitive memory engine. Exposes recall, capture, focus scenes, persona, contradictions, skills, and graph queries as MCP tools for any MCP-speaking agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"setup:admin": "node scripts/setup-admin.js"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"@kinqs/brainrouter-types": "^0.3.8",
|
|
41
42
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
42
43
|
"dotenv": "^16.4.5",
|
|
43
44
|
"express": "^5.2.1",
|
|
44
45
|
"glob": "^11.0.0",
|
|
45
46
|
"gray-matter": "^4.0.3",
|
|
46
47
|
"sqlite-vec": "^0.1.9",
|
|
47
|
-
"zod": "^3.22.4"
|
|
48
|
-
"@kinqs/brainrouter-types": "^0.3.6"
|
|
48
|
+
"zod": "^3.22.4"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=22.0.0"
|