@p-sw/brainbox 0.1.0 → 0.1.1
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/dist/index.js +5312 -0
- package/package.json +7 -4
- package/scripts/smoke_providers.ts +0 -176
- package/src/brain/index.ts +0 -936
- package/src/brain/manager.ts +0 -144
- package/src/brain/memory.ts +0 -99
- package/src/brain/messageHistory.ts +0 -26
- package/src/brain/schedule.ts +0 -11
- package/src/brain/types.ts +0 -26
- package/src/channel/base.ts +0 -527
- package/src/channel/discord.ts +0 -227
- package/src/channel/telegram.ts +0 -150
- package/src/commands/auth.tsx +0 -306
- package/src/commands/brain.ts +0 -119
- package/src/commands/daemon/commands.ts +0 -56
- package/src/commands/daemon/pairingCommand.ts +0 -15
- package/src/commands/daemon/restartCommand.ts +0 -17
- package/src/commands/daemon.ts +0 -168
- package/src/commands/index.ts +0 -16
- package/src/commands/model.tsx +0 -138
- package/src/commands/onboard.tsx +0 -473
- package/src/commands/pairing.ts +0 -32
- package/src/commands/restart.ts +0 -24
- package/src/config/file/auth.ts +0 -105
- package/src/config/file/root.ts +0 -37
- package/src/config/index.ts +0 -21
- package/src/config/loader.ts +0 -115
- package/src/index.ts +0 -61
- package/src/provider/index.ts +0 -122
- package/src/provider/llm.ts +0 -191
- package/src/provider/promptLoader.ts +0 -36
- package/src/provider/providers/302ai.ts +0 -17
- package/src/provider/providers/MiniMax.ts +0 -17
- package/src/provider/providers/anthropic.ts +0 -257
- package/src/provider/providers/azure_cognitive.ts +0 -40
- package/src/provider/providers/azure_openai.ts +0 -49
- package/src/provider/providers/baseten.ts +0 -17
- package/src/provider/providers/bedrock.ts +0 -312
- package/src/provider/providers/cerebras.ts +0 -17
- package/src/provider/providers/cloudflare_gateway.ts +0 -34
- package/src/provider/providers/cloudflare_workers.ts +0 -178
- package/src/provider/providers/copilot.ts +0 -22
- package/src/provider/providers/cortecs.ts +0 -17
- package/src/provider/providers/deepinfra.ts +0 -17
- package/src/provider/providers/deepseek.ts +0 -17
- package/src/provider/providers/digitalocean.ts +0 -17
- package/src/provider/providers/fireworks.ts +0 -17
- package/src/provider/providers/gitlab_duo.ts +0 -184
- package/src/provider/providers/gmi.ts +0 -17
- package/src/provider/providers/groq.ts +0 -17
- package/src/provider/providers/helicone.ts +0 -17
- package/src/provider/providers/huggingface.ts +0 -17
- package/src/provider/providers/ionet.ts +0 -17
- package/src/provider/providers/llamacpp.ts +0 -17
- package/src/provider/providers/llmgateway.ts +0 -17
- package/src/provider/providers/lmstudio.ts +0 -17
- package/src/provider/providers/mistral.ts +0 -17
- package/src/provider/providers/moonshot.ts +0 -17
- package/src/provider/providers/nebius.ts +0 -17
- package/src/provider/providers/nvidia.ts +0 -17
- package/src/provider/providers/ollama.ts +0 -17
- package/src/provider/providers/ollama_cloud.ts +0 -17
- package/src/provider/providers/openai.ts +0 -17
- package/src/provider/providers/openai_compatible.ts +0 -293
- package/src/provider/providers/openrouter.ts +0 -175
- package/src/provider/providers/ovhcloud.ts +0 -17
- package/src/provider/providers/sap_aicore.ts +0 -22
- package/src/provider/providers/scaleway.ts +0 -17
- package/src/provider/providers/snowflake_cortex.ts +0 -207
- package/src/provider/providers/stackit.ts +0 -17
- package/src/provider/providers/together.ts +0 -17
- package/src/provider/providers/venice.ts +0 -17
- package/src/provider/providers/vercel.ts +0 -17
- package/src/provider/providers/vertex.ts +0 -229
- package/src/provider/providers/xai.ts +0 -17
- package/src/provider/providers/zai.ts +0 -17
- package/src/provider/providers/zenmux.ts +0 -17
- package/src/provider/schema.ts +0 -143
- package/src/ui/TextInput.tsx +0 -114
- package/src/utils/daemonClient.ts +0 -85
- package/src/utils/logger.ts +0 -204
- package/tsconfig.json +0 -34
- /package/{prompts → dist/prompts}/daily_schedule.md +0 -0
- /package/{prompts → dist/prompts}/memoir.md +0 -0
- /package/{prompts → dist/prompts}/monthly_schedule.md +0 -0
- /package/{prompts → dist/prompts}/objectifier.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt_fixed.md +0 -0
- /package/{prompts → dist/prompts}/persona_init.md +0 -0
- /package/{prompts → dist/prompts}/schedule_availability.md +0 -0
- /package/{prompts → dist/prompts}/send_message.md +0 -0
- /package/{prompts → dist/prompts}/start_conversation.md +0 -0
package/src/commands/onboard.tsx
DELETED
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Box, Text, render } from "ink";
|
|
3
|
-
import type { Command } from "commander";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { logger } from "@/utils/logger";
|
|
6
|
-
import { TextInput } from "@/ui/TextInput";
|
|
7
|
-
import { listProviderNames } from "@/provider/llm";
|
|
8
|
-
import { registerCommand } from "@/commands";
|
|
9
|
-
import { PROVIDER_EXTRA_FIELDS, setProviderAuth } from "@/config/file/auth";
|
|
10
|
-
import { setModelSlot, setSupermemoryKey } from "@/config/file/root";
|
|
11
|
-
import { brainManager } from "@/brain/manager";
|
|
12
|
-
import { Brain } from "@/brain";
|
|
13
|
-
|
|
14
|
-
// ponytail: chain terminal screens by re-rendering inside one promise — same
|
|
15
|
-
// pattern as model.tsx, keeps ink's stdin listeners sane (one raw-mode at a time).
|
|
16
|
-
|
|
17
|
-
type ProviderStage =
|
|
18
|
-
| { kind: "pick" }
|
|
19
|
-
| { kind: "apiKey"; provider: string }
|
|
20
|
-
| {
|
|
21
|
-
kind: "extras";
|
|
22
|
-
provider: string;
|
|
23
|
-
fields: string[];
|
|
24
|
-
values: Record<string, string>;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function ProviderApp({
|
|
28
|
-
providers,
|
|
29
|
-
onDone,
|
|
30
|
-
}: {
|
|
31
|
-
providers: string[];
|
|
32
|
-
onDone: (ctx: { provider: string }) => void;
|
|
33
|
-
}): React.ReactElement {
|
|
34
|
-
const [stage, setStage] = useState<ProviderStage>({ kind: "pick" });
|
|
35
|
-
const [error, setError] = useState<string | null>(null);
|
|
36
|
-
|
|
37
|
-
if (stage.kind === "pick") {
|
|
38
|
-
return (
|
|
39
|
-
<Box flexDirection="column">
|
|
40
|
-
<Text>{chalk.bold("Step 1/4")} — Choose your first provider</Text>
|
|
41
|
-
<Text dimColor>Enter an exact provider name (see list below)</Text>
|
|
42
|
-
<TextInput
|
|
43
|
-
prompt="provider> "
|
|
44
|
-
onSubmit={(v) => {
|
|
45
|
-
const p = v.trim();
|
|
46
|
-
if (!p) {
|
|
47
|
-
setError("Provider name is required");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (!providers.includes(p)) {
|
|
51
|
-
setError(`Unknown provider "${p}"`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
setError(null);
|
|
55
|
-
setStage({ kind: "apiKey", provider: p });
|
|
56
|
-
}}
|
|
57
|
-
/>
|
|
58
|
-
<Text dimColor>
|
|
59
|
-
known ({providers.length}): {providers.slice(0, 10).join(", ")}
|
|
60
|
-
{providers.length > 10 ? "…" : ""}
|
|
61
|
-
</Text>
|
|
62
|
-
{error && <Text color="red">{error}</Text>}
|
|
63
|
-
</Box>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (stage.kind === "apiKey") {
|
|
68
|
-
const envName = `${stage.provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
69
|
-
return (
|
|
70
|
-
<Box flexDirection="column">
|
|
71
|
-
<Text>
|
|
72
|
-
{chalk.bold("Step 1/4")} — <Text color="cyan">{stage.provider}</Text>{" "}
|
|
73
|
-
api key
|
|
74
|
-
</Text>
|
|
75
|
-
<Text dimColor>(blank reuses ${envName} from env)</Text>
|
|
76
|
-
<TextInput
|
|
77
|
-
prompt="apiKey> "
|
|
78
|
-
onSubmit={(raw) => {
|
|
79
|
-
const apiKey = raw.trim() || (process.env[envName] ?? "");
|
|
80
|
-
if (!apiKey) {
|
|
81
|
-
setError("apiKey cannot be empty");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const extras = PROVIDER_EXTRA_FIELDS[stage.provider] ?? [];
|
|
85
|
-
if (extras.length === 0) {
|
|
86
|
-
setProviderAuth(stage.provider, { apiKey });
|
|
87
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
88
|
-
onDone({ provider: stage.provider });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
setError(null);
|
|
92
|
-
setStage({
|
|
93
|
-
kind: "extras",
|
|
94
|
-
provider: stage.provider,
|
|
95
|
-
fields: extras,
|
|
96
|
-
values: { apiKey },
|
|
97
|
-
});
|
|
98
|
-
}}
|
|
99
|
-
/>
|
|
100
|
-
{error && <Text color="red">{error}</Text>}
|
|
101
|
-
</Box>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// extras
|
|
106
|
-
const nextField = stage.fields[0];
|
|
107
|
-
if (!nextField) {
|
|
108
|
-
setProviderAuth(stage.provider, stage.values);
|
|
109
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
110
|
-
return <Text>Continuing…</Text>;
|
|
111
|
-
}
|
|
112
|
-
return (
|
|
113
|
-
<Box flexDirection="column">
|
|
114
|
-
<Text>
|
|
115
|
-
{chalk.bold("Step 1/4")} — {stage.provider} extra:{" "}
|
|
116
|
-
<Text color="cyan">{nextField}</Text> (blank to skip)
|
|
117
|
-
</Text>
|
|
118
|
-
<TextInput
|
|
119
|
-
prompt={`${nextField}> `}
|
|
120
|
-
onSubmit={(raw) => {
|
|
121
|
-
const value = raw.trim();
|
|
122
|
-
const remaining = stage.fields.slice(1);
|
|
123
|
-
const values: Record<string, string> = { ...stage.values };
|
|
124
|
-
if (value) values[nextField] = value;
|
|
125
|
-
setStage({
|
|
126
|
-
kind: "extras",
|
|
127
|
-
provider: stage.provider,
|
|
128
|
-
fields: remaining,
|
|
129
|
-
values,
|
|
130
|
-
});
|
|
131
|
-
}}
|
|
132
|
-
/>
|
|
133
|
-
</Box>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function ModelApp({
|
|
138
|
-
provider,
|
|
139
|
-
onDone,
|
|
140
|
-
}: {
|
|
141
|
-
provider: string;
|
|
142
|
-
onDone: () => void;
|
|
143
|
-
}): React.ReactElement {
|
|
144
|
-
const [error, setError] = useState<string | null>(null);
|
|
145
|
-
return (
|
|
146
|
-
<Box flexDirection="column">
|
|
147
|
-
<Text>{chalk.bold("Step 1/4")} — Default model (both slots)</Text>
|
|
148
|
-
<Text dimColor>
|
|
149
|
-
e.g. <Text color="cyan">{provider}/</Text>model-name — fine-tune later
|
|
150
|
-
with <Text color="cyan">brainbox model</Text>
|
|
151
|
-
</Text>
|
|
152
|
-
<TextInput
|
|
153
|
-
prompt={`${provider}/model> `}
|
|
154
|
-
onSubmit={(raw) => {
|
|
155
|
-
const value = raw.trim();
|
|
156
|
-
if (!value) {
|
|
157
|
-
setError("Model cannot be empty");
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (!value.startsWith(`${provider}/`)) {
|
|
161
|
-
setError(`Must start with "${provider}/"`);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
setModelSlot("identity", value);
|
|
165
|
-
setModelSlot("conversation", value);
|
|
166
|
-
logger.success(`Set identity + conversation model to ${value}`);
|
|
167
|
-
onDone();
|
|
168
|
-
}}
|
|
169
|
-
/>
|
|
170
|
-
{error && <Text color="red">{error}</Text>}
|
|
171
|
-
</Box>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function SuperMemoryApp({
|
|
176
|
-
onDone,
|
|
177
|
-
}: {
|
|
178
|
-
onDone: () => void;
|
|
179
|
-
}): React.ReactElement {
|
|
180
|
-
const [error, setError] = useState<string | null>(null);
|
|
181
|
-
return (
|
|
182
|
-
<Box flexDirection="column">
|
|
183
|
-
<Text>{chalk.bold("Step 2/4")} — Supermemory API key</Text>
|
|
184
|
-
<Text dimColor>
|
|
185
|
-
powers each brain's long-term memory. Blank reuses $SUPERMEMORY_API_KEY
|
|
186
|
-
from env.
|
|
187
|
-
</Text>
|
|
188
|
-
<TextInput
|
|
189
|
-
prompt="supermemory apiKey> "
|
|
190
|
-
onSubmit={(raw) => {
|
|
191
|
-
const key = raw.trim() || (process.env["SUPERMEMORY_API_KEY"] ?? "");
|
|
192
|
-
if (!key) {
|
|
193
|
-
setError("Supermemory API key cannot be empty");
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
setSupermemoryKey(key);
|
|
197
|
-
logger.success("Saved supermemory key to brainbox.yaml");
|
|
198
|
-
onDone();
|
|
199
|
-
}}
|
|
200
|
-
/>
|
|
201
|
-
{error && <Text color="red">{error}</Text>}
|
|
202
|
-
</Box>
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function BrainApp({
|
|
207
|
-
onDone,
|
|
208
|
-
}: {
|
|
209
|
-
onDone: (ctx: { brainId: string; displayName: string }) => void;
|
|
210
|
-
}): React.ReactElement {
|
|
211
|
-
const [stage, setStage] = useState<
|
|
212
|
-
{ kind: "name" } | { kind: "seed"; displayName: string }
|
|
213
|
-
>({ kind: "name" });
|
|
214
|
-
const [error, setError] = useState<string | null>(null);
|
|
215
|
-
|
|
216
|
-
if (stage.kind === "name") {
|
|
217
|
-
return (
|
|
218
|
-
<Box flexDirection="column">
|
|
219
|
-
<Text>{chalk.bold("Step 3/4")} — Brain name</Text>
|
|
220
|
-
<Text dimColor>The display name your channel will see</Text>
|
|
221
|
-
<TextInput
|
|
222
|
-
prompt="name> "
|
|
223
|
-
onSubmit={(raw) => {
|
|
224
|
-
const v = raw.trim();
|
|
225
|
-
if (!v) {
|
|
226
|
-
setError("Name cannot be empty");
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
setError(null);
|
|
230
|
-
setStage({ kind: "seed", displayName: v });
|
|
231
|
-
}}
|
|
232
|
-
/>
|
|
233
|
-
{error && <Text color="red">{error}</Text>}
|
|
234
|
-
</Box>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<Box flexDirection="column">
|
|
240
|
-
<Text>
|
|
241
|
-
{chalk.bold("Step 3/4")} — Seed for{" "}
|
|
242
|
-
<Text color="cyan">{stage.displayName}</Text>
|
|
243
|
-
</Text>
|
|
244
|
-
<Text dimColor>
|
|
245
|
-
One sentence about who they are. The model will expand it.
|
|
246
|
-
</Text>
|
|
247
|
-
<TextInput
|
|
248
|
-
prompt="seed> "
|
|
249
|
-
onSubmit={async (raw) => {
|
|
250
|
-
const seed = raw.trim();
|
|
251
|
-
if (seed === "skip") {
|
|
252
|
-
logger.info("Skipped brain creation.");
|
|
253
|
-
onDone({ brainId: "", displayName: stage.displayName });
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (!seed) {
|
|
257
|
-
setError("Seed cannot be empty (or type 'skip')");
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const result = await Brain.create(stage.displayName, seed);
|
|
261
|
-
if (!result) {
|
|
262
|
-
setError(
|
|
263
|
-
"Brain creation failed (check logs above, or type 'skip')",
|
|
264
|
-
);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
logger.success(
|
|
268
|
-
`Created brain "${stage.displayName}" (${chalk.cyan(
|
|
269
|
-
result.brainId,
|
|
270
|
-
)})`,
|
|
271
|
-
);
|
|
272
|
-
onDone({ brainId: result.brainId, displayName: stage.displayName });
|
|
273
|
-
}}
|
|
274
|
-
/>
|
|
275
|
-
{error && <Text color="red">{error}</Text>}
|
|
276
|
-
</Box>
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
type ChannelStage =
|
|
281
|
-
| { kind: "kind" }
|
|
282
|
-
| { kind: "token"; kind_: "discord" | "telegram" }
|
|
283
|
-
| {
|
|
284
|
-
kind: "target";
|
|
285
|
-
kind_: "discord" | "telegram";
|
|
286
|
-
token: string;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
function ChannelApp({
|
|
290
|
-
brainId,
|
|
291
|
-
displayName,
|
|
292
|
-
onDone,
|
|
293
|
-
}: {
|
|
294
|
-
brainId: string;
|
|
295
|
-
displayName: string;
|
|
296
|
-
onDone: () => void;
|
|
297
|
-
}): React.ReactElement {
|
|
298
|
-
const [stage, setStage] = useState<ChannelStage>({ kind: "kind" });
|
|
299
|
-
const [error, setError] = useState<string | null>(null);
|
|
300
|
-
|
|
301
|
-
if (stage.kind === "kind") {
|
|
302
|
-
return (
|
|
303
|
-
<Box flexDirection="column">
|
|
304
|
-
<Text>
|
|
305
|
-
{chalk.bold("Step 4/4")} — Channel for{" "}
|
|
306
|
-
<Text color="cyan">{displayName}</Text>
|
|
307
|
-
</Text>
|
|
308
|
-
<Text dimColor>discord | telegram | skip</Text>
|
|
309
|
-
<TextInput
|
|
310
|
-
prompt="channel> "
|
|
311
|
-
onSubmit={(raw) => {
|
|
312
|
-
const v = raw.trim().toLowerCase();
|
|
313
|
-
if (v === "skip") {
|
|
314
|
-
logger.info("Skipped channel setup.");
|
|
315
|
-
onDone();
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (v !== "discord" && v !== "telegram") {
|
|
319
|
-
setError(`Expected "discord", "telegram", or "skip"`);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
setError(null);
|
|
323
|
-
setStage({ kind: "token", kind_: v });
|
|
324
|
-
}}
|
|
325
|
-
/>
|
|
326
|
-
{error && <Text color="red">{error}</Text>}
|
|
327
|
-
</Box>
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (stage.kind === "token") {
|
|
332
|
-
return (
|
|
333
|
-
<Box flexDirection="column">
|
|
334
|
-
<Text>
|
|
335
|
-
{chalk.bold("Step 4/4")} — {stage.kind_} bot token
|
|
336
|
-
</Text>
|
|
337
|
-
<TextInput
|
|
338
|
-
prompt="token> "
|
|
339
|
-
onSubmit={(raw) => {
|
|
340
|
-
const token = raw.trim();
|
|
341
|
-
if (!token) {
|
|
342
|
-
setError("Token cannot be empty");
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
setError(null);
|
|
346
|
-
setStage({ kind: "target", kind_: stage.kind_, token });
|
|
347
|
-
}}
|
|
348
|
-
/>
|
|
349
|
-
{error && <Text color="red">{error}</Text>}
|
|
350
|
-
</Box>
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// target stage — optionally pre-bind channelId/chatId; blank defers to pairing
|
|
355
|
-
return (
|
|
356
|
-
<Box flexDirection="column">
|
|
357
|
-
<Text>
|
|
358
|
-
{chalk.bold("Step 4/4")} — Optional{" "}
|
|
359
|
-
{stage.kind_ === "discord" ? "channelId" : "chatId"} (blank = pair
|
|
360
|
-
later)
|
|
361
|
-
</Text>
|
|
362
|
-
<TextInput
|
|
363
|
-
prompt={`${stage.kind_ === "discord" ? "channelId" : "chatId"}> `}
|
|
364
|
-
onSubmit={async (raw) => {
|
|
365
|
-
const target = raw.trim();
|
|
366
|
-
const existing = await brainManager.loadBrain(brainId);
|
|
367
|
-
if (!existing) {
|
|
368
|
-
setError(`Brain ${brainId} no longer exists`);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
let updated;
|
|
372
|
-
if (stage.kind_ === "discord") {
|
|
373
|
-
updated = {
|
|
374
|
-
...existing,
|
|
375
|
-
channel: "discord" as const,
|
|
376
|
-
discord: { token: stage.token, channelId: target || undefined },
|
|
377
|
-
activated: true,
|
|
378
|
-
};
|
|
379
|
-
} else {
|
|
380
|
-
const chatId = target ? Number(target) : undefined;
|
|
381
|
-
if (target && Number.isNaN(chatId)) {
|
|
382
|
-
setError("chatId must be a number");
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
updated = {
|
|
386
|
-
...existing,
|
|
387
|
-
channel: "telegram" as const,
|
|
388
|
-
telegram: { token: stage.token, chatId },
|
|
389
|
-
activated: true,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
await brainManager.saveBrain(brainId, updated);
|
|
393
|
-
logger.success(
|
|
394
|
-
`Bound ${displayName} → ${stage.kind_}${
|
|
395
|
-
target ? ` (${target})` : " (pairing mode)"
|
|
396
|
-
}`,
|
|
397
|
-
);
|
|
398
|
-
onDone();
|
|
399
|
-
}}
|
|
400
|
-
/>
|
|
401
|
-
{error && <Text color="red">{error}</Text>}
|
|
402
|
-
</Box>
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async function runOnboard(): Promise<void> {
|
|
407
|
-
logger.info(`Welcome — let's get ${chalk.bold("brainbox")} ready.`);
|
|
408
|
-
|
|
409
|
-
const providers = listProviderNames().slice().sort();
|
|
410
|
-
const { promise, resolve } = Promise.withResolvers<void>();
|
|
411
|
-
|
|
412
|
-
// ponytail: chain screens inline inside one promise — one terminal render
|
|
413
|
-
// at a time keeps ink's stdin listeners sane (same pattern as model.tsx).
|
|
414
|
-
// Only the LAST screen resolves — waitUntilExit would race with chained
|
|
415
|
-
// re-renders and resolve after the first screen's unmount.
|
|
416
|
-
let active = render(
|
|
417
|
-
<ProviderApp
|
|
418
|
-
providers={providers}
|
|
419
|
-
onDone={(p) => {
|
|
420
|
-
active.unmount();
|
|
421
|
-
active = render(
|
|
422
|
-
<ModelApp
|
|
423
|
-
provider={p.provider}
|
|
424
|
-
onDone={() => {
|
|
425
|
-
active.unmount();
|
|
426
|
-
active = render(
|
|
427
|
-
<SuperMemoryApp
|
|
428
|
-
onDone={() => {
|
|
429
|
-
active.unmount();
|
|
430
|
-
active = render(
|
|
431
|
-
<BrainApp
|
|
432
|
-
onDone={(b) => {
|
|
433
|
-
active.unmount();
|
|
434
|
-
if (!b.brainId) {
|
|
435
|
-
resolve();
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
active = render(
|
|
439
|
-
<ChannelApp
|
|
440
|
-
brainId={b.brainId}
|
|
441
|
-
displayName={b.displayName}
|
|
442
|
-
onDone={() => {
|
|
443
|
-
active.unmount();
|
|
444
|
-
resolve();
|
|
445
|
-
}}
|
|
446
|
-
/>,
|
|
447
|
-
);
|
|
448
|
-
}}
|
|
449
|
-
/>,
|
|
450
|
-
);
|
|
451
|
-
}}
|
|
452
|
-
/>,
|
|
453
|
-
);
|
|
454
|
-
}}
|
|
455
|
-
/>,
|
|
456
|
-
);
|
|
457
|
-
}}
|
|
458
|
-
/>,
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
await promise;
|
|
462
|
-
|
|
463
|
-
logger.success("Onboarding complete.");
|
|
464
|
-
logger.info(`Run ${chalk.cyan("brainbox daemon")} to bring it online.`);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export function register(program: Command): Command {
|
|
468
|
-
return registerCommand(program, {
|
|
469
|
-
name: "onboard",
|
|
470
|
-
description: "Interactively initialize the brainbox project",
|
|
471
|
-
configure: (cmd) => cmd.action(runOnboard),
|
|
472
|
-
});
|
|
473
|
-
}
|
package/src/commands/pairing.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import { registerCommand } from "@/commands";
|
|
3
|
-
import { logger } from "@/utils/logger";
|
|
4
|
-
import { sendToDaemon, type DaemonResponse } from "@/utils/daemonClient";
|
|
5
|
-
|
|
6
|
-
interface PairingResponse extends DaemonResponse {
|
|
7
|
-
brainId?: string;
|
|
8
|
-
displayName?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function pair(code: string): Promise<void> {
|
|
12
|
-
logger.debug(`pair: sending code "${code}" to daemon`);
|
|
13
|
-
// ponytail: sendToDaemon logs and process.exit(1)s on any failure — no try/catch needed here.
|
|
14
|
-
const response = await sendToDaemon<PairingResponse>({
|
|
15
|
-
command: "pairing",
|
|
16
|
-
args: { code },
|
|
17
|
-
});
|
|
18
|
-
logger.success(
|
|
19
|
-
`Paired "${response.displayName ?? response.brainId}" successfully.`,
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function register(program: Command): Command {
|
|
24
|
-
return registerCommand(program, {
|
|
25
|
-
name: "pairing",
|
|
26
|
-
description: "Complete channel pairing with a pairing code",
|
|
27
|
-
configure: (cmd) =>
|
|
28
|
-
cmd
|
|
29
|
-
.argument("<code>", "Pairing code issued by the channel")
|
|
30
|
-
.action(pair),
|
|
31
|
-
});
|
|
32
|
-
}
|
package/src/commands/restart.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import { registerCommand } from "@/commands";
|
|
3
|
-
import { logger } from "@/utils/logger";
|
|
4
|
-
import { sendToDaemon, type DaemonResponse } from "@/utils/daemonClient";
|
|
5
|
-
|
|
6
|
-
interface RestartResponse extends DaemonResponse {
|
|
7
|
-
result?: { restarted?: number };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function restart(): Promise<void> {
|
|
11
|
-
logger.debug(`restart: sending restart to daemon`);
|
|
12
|
-
// ponytail: sendToDaemon logs and process.exit(1)s on any failure — no try/catch needed.
|
|
13
|
-
const response = await sendToDaemon<RestartResponse>({ command: "restart" });
|
|
14
|
-
const count = response.result?.restarted ?? 0;
|
|
15
|
-
logger.success(`Daemon restarted (${count} channel(s) re-initialised).`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function register(program: Command): Command {
|
|
19
|
-
return registerCommand(program, {
|
|
20
|
-
name: "restart",
|
|
21
|
-
description: "Shutdown channels, reload configs/brain index, re-init",
|
|
22
|
-
configure: (cmd) => cmd.action(restart),
|
|
23
|
-
});
|
|
24
|
-
}
|
package/src/config/file/auth.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import z from "zod";
|
|
2
|
-
import { configFile } from "../loader";
|
|
3
|
-
|
|
4
|
-
// ponytail: provider-specific knobs (region, project, deployment, endpoint, account)
|
|
5
|
-
// live alongside apiKey in the same record. .loose() lets each provider add fields
|
|
6
|
-
// without us having to enumerate them in the schema.
|
|
7
|
-
//
|
|
8
|
-
// Reference — keys read by each provider (defaults: env vars listed in parens):
|
|
9
|
-
//
|
|
10
|
-
// openrouter apiKey
|
|
11
|
-
// openai apiKey
|
|
12
|
-
// mistral apiKey
|
|
13
|
-
// deepseek apiKey
|
|
14
|
-
// groq apiKey
|
|
15
|
-
// cerebras apiKey
|
|
16
|
-
// fireworks apiKey
|
|
17
|
-
// together apiKey
|
|
18
|
-
// xai apiKey
|
|
19
|
-
// moonshot apiKey
|
|
20
|
-
// nvidia apiKey
|
|
21
|
-
// deepinfra apiKey
|
|
22
|
-
// 302ai apiKey
|
|
23
|
-
// digitalocean apiKey
|
|
24
|
-
// helicone apiKey
|
|
25
|
-
// scaleway apiKey
|
|
26
|
-
// venice apiKey
|
|
27
|
-
// nebius apiKey
|
|
28
|
-
// ovhcloud apiKey
|
|
29
|
-
// stackit apiKey
|
|
30
|
-
// gmi apiKey
|
|
31
|
-
// zai apiKey
|
|
32
|
-
// zenmux apiKey
|
|
33
|
-
// MiniMax apiKey
|
|
34
|
-
// ionet apiKey
|
|
35
|
-
// baseten apiKey
|
|
36
|
-
// cortecs apiKey
|
|
37
|
-
// huggingface apiKey
|
|
38
|
-
// lmstudio apiKey (no real key — any value works)
|
|
39
|
-
// ollama apiKey (no real key — any value works)
|
|
40
|
-
// ollama-cloud apiKey
|
|
41
|
-
// llamacpp apiKey (no real key — any value works)
|
|
42
|
-
// vercel apiKey
|
|
43
|
-
// llmgateway apiKey
|
|
44
|
-
// cloudflare-gateway apiKey + accountId (CLOUDFLARE_ACCOUNT_ID) + gatewayId (CLOUDFLARE_GATEWAY_ID)
|
|
45
|
-
// cloudflare-workers apiKey + accountId (CLOUDFLARE_ACCOUNT_ID)
|
|
46
|
-
// sap-aicore apiKey + baseURL (SAP_AI_CORE_BASE_URL)
|
|
47
|
-
// azure-openai apiKey + resource (AZURE_OPENAI_RESOURCE) + apiVersion (AZURE_OPENAI_API_VERSION)
|
|
48
|
-
// azure-cognitive apiKey + resource (AZURE_COGNITIVE_RESOURCE) + apiVersion (AZURE_COGNITIVE_API_VERSION)
|
|
49
|
-
// anthropic apiKey + baseURL (ANTHROPIC_BASE_URL) + apiVersion
|
|
50
|
-
// bedrock apiKey = "accessKeyId:secretAccessKey" + region (AWS_REGION) + sessionToken (AWS_SESSION_TOKEN)
|
|
51
|
-
// vertex apiKey (OAuth access token, GOOGLE_ACCESS_TOKEN) + project (GOOGLE_CLOUD_PROJECT) + region (GOOGLE_CLOUD_REGION)
|
|
52
|
-
// copilot apiKey (pre-exchanged Copilot session token)
|
|
53
|
-
// gitlab-duo apiKey (GitLab PAT) + baseURL (GITLAB_BASE_URL)
|
|
54
|
-
export type AuthRecord = Record<string, string>;
|
|
55
|
-
export type AuthFile = Record<string, AuthRecord>;
|
|
56
|
-
|
|
57
|
-
// ponytail: .loose() lets each provider add fields (region, project, deployment,
|
|
58
|
-
// endpoint, account) without us enumerating them in the schema. Writers only
|
|
59
|
-
// touch string values, so we cast to z.ZodType<AuthFile> to keep the public
|
|
60
|
-
// type tight while the YAML round-trip stays permissive.
|
|
61
|
-
const AuthSchema = z.record(
|
|
62
|
-
z.string(),
|
|
63
|
-
z.object({ apiKey: z.string().default("") }).loose(),
|
|
64
|
-
) as z.ZodType<AuthFile>;
|
|
65
|
-
|
|
66
|
-
const authCfg = configFile<AuthFile>("auth.yaml", { schema: AuthSchema });
|
|
67
|
-
|
|
68
|
-
// ponytail: providers freely add apiKey + extras. .loose() on the schema keeps
|
|
69
|
-
// the writer provider-agnostic — unknown fields round-trip through YAML as-is.
|
|
70
|
-
export const PROVIDER_EXTRA_FIELDS: Record<string, string[]> = {
|
|
71
|
-
"cloudflare-gateway": ["accountId", "gatewayId"],
|
|
72
|
-
"cloudflare-workers": ["accountId"],
|
|
73
|
-
"sap-aicore": ["baseURL"],
|
|
74
|
-
"azure-openai": ["resource", "apiVersion"],
|
|
75
|
-
"azure-cognitive": ["resource", "apiVersion"],
|
|
76
|
-
anthropic: ["baseURL", "apiVersion"],
|
|
77
|
-
bedrock: ["region", "sessionToken"],
|
|
78
|
-
vertex: ["project", "region"],
|
|
79
|
-
"gitlab-duo": ["baseURL"],
|
|
80
|
-
"snowflake-cortex": ["account"],
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export function readAuthFile(): AuthFile {
|
|
84
|
-
return authCfg.read();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function setProviderAuth(
|
|
88
|
-
provider: string,
|
|
89
|
-
fields: AuthRecord,
|
|
90
|
-
): AuthFile {
|
|
91
|
-
return authCfg.update((auth) => ({
|
|
92
|
-
...auth,
|
|
93
|
-
[provider]: { ...(auth[provider] ?? {}), ...fields },
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function removeProviderAuth(provider: string): AuthFile {
|
|
98
|
-
return authCfg.update((auth) => {
|
|
99
|
-
const next = { ...auth };
|
|
100
|
-
delete next[provider];
|
|
101
|
-
return next;
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export default authCfg.read();
|
package/src/config/file/root.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import z from "zod";
|
|
2
|
-
import { configFile } from "../loader";
|
|
3
|
-
|
|
4
|
-
const RootConfigSchema = z.object({
|
|
5
|
-
debug: z.boolean().default(false),
|
|
6
|
-
supermemory: z.object({ apiKey: z.string().default("") }),
|
|
7
|
-
conversationModel: z.string().default(""),
|
|
8
|
-
identityModel: z.string().default(""),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export type RootConfig = z.infer<typeof RootConfigSchema>;
|
|
12
|
-
export type ModelSlot = "identity" | "conversation";
|
|
13
|
-
|
|
14
|
-
const rootCfg = configFile<RootConfig>("brainbox.yaml", {
|
|
15
|
-
schema: RootConfigSchema,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export function readRootFile(): RootConfig {
|
|
19
|
-
return rootCfg.read();
|
|
20
|
-
}
|
|
21
|
-
const SLOT_KEY: Record<ModelSlot, keyof RootConfig> = {
|
|
22
|
-
identity: "identityModel",
|
|
23
|
-
conversation: "conversationModel",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export function setModelSlot(slot: ModelSlot, value: string): RootConfig {
|
|
27
|
-
return rootCfg.update((root) => ({ ...root, [SLOT_KEY[slot]]: value }));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function setSupermemoryKey(key: string): RootConfig {
|
|
31
|
-
return rootCfg.update((root) => ({
|
|
32
|
-
...root,
|
|
33
|
-
supermemory: { ...root.supermemory, apiKey: key },
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export default rootCfg.read();
|
package/src/config/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { brainboxRoot } from "./loader";
|
|
2
|
-
import rootConfig from "./file/root";
|
|
3
|
-
import authConfig from "./file/auth";
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
debug: boolean;
|
|
7
|
-
brainboxRoot: string;
|
|
8
|
-
supermemoryApiKey: string;
|
|
9
|
-
conversationModel: string;
|
|
10
|
-
identityModel: string;
|
|
11
|
-
auth: Record<string, { apiKey: string; [k: string]: unknown }>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const config: Config = {
|
|
15
|
-
debug: rootConfig.debug,
|
|
16
|
-
brainboxRoot,
|
|
17
|
-
supermemoryApiKey: rootConfig.supermemory.apiKey,
|
|
18
|
-
conversationModel: rootConfig.conversationModel,
|
|
19
|
-
identityModel: rootConfig.identityModel,
|
|
20
|
-
auth: authConfig as Config["auth"],
|
|
21
|
-
};
|