@kinqs/brainrouter-cli 0.3.6 → 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 (96) 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/dist/agent/agent.d.ts +12 -1
  8. package/dist/agent/agent.js +134 -18
  9. package/dist/cli/banner.d.ts +20 -0
  10. package/dist/cli/banner.js +47 -14
  11. package/dist/cli/cliPrompt.d.ts +40 -3
  12. package/dist/cli/cliPrompt.js +52 -25
  13. package/dist/cli/commands/_context.d.ts +3 -1
  14. package/dist/cli/commands/_helpers.d.ts +1 -1
  15. package/dist/cli/commands/config.d.ts +46 -0
  16. package/dist/cli/commands/config.js +1042 -0
  17. package/dist/cli/commands/init.d.ts +20 -0
  18. package/dist/cli/commands/init.js +64 -0
  19. package/dist/cli/commands/login.d.ts +13 -0
  20. package/dist/cli/commands/login.js +179 -0
  21. package/dist/cli/commands/mcp.d.ts +13 -11
  22. package/dist/cli/commands/mcp.js +239 -74
  23. package/dist/cli/commands/orchestration.js +18 -0
  24. package/dist/cli/commands/ui.js +117 -58
  25. package/dist/cli/commands/workflow.d.ts +2 -0
  26. package/dist/cli/commands/workflow.js +54 -8
  27. package/dist/cli/ink/ChatApp.d.ts +206 -0
  28. package/dist/cli/ink/ChatApp.js +493 -0
  29. package/dist/cli/ink/Frame.d.ts +26 -0
  30. package/dist/cli/ink/Frame.js +5 -0
  31. package/dist/cli/ink/Picker.d.ts +65 -0
  32. package/dist/cli/ink/Picker.js +133 -0
  33. package/dist/cli/ink/SlashPalette.d.ts +51 -0
  34. package/dist/cli/ink/SlashPalette.js +136 -0
  35. package/dist/cli/ink/TextField.d.ts +34 -0
  36. package/dist/cli/ink/TextField.js +47 -0
  37. package/dist/cli/ink/WizardApp.d.ts +7 -0
  38. package/dist/cli/ink/WizardApp.js +422 -0
  39. package/dist/cli/ink/ambientChat.d.ts +34 -0
  40. package/dist/cli/ink/ambientChat.js +7 -0
  41. package/dist/cli/ink/consoleCapture.d.ts +11 -0
  42. package/dist/cli/ink/consoleCapture.js +33 -0
  43. package/dist/cli/ink/markdownRender.d.ts +41 -0
  44. package/dist/cli/ink/markdownRender.js +278 -0
  45. package/dist/cli/ink/renderWithResizeClear.d.ts +14 -0
  46. package/dist/cli/ink/renderWithResizeClear.js +33 -0
  47. package/dist/cli/ink/runChat.d.ts +34 -0
  48. package/dist/cli/ink/runChat.js +571 -0
  49. package/dist/cli/ink/runPicker.d.ts +31 -0
  50. package/dist/cli/ink/runPicker.js +139 -0
  51. package/dist/cli/ink/runSlashPalette.d.ts +23 -0
  52. package/dist/cli/ink/runSlashPalette.js +33 -0
  53. package/dist/cli/ink/runWizard.d.ts +22 -0
  54. package/dist/cli/ink/runWizard.js +133 -0
  55. package/dist/cli/ink/stdinHandoff.d.ts +51 -0
  56. package/dist/cli/ink/stdinHandoff.js +78 -0
  57. package/dist/cli/ink/toolFormat.d.ts +73 -0
  58. package/dist/cli/ink/toolFormat.js +180 -0
  59. package/dist/cli/ink/useTerminalSize.d.ts +35 -0
  60. package/dist/cli/ink/useTerminalSize.js +26 -0
  61. package/dist/cli/repl.d.ts +25 -3
  62. package/dist/cli/repl.js +43 -712
  63. package/dist/cli/slashSuggest.d.ts +32 -0
  64. package/dist/cli/slashSuggest.js +146 -0
  65. package/dist/cli/wizard/modelsApi.d.ts +72 -0
  66. package/dist/cli/wizard/modelsApi.js +166 -0
  67. package/dist/cli/wizard/picker.d.ts +202 -0
  68. package/dist/cli/wizard/picker.js +547 -0
  69. package/dist/cli/wizard/providers.d.ts +86 -0
  70. package/dist/cli/wizard/providers.js +190 -0
  71. package/dist/cli/wizard/runner.d.ts +13 -0
  72. package/dist/cli/wizard/runner.js +488 -0
  73. package/dist/cli/wizard/types.d.ts +122 -0
  74. package/dist/cli/wizard/types.js +109 -0
  75. package/dist/config/config.d.ts +12 -0
  76. package/dist/config/config.js +45 -3
  77. package/dist/index.js +148 -206
  78. package/dist/memory/briefing.d.ts +1 -1
  79. package/dist/memory/consolidation.d.ts +1 -1
  80. package/dist/orchestration/agentRegistry.d.ts +36 -0
  81. package/dist/orchestration/agentRegistry.js +64 -0
  82. package/dist/orchestration/orchestrator.d.ts +7 -0
  83. package/dist/orchestration/orchestrator.js +2 -0
  84. package/dist/orchestration/tools.d.ts +10 -1
  85. package/dist/orchestration/tools.js +48 -4
  86. package/dist/prompt/skillCatalog.d.ts +11 -0
  87. package/dist/prompt/skillCatalog.js +134 -0
  88. package/dist/prompt/skillRunner.d.ts +2 -2
  89. package/dist/prompt/skillRunner.js +2 -31
  90. package/dist/prompt/systemPrompt.js +5 -1
  91. package/dist/runtime/mcpClient.js +14 -11
  92. package/dist/runtime/mcpPool.d.ts +162 -0
  93. package/dist/runtime/mcpPool.js +423 -0
  94. package/dist/runtime/mcpUtils.d.ts +3 -1
  95. package/package.json +8 -2
  96. package/.env.example +0 -116
@@ -17,6 +17,7 @@ import { askYesNo } from '../cliPrompt.js';
17
17
  import { formatPlan, readPlan, updatePlan } from '../../state/taskStore.js';
18
18
  import { getLoopState, parseInterval, startLoop, stopLoop } from '../../runtime/loopRunner.js';
19
19
  import { SLASH_TO_SKILL } from '../../prompt/skillRunner.js';
20
+ import { listFilesystemSkills, mergeSkillLists } from '../../prompt/skillCatalog.js';
20
21
  import { buildGoalKickoffPrompt, runSkillByName, runSkillCommand } from './_helpers.js';
21
22
  // Promise-flavored exec for case bodies that shell out.
22
23
  const execPromise = promisify(exec);
@@ -83,16 +84,29 @@ export async function tryHandleWorkflowCommand(ctx) {
83
84
  switch (command) {
84
85
  case '/skills':
85
86
  {
87
+ const verbose = args.includes('--verbose') || args.includes('-v');
86
88
  const spinner = makeSpinner(chalk.gray('Fetching skills...')).start();
87
89
  try {
88
90
  const res = await callMcpTool(mcpClient, 'list_skills', { scope: 'all' });
89
91
  spinner.stop();
90
- if (!res.isError && Array.isArray(res.parsed)) {
91
- const skillsList = res.parsed;
92
- console.log(chalk.bold('\n🧠 BrainRouter Skills:'));
92
+ const mcpSkills = !res.isError ? normalizeSkillsList(res.parsed) : undefined;
93
+ const filesystemSkills = listFilesystemSkills(agent.workspaceRoot);
94
+ const skillsList = mcpSkills
95
+ ? mergeSkillLists(mcpSkills, filesystemSkills)
96
+ : filesystemSkills;
97
+ if (skillsList) {
98
+ console.log(chalk.bold(`\n🧠 BrainRouter Skills (${skillsList.length}):`));
93
99
  if (skillsList.length > 0) {
94
100
  for (const skill of skillsList) {
95
- console.log(` • ${chalk.cyan(skill.name)} (${chalk.gray(skill.scope)}) - ${skill.description}`);
101
+ const category = skill.category ? `${skill.category}/` : '';
102
+ const suffix = verbose && skill.description ? ` - ${skill.description}` : '';
103
+ console.log(` • ${chalk.cyan(`${category}${skill.name}`)} (${chalk.gray(skill.scope ?? 'unknown')})${suffix}`);
104
+ }
105
+ if (mcpSkills && skillsList.length > mcpSkills.length) {
106
+ console.log(chalk.gray(` Showing ${skillsList.length} skills (${mcpSkills.length} from MCP, ${skillsList.length - mcpSkills.length} filled from local files).`));
107
+ }
108
+ else if (!mcpSkills && filesystemSkills.length > 0) {
109
+ console.log(chalk.gray(' MCP list unavailable; showing local filesystem skills.'));
96
110
  }
97
111
  }
98
112
  else {
@@ -101,6 +115,8 @@ export async function tryHandleWorkflowCommand(ctx) {
101
115
  }
102
116
  else {
103
117
  console.log(chalk.red('\nFailed to parse skills list response.'));
118
+ if (res.text)
119
+ console.log(chalk.gray(` ${res.text.slice(0, 300)}`));
104
120
  }
105
121
  }
106
122
  catch (err) {
@@ -112,24 +128,29 @@ export async function tryHandleWorkflowCommand(ctx) {
112
128
  }
113
129
  case '/tools':
114
130
  {
115
- console.log(chalk.bold('\nLocal Workspace Tools:'));
131
+ const verbose = args.includes('--verbose') || args.includes('-v');
132
+ console.log(chalk.bold(`\nLocal Workspace Tools (${LOCAL_TOOLS.length}):`));
116
133
  for (const tool of LOCAL_TOOLS) {
117
- console.log(` ${chalk.cyan(tool.name)} - ${tool.description}`);
134
+ const suffix = verbose ? ` - ${tool.description}` : '';
135
+ console.log(` • ${chalk.cyan(tool.name)}${suffix}`);
118
136
  }
119
137
  const spinner = makeSpinner(chalk.gray('Fetching MCP tools...')).start();
120
138
  try {
121
139
  const res = await mcpClient.listTools();
122
140
  spinner.stop();
123
141
  const tools = res.tools || [];
124
- console.log(chalk.bold('\nMCP Tools:'));
142
+ console.log(chalk.bold(`\nMCP Tools (${tools.length}):`));
125
143
  if (tools.length === 0) {
126
144
  console.log(chalk.yellow(' No MCP tools exposed by the active server.'));
127
145
  }
128
146
  else {
129
147
  for (const tool of tools) {
130
- console.log(` ${chalk.cyan(tool.name)} - ${tool.description || 'No description'}`);
148
+ const suffix = verbose ? ` - ${tool.description || 'No description'}` : '';
149
+ console.log(` • ${chalk.cyan(tool.name)}${suffix}`);
131
150
  }
132
151
  }
152
+ if (!verbose)
153
+ console.log(chalk.gray(' Use /tools --verbose to include descriptions.'));
133
154
  }
134
155
  catch (err) {
135
156
  spinner.fail(chalk.red('Failed to list MCP tools.'));
@@ -960,3 +981,28 @@ export async function tryHandleWorkflowCommand(ctx) {
960
981
  }
961
982
  return false;
962
983
  }
984
+ export function normalizeSkillsList(payload) {
985
+ const list = Array.isArray(payload)
986
+ ? payload
987
+ : Array.isArray(payload?.skills)
988
+ ? payload.skills
989
+ : Array.isArray(payload?.items)
990
+ ? payload.items
991
+ : Array.isArray(payload?.results)
992
+ ? payload.results
993
+ : undefined;
994
+ if (!Array.isArray(list))
995
+ return undefined;
996
+ return list
997
+ .filter((item) => item && typeof item === 'object' && typeof item.name === 'string')
998
+ .map((item) => {
999
+ const normalized = { name: item.name };
1000
+ if (typeof item.scope === 'string')
1001
+ normalized.scope = item.scope;
1002
+ if (typeof item.category === 'string')
1003
+ normalized.category = item.category;
1004
+ if (typeof item.description === 'string')
1005
+ normalized.description = item.description;
1006
+ return normalized;
1007
+ });
1008
+ }
@@ -0,0 +1,206 @@
1
+ import React from 'react';
2
+ import { type SlashCommandDef } from './SlashPalette.js';
3
+ /**
4
+ * Ink-based chat REPL — replaces the readline-based `startREPL` shell.
5
+ *
6
+ * Layout (matches claude-code's chrome):
7
+ *
8
+ * ┌─────────────────────────────────────────────────────────────┐
9
+ * │ banner (one-time, at top of scrollback) │
10
+ * │ ⏺ assistant turn 1 │
11
+ * │ ⎿ tool call result │
12
+ * │ ❯ user: what about X? │
13
+ * │ ⏺ assistant turn 2 │
14
+ * │ ... │
15
+ * │ │
16
+ * ├──────────────────────────────────────────────────────────────┤
17
+ * │ ❯ <input cursor here> │ ← composer
18
+ * ├──────────────────────────────────────────────────────────────┤
19
+ * │ model · session · ◉ effort ? for shortcuts │ ← footer
20
+ * └──────────────────────────────────────────────────────────────┘
21
+ *
22
+ * Scrollback stays in Ink's normal render tree. It is tempting to use
23
+ * `<Static>` for finished entries, but a chat shell has a permanently
24
+ * live composer below it; on terminal resize, the Static/dynamic split
25
+ * can leave old composer frames behind because the resize clear only
26
+ * applies to Ink's live region. Keeping the full frame diffable makes
27
+ * resize redraw exactly one prompt block.
28
+ *
29
+ * Slash palette is a child component: when the input buffer becomes
30
+ * `/`, the palette renders BELOW the composer with the filtered
31
+ * command list. No more readline detach/Ink mount cycle — Ink owns
32
+ * stdin for the entire REPL lifetime.
33
+ *
34
+ * State machine:
35
+ * - phase: 'idle' | 'turn-running' | 'side-conversation'
36
+ * - scrollback: ScrollbackEntry[] — completed entries (banner, turns, slash output)
37
+ * - composerValue: string — current input buffer
38
+ * - palette: 'closed' | 'open' — visible when value starts with `/`
39
+ */
40
+ export interface ChatAppProps {
41
+ initialBanner: string;
42
+ initialOfflineWarning?: string;
43
+ initialHint: string;
44
+ /** Static description of the slash commands the user can run. */
45
+ slashCommands: SlashCommandDef[];
46
+ /** Initial prompt label, e.g. "brainrouter[effort:low]". */
47
+ promptLabel: string;
48
+ /** Accent color (hex) for chrome. */
49
+ accentColor?: string;
50
+ /** Called when the user submits a line (slash command OR free-form prompt). */
51
+ onSubmit: (text: string, push: PushScrollback) => Promise<void>;
52
+ /**
53
+ * Imperative hook — invoked once during mount with a controller object the
54
+ * orchestrator can use to push scrollback / footer updates from outside the
55
+ * React tree (e.g. when the parent-turn closure wants to print a side-channel
56
+ * message after `agent.runTurn` resolved but before the next prompt cycle).
57
+ */
58
+ onReady?: (controller: ChatController) => void;
59
+ /**
60
+ * Cycle the access mode (read → write → shell → read). Returned label is
61
+ * appended to the footer pill. Called when the user presses Shift+Tab.
62
+ */
63
+ onAccessModeCycle?: () => string;
64
+ /**
65
+ * Initial access mode for the footer pill — kept in sync via
66
+ * `controller.setFooter({ accessMode })`. Defaults to 'read'.
67
+ */
68
+ initialAccessMode?: 'read' | 'write' | 'shell';
69
+ /**
70
+ * Initial extra footer segments (model, session, effort, branch). Updated
71
+ * after each turn via `controller.setFooter`.
72
+ */
73
+ initialFooter?: FooterState;
74
+ }
75
+ export interface FooterState {
76
+ /** e.g. "gpt-4o-mini". */
77
+ model?: string;
78
+ /** e.g. "rep-2026-…-abc123". Truncated for display. */
79
+ session?: string;
80
+ /** e.g. "main". */
81
+ branch?: string;
82
+ /** "low" | "medium" | "high". Rendered as a pill. */
83
+ effort?: string;
84
+ /** Free-form right-side text (statusline segments). */
85
+ rightExtra?: string;
86
+ }
87
+ export interface ChatController {
88
+ /** Push entries from outside the React tree (e.g. after the parent turn ended). */
89
+ push: PushScrollback;
90
+ /** Replace the startup banner row without clearing the chat scrollback. */
91
+ replaceBanner: (text: string) => void;
92
+ /** Update the footer status row (model, session, access mode, effort, etc.). */
93
+ setFooter: (patch: Partial<FooterState & {
94
+ accessMode: 'read' | 'write' | 'shell';
95
+ }>) => void;
96
+ /** Programmatically inject text into the composer (e.g. workflow.ts loop tick). */
97
+ setComposer: (text: string) => void;
98
+ /**
99
+ * Render `node` as an overlay above the chat composer. The composer
100
+ * hides while the overlay is active so the overlay's own useInput
101
+ * handlers own keystrokes. Used by `runPicker` / `runTextField` to
102
+ * show /config, /login, /init pickers WITHOUT mounting a second Ink
103
+ * instance (which would race the chat for stdin and terminal state).
104
+ * Promise resolves when `clearOverlay()` is called.
105
+ */
106
+ showOverlay: (node: React.ReactElement) => Promise<void>;
107
+ /** Remove whatever overlay is currently shown; safe to call when none is set. */
108
+ clearOverlay: () => void;
109
+ /** Exit the chat app gracefully. */
110
+ exit: () => void;
111
+ }
112
+ export type ScrollbackEntry = {
113
+ id: number;
114
+ kind: 'raw';
115
+ text: string;
116
+ noWrap?: boolean;
117
+ } | {
118
+ id: number;
119
+ kind: 'user';
120
+ text: string;
121
+ } | {
122
+ id: number;
123
+ kind: 'assistant';
124
+ text: string;
125
+ raw?: boolean;
126
+ durationMs?: number;
127
+ tokensIn?: number;
128
+ tokensOut?: number;
129
+ calls?: number;
130
+ }
131
+ /**
132
+ * Tool call result row — claude-code style:
133
+ * ⏺ Read(src/foo.ts) (green ⏺ when ok, red when failed)
134
+ * ⎿ <preview line 1> (if preview present, with ⎿ connector)
135
+ * <preview line 2> (continuation lines plain indent)
136
+ * (+N more lines hidden) (truncation hint)
137
+ * `header` is the formatToolCall'd string. `kind` of preview rendering
138
+ * is derived: if the preview looks like a diff, lines colored +green/-red.
139
+ */
140
+ | {
141
+ id: number;
142
+ kind: 'tool';
143
+ header: string;
144
+ ok: boolean;
145
+ durationMs?: number;
146
+ preview?: string;
147
+ } | {
148
+ id: number;
149
+ kind: 'memory';
150
+ level: 'info' | 'warn';
151
+ text: string;
152
+ }
153
+ /** Plan rendering: optional `explanation` renders above the checklist as a dim line. */
154
+ | {
155
+ id: number;
156
+ kind: 'plan';
157
+ items: {
158
+ step: string;
159
+ status: 'pending' | 'in_progress' | 'completed';
160
+ }[];
161
+ explanation?: string;
162
+ }
163
+ /** Notice severity: info → gray dim · warn → yellow · error → red bold. */
164
+ | {
165
+ id: number;
166
+ kind: 'notice';
167
+ text: string;
168
+ level?: 'info' | 'warn' | 'error';
169
+ };
170
+ export interface PushScrollback {
171
+ raw(text: string, opts?: {
172
+ noWrap?: boolean;
173
+ }): void;
174
+ user(text: string): void;
175
+ /** `raw: true` skips marked-terminal rendering (use when caller already pre-rendered or user wants raw scrollback). */
176
+ assistant(text: string, meta?: {
177
+ raw?: boolean;
178
+ durationMs?: number;
179
+ tokensIn?: number;
180
+ tokensOut?: number;
181
+ calls?: number;
182
+ }): void;
183
+ /**
184
+ * `header` is the formatted call (e.g. `Read(src/foo.ts)` from
185
+ * `formatToolCall` in toolFormat.ts), NOT the raw tool name. Pass the
186
+ * full result preview unmodified — the renderer applies diff coloring
187
+ * + truncation hints.
188
+ */
189
+ tool(header: string, ok: boolean, opts?: {
190
+ preview?: string;
191
+ durationMs?: number;
192
+ }): void;
193
+ memory(level: 'info' | 'warn', text: string): void;
194
+ plan(items: {
195
+ step: string;
196
+ status: 'pending' | 'in_progress' | 'completed';
197
+ }[], explanation?: string): void;
198
+ /** Severity defaults to 'info' when omitted (back-compat). */
199
+ notice(text: string, level?: 'info' | 'warn' | 'error'): void;
200
+ /** Update the live spinner label (e.g. "Thinking 5s 1.2k↑ 0.4k↓"). */
201
+ setStatus(label: string): void;
202
+ /** Show / hide the spinner without pushing a scrollback entry. */
203
+ setPhase(phase: 'idle' | 'turn-running'): void;
204
+ }
205
+ export declare function ChatApp({ initialBanner, initialOfflineWarning, initialHint, slashCommands, promptLabel, accentColor, onSubmit, onReady, onAccessModeCycle, initialAccessMode, initialFooter, }: ChatAppProps): import("react/jsx-runtime").JSX.Element;
206
+ export declare function filterPaletteCommands(commands: SlashCommandDef[], query: string): SlashCommandDef[];