@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.
- package/README.md +29 -52
- package/agents/architect.json +18 -0
- package/agents/explorer.json +18 -0
- package/agents/reviewer.json +18 -0
- package/agents/verifier.json +18 -0
- package/agents/worker.json +18 -0
- package/bin/cli.cjs +71 -0
- package/dist/agent/agent.d.ts +224 -3
- package/dist/agent/agent.js +561 -55
- package/dist/cli/banner.d.ts +80 -0
- package/dist/cli/banner.js +232 -0
- package/dist/cli/cliPrompt.d.ts +106 -0
- package/dist/cli/cliPrompt.js +314 -0
- package/dist/cli/commands/_context.d.ts +3 -1
- package/dist/cli/commands/_helpers.d.ts +1 -1
- package/dist/cli/commands/_helpers.js +6 -6
- package/dist/cli/commands/config.d.ts +46 -0
- package/dist/cli/commands/config.js +1042 -0
- package/dist/cli/commands/guard.js +75 -10
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.js +64 -0
- package/dist/cli/commands/login.d.ts +13 -0
- package/dist/cli/commands/login.js +179 -0
- package/dist/cli/commands/mcp.d.ts +19 -0
- package/dist/cli/commands/mcp.js +286 -0
- package/dist/cli/commands/memory.js +2 -2
- package/dist/cli/commands/obs.js +22 -22
- package/dist/cli/commands/orchestration.js +18 -0
- package/dist/cli/commands/session.js +13 -5
- package/dist/cli/commands/ui.js +202 -91
- package/dist/cli/commands/workflow.d.ts +20 -0
- package/dist/cli/commands/workflow.js +368 -51
- package/dist/cli/ink/ChatApp.d.ts +206 -0
- package/dist/cli/ink/ChatApp.js +493 -0
- package/dist/cli/ink/Frame.d.ts +26 -0
- package/dist/cli/ink/Frame.js +5 -0
- package/dist/cli/ink/Picker.d.ts +65 -0
- package/dist/cli/ink/Picker.js +133 -0
- package/dist/cli/ink/SlashPalette.d.ts +51 -0
- package/dist/cli/ink/SlashPalette.js +136 -0
- package/dist/cli/ink/TextField.d.ts +34 -0
- package/dist/cli/ink/TextField.js +47 -0
- package/dist/cli/ink/WizardApp.d.ts +7 -0
- package/dist/cli/ink/WizardApp.js +422 -0
- package/dist/cli/ink/ambientChat.d.ts +34 -0
- package/dist/cli/ink/ambientChat.js +7 -0
- package/dist/cli/ink/consoleCapture.d.ts +11 -0
- package/dist/cli/ink/consoleCapture.js +33 -0
- package/dist/cli/ink/markdownRender.d.ts +41 -0
- package/dist/cli/ink/markdownRender.js +278 -0
- package/dist/cli/ink/renderWithResizeClear.d.ts +14 -0
- package/dist/cli/ink/renderWithResizeClear.js +33 -0
- package/dist/cli/ink/runChat.d.ts +34 -0
- package/dist/cli/ink/runChat.js +571 -0
- package/dist/cli/ink/runPicker.d.ts +31 -0
- package/dist/cli/ink/runPicker.js +139 -0
- package/dist/cli/ink/runSlashPalette.d.ts +23 -0
- package/dist/cli/ink/runSlashPalette.js +33 -0
- package/dist/cli/ink/runWizard.d.ts +22 -0
- package/dist/cli/ink/runWizard.js +133 -0
- package/dist/cli/ink/stdinHandoff.d.ts +51 -0
- package/dist/cli/ink/stdinHandoff.js +78 -0
- package/dist/cli/ink/toolFormat.d.ts +73 -0
- package/dist/cli/ink/toolFormat.js +180 -0
- package/dist/cli/ink/useTerminalSize.d.ts +35 -0
- package/dist/cli/ink/useTerminalSize.js +26 -0
- package/dist/cli/repl.d.ts +25 -3
- package/dist/cli/repl.js +64 -646
- package/dist/cli/slashSuggest.d.ts +32 -0
- package/dist/cli/slashSuggest.js +146 -0
- package/dist/cli/spinner.d.ts +34 -0
- package/dist/cli/spinner.js +36 -0
- package/dist/cli/statusline.d.ts +67 -0
- package/dist/cli/statusline.js +204 -0
- package/dist/cli/theme.d.ts +79 -0
- package/dist/cli/theme.js +106 -0
- package/dist/cli/whereView.d.ts +81 -0
- package/dist/cli/whereView.js +245 -0
- package/dist/cli/wizard/modelsApi.d.ts +72 -0
- package/dist/cli/wizard/modelsApi.js +166 -0
- package/dist/cli/wizard/picker.d.ts +202 -0
- package/dist/cli/wizard/picker.js +547 -0
- package/dist/cli/wizard/providers.d.ts +86 -0
- package/dist/cli/wizard/providers.js +190 -0
- package/dist/cli/wizard/runner.d.ts +13 -0
- package/dist/cli/wizard/runner.js +488 -0
- package/dist/cli/wizard/types.d.ts +122 -0
- package/dist/cli/wizard/types.js +109 -0
- package/dist/config/config.d.ts +52 -0
- package/dist/config/config.js +89 -75
- package/dist/index.js +215 -206
- package/dist/memory/briefing.d.ts +11 -1
- package/dist/memory/briefing.js +69 -1
- package/dist/memory/consolidation.d.ts +1 -1
- package/dist/orchestration/agentRegistry.d.ts +36 -0
- package/dist/orchestration/agentRegistry.js +64 -0
- package/dist/orchestration/orchestrator.d.ts +7 -0
- package/dist/orchestration/orchestrator.js +2 -0
- package/dist/orchestration/tools.d.ts +10 -1
- package/dist/orchestration/tools.js +48 -4
- package/dist/prompt/breadthHint.d.ts +5 -0
- package/dist/prompt/breadthHint.js +44 -0
- package/dist/prompt/skillCatalog.d.ts +11 -0
- package/dist/prompt/skillCatalog.js +134 -0
- package/dist/prompt/skillRunner.d.ts +2 -2
- package/dist/prompt/skillRunner.js +2 -31
- package/dist/prompt/systemPrompt.d.ts +34 -0
- package/dist/prompt/systemPrompt.js +128 -108
- package/dist/runtime/dangerousCommand.d.ts +53 -0
- package/dist/runtime/dangerousCommand.js +105 -0
- package/dist/runtime/mcpClient.d.ts +38 -1
- package/dist/runtime/mcpClient.js +104 -13
- package/dist/runtime/mcpPool.d.ts +162 -0
- package/dist/runtime/mcpPool.js +423 -0
- package/dist/runtime/mcpUtils.d.ts +3 -1
- package/dist/state/goalStore.d.ts +98 -17
- package/dist/state/goalStore.js +132 -42
- package/dist/state/preferencesStore.d.ts +67 -3
- package/dist/state/preferencesStore.js +84 -1
- package/dist/state/workflowArtifacts.d.ts +63 -2
- package/dist/state/workflowArtifacts.js +120 -8
- package/dist/tests/_helpers.d.ts +31 -0
- package/dist/tests/_helpers.js +91 -0
- package/package.json +12 -5
- package/.env.example +0 -109
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
import { markedTerminal } from 'marked-terminal';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
/**
|
|
5
|
+
* Configure `marked` + `marked-terminal` for the Ink chat REPL, then
|
|
6
|
+
* expose a `renderMarkdown(text)` helper that:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Unwraps `` ```md `` / `` ```markdown `` fences** that LLMs
|
|
9
|
+
* sometimes wrap a whole response in (or wrap a table in to
|
|
10
|
+
* get it past their own safety filters). Without unwrapping,
|
|
11
|
+
* the entire content renders as a yellow code block instead of
|
|
12
|
+
* formatted markdown. Pattern lifted from
|
|
13
|
+
* `openSrc/codex/codex-rs/tui/src/markdown.rs:86–123`
|
|
14
|
+
* (`unwrap_markdown_fences`).
|
|
15
|
+
*
|
|
16
|
+
* 2. **Disables marked-terminal's internal wrapping** so Ink owns
|
|
17
|
+
* reflow. marked-terminal's `width` wrap doesn't understand the
|
|
18
|
+
* surrounding flex layout (the chat is rendered inside a flex
|
|
19
|
+
* Box that subtracts ~2 cols for the `⏺ ` prefix) so its wrap
|
|
20
|
+
* points are always wrong. Letting Ink wrap means the width is
|
|
21
|
+
* always correct.
|
|
22
|
+
*
|
|
23
|
+
* 3. **Preserves ANSI styling across newlines** — marked-terminal
|
|
24
|
+
* emits a single open/close ANSI scope per block (e.g. a
|
|
25
|
+
* blockquote is `\x1b[90m\x1b[3m...content with \n in
|
|
26
|
+
* it...\x1b[39m\x1b[23m`), so when Ink's wrap-ansi splits at the
|
|
27
|
+
* newlines, lines 2+ lose their style. We post-process the
|
|
28
|
+
* output to close active codes before each `\n` and reopen them
|
|
29
|
+
* after, so every rendered line is a self-contained ANSI scope.
|
|
30
|
+
*
|
|
31
|
+
* 4. **Stronger visual hierarchy** — h1 is bold cyan, h2+ bold,
|
|
32
|
+
* inline code yellow, fenced code dim-yellow, blockquote
|
|
33
|
+
* gray-italic, links cyan-underline, hr dim-gray. Defaults gave
|
|
34
|
+
* headings all the same color (green bold) which made nested
|
|
35
|
+
* sections impossible to scan.
|
|
36
|
+
*/
|
|
37
|
+
// `marked.use` registers an extension globally on the `marked` singleton.
|
|
38
|
+
// Both the readline REPL (cli/repl.ts) and this Ink path import marked;
|
|
39
|
+
// the LAST registration wins for renderer overrides. We register here at
|
|
40
|
+
// module-load and ChatApp.tsx imports `renderMarkdown` from this file, so
|
|
41
|
+
// any caller that imports this module gets the Ink-friendly config.
|
|
42
|
+
let configured = false;
|
|
43
|
+
function ensureConfigured() {
|
|
44
|
+
if (configured)
|
|
45
|
+
return;
|
|
46
|
+
configured = true;
|
|
47
|
+
marked.use(markedTerminal({
|
|
48
|
+
showSectionPrefix: false,
|
|
49
|
+
reflowText: false,
|
|
50
|
+
// Effectively disable marked-terminal's own wrapping — Ink reflows
|
|
51
|
+
// the rendered string inside its flex layout, which knows the real
|
|
52
|
+
// available width.
|
|
53
|
+
width: Number.MAX_SAFE_INTEGER,
|
|
54
|
+
// 2-space indent matches the composer / scrollback prefix width.
|
|
55
|
+
tab: 2,
|
|
56
|
+
// Visual style overrides — see module docstring.
|
|
57
|
+
firstHeading: chalk.bold.cyan,
|
|
58
|
+
heading: chalk.bold,
|
|
59
|
+
code: chalk.dim.yellow,
|
|
60
|
+
codespan: chalk.yellow,
|
|
61
|
+
blockquote: chalk.gray.italic,
|
|
62
|
+
strong: chalk.bold,
|
|
63
|
+
em: chalk.italic,
|
|
64
|
+
link: chalk.cyan.underline,
|
|
65
|
+
href: chalk.cyan,
|
|
66
|
+
hr: chalk.gray.dim,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render markdown source to ANSI-styled terminal text suitable for an
|
|
71
|
+
* Ink `<Text>` element. Idempotent across calls (configures `marked`
|
|
72
|
+
* lazily on the first invocation).
|
|
73
|
+
*
|
|
74
|
+
* Empty / non-string input returns the input verbatim.
|
|
75
|
+
*/
|
|
76
|
+
export function renderMarkdown(source) {
|
|
77
|
+
if (typeof source !== 'string' || source.length === 0)
|
|
78
|
+
return source;
|
|
79
|
+
ensureConfigured();
|
|
80
|
+
const unwrapped = unwrapMarkdownFences(source);
|
|
81
|
+
let out;
|
|
82
|
+
try {
|
|
83
|
+
out = String(marked.parse(unwrapped));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Some malformed input crashes marked. Fall back to verbatim so the
|
|
87
|
+
// user still sees the LLM's reply.
|
|
88
|
+
return unwrapped;
|
|
89
|
+
}
|
|
90
|
+
return preserveAnsiAcrossNewlines(out);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Strip a single outer ``` markdown / ``` md fence pair when it wraps the
|
|
94
|
+
* entire input. Some LLMs (especially when asked to "format your reply
|
|
95
|
+
* in markdown") emit the whole response inside ``` markdown ... ``` —
|
|
96
|
+
* which then renders as a single yellow code block instead of formatted
|
|
97
|
+
* text. Direct port of codex's helper (markdown.rs:86–123).
|
|
98
|
+
*
|
|
99
|
+
* Also strips fences around tables — LLMs sometimes wrap tables in ``` md
|
|
100
|
+
* to "protect" the pipe characters, but marked then renders the table
|
|
101
|
+
* as code instead of as a native table.
|
|
102
|
+
*
|
|
103
|
+
* Exported for tests; the renderMarkdown caller chains this in.
|
|
104
|
+
*/
|
|
105
|
+
export function unwrapMarkdownFences(source) {
|
|
106
|
+
const trimmed = source.trimEnd();
|
|
107
|
+
const lines = trimmed.split('\n');
|
|
108
|
+
// Outer wrap case: first line is ```md / ```markdown, last line is ```.
|
|
109
|
+
if (lines.length >= 2) {
|
|
110
|
+
const first = lines[0].trim().toLowerCase();
|
|
111
|
+
const last = lines[lines.length - 1].trim();
|
|
112
|
+
if ((first === '```md' || first === '```markdown') && last === '```') {
|
|
113
|
+
return lines.slice(1, -1).join('\n');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return source;
|
|
117
|
+
}
|
|
118
|
+
// --- ANSI preservation ------------------------------------------------
|
|
119
|
+
// SGR (Select Graphic Rendition) escape sequence: ESC [ <params> m.
|
|
120
|
+
// Capture the param list so we can parse it into individual codes.
|
|
121
|
+
const ANSI_SGR_REGEX = /\x1b\[([0-9;]*)m/g;
|
|
122
|
+
// Closing-attribute codes — when we see one, drop the matching attr
|
|
123
|
+
// from active state. (22 closes both 1=bold and 2=dim.)
|
|
124
|
+
const ATTR_CLOSE_TO_OPENS = new Map([
|
|
125
|
+
['22', ['1', '2']],
|
|
126
|
+
['23', ['3']],
|
|
127
|
+
['24', ['4']],
|
|
128
|
+
['25', ['5']],
|
|
129
|
+
['27', ['7']],
|
|
130
|
+
['28', ['8']],
|
|
131
|
+
['29', ['9']],
|
|
132
|
+
]);
|
|
133
|
+
const ATTR_OPENS = new Set(['1', '2', '3', '4', '5', '7', '8', '9']);
|
|
134
|
+
function isForegroundOpen(p) {
|
|
135
|
+
// 30–37 standard FG, 90–97 bright FG.
|
|
136
|
+
const n = Number(p);
|
|
137
|
+
return (n >= 30 && n <= 37) || (n >= 90 && n <= 97);
|
|
138
|
+
}
|
|
139
|
+
function isBackgroundOpen(p) {
|
|
140
|
+
// 40–47 standard BG, 100–107 bright BG.
|
|
141
|
+
const n = Number(p);
|
|
142
|
+
return (n >= 40 && n <= 47) || (n >= 100 && n <= 107);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Re-scope ANSI styling across newline boundaries so each rendered line
|
|
146
|
+
* carries its own complete open/close pair.
|
|
147
|
+
*
|
|
148
|
+
* Walks the input as a stream of segments — plain text, ANSI SGR
|
|
149
|
+
* sequences, and `\n` — maintaining a small state machine of active
|
|
150
|
+
* styles (foreground color, background color, set of attribute flags).
|
|
151
|
+
* At every `\n`, emit a "close everything currently open" sequence,
|
|
152
|
+
* then the newline, then a "reopen everything that was open" sequence.
|
|
153
|
+
*
|
|
154
|
+
* Edge cases handled:
|
|
155
|
+
* - 256-color (38;5;N) and truecolor (38;2;R;G;B) sequences — treated
|
|
156
|
+
* as opaque opening codes, replayed verbatim
|
|
157
|
+
* - `0` / empty params reset all state
|
|
158
|
+
* - already-empty active state at a newline → emit just the newline
|
|
159
|
+
*
|
|
160
|
+
* Exported for tests.
|
|
161
|
+
*/
|
|
162
|
+
export function preserveAnsiAcrossNewlines(text) {
|
|
163
|
+
if (!text.includes('\n') || !text.includes('\x1b['))
|
|
164
|
+
return text;
|
|
165
|
+
// Active style state.
|
|
166
|
+
let fg = null; // e.g. "32" or "38;5;208"
|
|
167
|
+
let bg = null; // e.g. "42" or "48;2;100;150;200"
|
|
168
|
+
const attrs = new Set(); // "1", "2", "3", etc.
|
|
169
|
+
const buildSgr = (parts) => parts.length ? `\x1b[${parts.join(';')}m` : '';
|
|
170
|
+
const buildClose = () => {
|
|
171
|
+
const parts = [];
|
|
172
|
+
if (fg)
|
|
173
|
+
parts.push('39');
|
|
174
|
+
if (bg)
|
|
175
|
+
parts.push('49');
|
|
176
|
+
for (const a of attrs) {
|
|
177
|
+
// Pick the close code that matches.
|
|
178
|
+
for (const [closeCode, opens] of ATTR_CLOSE_TO_OPENS) {
|
|
179
|
+
if (opens.includes(a)) {
|
|
180
|
+
parts.push(closeCode);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return buildSgr(parts);
|
|
186
|
+
};
|
|
187
|
+
const buildReopen = () => {
|
|
188
|
+
const parts = [];
|
|
189
|
+
if (fg)
|
|
190
|
+
parts.push(fg);
|
|
191
|
+
if (bg)
|
|
192
|
+
parts.push(bg);
|
|
193
|
+
for (const a of attrs)
|
|
194
|
+
parts.push(a);
|
|
195
|
+
return buildSgr(parts);
|
|
196
|
+
};
|
|
197
|
+
// Walk the text. We use a fresh regex with `lastIndex` since ANSI_SGR_REGEX
|
|
198
|
+
// is module-level and stateful.
|
|
199
|
+
const re = /\x1b\[([0-9;]*)m|\n/g;
|
|
200
|
+
let i = 0;
|
|
201
|
+
let out = '';
|
|
202
|
+
let m;
|
|
203
|
+
while ((m = re.exec(text)) !== null) {
|
|
204
|
+
// Emit any plain text leading up to the match.
|
|
205
|
+
if (m.index > i)
|
|
206
|
+
out += text.slice(i, m.index);
|
|
207
|
+
if (m[0] === '\n') {
|
|
208
|
+
// Close active codes, newline, reopen.
|
|
209
|
+
out += buildClose() + '\n' + buildReopen();
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// SGR sequence — update state and pass through verbatim.
|
|
213
|
+
const params = m[1].split(';');
|
|
214
|
+
// 256-color / truecolor: 38;5;N and 38;2;R;G;B are foreground;
|
|
215
|
+
// 48;5;N and 48;2;R;G;B are background. Handle by joining the
|
|
216
|
+
// surrounding params into one logical fg/bg code.
|
|
217
|
+
let j = 0;
|
|
218
|
+
while (j < params.length) {
|
|
219
|
+
const p = params[j];
|
|
220
|
+
if (p === '0' || p === '') {
|
|
221
|
+
fg = null;
|
|
222
|
+
bg = null;
|
|
223
|
+
attrs.clear();
|
|
224
|
+
j++;
|
|
225
|
+
}
|
|
226
|
+
else if (p === '38' && params[j + 1] === '5' && params[j + 2] !== undefined) {
|
|
227
|
+
fg = `38;5;${params[j + 2]}`;
|
|
228
|
+
j += 3;
|
|
229
|
+
}
|
|
230
|
+
else if (p === '38' && params[j + 1] === '2' && params[j + 4] !== undefined) {
|
|
231
|
+
fg = `38;2;${params[j + 2]};${params[j + 3]};${params[j + 4]}`;
|
|
232
|
+
j += 5;
|
|
233
|
+
}
|
|
234
|
+
else if (p === '48' && params[j + 1] === '5' && params[j + 2] !== undefined) {
|
|
235
|
+
bg = `48;5;${params[j + 2]}`;
|
|
236
|
+
j += 3;
|
|
237
|
+
}
|
|
238
|
+
else if (p === '48' && params[j + 1] === '2' && params[j + 4] !== undefined) {
|
|
239
|
+
bg = `48;2;${params[j + 2]};${params[j + 3]};${params[j + 4]}`;
|
|
240
|
+
j += 5;
|
|
241
|
+
}
|
|
242
|
+
else if (p === '39') {
|
|
243
|
+
fg = null;
|
|
244
|
+
j++;
|
|
245
|
+
}
|
|
246
|
+
else if (p === '49') {
|
|
247
|
+
bg = null;
|
|
248
|
+
j++;
|
|
249
|
+
}
|
|
250
|
+
else if (ATTR_CLOSE_TO_OPENS.has(p)) {
|
|
251
|
+
for (const open of ATTR_CLOSE_TO_OPENS.get(p))
|
|
252
|
+
attrs.delete(open);
|
|
253
|
+
j++;
|
|
254
|
+
}
|
|
255
|
+
else if (isForegroundOpen(p)) {
|
|
256
|
+
fg = p;
|
|
257
|
+
j++;
|
|
258
|
+
}
|
|
259
|
+
else if (isBackgroundOpen(p)) {
|
|
260
|
+
bg = p;
|
|
261
|
+
j++;
|
|
262
|
+
}
|
|
263
|
+
else if (ATTR_OPENS.has(p)) {
|
|
264
|
+
attrs.add(p);
|
|
265
|
+
j++;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
j++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
out += m[0];
|
|
272
|
+
}
|
|
273
|
+
i = m.index + m[0].length;
|
|
274
|
+
}
|
|
275
|
+
if (i < text.length)
|
|
276
|
+
out += text.slice(i);
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type Instance, type RenderOptions } from 'ink';
|
|
3
|
+
export interface ResizeClearInkInstance {
|
|
4
|
+
instance: Instance;
|
|
5
|
+
cleanupResizeClear: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Ink only force-clears on selected resize paths. BrainRouter's Ink
|
|
9
|
+
* panels redraw full frames, so every terminal resize must clear the
|
|
10
|
+
* previous frame first or old banners/prompts can remain in scrollback.
|
|
11
|
+
* We also clear the terminal scrollback buffer so stale resize frames
|
|
12
|
+
* cannot be reached by scrolling up after the layout settles.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderWithResizeClear(node: ReactNode, options?: NodeJS.WriteStream | RenderOptions): ResizeClearInkInstance;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { render } from 'ink';
|
|
2
|
+
const ERASE_SCROLLBACK = '\x1b[3J';
|
|
3
|
+
/**
|
|
4
|
+
* Ink only force-clears on selected resize paths. BrainRouter's Ink
|
|
5
|
+
* panels redraw full frames, so every terminal resize must clear the
|
|
6
|
+
* previous frame first or old banners/prompts can remain in scrollback.
|
|
7
|
+
* We also clear the terminal scrollback buffer so stale resize frames
|
|
8
|
+
* cannot be reached by scrolling up after the layout settles.
|
|
9
|
+
*/
|
|
10
|
+
export function renderWithResizeClear(node, options) {
|
|
11
|
+
const instance = render(node, options);
|
|
12
|
+
const stdout = resolveStdout(options);
|
|
13
|
+
const clearBeforeResize = () => {
|
|
14
|
+
instance.clear();
|
|
15
|
+
if (stdout.isTTY) {
|
|
16
|
+
stdout.write(ERASE_SCROLLBACK);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
stdout.prependListener('resize', clearBeforeResize);
|
|
20
|
+
return {
|
|
21
|
+
instance,
|
|
22
|
+
cleanupResizeClear: () => {
|
|
23
|
+
stdout.off('resize', clearBeforeResize);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function resolveStdout(options) {
|
|
28
|
+
if (!options)
|
|
29
|
+
return process.stdout;
|
|
30
|
+
if ('write' in options && !('stdout' in options))
|
|
31
|
+
return options;
|
|
32
|
+
return options.stdout ?? process.stdout;
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Agent } from '../../agent/agent.js';
|
|
2
|
+
import type { McpClientPool as McpClientWrapper } from '../../runtime/mcpPool.js';
|
|
3
|
+
import type { Config } from '../../config/config.js';
|
|
4
|
+
import type { WorkspaceInfo } from '../../config/workspace.js';
|
|
5
|
+
/**
|
|
6
|
+
* Mount the full Ink-based chat REPL and run it until the user exits.
|
|
7
|
+
*
|
|
8
|
+
* The CLI's only chat surface as of 0.3.7 — the old readline-based REPL
|
|
9
|
+
* was removed in favour of this single Ink tree. The Ink REPL owns stdin
|
|
10
|
+
* for the entire CLI lifetime — no handoff back to readline — so unlike
|
|
11
|
+
* `runWizard` / `runPicker` we don't call `resetStdinForReadline` after
|
|
12
|
+
* unmount; the process exits the moment Ink does.
|
|
13
|
+
*
|
|
14
|
+
* Orchestration:
|
|
15
|
+
* 1. Build the banner, slash catalog, and theme.
|
|
16
|
+
* 2. Mount ChatApp, grabbing its imperative controller via `onReady`.
|
|
17
|
+
* 3. ChatApp's `onSubmit` dispatches each line:
|
|
18
|
+
* - slash command → `handleSlashCommand` (via a shim readline
|
|
19
|
+
* that satisfies the type but no-ops the readline-specific
|
|
20
|
+
* surface area).
|
|
21
|
+
* - free text → `runChatTurn` (the agent.runTurn adapter).
|
|
22
|
+
* 4. Post-turn surface behaviours run inside `runChatTurn`'s finally:
|
|
23
|
+
* goal continuation queue, footer refresh, idle hint re-arm.
|
|
24
|
+
* 5. Ctrl+C / Ctrl+D inside Ink (via ChatApp's useInput) triggers
|
|
25
|
+
* Ink's `exit()`, which resolves `instance.waitUntilExit()` →
|
|
26
|
+
* we close the mcpClient and let the process drain.
|
|
27
|
+
*/
|
|
28
|
+
export interface RunChatOptions {
|
|
29
|
+
agent: Agent;
|
|
30
|
+
mcpClient: McpClientWrapper;
|
|
31
|
+
config: Config;
|
|
32
|
+
workspace?: WorkspaceInfo;
|
|
33
|
+
}
|
|
34
|
+
export declare function runChat(opts: RunChatOptions): Promise<void>;
|