@p-sw/brainbox 0.1.0 → 0.1.2-alpha.0

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.
Files changed (92) hide show
  1. package/dist/index.js +5319 -0
  2. package/package.json +10 -4
  3. package/scripts/smoke_providers.ts +0 -176
  4. package/src/brain/index.ts +0 -936
  5. package/src/brain/manager.ts +0 -144
  6. package/src/brain/memory.ts +0 -99
  7. package/src/brain/messageHistory.ts +0 -26
  8. package/src/brain/schedule.ts +0 -11
  9. package/src/brain/types.ts +0 -26
  10. package/src/channel/base.ts +0 -527
  11. package/src/channel/discord.ts +0 -227
  12. package/src/channel/telegram.ts +0 -150
  13. package/src/commands/auth.tsx +0 -306
  14. package/src/commands/brain.ts +0 -119
  15. package/src/commands/daemon/commands.ts +0 -56
  16. package/src/commands/daemon/pairingCommand.ts +0 -15
  17. package/src/commands/daemon/restartCommand.ts +0 -17
  18. package/src/commands/daemon.ts +0 -168
  19. package/src/commands/index.ts +0 -16
  20. package/src/commands/model.tsx +0 -138
  21. package/src/commands/onboard.tsx +0 -473
  22. package/src/commands/pairing.ts +0 -32
  23. package/src/commands/restart.ts +0 -24
  24. package/src/config/file/auth.ts +0 -105
  25. package/src/config/file/root.ts +0 -37
  26. package/src/config/index.ts +0 -21
  27. package/src/config/loader.ts +0 -115
  28. package/src/index.ts +0 -61
  29. package/src/provider/index.ts +0 -122
  30. package/src/provider/llm.ts +0 -191
  31. package/src/provider/promptLoader.ts +0 -36
  32. package/src/provider/providers/302ai.ts +0 -17
  33. package/src/provider/providers/MiniMax.ts +0 -17
  34. package/src/provider/providers/anthropic.ts +0 -257
  35. package/src/provider/providers/azure_cognitive.ts +0 -40
  36. package/src/provider/providers/azure_openai.ts +0 -49
  37. package/src/provider/providers/baseten.ts +0 -17
  38. package/src/provider/providers/bedrock.ts +0 -312
  39. package/src/provider/providers/cerebras.ts +0 -17
  40. package/src/provider/providers/cloudflare_gateway.ts +0 -34
  41. package/src/provider/providers/cloudflare_workers.ts +0 -178
  42. package/src/provider/providers/copilot.ts +0 -22
  43. package/src/provider/providers/cortecs.ts +0 -17
  44. package/src/provider/providers/deepinfra.ts +0 -17
  45. package/src/provider/providers/deepseek.ts +0 -17
  46. package/src/provider/providers/digitalocean.ts +0 -17
  47. package/src/provider/providers/fireworks.ts +0 -17
  48. package/src/provider/providers/gitlab_duo.ts +0 -184
  49. package/src/provider/providers/gmi.ts +0 -17
  50. package/src/provider/providers/groq.ts +0 -17
  51. package/src/provider/providers/helicone.ts +0 -17
  52. package/src/provider/providers/huggingface.ts +0 -17
  53. package/src/provider/providers/ionet.ts +0 -17
  54. package/src/provider/providers/llamacpp.ts +0 -17
  55. package/src/provider/providers/llmgateway.ts +0 -17
  56. package/src/provider/providers/lmstudio.ts +0 -17
  57. package/src/provider/providers/mistral.ts +0 -17
  58. package/src/provider/providers/moonshot.ts +0 -17
  59. package/src/provider/providers/nebius.ts +0 -17
  60. package/src/provider/providers/nvidia.ts +0 -17
  61. package/src/provider/providers/ollama.ts +0 -17
  62. package/src/provider/providers/ollama_cloud.ts +0 -17
  63. package/src/provider/providers/openai.ts +0 -17
  64. package/src/provider/providers/openai_compatible.ts +0 -293
  65. package/src/provider/providers/openrouter.ts +0 -175
  66. package/src/provider/providers/ovhcloud.ts +0 -17
  67. package/src/provider/providers/sap_aicore.ts +0 -22
  68. package/src/provider/providers/scaleway.ts +0 -17
  69. package/src/provider/providers/snowflake_cortex.ts +0 -207
  70. package/src/provider/providers/stackit.ts +0 -17
  71. package/src/provider/providers/together.ts +0 -17
  72. package/src/provider/providers/venice.ts +0 -17
  73. package/src/provider/providers/vercel.ts +0 -17
  74. package/src/provider/providers/vertex.ts +0 -229
  75. package/src/provider/providers/xai.ts +0 -17
  76. package/src/provider/providers/zai.ts +0 -17
  77. package/src/provider/providers/zenmux.ts +0 -17
  78. package/src/provider/schema.ts +0 -143
  79. package/src/ui/TextInput.tsx +0 -114
  80. package/src/utils/daemonClient.ts +0 -85
  81. package/src/utils/logger.ts +0 -204
  82. package/tsconfig.json +0 -34
  83. /package/{prompts → dist/prompts}/daily_schedule.md +0 -0
  84. /package/{prompts → dist/prompts}/memoir.md +0 -0
  85. /package/{prompts → dist/prompts}/monthly_schedule.md +0 -0
  86. /package/{prompts → dist/prompts}/objectifier.md +0 -0
  87. /package/{prompts → dist/prompts}/persona_base_system_prompt.md +0 -0
  88. /package/{prompts → dist/prompts}/persona_base_system_prompt_fixed.md +0 -0
  89. /package/{prompts → dist/prompts}/persona_init.md +0 -0
  90. /package/{prompts → dist/prompts}/schedule_availability.md +0 -0
  91. /package/{prompts → dist/prompts}/send_message.md +0 -0
  92. /package/{prompts → dist/prompts}/start_conversation.md +0 -0
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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();
@@ -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();
@@ -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
- };