@kinqs/brainrouter-cli 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 +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/changelog/0.2.0.md +15 -0
- package/changelog/0.3.0.md +20 -0
- package/changelog/0.3.1.md +22 -0
- package/changelog/0.3.2.md +15 -0
- package/changelog/0.3.3.md +19 -0
- package/changelog/0.3.4.md +20 -0
- package/changelog/0.3.5.md +9 -0
- package/changelog/0.3.6.md +9 -0
- package/changelog/0.3.7.md +20 -0
- package/changelog/0.3.8.md +30 -0
- package/changelog/README.md +41 -0
- package/dist/agent/agent.d.ts +34 -1
- package/dist/agent/agent.js +372 -79
- package/dist/agent/toolCallRecovery.d.ts +57 -0
- package/dist/agent/toolCallRecovery.js +130 -0
- package/dist/agent/toolSafety.d.ts +17 -0
- package/dist/agent/toolSafety.js +102 -0
- package/dist/cli/banner.d.ts +20 -0
- package/dist/cli/banner.js +47 -14
- package/dist/cli/cliPrompt.d.ts +40 -3
- package/dist/cli/cliPrompt.js +117 -25
- package/dist/cli/commands/_context.d.ts +3 -1
- package/dist/cli/commands/_helpers.d.ts +1 -1
- package/dist/cli/commands/config.d.ts +46 -0
- package/dist/cli/commands/config.js +1042 -0
- 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 +13 -11
- package/dist/cli/commands/mcp.js +261 -74
- package/dist/cli/commands/mcpInstall.d.ts +20 -0
- package/dist/cli/commands/mcpInstall.js +87 -0
- package/dist/cli/commands/orchestration.js +51 -0
- package/dist/cli/commands/releaseNotes.d.ts +24 -0
- package/dist/cli/commands/releaseNotes.js +109 -0
- package/dist/cli/commands/schedule.d.ts +18 -0
- package/dist/cli/commands/schedule.js +189 -0
- package/dist/cli/commands/ui.js +119 -60
- package/dist/cli/commands/workflow.d.ts +2 -0
- package/dist/cli/commands/workflow.js +54 -8
- 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 +71 -0
- package/dist/cli/ink/Picker.js +168 -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 +682 -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 +75 -0
- package/dist/cli/ink/toolFormat.js +206 -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 +52 -714
- package/dist/cli/slashSuggest.d.ts +32 -0
- package/dist/cli/slashSuggest.js +146 -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 +13 -1
- package/dist/config/config.js +45 -3
- package/dist/index.js +157 -206
- package/dist/memory/briefing.d.ts +1 -1
- package/dist/memory/briefing.js +4 -4
- 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 +105 -3
- package/dist/orchestration/tools.js +167 -8
- 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.js +7 -2
- package/dist/runtime/anthropicAdapter.d.ts +100 -0
- package/dist/runtime/anthropicAdapter.js +293 -0
- package/dist/runtime/cronParser.d.ts +23 -0
- package/dist/runtime/cronParser.js +122 -0
- package/dist/runtime/mcpClient.js +14 -11
- package/dist/runtime/mcpPool.d.ts +170 -0
- package/dist/runtime/mcpPool.js +442 -0
- package/dist/runtime/mcpUtils.d.ts +17 -1
- package/dist/runtime/mcpUtils.js +23 -0
- package/dist/runtime/scheduleTicker.d.ts +33 -0
- package/dist/runtime/scheduleTicker.js +99 -0
- package/dist/runtime/vendorSnippets.d.ts +45 -0
- package/dist/runtime/vendorSnippets.js +153 -0
- package/dist/state/scheduleStore.d.ts +37 -0
- package/dist/state/scheduleStore.js +64 -0
- package/package.json +14 -5
- package/.env.example +0 -116
package/dist/cli/cliPrompt.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
|
+
import { getAmbientChat } from './ink/ambientChat.js';
|
|
2
3
|
/**
|
|
3
4
|
* Shared bridge between the REPL's readline interface and modules outside
|
|
4
5
|
* repl.ts that need to (a) write above the prompt without scrambling input,
|
|
@@ -33,6 +34,9 @@ export function isPickerActive() { return pickerActive; }
|
|
|
33
34
|
* (e.g. piped non-interactive runs).
|
|
34
35
|
*/
|
|
35
36
|
export function askYesNo(question, defaultValue = false) {
|
|
37
|
+
if (getAmbientChat() && process.stdin.isTTY) {
|
|
38
|
+
return runInkYesNo(question, defaultValue);
|
|
39
|
+
}
|
|
36
40
|
if (!activeReadline || !process.stdin.isTTY) {
|
|
37
41
|
return Promise.resolve(defaultValue);
|
|
38
42
|
}
|
|
@@ -80,15 +84,23 @@ export class CancelledChoiceError extends Error {
|
|
|
80
84
|
/** Synthetic always-on "Other" option appended to every picker. */
|
|
81
85
|
const OTHER_LABEL = 'Other';
|
|
82
86
|
const OTHER_DESCRIPTION = 'Type a free-form answer not listed above';
|
|
83
|
-
export function initPickerState(options, multiSelect) {
|
|
87
|
+
export function initPickerState(options, multiSelect, init = {}) {
|
|
84
88
|
const augmented = [...options, { label: OTHER_LABEL, description: OTHER_DESCRIPTION }];
|
|
89
|
+
const otherText = init.prefilledOther ?? '';
|
|
90
|
+
const awaitingOther = otherText.length > 0;
|
|
91
|
+
// When pre-filled "Other" is requested, position the cursor on the
|
|
92
|
+
// Other row so a subsequent Esc → re-render lands the user there
|
|
93
|
+
// (otherwise they'd snap back to row 0 with no explanation).
|
|
94
|
+
const cursor = awaitingOther
|
|
95
|
+
? augmented.length - 1
|
|
96
|
+
: Math.max(0, Math.min(init.initialCursor ?? 0, augmented.length - 1));
|
|
85
97
|
return {
|
|
86
98
|
options: augmented,
|
|
87
|
-
cursor
|
|
99
|
+
cursor,
|
|
88
100
|
multiSelect,
|
|
89
101
|
selected: new Set(),
|
|
90
|
-
awaitingOther
|
|
91
|
-
otherText
|
|
102
|
+
awaitingOther,
|
|
103
|
+
otherText,
|
|
92
104
|
done: false,
|
|
93
105
|
cancelled: false,
|
|
94
106
|
result: null,
|
|
@@ -197,20 +209,6 @@ export function renderPicker(state, question, header) {
|
|
|
197
209
|
}
|
|
198
210
|
return lines.join('\n');
|
|
199
211
|
}
|
|
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
212
|
export function askChoice(question, options, opts = {}) {
|
|
215
213
|
// Input-shape validation first — bad shape is a caller bug regardless of
|
|
216
214
|
// TTY availability, and surfacing it as "no TTY" would misdirect the agent
|
|
@@ -241,14 +239,79 @@ export function askChoice(question, options, opts = {}) {
|
|
|
241
239
|
return Promise.reject(new NoTTYError('ask_user_choice requires an interactive TTY (no readline interface is active or stdin is not a TTY). ' +
|
|
242
240
|
'Fall back to deciding yourself based on the available context, and state which option you picked and why in your reply.'));
|
|
243
241
|
}
|
|
242
|
+
if (getAmbientChat()) {
|
|
243
|
+
return runInkChoice(question, options, opts);
|
|
244
|
+
}
|
|
244
245
|
return runPicker(question, options, opts);
|
|
245
246
|
}
|
|
247
|
+
async function runInkYesNo(question, defaultValue) {
|
|
248
|
+
const { runPicker } = await import('./ink/runPicker.js');
|
|
249
|
+
const result = await runPicker({
|
|
250
|
+
title: question,
|
|
251
|
+
badge: 'Confirm',
|
|
252
|
+
rows: [
|
|
253
|
+
{ id: 'yes', label: 'Yes', description: 'Allow this action' },
|
|
254
|
+
{ id: 'no', label: 'No', description: 'Do not allow this action' },
|
|
255
|
+
],
|
|
256
|
+
initialCursor: defaultValue ? 0 : 1,
|
|
257
|
+
allowOther: false,
|
|
258
|
+
});
|
|
259
|
+
if (result.kind !== 'pick')
|
|
260
|
+
return defaultValue;
|
|
261
|
+
return result.id === 'yes';
|
|
262
|
+
}
|
|
263
|
+
async function runInkChoice(question, options, opts) {
|
|
264
|
+
const { runPicker } = await import('./ink/runPicker.js');
|
|
265
|
+
const rows = options.map((option, i) => ({
|
|
266
|
+
id: `choice:${i}`,
|
|
267
|
+
label: option.label,
|
|
268
|
+
description: option.description,
|
|
269
|
+
}));
|
|
270
|
+
const result = await runPicker({
|
|
271
|
+
title: question,
|
|
272
|
+
badge: opts.header,
|
|
273
|
+
rows,
|
|
274
|
+
initialCursor: opts.initialCursor,
|
|
275
|
+
allowOther: true,
|
|
276
|
+
otherLabel: OTHER_LABEL,
|
|
277
|
+
otherDescription: OTHER_DESCRIPTION,
|
|
278
|
+
prefilledOther: opts.prefilledOther,
|
|
279
|
+
multiSelect: !!opts.multiSelect,
|
|
280
|
+
onCursorChange: opts.onCursorChange
|
|
281
|
+
? (_id, index) => {
|
|
282
|
+
opts.onCursorChange?.(index);
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
: undefined,
|
|
286
|
+
});
|
|
287
|
+
if (result.kind === 'cancelled') {
|
|
288
|
+
throw new CancelledChoiceError();
|
|
289
|
+
}
|
|
290
|
+
if (result.kind === 'other') {
|
|
291
|
+
return result.text;
|
|
292
|
+
}
|
|
293
|
+
if (result.kind === 'multi') {
|
|
294
|
+
const answers = result.ids.map((id) => {
|
|
295
|
+
const idx = Number(id.slice('choice:'.length));
|
|
296
|
+
return options[idx]?.label;
|
|
297
|
+
}).filter((label) => !!label);
|
|
298
|
+
if (result.otherText)
|
|
299
|
+
answers.push(result.otherText);
|
|
300
|
+
return answers;
|
|
301
|
+
}
|
|
302
|
+
const idx = Number(result.id.slice('choice:'.length));
|
|
303
|
+
return options[idx]?.label ?? options[0].label;
|
|
304
|
+
}
|
|
246
305
|
function runPicker(question, options, opts) {
|
|
247
306
|
return new Promise((resolve, reject) => {
|
|
248
307
|
const rl = activeReadline;
|
|
249
308
|
const stdout = process.stdout;
|
|
250
|
-
let state = initPickerState(options, !!opts.multiSelect
|
|
251
|
-
|
|
309
|
+
let state = initPickerState(options, !!opts.multiSelect, {
|
|
310
|
+
prefilledOther: opts.prefilledOther,
|
|
311
|
+
initialCursor: opts.initialCursor,
|
|
312
|
+
});
|
|
313
|
+
let renderedNewlines = 0;
|
|
314
|
+
let renderedAtLeastOnce = false;
|
|
252
315
|
// Pause the parent rl so its `line` handler doesn't fire on our ENTER
|
|
253
316
|
// press. We restore on cleanup.
|
|
254
317
|
rl.pause();
|
|
@@ -265,16 +328,28 @@ function runPicker(question, options, opts) {
|
|
|
265
328
|
stdout.write('\x1b[?25l');
|
|
266
329
|
pickerActive = true;
|
|
267
330
|
const clear = () => {
|
|
268
|
-
if (
|
|
269
|
-
//
|
|
270
|
-
|
|
331
|
+
if (renderedNewlines > 0) {
|
|
332
|
+
// `\x1b[<n>F` = cursor up n lines AND col 1 (atomic). For an
|
|
333
|
+
// M-line frame containing M-1 newlines, the cursor sits at
|
|
334
|
+
// the END of line M after the write (we don't write a
|
|
335
|
+
// trailing newline). Going up `renderedNewlines` (= M-1)
|
|
336
|
+
// lines lands EXACTLY at the start of line 1 — no off-by-one.
|
|
337
|
+
stdout.write(`\x1b[${renderedNewlines}F\x1b[J`);
|
|
338
|
+
}
|
|
339
|
+
else if (renderedNewlines === 0 && renderedAtLeastOnce) {
|
|
340
|
+
// Single-line frame edge case: nothing to scroll up; just
|
|
341
|
+
// wipe the current line.
|
|
342
|
+
stdout.write('\r\x1b[K');
|
|
271
343
|
}
|
|
272
344
|
};
|
|
273
345
|
const render = () => {
|
|
274
346
|
clear();
|
|
275
347
|
const text = renderPicker(state, question, opts.header);
|
|
276
|
-
stdout.write(text
|
|
277
|
-
|
|
348
|
+
stdout.write(text);
|
|
349
|
+
// Track newlines (NOT lines). For "a\nb\nc" that's 2 — which
|
|
350
|
+
// is exactly the cursor-up count we need for the next clear().
|
|
351
|
+
renderedNewlines = (text.match(/\n/g) ?? []).length;
|
|
352
|
+
renderedAtLeastOnce = true;
|
|
278
353
|
};
|
|
279
354
|
const cleanup = () => {
|
|
280
355
|
process.stdin.removeListener('keypress', onKeypress);
|
|
@@ -309,10 +384,27 @@ function runPicker(question, options, opts) {
|
|
|
309
384
|
sequence: key?.sequence,
|
|
310
385
|
char: isPrintable ? str : undefined,
|
|
311
386
|
};
|
|
387
|
+
const prevCursor = state.cursor;
|
|
388
|
+
const wasAwaitingOther = state.awaitingOther;
|
|
312
389
|
const nextState = reducePicker(state, pk);
|
|
313
390
|
if (nextState === state)
|
|
314
391
|
return;
|
|
315
392
|
state = nextState;
|
|
393
|
+
// Live-preview hook (0.3.7): fire on a genuine cursor move in the
|
|
394
|
+
// picker phase only. Don't fire while collecting free-text in the
|
|
395
|
+
// "Other" phase — that would spam the callback on every keystroke
|
|
396
|
+
// for no useful signal. Settling back into picker phase from Other
|
|
397
|
+
// (Esc) doesn't fire either (the cursor "stayed" on Other).
|
|
398
|
+
if (opts.onCursorChange
|
|
399
|
+
&& !state.done
|
|
400
|
+
&& !state.awaitingOther
|
|
401
|
+
&& !wasAwaitingOther
|
|
402
|
+
&& state.cursor !== prevCursor) {
|
|
403
|
+
try {
|
|
404
|
+
opts.onCursorChange(state.cursor);
|
|
405
|
+
}
|
|
406
|
+
catch { /* preview callbacks must never crash the picker */ }
|
|
407
|
+
}
|
|
316
408
|
render();
|
|
317
409
|
if (state.done) {
|
|
318
410
|
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/
|
|
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/
|
|
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;
|