@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,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.3.7 wizard — pure types + step state machine.
|
|
3
|
+
*
|
|
4
|
+
* The wizard walks the user through a small, ordered sequence of
|
|
5
|
+
* decisions. Each step has its own decision shape; together they fill
|
|
6
|
+
* in a `WizardDraft` that the Done step commits to disk.
|
|
7
|
+
*
|
|
8
|
+
* Why a typed Step enum + draft (instead of one giant async function
|
|
9
|
+
* with awaits in sequence)? Three reasons:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Esc backs out one step at a time.** A reducer transition lets
|
|
12
|
+
* us model "back" cleanly (Step.Provider → Step.Theme) without
|
|
13
|
+
* unwinding an async stack.
|
|
14
|
+
* 2. **The runner is testable.** Driving the reducer with synthetic
|
|
15
|
+
* events (`pick`, `back`, `abort`) lets us assert the wizard ends
|
|
16
|
+
* in a known terminal state without simulating a real TTY.
|
|
17
|
+
* 3. **The shape lifts straight from peer references.**
|
|
18
|
+
* `openSrc/codex/codex-rs/tui/src/onboarding/onboarding_screen.rs`
|
|
19
|
+
* uses the same Step enum + per-step state pattern; we copy the
|
|
20
|
+
* pattern, not the code.
|
|
21
|
+
*/
|
|
22
|
+
/** Ordered list — used by the runner to compute "next" and "previous". */
|
|
23
|
+
export const STEP_ORDER = [
|
|
24
|
+
'welcome',
|
|
25
|
+
'theme',
|
|
26
|
+
'provider',
|
|
27
|
+
'apiKey',
|
|
28
|
+
'model',
|
|
29
|
+
'mcp',
|
|
30
|
+
'agentMd',
|
|
31
|
+
'done',
|
|
32
|
+
];
|
|
33
|
+
export function initWizardState() {
|
|
34
|
+
return {
|
|
35
|
+
currentStep: 'welcome',
|
|
36
|
+
draft: {},
|
|
37
|
+
warnings: [],
|
|
38
|
+
committed: false,
|
|
39
|
+
aborted: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compute the next step. Pure — used by `reduceWizard` and exposed for
|
|
44
|
+
* tests + the runner's progress indicator ("step 3 of 7").
|
|
45
|
+
*/
|
|
46
|
+
export function nextStep(current) {
|
|
47
|
+
const idx = STEP_ORDER.indexOf(current);
|
|
48
|
+
if (idx < 0 || idx === STEP_ORDER.length - 1)
|
|
49
|
+
return undefined;
|
|
50
|
+
return STEP_ORDER[idx + 1];
|
|
51
|
+
}
|
|
52
|
+
export function prevStep(current) {
|
|
53
|
+
const idx = STEP_ORDER.indexOf(current);
|
|
54
|
+
if (idx <= 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
return STEP_ORDER[idx - 1];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Pure reducer. Every wizard transition must go through here so the
|
|
60
|
+
* test suite can replay the same event sequence the runner emits.
|
|
61
|
+
*
|
|
62
|
+
* Contract:
|
|
63
|
+
* - `advance` applies the patch into the draft and steps forward;
|
|
64
|
+
* a no-op when called on the Done step.
|
|
65
|
+
* - `back` rewinds one step; a no-op on the first step.
|
|
66
|
+
* - `abort` lands the wizard in a terminal state with `aborted: true`
|
|
67
|
+
* and the draft preserved (caller may inspect for partial intent).
|
|
68
|
+
* - `warn` appends an advisory; doesn't move the step pointer.
|
|
69
|
+
* - `commit` flips `committed: true` on the Done step only.
|
|
70
|
+
*
|
|
71
|
+
* The reducer never throws — bad inputs are silently ignored so a
|
|
72
|
+
* stray key event doesn't crash the wizard mid-render.
|
|
73
|
+
*/
|
|
74
|
+
export function reduceWizard(state, event) {
|
|
75
|
+
if (state.aborted || state.committed)
|
|
76
|
+
return state;
|
|
77
|
+
switch (event.kind) {
|
|
78
|
+
case 'advance': {
|
|
79
|
+
const after = nextStep(state.currentStep);
|
|
80
|
+
if (!after)
|
|
81
|
+
return state;
|
|
82
|
+
return {
|
|
83
|
+
...state,
|
|
84
|
+
currentStep: after,
|
|
85
|
+
draft: { ...state.draft, ...event.patch },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case 'back': {
|
|
89
|
+
const before = prevStep(state.currentStep);
|
|
90
|
+
if (!before)
|
|
91
|
+
return state;
|
|
92
|
+
return { ...state, currentStep: before };
|
|
93
|
+
}
|
|
94
|
+
case 'abort':
|
|
95
|
+
return { ...state, aborted: true };
|
|
96
|
+
case 'warn':
|
|
97
|
+
return {
|
|
98
|
+
...state,
|
|
99
|
+
warnings: [
|
|
100
|
+
...state.warnings,
|
|
101
|
+
{ step: state.currentStep, message: event.message },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
case 'commit':
|
|
105
|
+
if (state.currentStep !== 'done')
|
|
106
|
+
return state;
|
|
107
|
+
return { ...state, committed: true };
|
|
108
|
+
}
|
|
109
|
+
}
|
package/dist/config/config.d.ts
CHANGED
|
@@ -5,6 +5,25 @@ export interface ServerConfig {
|
|
|
5
5
|
env?: Record<string, string>;
|
|
6
6
|
url?: string;
|
|
7
7
|
apiKey?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 0.3.6 item 10a: identity tag for distinguishing the BrainRouter cloud
|
|
10
|
+
* brain ("our MCP") from third-party MCPs the user might attach (GitHub,
|
|
11
|
+
* filesystem, Slack, etc.). Drives status surfaces (banner / statusline /
|
|
12
|
+
* `/where`) and the offline-mode prompt swap: when "the brain" is down
|
|
13
|
+
* the user gets a clear signal, not a generic "MCP offline" message.
|
|
14
|
+
*
|
|
15
|
+
* Detection priority when this field is unset:
|
|
16
|
+
* 1. Server profile name starts with `brainrouter` (case-insensitive).
|
|
17
|
+
* 2. URL hostname matches `*.brainrouter.cloud` or `*.brainrouter.dev`.
|
|
18
|
+
* 3. (Run-time fallback) first successful `listTools()` includes
|
|
19
|
+
* both `memory_recall` AND `list_skills` — the BrainRouter signature
|
|
20
|
+
* pair. See `detectMcpIdentity` in `runtime/mcpClient.ts`.
|
|
21
|
+
*
|
|
22
|
+
* Explicit values always win — if the user marks a third-party MCP as
|
|
23
|
+
* `identity: 'brainrouter'`, that's their call (e.g. they're running a
|
|
24
|
+
* local fork that exposes the same tool surface).
|
|
25
|
+
*/
|
|
26
|
+
identity?: 'brainrouter' | 'third-party';
|
|
8
27
|
}
|
|
9
28
|
export interface LLMConfig {
|
|
10
29
|
provider: 'openai';
|
|
@@ -18,5 +37,38 @@ export interface Config {
|
|
|
18
37
|
llm?: LLMConfig;
|
|
19
38
|
}
|
|
20
39
|
export declare function getConfigPath(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Read the existing config.json or exit with a clear error. The CLI owns
|
|
42
|
+
* READS of this file — writes are the user's job (via `brainrouter login`,
|
|
43
|
+
* `brainrouter config`, or direct edit). Auto-fabricating a default config
|
|
44
|
+
* was a holdover from the monorepo dev story; it only ever produced a
|
|
45
|
+
* broken stdio profile pointing at a sibling `brainrouter/` package that
|
|
46
|
+
* doesn't exist outside the monorepo, so npm-installed users got a config
|
|
47
|
+
* file they had to fix anyway.
|
|
48
|
+
*
|
|
49
|
+
* Setup commands (login / config) that need to BUILD a fresh config from
|
|
50
|
+
* scratch should call `loadOrInitConfig` instead — it returns an empty
|
|
51
|
+
* skeleton when no file exists rather than exiting.
|
|
52
|
+
*/
|
|
21
53
|
export declare function loadConfig(): Config;
|
|
54
|
+
/**
|
|
55
|
+
* Pick the best API key from the environment for a given endpoint.
|
|
56
|
+
* Order: provider-specific envKey (matched against `PROVIDER_CATALOG`
|
|
57
|
+
* by endpoint), then `OPENAI_API_KEY` (most common default), then the
|
|
58
|
+
* generic `BRAINROUTER_LLM_API_KEY`. Returns undefined if nothing is
|
|
59
|
+
* set so the caller can choose how to surface that.
|
|
60
|
+
*
|
|
61
|
+
* Kept here (vs imported from `cli/wizard/providers.ts`) so non-CLI
|
|
62
|
+
* callers — the MCP child env propagation, future SDK clients — can
|
|
63
|
+
* use it without dragging in the wizard surface.
|
|
64
|
+
*/
|
|
65
|
+
export declare function backfillApiKeyFromEnv(endpoint: string | undefined): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Setup-wizard variant of `loadConfig`. Returns the existing config when
|
|
68
|
+
* one is on disk, or an empty skeleton when none exists yet. Used by
|
|
69
|
+
* `brainrouter login` and `brainrouter config` so a first-run user can
|
|
70
|
+
* BUILD their config interactively without hitting the strict
|
|
71
|
+
* "no config — run setup" error from `loadConfig`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function loadOrInitConfig(): Config;
|
|
22
74
|
export declare function saveConfig(config: Config): void;
|
package/dist/config/config.js
CHANGED
|
@@ -1,46 +1,110 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
5
4
|
const CONFIG_DIR = path.join(os.homedir(), '.config', 'brainrouter');
|
|
6
5
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
6
|
export function getConfigPath() {
|
|
8
7
|
return CONFIG_FILE;
|
|
9
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Read the existing config.json or exit with a clear error. The CLI owns
|
|
11
|
+
* READS of this file — writes are the user's job (via `brainrouter login`,
|
|
12
|
+
* `brainrouter config`, or direct edit). Auto-fabricating a default config
|
|
13
|
+
* was a holdover from the monorepo dev story; it only ever produced a
|
|
14
|
+
* broken stdio profile pointing at a sibling `brainrouter/` package that
|
|
15
|
+
* doesn't exist outside the monorepo, so npm-installed users got a config
|
|
16
|
+
* file they had to fix anyway.
|
|
17
|
+
*
|
|
18
|
+
* Setup commands (login / config) that need to BUILD a fresh config from
|
|
19
|
+
* scratch should call `loadOrInitConfig` instead — it returns an empty
|
|
20
|
+
* skeleton when no file exists rather than exiting.
|
|
21
|
+
*/
|
|
10
22
|
export function loadConfig() {
|
|
11
|
-
let config;
|
|
12
23
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
13
|
-
config
|
|
24
|
+
console.error(`No BrainRouter config found at ${CONFIG_FILE}.`);
|
|
25
|
+
console.error(`Run \`brainrouter login\` to connect to a hosted MCP server, or \`brainrouter config\` to set one up.`);
|
|
26
|
+
process.exit(1);
|
|
14
27
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
config = parsed;
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
console.error(`Warning: Failed to parse config file at ${CONFIG_FILE}. Using default config.`);
|
|
28
|
-
config = createDefaultConfig();
|
|
29
|
-
}
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
31
|
+
parsed = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error(`Error: Failed to parse config file at ${CONFIG_FILE}: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
console.error(`Fix the file by hand, or delete it and run \`brainrouter config\` to recreate.`);
|
|
36
|
+
process.exit(1);
|
|
30
37
|
}
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
if (!parsed.servers)
|
|
39
|
+
parsed.servers = {};
|
|
40
|
+
if (!parsed.activeServer)
|
|
41
|
+
parsed.activeServer = '';
|
|
33
42
|
// The default config writes `llm.apiKey: ''` so it never appears as a
|
|
34
43
|
// secret in the committed file. Backfill from the standard env vars at
|
|
35
44
|
// load time so every downstream consumer (callOpenAI, mcpClient env
|
|
36
45
|
// propagation, the cognitive extractor LLM runner) sees a real value
|
|
37
46
|
// instead of the empty string.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
//
|
|
48
|
+
// 0.3.7 — provider-specific fallback. Pre-0.3.7 we only checked
|
|
49
|
+
// OPENAI_API_KEY / BRAINROUTER_LLM_API_KEY, which silently broke
|
|
50
|
+
// users with config.llm.endpoint pointing at DeepSeek / OpenRouter /
|
|
51
|
+
// Gemini / etc. who had the *correct* provider key in their shell
|
|
52
|
+
// (DEEPSEEK_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY, …). Now we
|
|
53
|
+
// match the saved endpoint to a provider entry and try ITS envKey
|
|
54
|
+
// FIRST, then fall through to the generic vars.
|
|
55
|
+
if (parsed.llm && !parsed.llm.apiKey.trim()) {
|
|
56
|
+
parsed.llm.apiKey = backfillApiKeyFromEnv(parsed.llm.endpoint) ?? '';
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pick the best API key from the environment for a given endpoint.
|
|
62
|
+
* Order: provider-specific envKey (matched against `PROVIDER_CATALOG`
|
|
63
|
+
* by endpoint), then `OPENAI_API_KEY` (most common default), then the
|
|
64
|
+
* generic `BRAINROUTER_LLM_API_KEY`. Returns undefined if nothing is
|
|
65
|
+
* set so the caller can choose how to surface that.
|
|
66
|
+
*
|
|
67
|
+
* Kept here (vs imported from `cli/wizard/providers.ts`) so non-CLI
|
|
68
|
+
* callers — the MCP child env propagation, future SDK clients — can
|
|
69
|
+
* use it without dragging in the wizard surface.
|
|
70
|
+
*/
|
|
71
|
+
export function backfillApiKeyFromEnv(endpoint) {
|
|
72
|
+
// Provider-specific env vars in order of catalog precedence. Hardcoded
|
|
73
|
+
// here so this function stays free of the wizard import (which pulls
|
|
74
|
+
// in chalk, ink picker types, etc.). Keep in lockstep with
|
|
75
|
+
// `cli/wizard/providers.ts → PROVIDER_CATALOG`.
|
|
76
|
+
const PROVIDER_ENV_BY_ENDPOINT = [
|
|
77
|
+
{ endpoint: 'https://api.openai.com/v1', envKey: 'OPENAI_API_KEY' },
|
|
78
|
+
{ endpoint: 'https://api.deepseek.com/v1', envKey: 'DEEPSEEK_API_KEY' },
|
|
79
|
+
{ endpoint: 'https://openrouter.ai/api/v1', envKey: 'OPENROUTER_API_KEY' },
|
|
80
|
+
{ endpoint: 'https://generativelanguage.googleapis.com/v1beta/openai', envKey: 'GEMINI_API_KEY' },
|
|
81
|
+
{ endpoint: 'https://api.anthropic.com/v1', envKey: 'ANTHROPIC_API_KEY' },
|
|
82
|
+
{ endpoint: 'http://localhost:1234/v1', envKey: 'LMSTUDIO_API_KEY' },
|
|
83
|
+
{ endpoint: 'http://localhost:11434/v1', envKey: 'OLLAMA_API_KEY' },
|
|
84
|
+
];
|
|
85
|
+
if (endpoint) {
|
|
86
|
+
const trimmed = endpoint.replace(/\/$/, '');
|
|
87
|
+
const match = PROVIDER_ENV_BY_ENDPOINT.find((p) => p.endpoint === trimmed);
|
|
88
|
+
if (match) {
|
|
89
|
+
const value = process.env[match.envKey];
|
|
90
|
+
if (value && value.trim())
|
|
91
|
+
return value.trim();
|
|
92
|
+
}
|
|
42
93
|
}
|
|
43
|
-
return
|
|
94
|
+
return process.env.OPENAI_API_KEY?.trim() || process.env.BRAINROUTER_LLM_API_KEY?.trim() || undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Setup-wizard variant of `loadConfig`. Returns the existing config when
|
|
98
|
+
* one is on disk, or an empty skeleton when none exists yet. Used by
|
|
99
|
+
* `brainrouter login` and `brainrouter config` so a first-run user can
|
|
100
|
+
* BUILD their config interactively without hitting the strict
|
|
101
|
+
* "no config — run setup" error from `loadConfig`.
|
|
102
|
+
*/
|
|
103
|
+
export function loadOrInitConfig() {
|
|
104
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
105
|
+
return { activeServer: '', servers: {} };
|
|
106
|
+
}
|
|
107
|
+
return loadConfig();
|
|
44
108
|
}
|
|
45
109
|
export function saveConfig(config) {
|
|
46
110
|
try {
|
|
@@ -53,53 +117,3 @@ export function saveConfig(config) {
|
|
|
53
117
|
console.error(`Error: Failed to save config to ${CONFIG_FILE}:`, error instanceof Error ? error.message : error);
|
|
54
118
|
}
|
|
55
119
|
}
|
|
56
|
-
function createDefaultConfig() {
|
|
57
|
-
// Derive path to the default local MCP server dist relative to this module.
|
|
58
|
-
// After build: brainrouter-cli/dist/config/config.js → walk three levels up
|
|
59
|
-
// to the monorepo root, then into the sibling `brainrouter/` package
|
|
60
|
-
// (formerly `mcp/`) which is the MCP server.
|
|
61
|
-
const defaultMcpPath = path.resolve(import.meta.dirname, '..', '..', '..', 'brainrouter', 'dist', 'index.js');
|
|
62
|
-
const config = {
|
|
63
|
-
activeServer: 'default',
|
|
64
|
-
servers: {
|
|
65
|
-
default: {
|
|
66
|
-
type: 'stdio',
|
|
67
|
-
command: 'node',
|
|
68
|
-
args: [defaultMcpPath, '--root', './'],
|
|
69
|
-
env: {
|
|
70
|
-
BRAINROUTER_API_KEY: 'br_admin_key_placeholder'
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
llm: {
|
|
75
|
-
provider: 'openai',
|
|
76
|
-
apiKey: '',
|
|
77
|
-
model: 'gpt-4o-mini',
|
|
78
|
-
endpoint: 'https://api.openai.com/v1'
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
saveConfig(config);
|
|
82
|
-
return config;
|
|
83
|
-
}
|
|
84
|
-
function resolveDefaultApiKey(config) {
|
|
85
|
-
const defaultServer = config.servers.default;
|
|
86
|
-
if (defaultServer &&
|
|
87
|
-
defaultServer.type === 'stdio' &&
|
|
88
|
-
defaultServer.env &&
|
|
89
|
-
defaultServer.env.BRAINROUTER_API_KEY === 'br_admin_key_placeholder') {
|
|
90
|
-
const dbPath = process.env.BRAINROUTER_MEMORY_DB || path.join(os.homedir(), '.brainrouter', 'memory.db');
|
|
91
|
-
if (fs.existsSync(dbPath)) {
|
|
92
|
-
try {
|
|
93
|
-
const db = new DatabaseSync(dbPath);
|
|
94
|
-
const row = db.prepare("SELECT api_key FROM users WHERE is_admin = 1 LIMIT 1").get();
|
|
95
|
-
if (row && row.api_key) {
|
|
96
|
-
defaultServer.env.BRAINROUTER_API_KEY = row.api_key;
|
|
97
|
-
saveConfig(config);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
// ignore errors
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|