@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
@@ -55,7 +55,22 @@ export interface PickerKey {
55
55
  /** A single printable character for free-text capture (Other phase). */
56
56
  char?: string;
57
57
  }
58
- export declare function initPickerState(options: ChoiceOption[], multiSelect: boolean): PickerState;
58
+ /**
59
+ * `prefilledOther` drops the picker straight into the free-text "Other"
60
+ * phase with the supplied string already in the buffer. Used by the
61
+ * 0.3.7 wizard / `/config` panel when a value can be derived from an
62
+ * env var — the user sees the env value and presses ENTER to accept or
63
+ * edits to override. Pass an empty string to keep today's behaviour.
64
+ *
65
+ * `initialCursor` lets a picker open with a non-zero highlight so the
66
+ * settings home panel can re-open on the row the user just edited
67
+ * without re-scrolling them to the top.
68
+ */
69
+ export interface InitPickerStateOptions {
70
+ prefilledOther?: string;
71
+ initialCursor?: number;
72
+ }
73
+ export declare function initPickerState(options: ChoiceOption[], multiSelect: boolean, init?: InitPickerStateOptions): PickerState;
59
74
  export declare function reducePicker(state: PickerState, key: PickerKey): PickerState;
60
75
  export declare function renderPicker(state: PickerState, question: string, header?: string): string;
61
76
  /**
@@ -72,10 +87,32 @@ export declare function renderPicker(state: PickerState, question: string, heade
72
87
  * User cancellation (Esc, q, Ctrl+C) throws `CancelledChoiceError` so the
73
88
  * tool wrapper can surface "user declined to commit" as a tool-call error.
74
89
  */
75
- export declare function askChoice(question: string, options: ChoiceOption[], opts?: {
90
+ /**
91
+ * 0.3.7 picker opts.
92
+ *
93
+ * `onCursorChange(index)` fires after every arrow-key move that actually
94
+ * moves the cursor (no-op keys, ENTER, SPACE don't fire it). The 0.3.7
95
+ * theme picker uses this to live-preview the selected theme by redrawing
96
+ * the banner accent before the user confirms — pattern lifted from
97
+ * `openSrc/codex/codex-rs/tui/src/bottom_pane/list_selection_view.rs` and
98
+ * `openSrc/codex/codex-rs/tui/src/theme_picker.rs`.
99
+ *
100
+ * `prefilledOther` opens the picker with the synthetic "Other" row
101
+ * already selected AND the free-text input pre-filled. Used when a value
102
+ * is derived from an env var so the user can press ENTER to accept or
103
+ * edit in-place. Pre-fill flips `awaitingOther` true on init.
104
+ *
105
+ * `initialCursor` lets the settings home panel re-open on the row the
106
+ * user just left, avoiding a snap-to-row-0 after every sub-picker.
107
+ */
108
+ export interface AskChoiceOptions {
76
109
  multiSelect?: boolean;
77
110
  header?: string;
78
- }): Promise<string | string[]>;
111
+ onCursorChange?: (cursor: number) => void;
112
+ prefilledOther?: string;
113
+ initialCursor?: number;
114
+ }
115
+ export declare function askChoice(question: string, options: ChoiceOption[], opts?: AskChoiceOptions): Promise<string | string[]>;
79
116
  /**
80
117
  * Print a line of output while the prompt is showing, then redraw the prompt
81
118
  * with whatever the user was mid-typing. Used by callbacks that fire while the
@@ -80,15 +80,23 @@ export class CancelledChoiceError extends Error {
80
80
  /** Synthetic always-on "Other" option appended to every picker. */
81
81
  const OTHER_LABEL = 'Other';
82
82
  const OTHER_DESCRIPTION = 'Type a free-form answer not listed above';
83
- export function initPickerState(options, multiSelect) {
83
+ export function initPickerState(options, multiSelect, init = {}) {
84
84
  const augmented = [...options, { label: OTHER_LABEL, description: OTHER_DESCRIPTION }];
85
+ const otherText = init.prefilledOther ?? '';
86
+ const awaitingOther = otherText.length > 0;
87
+ // When pre-filled "Other" is requested, position the cursor on the
88
+ // Other row so a subsequent Esc → re-render lands the user there
89
+ // (otherwise they'd snap back to row 0 with no explanation).
90
+ const cursor = awaitingOther
91
+ ? augmented.length - 1
92
+ : Math.max(0, Math.min(init.initialCursor ?? 0, augmented.length - 1));
85
93
  return {
86
94
  options: augmented,
87
- cursor: 0,
95
+ cursor,
88
96
  multiSelect,
89
97
  selected: new Set(),
90
- awaitingOther: false,
91
- otherText: '',
98
+ awaitingOther,
99
+ otherText,
92
100
  done: false,
93
101
  cancelled: false,
94
102
  result: null,
@@ -197,20 +205,6 @@ export function renderPicker(state, question, header) {
197
205
  }
198
206
  return lines.join('\n');
199
207
  }
200
- /**
201
- * Mid-turn multi-choice prompt with arrow-key navigation, a checkbox UI
202
- * for multi-select, and an always-on "Other" option that drops to free-text
203
- * input. Pause/resume the parent REPL the same way `askYesNo` does, so it
204
- * composes cleanly with the existing readline bridge.
205
- *
206
- * Non-TTY behavior is strict: throws `NoTTYError` instead of defaulting to
207
- * option 1. The agent calling this is asking the human for judgment; making
208
- * the call for them in CI / piped / `brainrouter run` would silently commit
209
- * to a path the user never saw.
210
- *
211
- * User cancellation (Esc, q, Ctrl+C) throws `CancelledChoiceError` so the
212
- * tool wrapper can surface "user declined to commit" as a tool-call error.
213
- */
214
208
  export function askChoice(question, options, opts = {}) {
215
209
  // Input-shape validation first — bad shape is a caller bug regardless of
216
210
  // TTY availability, and surfacing it as "no TTY" would misdirect the agent
@@ -247,8 +241,12 @@ function runPicker(question, options, opts) {
247
241
  return new Promise((resolve, reject) => {
248
242
  const rl = activeReadline;
249
243
  const stdout = process.stdout;
250
- let state = initPickerState(options, !!opts.multiSelect);
251
- let renderedLines = 0;
244
+ let state = initPickerState(options, !!opts.multiSelect, {
245
+ prefilledOther: opts.prefilledOther,
246
+ initialCursor: opts.initialCursor,
247
+ });
248
+ let renderedNewlines = 0;
249
+ let renderedAtLeastOnce = false;
252
250
  // Pause the parent rl so its `line` handler doesn't fire on our ENTER
253
251
  // press. We restore on cleanup.
254
252
  rl.pause();
@@ -265,16 +263,28 @@ function runPicker(question, options, opts) {
265
263
  stdout.write('\x1b[?25l');
266
264
  pickerActive = true;
267
265
  const clear = () => {
268
- if (renderedLines > 0) {
269
- // Move cursor up `renderedLines` then clear to end of screen.
270
- stdout.write(`\x1b[${renderedLines}A\r\x1b[J`);
266
+ if (renderedNewlines > 0) {
267
+ // `\x1b[<n>F` = cursor up n lines AND col 1 (atomic). For an
268
+ // M-line frame containing M-1 newlines, the cursor sits at
269
+ // the END of line M after the write (we don't write a
270
+ // trailing newline). Going up `renderedNewlines` (= M-1)
271
+ // lines lands EXACTLY at the start of line 1 — no off-by-one.
272
+ stdout.write(`\x1b[${renderedNewlines}F\x1b[J`);
273
+ }
274
+ else if (renderedNewlines === 0 && renderedAtLeastOnce) {
275
+ // Single-line frame edge case: nothing to scroll up; just
276
+ // wipe the current line.
277
+ stdout.write('\r\x1b[K');
271
278
  }
272
279
  };
273
280
  const render = () => {
274
281
  clear();
275
282
  const text = renderPicker(state, question, opts.header);
276
- stdout.write(text + '\n');
277
- renderedLines = text.split('\n').length;
283
+ stdout.write(text);
284
+ // Track newlines (NOT lines). For "a\nb\nc" that's 2 — which
285
+ // is exactly the cursor-up count we need for the next clear().
286
+ renderedNewlines = (text.match(/\n/g) ?? []).length;
287
+ renderedAtLeastOnce = true;
278
288
  };
279
289
  const cleanup = () => {
280
290
  process.stdin.removeListener('keypress', onKeypress);
@@ -309,10 +319,27 @@ function runPicker(question, options, opts) {
309
319
  sequence: key?.sequence,
310
320
  char: isPrintable ? str : undefined,
311
321
  };
322
+ const prevCursor = state.cursor;
323
+ const wasAwaitingOther = state.awaitingOther;
312
324
  const nextState = reducePicker(state, pk);
313
325
  if (nextState === state)
314
326
  return;
315
327
  state = nextState;
328
+ // Live-preview hook (0.3.7): fire on a genuine cursor move in the
329
+ // picker phase only. Don't fire while collecting free-text in the
330
+ // "Other" phase — that would spam the callback on every keystroke
331
+ // for no useful signal. Settling back into picker phase from Other
332
+ // (Esc) doesn't fire either (the cursor "stayed" on Other).
333
+ if (opts.onCursorChange
334
+ && !state.done
335
+ && !state.awaitingOther
336
+ && !wasAwaitingOther
337
+ && state.cursor !== prevCursor) {
338
+ try {
339
+ opts.onCursorChange(state.cursor);
340
+ }
341
+ catch { /* preview callbacks must never crash the picker */ }
342
+ }
316
343
  render();
317
344
  if (state.done) {
318
345
  cleanup();
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import type readline from 'node:readline';
15
15
  import type { Agent } from '../../agent/agent.js';
16
- import type { McpClientWrapper } from '../../runtime/mcpClient.js';
16
+ import type { McpClientPool as McpClientWrapper } from '../../runtime/mcpPool.js';
17
17
  import type { Config } from '../../config/config.js';
18
18
  /**
19
19
  * Lifecycle / REPL-scoped state that command handlers can read or mutate.
@@ -24,6 +24,8 @@ import type { Config } from '../../config/config.js';
24
24
  export interface ReplContext {
25
25
  /** Refresh the readline prompt (color reflects access mode + status segments). */
26
26
  refreshPromptForMode: () => void;
27
+ /** Replace the startup banner in the active chat scrollback, if the UI supports it. */
28
+ replaceBanner?: (text: string) => void;
27
29
  /** True while the REPL is mid-turn; loop ticks should defer when set. */
28
30
  isProcessing: () => boolean;
29
31
  /** Programmatically run an agent turn (used by /continue and friends). */
@@ -8,7 +8,7 @@
8
8
  * wrapper.
9
9
  */
10
10
  import type { Agent } from '../../agent/agent.js';
11
- import type { McpClientWrapper } from '../../runtime/mcpClient.js';
11
+ import type { McpClientPool as McpClientWrapper } from '../../runtime/mcpPool.js';
12
12
  /**
13
13
  * Memory-aware variant of printMcpCall. Calls the tool, extracts the flat
14
14
  * record list from whatever shape it returns, and renders compact cards
@@ -0,0 +1,46 @@
1
+ import type { CommandContext } from './_context.js';
2
+ import { type Theme } from '../theme.js';
3
+ /**
4
+ * `/config` slash command — 0.3.7 redesign on the new atomic-frame picker
5
+ * (`../wizard/picker.ts`).
6
+ *
7
+ * Verb-overloaded (lifted from
8
+ * `openSrc/DeepSeek-TUI/crates/tui/src/commands/config.rs:43`):
9
+ *
10
+ * - `/config` — open the settings home panel
11
+ * - `/config <key>` — print the current value for <key>
12
+ * - `/config <key> <val>` — set <key> to <val> and persist
13
+ * - `/config raw|json` — print scrubbed JSON dump
14
+ *
15
+ * Persistence routes through `saveConfig` / `writePreferences` — never
16
+ * touches JSON files directly so future schema changes stay centralized.
17
+ */
18
+ export declare function tryHandleConfigCommand(ctx: CommandContext): Promise<boolean>;
19
+ export type ParsedConfigArgs = {
20
+ mode: 'home';
21
+ } | {
22
+ mode: 'raw';
23
+ } | {
24
+ mode: 'get';
25
+ key: string;
26
+ } | {
27
+ mode: 'set';
28
+ key: string;
29
+ value: string;
30
+ };
31
+ export declare function parseConfigArgs(args: string[]): ParsedConfigArgs;
32
+ export declare function listKnownConfigKeys(): string[];
33
+ export declare function editLlm(ctx: CommandContext): Promise<boolean>;
34
+ /**
35
+ * Shared prompt for the BrainRouter MCP HTTP API key (the
36
+ * `BRAINROUTER_API_KEY` bearer token). Pre-fills from the env var if
37
+ * set, then from the previously-saved key, then blank. Returns:
38
+ * - the trimmed key string (possibly empty when user chose "no key")
39
+ * - undefined when the user pressed Esc
40
+ *
41
+ * Exported so `/login` and any future MCP-setup surfaces share one
42
+ * prompt copy — same subtitle text, same env-var pre-fill, same
43
+ * "blank OK" semantics.
44
+ */
45
+ export declare function promptBrainrouterApiKey(theme: Theme, kind: 'local' | 'remote', existing?: string): Promise<string | undefined>;
46
+ export declare function buildScrubbedConfigJson(config: CommandContext['config']): string;