@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
package/dist/cli/commands/ui.js
CHANGED
|
@@ -6,16 +6,23 @@ import fs from 'node:fs';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
-
import
|
|
9
|
+
import { spinner as makeSpinner } from '../spinner.js';
|
|
10
10
|
import { LOCAL_TOOLS } from '../../agent/agent.js';
|
|
11
11
|
import { callMcpTool } from '../../runtime/mcpUtils.js';
|
|
12
12
|
import { listSessions, reconcileStale } from '../../orchestration/orchestrator.js';
|
|
13
|
-
import { readPreferences, writePreferences } from '../../state/preferencesStore.js';
|
|
13
|
+
import { readPreferences, resolveEffort, writePreferences } from '../../state/preferencesStore.js';
|
|
14
14
|
import { readPlan } from '../../state/taskStore.js';
|
|
15
|
-
|
|
15
|
+
// initAgentMd usage moved to commands/init.ts (0.3.7 wizard). The
|
|
16
|
+
// legacy /config + /init switch cases here are gone — the dispatcher
|
|
17
|
+
// in repl.ts routes them to the new handlers first. getConfigPath
|
|
18
|
+
// stays in scope because /doctor still surfaces the path.
|
|
19
|
+
import { getConfigPath, saveConfig } from '../../config/config.js';
|
|
16
20
|
import { copyToClipboard } from '../../runtime/clipboard.js';
|
|
17
|
-
import { initAgentMd } from '../../prompt/initAgentMd.js';
|
|
18
21
|
import { completeWorkspacePath, renderHelp } from '../repl.js';
|
|
22
|
+
import { PROVIDER_CATALOG, findProvider } from '../wizard/providers.js';
|
|
23
|
+
import { selectModel } from '../wizard/modelsApi.js';
|
|
24
|
+
import { buildTheme } from '../theme.js';
|
|
25
|
+
import { listFilesystemSkills } from '../../prompt/skillCatalog.js';
|
|
19
26
|
export async function tryHandleUiCommand(ctx) {
|
|
20
27
|
const { command, args, agent, mcpClient, config, rl, repl } = ctx;
|
|
21
28
|
// 'ctx' alias to keep references to the old ReplContext name working
|
|
@@ -41,7 +48,7 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
41
48
|
console.log(` LLM Endpoint: ${chalk.blue(llm.endpoint)}`);
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
|
-
const spinner =
|
|
51
|
+
const spinner = makeSpinner(chalk.gray('Querying diagnostics & testing latency...')).start();
|
|
45
52
|
try {
|
|
46
53
|
const start = Date.now();
|
|
47
54
|
const testRes = await mcpClient.callTool('list_skills', { scope: 'local' });
|
|
@@ -78,27 +85,10 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
78
85
|
console.log();
|
|
79
86
|
return true;
|
|
80
87
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Print config without API keys
|
|
86
|
-
const scrubbedConfig = JSON.parse(JSON.stringify(config));
|
|
87
|
-
if (scrubbedConfig.llm?.apiKey) {
|
|
88
|
-
scrubbedConfig.llm.apiKey = 'br_••••••••••••••••';
|
|
89
|
-
}
|
|
90
|
-
for (const s of Object.values(scrubbedConfig.servers)) {
|
|
91
|
-
const srv = s;
|
|
92
|
-
if (srv.apiKey)
|
|
93
|
-
srv.apiKey = 'br_••••••••••••••••';
|
|
94
|
-
if (srv.env?.BRAINROUTER_API_KEY) {
|
|
95
|
-
srv.env.BRAINROUTER_API_KEY = 'br_••••••••••••••••';
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
console.log(chalk.gray(JSON.stringify(scrubbedConfig, null, 2)));
|
|
99
|
-
console.log();
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
88
|
+
// /config now lives in commands/config.ts (0.3.7 settings home panel
|
|
89
|
+
// + verb-overloaded get/set). The dispatcher in repl.ts routes it
|
|
90
|
+
// before this case, so leaving anything here is dead — removed.
|
|
91
|
+
// Use `/config raw` if you want the old scrubbed-JSON dump.
|
|
102
92
|
case '/doctor':
|
|
103
93
|
{
|
|
104
94
|
console.log(chalk.bold('\nBrainRouter Doctor:'));
|
|
@@ -116,7 +106,7 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
116
106
|
else {
|
|
117
107
|
console.log(` Endpoint: ${chalk.blue(server.url)}`);
|
|
118
108
|
}
|
|
119
|
-
const spinner =
|
|
109
|
+
const spinner = makeSpinner(chalk.gray('Checking MCP tool surface...')).start();
|
|
120
110
|
try {
|
|
121
111
|
const startedAt = Date.now();
|
|
122
112
|
const res = await mcpClient.listTools();
|
|
@@ -178,68 +168,75 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
178
168
|
console.log();
|
|
179
169
|
return true;
|
|
180
170
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (result.status === 'created') {
|
|
185
|
-
console.log(chalk.green(`\n✓ Created ${result.path}`));
|
|
186
|
-
console.log(chalk.gray('Edit it to describe your project, conventions, and boundaries — any AGENT.md-aware coding agent will read it.\n'));
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
console.log(chalk.yellow(`\nFile already exists: ${result.path}`));
|
|
190
|
-
console.log(chalk.gray('Open it and edit by hand if you want to refresh it.\n'));
|
|
191
|
-
}
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
171
|
+
// /init is now the onboarding-wizard entrypoint (commands/init.ts).
|
|
172
|
+
// The AGENT.md-only path lives behind `/init agentmd` for back-compat.
|
|
173
|
+
// Routed before this case in repl.ts; no fall-through handler needed.
|
|
194
174
|
case '/model':
|
|
195
175
|
{
|
|
196
176
|
const newModel = args[0];
|
|
197
|
-
if (!newModel) {
|
|
198
|
-
console.log(chalk.bold(`\nCurrent model: ${chalk.cyan(agent.getModel())}`));
|
|
199
|
-
console.log(chalk.gray('Switch with: /model <model-name> (e.g. /model gpt-4o-mini, /model gpt-5, /model qwen2.5-coder)\n'));
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
177
|
const previous = agent.getModel();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const server = config.servers[profileName];
|
|
211
|
-
console.log(chalk.bold('\nMCP server'));
|
|
212
|
-
console.log(` Profile: ${chalk.green(profileName)} (${chalk.cyan(server?.type ?? 'unknown')})`);
|
|
213
|
-
if (server?.type === 'http') {
|
|
214
|
-
console.log(` URL: ${chalk.blue(server.url)}`);
|
|
215
|
-
}
|
|
216
|
-
else if (server?.type === 'stdio') {
|
|
217
|
-
console.log(` Cmd: ${chalk.blue(server.command)} ${server.args?.join(' ') || ''}`);
|
|
218
|
-
}
|
|
219
|
-
const spinner = ora(chalk.gray('Fetching MCP tool surface...')).start();
|
|
220
|
-
try {
|
|
221
|
-
const res = await mcpClient.listTools();
|
|
222
|
-
const tools = res.tools || [];
|
|
223
|
-
spinner.succeed(chalk.green(`${tools.length} MCP tools available`));
|
|
224
|
-
const namespaces = {};
|
|
225
|
-
for (const t of tools) {
|
|
226
|
-
const parts = (t.name || '').split('_');
|
|
227
|
-
const ns = parts.length > 1 ? parts[0] : 'misc';
|
|
228
|
-
(namespaces[ns] ||= []).push(t.name);
|
|
229
|
-
}
|
|
230
|
-
for (const ns of Object.keys(namespaces).sort()) {
|
|
231
|
-
console.log(`\n ${chalk.bold.cyan(ns)} (${namespaces[ns].length})`);
|
|
232
|
-
for (const name of namespaces[ns].sort()) {
|
|
233
|
-
console.log(` ${chalk.gray('•')} ${name}`);
|
|
234
|
-
}
|
|
178
|
+
// Direct-switch form `/model <name>` stays for scripts and muscle
|
|
179
|
+
// memory. No-arg opens the picker (0.3.7).
|
|
180
|
+
if (newModel) {
|
|
181
|
+
agent.setModel(newModel);
|
|
182
|
+
if (config.llm) {
|
|
183
|
+
config.llm.model = newModel;
|
|
184
|
+
saveConfig(config);
|
|
235
185
|
}
|
|
186
|
+
console.log(chalk.green(`\n✓ Model switched: ${chalk.gray(previous)} → ${chalk.cyan(newModel)}\n`));
|
|
187
|
+
return true;
|
|
236
188
|
}
|
|
237
|
-
|
|
238
|
-
|
|
189
|
+
// No-arg → open the picker. Resolves provider by matching the
|
|
190
|
+
// saved endpoint against PROVIDER_CATALOG; falls back to the
|
|
191
|
+
// OpenAI entry when nothing matches (the agent loop also
|
|
192
|
+
// defaults to OpenAI-compatible shapes).
|
|
193
|
+
const themeMode = readPreferences(agent.workspaceRoot).theme;
|
|
194
|
+
const theme = buildTheme(themeMode === 'mono' ? 'mono' : themeMode === 'light' ? 'light' : 'dark');
|
|
195
|
+
const llm = config.llm;
|
|
196
|
+
const provider = (llm?.endpoint && PROVIDER_CATALOG.find((p) => p.endpoint.replace(/\/$/, '') === (llm.endpoint ?? '').replace(/\/$/, ''))) ||
|
|
197
|
+
findProvider('openai');
|
|
198
|
+
const result = await selectModel({
|
|
199
|
+
theme,
|
|
200
|
+
provider,
|
|
201
|
+
apiKey: llm?.apiKey ?? '',
|
|
202
|
+
endpointOverride: llm?.endpoint,
|
|
203
|
+
currentModel: previous,
|
|
204
|
+
title: '/model — quick-swap',
|
|
205
|
+
badge: provider.label,
|
|
206
|
+
});
|
|
207
|
+
if (!result) {
|
|
208
|
+
console.log(chalk.yellow('\n /model cancelled.\n'));
|
|
209
|
+
return true;
|
|
239
210
|
}
|
|
240
|
-
|
|
211
|
+
if (result.model === previous) {
|
|
212
|
+
console.log(chalk.gray(`\n Model unchanged (${previous}).\n`));
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
// Cross-provider sanity check — if the picked model looks like
|
|
216
|
+
// a different vendor's namespace (anthropic/*, google/*, etc.)
|
|
217
|
+
// and the active provider isn't a multi-vendor gateway, warn so
|
|
218
|
+
// the user doesn't hit a confusing 404 on the next turn.
|
|
219
|
+
if (looksLikeForeignModel(result.model, provider)) {
|
|
220
|
+
console.log(chalk.yellow(`\n ⚠ "${result.model}" looks like a different provider's namespace. ` +
|
|
221
|
+
`Active endpoint: ${provider.label}.` +
|
|
222
|
+
`\n Run /config provider <id> to switch endpoints, or /model again to pick a native model.\n`));
|
|
223
|
+
}
|
|
224
|
+
agent.setModel(result.model);
|
|
225
|
+
if (config.llm) {
|
|
226
|
+
config.llm.model = result.model;
|
|
227
|
+
saveConfig(config);
|
|
228
|
+
}
|
|
229
|
+
const sourceTag = result.source === 'live' ? `live · ${result.liveCount} models` :
|
|
230
|
+
result.source === 'fallback' ? `offline · static catalog (${result.liveError ?? 'unknown'})` :
|
|
231
|
+
'static catalog';
|
|
232
|
+
console.log(chalk.green(`\n✓ Model switched: ${chalk.gray(previous)} → ${chalk.cyan(result.model)}`));
|
|
233
|
+
console.log(chalk.gray(` Source: ${sourceTag}\n`));
|
|
241
234
|
return true;
|
|
242
235
|
}
|
|
236
|
+
// /mcp moved to its own command file (commands/mcp.ts) as part of 0.3.6
|
|
237
|
+
// Item 11. The new dispatcher supports `/mcp list`, `/mcp reconnect`,
|
|
238
|
+
// and the original no-arg "show tools by namespace" behaviour is now
|
|
239
|
+
// covered by `/mcp tools` (handled in commands/mcp.ts).
|
|
243
240
|
case '/copy':
|
|
244
241
|
{
|
|
245
242
|
if (!agent.lastAnswer) {
|
|
@@ -267,18 +264,18 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
267
264
|
{
|
|
268
265
|
const prefs = readPreferences(agent.workspaceRoot);
|
|
269
266
|
const arg = args.join(' ').trim();
|
|
267
|
+
const { SEGMENT_NAMES, isKnownSegment } = await import('../statusline.js');
|
|
270
268
|
if (!arg) {
|
|
271
269
|
console.log(chalk.bold('\nStatusline'));
|
|
272
270
|
console.log(` Current: ${chalk.cyan(prefs.statusline)}`);
|
|
273
|
-
console.log(chalk.gray(
|
|
274
|
-
console.log(chalk.gray(' Example: /statusline mode,
|
|
271
|
+
console.log(chalk.gray(` Available segments: ${SEGMENT_NAMES.join(', ')}`));
|
|
272
|
+
console.log(chalk.gray(' Example: /statusline mode,workflow,goal,model,session,plan\n'));
|
|
275
273
|
return true;
|
|
276
274
|
}
|
|
277
|
-
const valid = new Set(['mode', 'branch', 'dirty', 'model', 'tokens', 'session']);
|
|
278
275
|
const requested = arg.split(',').map((s) => s.trim()).filter(Boolean);
|
|
279
|
-
const unknown = requested.filter((s) => !
|
|
276
|
+
const unknown = requested.filter((s) => !isKnownSegment(s));
|
|
280
277
|
if (unknown.length > 0) {
|
|
281
|
-
console.log(chalk.red(`\nUnknown segment(s): ${unknown.join(', ')}. Valid: ${
|
|
278
|
+
console.log(chalk.red(`\nUnknown segment(s): ${unknown.join(', ')}. Valid: ${SEGMENT_NAMES.join(', ')}\n`));
|
|
282
279
|
return true;
|
|
283
280
|
}
|
|
284
281
|
writePreferences(agent.workspaceRoot, { statusline: requested.join(',') });
|
|
@@ -373,6 +370,63 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
373
370
|
console.log(chalk.green(`\n✓ Raw scrollback ${next ? 'enabled' : 'disabled'}. Markdown rendering ${next ? 'OFF' : 'ON'} for next turn.\n`));
|
|
374
371
|
return true;
|
|
375
372
|
}
|
|
373
|
+
case '/effort':
|
|
374
|
+
{
|
|
375
|
+
const arg = (args[0] ?? '').toLowerCase();
|
|
376
|
+
const valid = ['low', 'medium', 'high'];
|
|
377
|
+
if (!arg) {
|
|
378
|
+
const resolved = resolveEffort(agent.workspaceRoot);
|
|
379
|
+
const sourceTag = resolved.source === 'env' ? chalk.gray(' (env: BRAINROUTER_EFFORT)') :
|
|
380
|
+
resolved.source === 'preference' ? chalk.gray(' (preference)') :
|
|
381
|
+
chalk.gray(' (default)');
|
|
382
|
+
console.log(chalk.bold(`\nReasoning depth: ${chalk.cyan(resolved.effort)}${sourceTag}`));
|
|
383
|
+
console.log(chalk.gray(' low — terse, one-paragraph answers; minimal ceremony.'));
|
|
384
|
+
console.log(chalk.gray(' medium — current default; no overlay, no provider reasoning slot. (default)'));
|
|
385
|
+
console.log(chalk.gray(' high — step-by-step reasoning; audits evidence before each tool call.'));
|
|
386
|
+
console.log(chalk.gray(' When the model supports it (gpt-5, o-series, gpt-oss, DeepSeek R1/V3+, Qwen3,'));
|
|
387
|
+
console.log(chalk.gray(' Magistral, *-reasoning, *-thinking — works on OpenAI, DeepSeek, OpenRouter,'));
|
|
388
|
+
console.log(chalk.gray(' LM Studio 0.3.29+, Ollama), the level is also forwarded as `reasoning_effort`.'));
|
|
389
|
+
console.log(chalk.gray(' Toggle with: /effort low | /effort medium | /effort high'));
|
|
390
|
+
console.log(chalk.gray(' Env override (one-shot): BRAINROUTER_EFFORT=high brainrouter\n'));
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
if (!valid.includes(arg)) {
|
|
394
|
+
console.log(chalk.red(`\nUnknown level "${arg}". Choose: ${valid.join(' | ')}\n`));
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
writePreferences(agent.workspaceRoot, { effort: arg });
|
|
398
|
+
agent.refreshSystemPrompt();
|
|
399
|
+
const after = resolveEffort(agent.workspaceRoot);
|
|
400
|
+
// Surface a friendly nudge when the env var would still shadow the new
|
|
401
|
+
// preference on the next process boot.
|
|
402
|
+
if (process.env.BRAINROUTER_EFFORT && after.source === 'env') {
|
|
403
|
+
console.log(chalk.yellow(`\n✓ Preference saved as ${arg}, but BRAINROUTER_EFFORT=${process.env.BRAINROUTER_EFFORT} is still active this process — env wins.\n`));
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
console.log(chalk.green(`\n✓ Reasoning depth → ${arg}. Applies on the next turn.\n`));
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
case '/quiet':
|
|
411
|
+
{
|
|
412
|
+
const prefs = readPreferences(agent.workspaceRoot);
|
|
413
|
+
const arg = (args[0] ?? '').toLowerCase();
|
|
414
|
+
const next = arg ? (arg === 'on' || arg === 'true' || arg === '1') : !prefs.quiet;
|
|
415
|
+
writePreferences(agent.workspaceRoot, { quiet: next });
|
|
416
|
+
// `--quiet` set a one-shot env override at startup; once the user
|
|
417
|
+
// explicitly toggles in-session their choice wins from now on.
|
|
418
|
+
if (next) {
|
|
419
|
+
process.env.BRAINROUTER_QUIET = '1';
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
delete process.env.BRAINROUTER_QUIET;
|
|
423
|
+
}
|
|
424
|
+
const detail = next
|
|
425
|
+
? 'recall tables, briefing dumps, and tool-completion previews are now hidden.'
|
|
426
|
+
: 'full chrome restored — recall tables, previews, and briefings will print again.';
|
|
427
|
+
console.log(chalk.green(`\n✓ Quiet mode ${next ? 'enabled' : 'disabled'}: ${detail}\n`));
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
376
430
|
case '/apps':
|
|
377
431
|
case '/plugins':
|
|
378
432
|
{
|
|
@@ -385,12 +439,22 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
385
439
|
console.log(chalk.gray(' Drop a folder under skills/<category>/<name>/SKILL.md to register one.\n'));
|
|
386
440
|
return true;
|
|
387
441
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
console.log(chalk.cyan(`
|
|
442
|
+
const skills = listFilesystemSkills(agent.workspaceRoot);
|
|
443
|
+
if (skills.length > 0) {
|
|
444
|
+
console.log(chalk.gray(' Skills'));
|
|
445
|
+
for (const skill of skills) {
|
|
446
|
+
const category = skill.category ? `${skill.category}/` : '';
|
|
447
|
+
console.log(` • ${chalk.cyan(`${category}${skill.name}`)} (${chalk.gray(skill.scope ?? 'filesystem')})`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (fs.existsSync(pluginsRoot)) {
|
|
451
|
+
const entries = fs.readdirSync(pluginsRoot, { withFileTypes: true });
|
|
452
|
+
const pluginDirs = entries.filter((entry) => entry.isDirectory());
|
|
453
|
+
if (pluginDirs.length > 0) {
|
|
454
|
+
console.log(chalk.gray(' Plugin folders'));
|
|
455
|
+
for (const entry of pluginDirs) {
|
|
456
|
+
console.log(` • ${chalk.cyan(path.relative(agent.workspaceRoot, path.join(pluginsRoot, entry.name)))}`);
|
|
457
|
+
}
|
|
394
458
|
}
|
|
395
459
|
}
|
|
396
460
|
console.log();
|
|
@@ -468,6 +532,29 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
468
532
|
console.log(chalk.gray(' Tip: configure IDE to launch brainrouter with -w <workspace> so paths match.\n'));
|
|
469
533
|
return true;
|
|
470
534
|
}
|
|
535
|
+
case '/where':
|
|
536
|
+
{
|
|
537
|
+
const { gatherWhereInputs, renderWhere } = await import('../whereView.js');
|
|
538
|
+
const { resolveDisplayedMcpState } = await import('../banner.js');
|
|
539
|
+
const { resolveTheme } = await import('../theme.js');
|
|
540
|
+
const theme = resolveTheme(agent.workspaceRoot);
|
|
541
|
+
const displayedMcp = resolveDisplayedMcpState(config, mcpClient);
|
|
542
|
+
const briefing = agent.getLastBriefing();
|
|
543
|
+
const inputs = gatherWhereInputs({
|
|
544
|
+
workspaceRoot: agent.workspaceRoot,
|
|
545
|
+
sessionKey: agent.sessionKey,
|
|
546
|
+
model: agent.getModel(),
|
|
547
|
+
mcpProfile: displayedMcp.profile,
|
|
548
|
+
mcpTransport: displayedMcp.transport,
|
|
549
|
+
mcpOnline: displayedMcp.online,
|
|
550
|
+
mcpIdentity: displayedMcp.identity,
|
|
551
|
+
accessMode: agent.getAccessMode(),
|
|
552
|
+
recalledRecords: agent.getRecalledRecords(),
|
|
553
|
+
briefingSources: briefing.sources,
|
|
554
|
+
});
|
|
555
|
+
console.log('\n' + renderWhere(inputs, theme) + '\n');
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
471
558
|
case '/help': {
|
|
472
559
|
renderHelp(args[0]?.toLowerCase());
|
|
473
560
|
return true;
|
|
@@ -475,3 +562,27 @@ export async function tryHandleUiCommand(ctx) {
|
|
|
475
562
|
}
|
|
476
563
|
return false;
|
|
477
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Heuristic — does the picked model id look like it belongs to a
|
|
567
|
+
* different vendor than the active provider's endpoint? Catches the
|
|
568
|
+
* common foot-gun of picking `anthropic/claude-*` while pointed at
|
|
569
|
+
* OpenAI direct, where the request 404s at the endpoint and the user
|
|
570
|
+
* has no obvious "you needed to switch endpoints" signal.
|
|
571
|
+
*
|
|
572
|
+
* Returns false for gateway providers (OpenRouter, "anthropic-via-gateway")
|
|
573
|
+
* since multi-vendor namespaces are expected there.
|
|
574
|
+
*/
|
|
575
|
+
function looksLikeForeignModel(model, provider) {
|
|
576
|
+
// Gateways are vendor-agnostic by design.
|
|
577
|
+
if (provider.id === 'openrouter' || provider.id === 'anthropic-via-gateway')
|
|
578
|
+
return false;
|
|
579
|
+
const FOREIGN_PREFIXES = {
|
|
580
|
+
openai: ['anthropic/', 'google/', 'meta/', 'mistralai/', 'qwen/', 'deepseek/'],
|
|
581
|
+
deepseek: ['anthropic/', 'google/', 'openai/', 'meta/', 'mistralai/'],
|
|
582
|
+
gemini: ['anthropic/', 'openai/', 'meta/', 'mistralai/', 'deepseek/'],
|
|
583
|
+
lmstudio: [],
|
|
584
|
+
ollama: [],
|
|
585
|
+
};
|
|
586
|
+
const list = FOREIGN_PREFIXES[provider.id] ?? [];
|
|
587
|
+
return list.some((prefix) => model.startsWith(prefix));
|
|
588
|
+
}
|
|
@@ -3,4 +3,24 @@
|
|
|
3
3
|
* Hand-tune imports if the compiler complains.
|
|
4
4
|
*/
|
|
5
5
|
import type { CommandContext } from './_context.js';
|
|
6
|
+
import { type SkillListItem } from '../../prompt/skillCatalog.js';
|
|
7
|
+
/**
|
|
8
|
+
* Decide whether `/grill-me` should refuse to fire because the current
|
|
9
|
+
* workflow already has a written `spec.md`. The clarifying pass is meant to
|
|
10
|
+
* happen BEFORE the spec is committed — once a spec exists, asking again
|
|
11
|
+
* usually means we're re-litigating answers the user already gave, which
|
|
12
|
+
* wastes a turn. `--force` is the explicit escape hatch when the user
|
|
13
|
+
* genuinely wants a second clarifying pass (e.g., scope has drifted).
|
|
14
|
+
*
|
|
15
|
+
* Exported helper for unit tests so the guard logic can be exercised
|
|
16
|
+
* without standing up the whole REPL context. NOT pure: reads workflow
|
|
17
|
+
* state from disk (`getCurrentWorkflow`, `readArtifact`) and the latter
|
|
18
|
+
* may mkdirSync the workflow folder as a side effect.
|
|
19
|
+
*/
|
|
20
|
+
export declare function shouldSkipGrillMe(workspaceRoot: string, force: boolean, sessionKey?: string): {
|
|
21
|
+
skip: boolean;
|
|
22
|
+
slug?: string;
|
|
23
|
+
specPath?: string;
|
|
24
|
+
};
|
|
6
25
|
export declare function tryHandleWorkflowCommand(ctx: CommandContext): Promise<boolean>;
|
|
26
|
+
export declare function normalizeSkillsList(payload: any): SkillListItem[] | undefined;
|