@kinqs/brainrouter-cli 0.3.5 → 0.3.7

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.
Files changed (125) hide show
  1. package/README.md +29 -52
  2. package/agents/architect.json +18 -0
  3. package/agents/explorer.json +18 -0
  4. package/agents/reviewer.json +18 -0
  5. package/agents/verifier.json +18 -0
  6. package/agents/worker.json +18 -0
  7. package/bin/cli.cjs +71 -0
  8. package/dist/agent/agent.d.ts +224 -3
  9. package/dist/agent/agent.js +561 -55
  10. package/dist/cli/banner.d.ts +80 -0
  11. package/dist/cli/banner.js +232 -0
  12. package/dist/cli/cliPrompt.d.ts +106 -0
  13. package/dist/cli/cliPrompt.js +314 -0
  14. package/dist/cli/commands/_context.d.ts +3 -1
  15. package/dist/cli/commands/_helpers.d.ts +1 -1
  16. package/dist/cli/commands/_helpers.js +6 -6
  17. package/dist/cli/commands/config.d.ts +46 -0
  18. package/dist/cli/commands/config.js +1042 -0
  19. package/dist/cli/commands/guard.js +75 -10
  20. package/dist/cli/commands/init.d.ts +20 -0
  21. package/dist/cli/commands/init.js +64 -0
  22. package/dist/cli/commands/login.d.ts +13 -0
  23. package/dist/cli/commands/login.js +179 -0
  24. package/dist/cli/commands/mcp.d.ts +19 -0
  25. package/dist/cli/commands/mcp.js +286 -0
  26. package/dist/cli/commands/memory.js +2 -2
  27. package/dist/cli/commands/obs.js +22 -22
  28. package/dist/cli/commands/orchestration.js +18 -0
  29. package/dist/cli/commands/session.js +13 -5
  30. package/dist/cli/commands/ui.js +202 -91
  31. package/dist/cli/commands/workflow.d.ts +20 -0
  32. package/dist/cli/commands/workflow.js +368 -51
  33. package/dist/cli/ink/ChatApp.d.ts +206 -0
  34. package/dist/cli/ink/ChatApp.js +493 -0
  35. package/dist/cli/ink/Frame.d.ts +26 -0
  36. package/dist/cli/ink/Frame.js +5 -0
  37. package/dist/cli/ink/Picker.d.ts +65 -0
  38. package/dist/cli/ink/Picker.js +133 -0
  39. package/dist/cli/ink/SlashPalette.d.ts +51 -0
  40. package/dist/cli/ink/SlashPalette.js +136 -0
  41. package/dist/cli/ink/TextField.d.ts +34 -0
  42. package/dist/cli/ink/TextField.js +47 -0
  43. package/dist/cli/ink/WizardApp.d.ts +7 -0
  44. package/dist/cli/ink/WizardApp.js +422 -0
  45. package/dist/cli/ink/ambientChat.d.ts +34 -0
  46. package/dist/cli/ink/ambientChat.js +7 -0
  47. package/dist/cli/ink/consoleCapture.d.ts +11 -0
  48. package/dist/cli/ink/consoleCapture.js +33 -0
  49. package/dist/cli/ink/markdownRender.d.ts +41 -0
  50. package/dist/cli/ink/markdownRender.js +278 -0
  51. package/dist/cli/ink/renderWithResizeClear.d.ts +14 -0
  52. package/dist/cli/ink/renderWithResizeClear.js +33 -0
  53. package/dist/cli/ink/runChat.d.ts +34 -0
  54. package/dist/cli/ink/runChat.js +571 -0
  55. package/dist/cli/ink/runPicker.d.ts +31 -0
  56. package/dist/cli/ink/runPicker.js +139 -0
  57. package/dist/cli/ink/runSlashPalette.d.ts +23 -0
  58. package/dist/cli/ink/runSlashPalette.js +33 -0
  59. package/dist/cli/ink/runWizard.d.ts +22 -0
  60. package/dist/cli/ink/runWizard.js +133 -0
  61. package/dist/cli/ink/stdinHandoff.d.ts +51 -0
  62. package/dist/cli/ink/stdinHandoff.js +78 -0
  63. package/dist/cli/ink/toolFormat.d.ts +73 -0
  64. package/dist/cli/ink/toolFormat.js +180 -0
  65. package/dist/cli/ink/useTerminalSize.d.ts +35 -0
  66. package/dist/cli/ink/useTerminalSize.js +26 -0
  67. package/dist/cli/repl.d.ts +25 -3
  68. package/dist/cli/repl.js +64 -646
  69. package/dist/cli/slashSuggest.d.ts +32 -0
  70. package/dist/cli/slashSuggest.js +146 -0
  71. package/dist/cli/spinner.d.ts +34 -0
  72. package/dist/cli/spinner.js +36 -0
  73. package/dist/cli/statusline.d.ts +67 -0
  74. package/dist/cli/statusline.js +204 -0
  75. package/dist/cli/theme.d.ts +79 -0
  76. package/dist/cli/theme.js +106 -0
  77. package/dist/cli/whereView.d.ts +81 -0
  78. package/dist/cli/whereView.js +245 -0
  79. package/dist/cli/wizard/modelsApi.d.ts +72 -0
  80. package/dist/cli/wizard/modelsApi.js +166 -0
  81. package/dist/cli/wizard/picker.d.ts +202 -0
  82. package/dist/cli/wizard/picker.js +547 -0
  83. package/dist/cli/wizard/providers.d.ts +86 -0
  84. package/dist/cli/wizard/providers.js +190 -0
  85. package/dist/cli/wizard/runner.d.ts +13 -0
  86. package/dist/cli/wizard/runner.js +488 -0
  87. package/dist/cli/wizard/types.d.ts +122 -0
  88. package/dist/cli/wizard/types.js +109 -0
  89. package/dist/config/config.d.ts +52 -0
  90. package/dist/config/config.js +89 -75
  91. package/dist/index.js +215 -206
  92. package/dist/memory/briefing.d.ts +11 -1
  93. package/dist/memory/briefing.js +69 -1
  94. package/dist/memory/consolidation.d.ts +1 -1
  95. package/dist/orchestration/agentRegistry.d.ts +36 -0
  96. package/dist/orchestration/agentRegistry.js +64 -0
  97. package/dist/orchestration/orchestrator.d.ts +7 -0
  98. package/dist/orchestration/orchestrator.js +2 -0
  99. package/dist/orchestration/tools.d.ts +10 -1
  100. package/dist/orchestration/tools.js +48 -4
  101. package/dist/prompt/breadthHint.d.ts +5 -0
  102. package/dist/prompt/breadthHint.js +44 -0
  103. package/dist/prompt/skillCatalog.d.ts +11 -0
  104. package/dist/prompt/skillCatalog.js +134 -0
  105. package/dist/prompt/skillRunner.d.ts +2 -2
  106. package/dist/prompt/skillRunner.js +2 -31
  107. package/dist/prompt/systemPrompt.d.ts +34 -0
  108. package/dist/prompt/systemPrompt.js +128 -108
  109. package/dist/runtime/dangerousCommand.d.ts +53 -0
  110. package/dist/runtime/dangerousCommand.js +105 -0
  111. package/dist/runtime/mcpClient.d.ts +38 -1
  112. package/dist/runtime/mcpClient.js +104 -13
  113. package/dist/runtime/mcpPool.d.ts +162 -0
  114. package/dist/runtime/mcpPool.js +423 -0
  115. package/dist/runtime/mcpUtils.d.ts +3 -1
  116. package/dist/state/goalStore.d.ts +98 -17
  117. package/dist/state/goalStore.js +132 -42
  118. package/dist/state/preferencesStore.d.ts +67 -3
  119. package/dist/state/preferencesStore.js +84 -1
  120. package/dist/state/workflowArtifacts.d.ts +63 -2
  121. package/dist/state/workflowArtifacts.js +120 -8
  122. package/dist/tests/_helpers.d.ts +31 -0
  123. package/dist/tests/_helpers.js +91 -0
  124. package/package.json +12 -5
  125. package/.env.example +0 -109
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { getCliStateFile, getWorkspaceLocalDir, isPathInside, readJsonFile, writeJsonFile } from './cliState.js';
3
+ import { getCliStateFile, getSessionStateFile, getWorkspaceLocalDir, isPathInside, readJsonFile, writeJsonFile } from './cliState.js';
4
4
  /**
5
5
  * Canonical home for durable workflow artifacts produced by the multi-agent
6
6
  * commands (/feature-dev, /spec, /review, /implement-plan).
@@ -18,12 +18,42 @@ import { getCliStateFile, getWorkspaceLocalDir, isPathInside, readJsonFile, writ
18
18
  * team shares them, and (b) the agent's `write_file` tool only accepts paths
19
19
  * relative to the workspace root. Personal CLI state (sessions, hooks,
20
20
  * memories, preferences) lives in `~/.brainrouter/workspaces/<encoded>/` and
21
- * never touches the project tree. The current-workflow pointer is still per-
22
- * user (it tracks which workflow YOU are focused on right now) so it lives
23
- * with the CLI state, not the workspace.
21
+ * never touches the project tree.
22
+ *
23
+ * **Workflows do NOT carry goal state.** Earlier 0.3.6 (Item 3) stored a
24
+ * `goal.json` inside each workflow folder so switching workflows would
25
+ * "carry the goal with it." That conflated two different concerns — a goal
26
+ * is **per-session runtime intent** (let the agent run autonomously until
27
+ * complete) while a workflow is **durable storage** (committed artifacts
28
+ * shared across users and CLIs). The conflation produced a cross-session
29
+ * goal leak (two CLIs in the same workspace bound to the same workflow
30
+ * shared a goal). The decoupling lives in goalStore.ts's `resolveGoalScope`
31
+ * — goals are always session-scoped, workflows are pure navigation. The
32
+ * per-session `workflow.json` pointer here is for "which folder am I
33
+ * writing artifacts to RIGHT NOW", not "which goal am I working on."
24
34
  */
25
35
  const WORKFLOWS_SUBDIR = 'workflows';
36
+ /**
37
+ * Workspace-level "last used workflow" pointer. Pre-9d-bugfix this was
38
+ * BOTH the source of truth for "which workflow is the current CLI
39
+ * session bound to" AND the display-only "what was the last workflow
40
+ * touched in this workspace" hint. The two responsibilities are now
41
+ * split: this file is the hint (any CLI in this workspace can see it),
42
+ * while `SESSION_POINTER_FILE` carries the per-session binding that
43
+ * actually drives goal scoping. The hint is still useful for the
44
+ * `/workflows` listing and for surfacing "you were last on X" in a
45
+ * fresh CLI without auto-binding it to that workflow's goal.
46
+ */
26
47
  const CURRENT_POINTER_FILE = 'current-workflow.json';
48
+ /**
49
+ * Per-session workflow binding (the actual source of truth for goal
50
+ * scoping). Lives under the session state directory so two CLIs in the
51
+ * same workspace can have independent workflows bound — fixes the
52
+ * "session A's `/feature-dev` automatically becomes session B's active
53
+ * workflow + active goal" leak that reintroduced Item 1's cross-session
54
+ * leak via the Item 3 workspace pointer.
55
+ */
56
+ const SESSION_POINTER_FILE = 'workflow.json';
27
57
  /** Canonical artifact names. Use the constants rather than hard-coded strings so a future rename is one edit. */
28
58
  export const ARTIFACT = {
29
59
  spec: 'spec.md',
@@ -58,6 +88,16 @@ export function getWorkflowDir(workspaceRoot, slug) {
58
88
  fs.mkdirSync(dir, { recursive: true });
59
89
  return dir;
60
90
  }
91
+ /**
92
+ * Create (or reopen) a workflow folder + bind it as the current
93
+ * workflow.
94
+ *
95
+ * `sessionKey` is threaded through to `setCurrentWorkflow` so that the
96
+ * created workflow is bound to THIS session (not to every other CLI
97
+ * session in the workspace via the workspace-level pointer). Legacy
98
+ * callers without a session context fall through to workspace-level
99
+ * binding only — same back-compat path `setCurrentWorkflow` provides.
100
+ */
61
101
  export function createWorkflow(workspaceRoot, input) {
62
102
  const slug = slugify(input.slug ?? input.title);
63
103
  const dir = getWorkflowDir(workspaceRoot, slug);
@@ -81,7 +121,7 @@ export function createWorkflow(workspaceRoot, input) {
81
121
  meta.kind = input.kind;
82
122
  }
83
123
  writeJsonFile(metaPath, meta);
84
- setCurrentWorkflow(workspaceRoot, slug);
124
+ setCurrentWorkflow(workspaceRoot, slug, input.sessionKey);
85
125
  return meta;
86
126
  }
87
127
  export function updateWorkflowStatus(workspaceRoot, slug, status) {
@@ -111,13 +151,85 @@ export function listWorkflows(workspaceRoot) {
111
151
  }
112
152
  return out.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? ''));
113
153
  }
114
- export function setCurrentWorkflow(workspaceRoot, slug) {
115
- writeJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), { slug, at: new Date().toISOString() });
154
+ /**
155
+ * Bind a workflow to the current CLI session AND update the workspace-
156
+ * level "last used" hint. When `sessionKey` is omitted (legacy callers,
157
+ * some first-run paths), only the workspace pointer is written — those
158
+ * callers don't have a session context yet, so per-session binding
159
+ * doesn't apply.
160
+ *
161
+ * The workspace pointer is updated unconditionally because we still
162
+ * want a fresh CLI in the same workspace to be ABLE to see "X is the
163
+ * last workflow that was touched here" — for display via
164
+ * `getLastUsedWorkflow`, for the `/workflows` listing's `★` marker, and
165
+ * for the post-9d "do you want to switch to <X>?" UX (the latter not
166
+ * yet shipped, tracked separately).
167
+ */
168
+ export function setCurrentWorkflow(workspaceRoot, slug, sessionKey) {
169
+ const ts = new Date().toISOString();
170
+ writeJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), { slug, at: ts });
171
+ if (sessionKey) {
172
+ writeJsonFile(getSessionStateFile(workspaceRoot, sessionKey, SESSION_POINTER_FILE), { slug, at: ts });
173
+ }
116
174
  }
117
- export function getCurrentWorkflow(workspaceRoot) {
175
+ /**
176
+ * Which workflow is bound to THIS CLI session?
177
+ *
178
+ * - With `sessionKey`: reads ONLY the session-level pointer. A fresh
179
+ * CLI session has no session-level pointer → returns `undefined`,
180
+ * even when a workspace-level "last used" hint exists. This is the
181
+ * load-bearing fix: new sessions don't auto-inherit another session's
182
+ * workflow binding (which previously dragged that workflow's goal
183
+ * into the new session via `resolveGoalScope`).
184
+ * - Without `sessionKey` (legacy / display-only callers): falls back
185
+ * to the workspace-level pointer for back-compat.
186
+ *
187
+ * Display surfaces that want to show "the last workflow touched here,
188
+ * regardless of session binding" should call `getLastUsedWorkflow`
189
+ * instead so the distinction stays explicit.
190
+ */
191
+ export function getCurrentWorkflow(workspaceRoot, sessionKey) {
192
+ if (sessionKey) {
193
+ const sessionPtr = readJsonFile(getSessionStateFile(workspaceRoot, sessionKey, SESSION_POINTER_FILE), null);
194
+ return sessionPtr?.slug || undefined;
195
+ }
196
+ const ptr = readJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), null);
197
+ return ptr?.slug;
198
+ }
199
+ /**
200
+ * Display-only "last workflow used in this workspace" lookup. Reads
201
+ * the workspace-level pointer unconditionally — never consults the
202
+ * session-level binding. Use when you want to render a hint like
203
+ * "you were last on workflow X" without implying that the current
204
+ * session is bound to it.
205
+ */
206
+ export function getLastUsedWorkflow(workspaceRoot) {
118
207
  const ptr = readJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), null);
119
208
  return ptr?.slug;
120
209
  }
210
+ /**
211
+ * Clear the session-level workflow binding (workspace-level hint
212
+ * preserved). Used by `/new` and `/fork` so a freshly-forked session
213
+ * doesn't drag the parent's binding along.
214
+ */
215
+ export function clearSessionWorkflow(workspaceRoot, sessionKey) {
216
+ const pointerPath = getSessionStateFile(workspaceRoot, sessionKey, SESSION_POINTER_FILE);
217
+ try {
218
+ fs.unlinkSync(pointerPath);
219
+ }
220
+ catch { /* idempotent — no file to remove is fine */ }
221
+ }
222
+ /**
223
+ * True iff a workflow folder with the given slug exists (and carries a
224
+ * meta.json). Used by `/workflow switch <slug>` to surface "no such
225
+ * workflow" without the side-effect mkdir that `getWorkflowDir` performs.
226
+ */
227
+ export function workflowExists(workspaceRoot, slug) {
228
+ const safeSlug = slugify(slug);
229
+ const root = getWorkflowsRoot(workspaceRoot);
230
+ const candidate = path.join(root, safeSlug, 'meta.json');
231
+ return fs.existsSync(candidate);
232
+ }
121
233
  /**
122
234
  * Path (relative to workspace root) the LLM should `write_file` to for a
123
235
  * given artifact. We return a workspace-relative path because that's the
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared fixtures + harness for the split-up CLI test suites.
3
+ *
4
+ * Lives under `src/tests/` alongside the actual `*.test.ts` files. The
5
+ * leading underscore is convention only — the test runner picks up files by
6
+ * the `*.test.js` glob, so a non-test filename is enough to keep node:test
7
+ * from trying to execute this module as a suite.
8
+ *
9
+ * Everything here was lifted verbatim out of the original `src/agent.test.ts`
10
+ * during the split. Don't add new fixtures unless they're used by ≥2 files.
11
+ */
12
+ import { Agent } from '../agent/agent.js';
13
+ /**
14
+ * Construct an Agent without touching MCP or the LLM. Only safe for tests
15
+ * that exercise pure state-machine extensions (model, accessMode, history,
16
+ * fork, refreshSystemPrompt) — anything that triggers `bootstrapSession`
17
+ * will hit the stub MCP and either no-op or surface a misleading error.
18
+ */
19
+ export declare function makeAgent(workspace: string): Agent;
20
+ /**
21
+ * Run a synchronous test body inside a fresh temp workspace. Restores cwd,
22
+ * BRAINROUTER_WORKSPACE, and BRAINROUTER_HOME afterwards. BRAINROUTER_HOME
23
+ * is also pinned to a sibling tmp dir so tests never touch the real
24
+ * `~/.brainrouter` on the developer's machine.
25
+ */
26
+ export declare function withTempWorkspace(fn: (workspace: string) => void): void;
27
+ /**
28
+ * Async sibling of `withTempWorkspace`. Same restore semantics; awaits the
29
+ * body so promise rejections still tear the workspace down.
30
+ */
31
+ export declare function withTempWorkspaceAsync<T>(fn: (workspace: string) => Promise<T>): Promise<T>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared fixtures + harness for the split-up CLI test suites.
3
+ *
4
+ * Lives under `src/tests/` alongside the actual `*.test.ts` files. The
5
+ * leading underscore is convention only — the test runner picks up files by
6
+ * the `*.test.js` glob, so a non-test filename is enough to keep node:test
7
+ * from trying to execute this module as a suite.
8
+ *
9
+ * Everything here was lifted verbatim out of the original `src/agent.test.ts`
10
+ * during the split. Don't add new fixtures unless they're used by ≥2 files.
11
+ */
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { Agent } from '../agent/agent.js';
16
+ /**
17
+ * Construct an Agent without touching MCP or the LLM. Only safe for tests
18
+ * that exercise pure state-machine extensions (model, accessMode, history,
19
+ * fork, refreshSystemPrompt) — anything that triggers `bootstrapSession`
20
+ * will hit the stub MCP and either no-op or surface a misleading error.
21
+ */
22
+ export function makeAgent(workspace) {
23
+ const stubMcp = {
24
+ listTools: async () => ({ tools: [] }),
25
+ callTool: async () => ({ content: [{ text: '{}' }] }),
26
+ close: async () => { },
27
+ };
28
+ const llm = { provider: 'openai', apiKey: 'k', model: 'test-model' };
29
+ return new Agent(stubMcp, llm, {
30
+ workspaceRoot: workspace,
31
+ launchCwd: workspace,
32
+ sessionKey: 'session:test',
33
+ silent: true, // skip bootstrap + briefing so we don't touch MCP at all
34
+ });
35
+ }
36
+ /**
37
+ * Run a synchronous test body inside a fresh temp workspace. Restores cwd,
38
+ * BRAINROUTER_WORKSPACE, and BRAINROUTER_HOME afterwards. BRAINROUTER_HOME
39
+ * is also pinned to a sibling tmp dir so tests never touch the real
40
+ * `~/.brainrouter` on the developer's machine.
41
+ */
42
+ export function withTempWorkspace(fn) {
43
+ const previousCwd = process.cwd();
44
+ const previousWorkspace = process.env.BRAINROUTER_WORKSPACE;
45
+ const previousHome = process.env.BRAINROUTER_HOME;
46
+ const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'brainrouter-cli-'));
47
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'brainrouter-home-'));
48
+ try {
49
+ delete process.env.BRAINROUTER_WORKSPACE;
50
+ process.env.BRAINROUTER_HOME = home;
51
+ process.chdir(workspace);
52
+ fn(workspace);
53
+ }
54
+ finally {
55
+ process.chdir(previousCwd);
56
+ if (previousWorkspace === undefined)
57
+ delete process.env.BRAINROUTER_WORKSPACE;
58
+ else
59
+ process.env.BRAINROUTER_WORKSPACE = previousWorkspace;
60
+ if (previousHome === undefined)
61
+ delete process.env.BRAINROUTER_HOME;
62
+ else
63
+ process.env.BRAINROUTER_HOME = previousHome;
64
+ fs.rmSync(workspace, { recursive: true, force: true });
65
+ fs.rmSync(home, { recursive: true, force: true });
66
+ }
67
+ }
68
+ /**
69
+ * Async sibling of `withTempWorkspace`. Same restore semantics; awaits the
70
+ * body so promise rejections still tear the workspace down.
71
+ */
72
+ export async function withTempWorkspaceAsync(fn) {
73
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'brainrouter-test-'));
74
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'brainrouter-home-'));
75
+ const previousCwd = process.cwd();
76
+ const previousHome = process.env.BRAINROUTER_HOME;
77
+ process.env.BRAINROUTER_HOME = home;
78
+ process.chdir(tmp);
79
+ try {
80
+ return await fn(tmp);
81
+ }
82
+ finally {
83
+ process.chdir(previousCwd);
84
+ if (previousHome === undefined)
85
+ delete process.env.BRAINROUTER_HOME;
86
+ else
87
+ process.env.BRAINROUTER_HOME = previousHome;
88
+ fs.rmSync(tmp, { recursive: true, force: true });
89
+ fs.rmSync(home, { recursive: true, force: true });
90
+ }
91
+ }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@kinqs/brainrouter-cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Memory-native terminal coding agent. Talks to the BrainRouter MCP cognitive engine for recall, skills, capture, persona, focus scenes, and contradiction tracking.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "brainrouter": "dist/index.js"
8
+ "brainrouter": "bin/cli.cjs"
9
9
  },
10
10
  "files": [
11
+ "agents",
12
+ "bin",
11
13
  "dist",
12
14
  "README.md",
13
15
  ".env.example"
@@ -21,21 +23,26 @@
21
23
  "prepack": "npm run build && find dist -name '*.test.*' -delete"
22
24
  },
23
25
  "dependencies": {
24
- "@kinqs/brainrouter-sdk": "^0.3.5",
25
- "@kinqs/brainrouter-types": "^0.3.5",
26
+ "@kinqs/brainrouter-sdk": "^0.3.6",
27
+ "@kinqs/brainrouter-types": "^0.3.6",
26
28
  "@modelcontextprotocol/sdk": "^1.11.0",
27
29
  "chalk": "^5.3.0",
28
30
  "commander": "^12.1.0",
29
31
  "dotenv": "^16.4.5",
32
+ "ink": "^7.0.3",
33
+ "ink-spinner": "^5.0.0",
34
+ "ink-text-input": "^6.0.0",
30
35
  "inquirer": "^9.3.2",
31
36
  "marked": "^12.0.1",
32
37
  "marked-terminal": "^7.0.0",
33
- "ora": "^8.0.1"
38
+ "ora": "^8.0.1",
39
+ "react": "^19.2.6"
34
40
  },
35
41
  "devDependencies": {
36
42
  "@types/inquirer": "^9.0.7",
37
43
  "@types/marked": "^4.0.8",
38
44
  "@types/node": "^22.0.0",
45
+ "@types/react": "^19.2.15",
39
46
  "tsx": "^4.7.0",
40
47
  "typescript": "^5.5.4"
41
48
  },
package/.env.example DELETED
@@ -1,109 +0,0 @@
1
- # BrainRouter CLI agent — environment
2
- #
3
- # Copy to brainrouter-cli/.env. Loaded by the CLI at startup.
4
- #
5
- # This file is for CLI-AGENT concerns only:
6
- # - chat LLM the terminal agent talks to
7
- # - tool runtime knobs (loop limit, result clamp, MCP timeout)
8
- # - auto-compaction trigger
9
- # - sandbox configuration for run_command
10
- # - web search backend
11
- # - trace logging
12
- # - workspace override
13
- #
14
- # MCP-server concerns (cognitive extraction, embeddings, reranker, memory
15
- # engine knobs, server auth) live in brainrouter/.env.example.
16
- #
17
- # Why split: the MCP and the CLI are separate processes with different
18
- # concerns. The CLI's chat LLM can be a smart cloud model while the MCP's
19
- # cognitive extractor is a cheap local one. Their concurrency caps differ
20
- # too (CLI default 4, MCP default 2). Keep them independent.
21
-
22
- # ==========================================
23
- # Chat LLM (for the agent's own conversation)
24
- # ==========================================
25
- # Same var names as the MCP, but a separate process — set them here for the
26
- # CLI's chat model. Falls back to OPENAI_API_KEY.
27
- #
28
- # If you don't set BRAINROUTER_LLM_API_KEY here, the CLI also reads it from
29
- # brainrouter/.env as a transitional fallback. Setting it here makes the
30
- # CLI's choice explicit and lets you use a different chat model than the
31
- # MCP's extractor (e.g. gpt-4o for chat, gpt-4o-mini for extraction).
32
- BRAINROUTER_LLM_API_KEY=your_api_key_here
33
-
34
- # OpenAI-compatible chat-completions endpoint.
35
- # Examples:
36
- # OpenAI: https://api.openai.com/v1/chat/completions
37
- # OpenRouter: https://openrouter.ai/api/v1/chat/completions
38
- # Anthropic via OpenRouter: anthropic/claude-sonnet-4
39
- # LM Studio: http://localhost:1234/v1/chat/completions
40
- BRAINROUTER_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions
41
-
42
- BRAINROUTER_LLM_MODEL=gpt-4o-mini
43
-
44
- # Per-call timeout for the CLI's chat LLM. Default: 120000.
45
- # BRAINROUTER_LLM_TIMEOUT_MS=120000
46
-
47
- # Cap on concurrent in-flight chat LLM calls FROM THE CLI PROCESS.
48
- # Default: 4 (separate from the MCP's own cap). Set to 1 for consumer-grade
49
- # local backends; crank to 16+ for cloud APIs.
50
- # BRAINROUTER_LLM_MAX_CONCURRENT=4
51
-
52
- # ==========================================
53
- # Tool runtime
54
- # ==========================================
55
- # Per-tool timeout for CLI → MCP requests. Default: 60000.
56
- # BRAINROUTER_MCP_TIMEOUT_MS=60000
57
-
58
- # LLM-visible clamp on a single tool-result body (full text still recorded
59
- # in the transcript on disk). Default: 8000.
60
- # BRAINROUTER_MAX_TOOL_RESULT_CHARS=8000
61
-
62
- # Hard ceiling on tool-call iterations per turn. Default: 60.
63
- # BRAINROUTER_MAX_TOOL_LOOPS=60
64
-
65
- # Estimated history-size trigger for auto-`/compact`. Default: 80000 tokens.
66
- # BRAINROUTER_AUTO_COMPACT_TOKENS=80000
67
-
68
- # ==========================================
69
- # Sandbox (run_command)
70
- # ==========================================
71
- # Wrap shell commands in the platform sandbox:
72
- # macOS: sandbox-exec
73
- # Linux: bwrap (preferred) or firejail
74
- # Set 'on' to enable. Off by default.
75
- # BRAINROUTER_SANDBOX=on
76
-
77
- # Allow outbound network from sandboxed commands. Off by default.
78
- # BRAINROUTER_SANDBOX_NETWORK=off
79
-
80
- # Colon-separated read/write path allowlists.
81
- # BRAINROUTER_SANDBOX_READ_PATHS=/usr/local:/opt
82
- # BRAINROUTER_SANDBOX_WRITE_PATHS=/tmp
83
-
84
- # ==========================================
85
- # Workspace
86
- # ==========================================
87
- # Override workspace root the CLI uses for file tools + session key.
88
- # Most users let the CLI auto-detect via git/closest package.json.
89
- # BRAINROUTER_WORKSPACE=/path/to/project
90
-
91
- # Override per-user state root. Default: ~/.brainrouter.
92
- # Both the CLI and MCP honor this — set it once and both processes use it.
93
- # BRAINROUTER_HOME=/path/to/state
94
-
95
- # ==========================================
96
- # Web search
97
- # ==========================================
98
- # Custom search backend for the web_search tool. Must accept
99
- # POST { query, maxResults } → { results: [{ title, url, snippet }] }.
100
- # Falls back to DuckDuckGo's Instant Answer API when unset.
101
- # Compatible with Brave Search API wrappers, Tavily, SerpAPI proxies.
102
- # BRAINROUTER_WEB_SEARCH_ENDPOINT=https://your-search-proxy.example.com/search
103
-
104
- # ==========================================
105
- # Observability
106
- # ==========================================
107
- # Path for OTEL-style JSONL turn traces. One line per turn/tool span.
108
- # Toggle at runtime with /trace on|off.
109
- # BRAINROUTER_TRACE_LOG=/path/to/trace.jsonl