@hasna/brains 0.0.8 → 0.0.9

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 (40) hide show
  1. package/LICENSE +170 -0
  2. package/dist/cli/index.js +635 -998
  3. package/dist/db/index.d.ts.map +1 -1
  4. package/dist/index.js +142 -4294
  5. package/dist/lib/gatherers/index.d.ts +0 -3
  6. package/dist/lib/gatherers/index.d.ts.map +1 -1
  7. package/dist/lib/index.d.ts +0 -3
  8. package/dist/lib/index.d.ts.map +1 -1
  9. package/dist/lib/providers/openai.d.ts +0 -2
  10. package/dist/lib/providers/openai.d.ts.map +1 -1
  11. package/dist/lib/providers/thinker-labs.d.ts +0 -1
  12. package/dist/lib/providers/thinker-labs.d.ts.map +1 -1
  13. package/dist/mcp/index.d.ts.map +1 -1
  14. package/dist/mcp/index.js +195 -4584
  15. package/dist/server/index.d.ts +1 -1
  16. package/dist/server/index.d.ts.map +1 -1
  17. package/dist/server/index.js +9 -4638
  18. package/package.json +2 -1
  19. package/dist/lib/config.d.ts +0 -11
  20. package/dist/lib/config.d.ts.map +0 -1
  21. package/dist/lib/gatherers/assistants.d.ts +0 -3
  22. package/dist/lib/gatherers/assistants.d.ts.map +0 -1
  23. package/dist/lib/gatherers/economy.d.ts +0 -3
  24. package/dist/lib/gatherers/economy.d.ts.map +0 -1
  25. package/dist/lib/gatherers/protocol.d.ts +0 -29
  26. package/dist/lib/gatherers/protocol.d.ts.map +0 -1
  27. package/dist/lib/gatherers/recordings.d.ts +0 -3
  28. package/dist/lib/gatherers/recordings.d.ts.map +0 -1
  29. package/dist/lib/gatherers/registry.d.ts +0 -7
  30. package/dist/lib/gatherers/registry.d.ts.map +0 -1
  31. package/dist/lib/gatherers/researcher.d.ts +0 -3
  32. package/dist/lib/gatherers/researcher.d.ts.map +0 -1
  33. package/dist/lib/gatherers/styles.d.ts +0 -3
  34. package/dist/lib/gatherers/styles.d.ts.map +0 -1
  35. package/dist/lib/gatherers/tickets.d.ts +0 -3
  36. package/dist/lib/gatherers/tickets.d.ts.map +0 -1
  37. package/dist/lib/retry.d.ts +0 -7
  38. package/dist/lib/retry.d.ts.map +0 -1
  39. package/dist/lib/schemas.d.ts +0 -87
  40. package/dist/lib/schemas.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -5,28 +5,45 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
47
  var __require = import.meta.require;
31
48
 
32
49
  // node_modules/commander/lib/error.js
@@ -2063,440 +2080,94 @@ var require_commander = __commonJS((exports) => {
2063
2080
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2064
2081
  });
2065
2082
 
2066
- // src/lib/gatherers/todos.ts
2067
- var exports_todos = {};
2068
- __export(exports_todos, {
2069
- gatherFromTodos: () => gatherFromTodos
2070
- });
2071
- import { Database as Database3 } from "bun:sqlite";
2072
- import { homedir as homedir3 } from "os";
2073
- import { join as join3 } from "path";
2074
- function taskToCreateExample(task) {
2075
- const userMsg = `Create a task: ${task.title}${task.description ? `
2083
+ // node_modules/commander/esm.mjs
2084
+ var import__ = __toESM(require_commander(), 1);
2085
+ var {
2086
+ program,
2087
+ createCommand,
2088
+ createArgument,
2089
+ createOption,
2090
+ CommanderError,
2091
+ InvalidArgumentError,
2092
+ InvalidOptionArgumentError,
2093
+ Command,
2094
+ Argument,
2095
+ Option,
2096
+ Help
2097
+ } = import__.default;
2076
2098
 
2077
- Description: ${task.description}` : ""}`;
2078
- const taskDetails = {
2079
- id: task.short_id ?? task.id,
2080
- title: task.title,
2081
- description: task.description ?? "",
2082
- status: task.status,
2083
- priority: task.priority,
2084
- tags: JSON.parse(task.tags ?? "[]"),
2085
- created_at: task.created_at
2086
- };
2087
- return {
2088
- messages: [
2089
- { role: "system", content: SYSTEM_PROMPT },
2090
- { role: "user", content: userMsg },
2091
- { role: "assistant", content: `Created task: ${JSON.stringify(taskDetails, null, 2)}` }
2092
- ]
2093
- };
2094
- }
2095
- function taskToStatusUpdateExample(task) {
2096
- if (!task.completed_at && task.status === "pending")
2097
- return null;
2098
- const id = task.short_id ?? task.id;
2099
- return {
2100
- messages: [
2101
- { role: "system", content: SYSTEM_PROMPT },
2102
- { role: "user", content: `Mark task ${id} as ${task.status}` },
2103
- { role: "assistant", content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim() }
2104
- ]
2105
- };
2106
- }
2107
- function taskToSearchExample(tasks, query) {
2108
- const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
2109
- return {
2110
- messages: [
2111
- { role: "system", content: SYSTEM_PROMPT },
2112
- { role: "user", content: `Search tasks for: "${query}"` },
2113
- {
2114
- role: "assistant",
2115
- content: matched.length > 0 ? `Found ${matched.length} task(s):
2116
- ${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
2117
- `)}` : `No tasks found matching "${query}".`
2099
+ // node_modules/drizzle-orm/entity.js
2100
+ var entityKind = Symbol.for("drizzle:entityKind");
2101
+ var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
2102
+ function is(value, type) {
2103
+ if (!value || typeof value !== "object") {
2104
+ return false;
2105
+ }
2106
+ if (value instanceof type) {
2107
+ return true;
2108
+ }
2109
+ if (!Object.prototype.hasOwnProperty.call(type, entityKind)) {
2110
+ throw new Error(`Class "${type.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`);
2111
+ }
2112
+ let cls = Object.getPrototypeOf(value).constructor;
2113
+ if (cls) {
2114
+ while (cls) {
2115
+ if (entityKind in cls && cls[entityKind] === type[entityKind]) {
2116
+ return true;
2118
2117
  }
2119
- ]
2120
- };
2121
- }
2122
- async function gatherFromTodos(options = {}) {
2123
- const dbPath = join3(homedir3(), ".todos", "todos.db");
2124
- const db = new Database3(dbPath, { readonly: true, create: false });
2125
- try {
2126
- let query = "SELECT * FROM tasks WHERE 1=1";
2127
- const params = [];
2128
- if (options.since) {
2129
- query += " AND created_at >= ?";
2130
- params.push(options.since.toISOString());
2131
- }
2132
- query += " ORDER BY created_at DESC";
2133
- if (options.limit) {
2134
- query += " LIMIT ?";
2135
- params.push(options.limit * 2);
2136
- }
2137
- const tasks = db.query(query).all(...params);
2138
- const examples = [];
2139
- for (const task of tasks) {
2140
- examples.push(taskToCreateExample(task));
2141
- const statusEx = taskToStatusUpdateExample(task);
2142
- if (statusEx)
2143
- examples.push(statusEx);
2144
- }
2145
- const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
2146
- for (const term of searchTerms) {
2147
- examples.push(taskToSearchExample(tasks, term));
2118
+ cls = Object.getPrototypeOf(cls);
2148
2119
  }
2149
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
2150
- return {
2151
- source: "todos",
2152
- examples: finalExamples,
2153
- count: finalExamples.length
2154
- };
2155
- } finally {
2156
- db.close();
2157
2120
  }
2121
+ return false;
2158
2122
  }
2159
- var SYSTEM_PROMPT = "You are a task management assistant that helps users create, update, search, and manage tasks and projects.";
2160
- var init_todos = () => {};
2161
-
2162
- // src/lib/gatherers/mementos.ts
2163
- var exports_mementos = {};
2164
- __export(exports_mementos, {
2165
- gatherFromMementos: () => gatherFromMementos
2166
- });
2167
- import { Database as Database4 } from "bun:sqlite";
2168
- import { homedir as homedir4 } from "os";
2169
- import { join as join4 } from "path";
2170
- function memoryToRecallExample(memory) {
2171
- return {
2172
- messages: [
2173
- { role: "system", content: SYSTEM_PROMPT2 },
2174
- { role: "user", content: `What do you remember about "${memory.key}"?` },
2175
- {
2176
- role: "assistant",
2177
- content: memory.summary ? `${memory.value}
2178
2123
 
2179
- Summary: ${memory.summary}` : memory.value
2180
- }
2181
- ]
2182
- };
2183
- }
2184
- function memoryToSaveExample(memory) {
2185
- const tags = JSON.parse(memory.tags ?? "[]");
2186
- return {
2187
- messages: [
2188
- { role: "system", content: SYSTEM_PROMPT2 },
2189
- {
2190
- role: "user",
2191
- content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
2192
- },
2193
- {
2194
- role: "assistant",
2195
- content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
2196
- }
2197
- ]
2198
- };
2199
- }
2200
- function memoryToSearchExample(memories, category) {
2201
- const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
2202
- return {
2203
- messages: [
2204
- { role: "system", content: SYSTEM_PROMPT2 },
2205
- { role: "user", content: `What ${category} memories do you have?` },
2206
- {
2207
- role: "assistant",
2208
- content: matched.length > 0 ? `Here are my ${category} memories:
2209
- ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
2210
- `)}` : `I don't have any ${category} memories stored yet.`
2211
- }
2212
- ]
2213
- };
2214
- }
2215
- async function gatherFromMementos(options = {}) {
2216
- const dbPath = join4(homedir4(), ".mementos", "mementos.db");
2217
- const db = new Database4(dbPath, { readonly: true, create: false });
2218
- try {
2219
- let query = "SELECT * FROM memories WHERE status = 'active'";
2220
- const params = [];
2221
- if (options.since) {
2222
- query += " AND created_at >= ?";
2223
- params.push(options.since.toISOString());
2224
- }
2225
- query += " ORDER BY importance DESC, created_at DESC";
2226
- if (options.limit) {
2227
- query += " LIMIT ?";
2228
- params.push(options.limit * 3);
2229
- }
2230
- const memories = db.query(query).all(...params);
2231
- const examples = [];
2232
- for (const memory of memories) {
2233
- examples.push(memoryToRecallExample(memory));
2234
- examples.push(memoryToSaveExample(memory));
2235
- }
2236
- const categories = [...new Set(memories.map((m) => m.category))];
2237
- for (const category of categories) {
2238
- examples.push(memoryToSearchExample(memories, category));
2239
- }
2240
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
2241
- return {
2242
- source: "mementos",
2243
- examples: finalExamples,
2244
- count: finalExamples.length
2245
- };
2246
- } finally {
2247
- db.close();
2248
- }
2249
- }
2250
- var SYSTEM_PROMPT2 = "You are an AI assistant with persistent memory. You can remember and recall information across sessions to provide better, more personalized assistance.";
2251
- var init_mementos = () => {};
2252
-
2253
- // src/lib/gatherers/conversations.ts
2254
- var exports_conversations = {};
2255
- __export(exports_conversations, {
2256
- gatherFromConversations: () => gatherFromConversations
2257
- });
2258
- import { Database as Database5 } from "bun:sqlite";
2259
- import { homedir as homedir5 } from "os";
2260
- import { join as join5 } from "path";
2261
- function windowToExample(window2) {
2262
- if (window2.length < 2)
2263
- return null;
2264
- const messages = [
2265
- { role: "system", content: SYSTEM_PROMPT3 }
2266
- ];
2267
- for (let i = 0;i < window2.length - 1; i++) {
2268
- const msg = window2[i];
2269
- if (!msg)
2270
- continue;
2271
- const role = i % 2 === 0 ? "user" : "assistant";
2272
- messages.push({
2273
- role,
2274
- content: `[${msg.from_agent} \u2192 ${msg.to_agent ?? msg.space ?? "all"}]: ${msg.content}`
2275
- });
2276
- }
2277
- const last = window2[window2.length - 1];
2278
- if (!last)
2279
- return null;
2280
- messages.push({
2281
- role: "assistant",
2282
- content: `[${last.from_agent} \u2192 ${last.to_agent ?? last.space ?? "all"}]: ${last.content}`
2283
- });
2284
- return { messages };
2285
- }
2286
- async function gatherFromConversations(options = {}) {
2287
- const dbPath = join5(homedir5(), ".conversations", "messages.db");
2288
- const db = new Database5(dbPath, { readonly: true, create: false });
2289
- try {
2290
- let query = "SELECT * FROM messages WHERE 1=1";
2291
- const params = [];
2292
- if (options.since) {
2293
- query += " AND created_at >= ?";
2294
- params.push(options.since.toISOString());
2295
- }
2296
- query += " ORDER BY session_id, created_at ASC";
2297
- const allMessages = db.query(query).all(...params);
2298
- const sessions = new Map;
2299
- for (const msg of allMessages) {
2300
- const msgs = sessions.get(msg.session_id) ?? [];
2301
- msgs.push(msg);
2302
- sessions.set(msg.session_id, msgs);
2303
- }
2304
- const examples = [];
2305
- const windowSize = 4;
2306
- for (const [, sessionMsgs] of sessions) {
2307
- if (sessionMsgs.length < 2)
2308
- continue;
2309
- for (let start = 0;start <= sessionMsgs.length - 2; start++) {
2310
- const end = Math.min(start + windowSize, sessionMsgs.length);
2311
- const window2 = sessionMsgs.slice(start, end);
2312
- const example = windowToExample(window2);
2313
- if (example)
2314
- examples.push(example);
2315
- }
2316
- }
2317
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
2318
- return {
2319
- source: "conversations",
2320
- examples: finalExamples,
2321
- count: finalExamples.length
2322
- };
2323
- } finally {
2324
- db.close();
2325
- }
2326
- }
2327
- var SYSTEM_PROMPT3 = "You are a helpful AI assistant participating in multi-agent conversations. You communicate clearly and collaboratively with other agents and users.";
2328
- var init_conversations = () => {};
2329
-
2330
- // src/lib/gatherers/sessions.ts
2331
- var exports_sessions2 = {};
2332
- __export(exports_sessions2, {
2333
- gatherFromSessions: () => gatherFromSessions
2334
- });
2335
- import { readdir, readFile, stat } from "fs/promises";
2336
- import { existsSync as existsSync2 } from "fs";
2337
- import { join as join6 } from "path";
2338
- import { homedir as homedir6 } from "os";
2339
- function extractText(content) {
2340
- if (typeof content === "string")
2341
- return content;
2342
- return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join(`
2343
- `).trim();
2344
- }
2345
- async function gatherFromSessions(options = {}) {
2346
- const { limit: limit2 = 1000 } = options;
2347
- const examples = [];
2348
- const claudeDir = join6(homedir6(), ".claude", "projects");
2349
- if (!existsSync2(claudeDir)) {
2350
- return { source: "sessions", examples: [], count: 0 };
2351
- }
2352
- const projectDirs = await readdir(claudeDir).catch(() => []);
2353
- for (const projectDir of projectDirs) {
2354
- if (examples.length >= limit2)
2355
- break;
2356
- const projectPath = join6(claudeDir, projectDir);
2357
- const files = await readdir(projectPath).catch(() => []);
2358
- for (const file of files) {
2359
- if (examples.length >= limit2)
2360
- break;
2361
- if (!file.endsWith(".jsonl"))
2362
- continue;
2363
- const filePath = join6(projectPath, file);
2364
- if (options.since) {
2365
- const fileStat = await stat(filePath).catch(() => null);
2366
- if (fileStat && fileStat.mtime < options.since)
2367
- continue;
2368
- }
2369
- const content = await readFile(filePath, "utf-8").catch(() => "");
2370
- if (!content.trim())
2371
- continue;
2372
- const lines = content.trim().split(`
2373
- `);
2374
- const turns = [];
2375
- for (const line of lines) {
2376
- try {
2377
- const entry = JSON.parse(line);
2378
- if ((entry.type === "user" || entry.type === "human") && entry.message?.content) {
2379
- const text2 = extractText(entry.message.content);
2380
- if (text2.trim())
2381
- turns.push({ role: "user", content: text2.trim() });
2382
- } else if (entry.type === "assistant" && entry.message?.content) {
2383
- const text2 = extractText(entry.message.content);
2384
- if (text2.trim())
2385
- turns.push({ role: "assistant", content: text2.trim() });
2386
- }
2387
- } catch {}
2388
- }
2389
- const windowSize = 6;
2390
- for (let start = 0;start < turns.length - 1 && examples.length < limit2; start++) {
2391
- const window2 = turns.slice(start, start + windowSize);
2392
- if (!window2[0] || window2[0].role !== "user")
2393
- continue;
2394
- const lastAssistantIdx = window2.map((t) => t.role).lastIndexOf("assistant");
2395
- if (lastAssistantIdx < 1)
2396
- continue;
2397
- const usedTurns = window2.slice(0, lastAssistantIdx + 1);
2398
- examples.push({
2399
- messages: [
2400
- { role: "system", content: SYSTEM_PROMPT4 },
2401
- ...usedTurns
2402
- ]
2403
- });
2404
- }
2405
- }
2406
- }
2407
- return { source: "sessions", examples, count: examples.length };
2408
- }
2409
- var SYSTEM_PROMPT4 = "You are Claude Code, an AI assistant built by Anthropic that helps developers with coding, architecture, debugging, and software engineering tasks.";
2410
- var init_sessions = () => {};
2411
-
2412
- // node_modules/commander/esm.mjs
2413
- var import__ = __toESM(require_commander(), 1);
2414
- var {
2415
- program,
2416
- createCommand,
2417
- createArgument,
2418
- createOption,
2419
- CommanderError,
2420
- InvalidArgumentError,
2421
- InvalidOptionArgumentError,
2422
- Command,
2423
- Argument,
2424
- Option,
2425
- Help
2426
- } = import__.default;
2427
-
2428
- // node_modules/drizzle-orm/entity.js
2429
- var entityKind = Symbol.for("drizzle:entityKind");
2430
- var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
2431
- function is(value, type) {
2432
- if (!value || typeof value !== "object") {
2433
- return false;
2434
- }
2435
- if (value instanceof type) {
2436
- return true;
2437
- }
2438
- if (!Object.prototype.hasOwnProperty.call(type, entityKind)) {
2439
- throw new Error(`Class "${type.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`);
2440
- }
2441
- let cls = Object.getPrototypeOf(value).constructor;
2442
- if (cls) {
2443
- while (cls) {
2444
- if (entityKind in cls && cls[entityKind] === type[entityKind]) {
2445
- return true;
2446
- }
2447
- cls = Object.getPrototypeOf(cls);
2448
- }
2449
- }
2450
- return false;
2451
- }
2452
-
2453
- // node_modules/drizzle-orm/column.js
2454
- class Column {
2455
- constructor(table, config) {
2456
- this.table = table;
2457
- this.config = config;
2458
- this.name = config.name;
2459
- this.keyAsName = config.keyAsName;
2460
- this.notNull = config.notNull;
2461
- this.default = config.default;
2462
- this.defaultFn = config.defaultFn;
2463
- this.onUpdateFn = config.onUpdateFn;
2464
- this.hasDefault = config.hasDefault;
2465
- this.primary = config.primaryKey;
2466
- this.isUnique = config.isUnique;
2467
- this.uniqueName = config.uniqueName;
2468
- this.uniqueType = config.uniqueType;
2469
- this.dataType = config.dataType;
2470
- this.columnType = config.columnType;
2471
- this.generated = config.generated;
2472
- this.generatedIdentity = config.generatedIdentity;
2473
- }
2474
- static [entityKind] = "Column";
2475
- name;
2476
- keyAsName;
2477
- primary;
2478
- notNull;
2479
- default;
2480
- defaultFn;
2481
- onUpdateFn;
2482
- hasDefault;
2483
- isUnique;
2484
- uniqueName;
2485
- uniqueType;
2486
- dataType;
2487
- columnType;
2488
- enumValues = undefined;
2489
- generated = undefined;
2490
- generatedIdentity = undefined;
2491
- config;
2492
- mapFromDriverValue(value) {
2493
- return value;
2494
- }
2495
- mapToDriverValue(value) {
2496
- return value;
2497
- }
2498
- shouldDisableInsert() {
2499
- return this.config.generated !== undefined && this.config.generated.type !== "byDefault";
2124
+ // node_modules/drizzle-orm/column.js
2125
+ class Column {
2126
+ constructor(table, config) {
2127
+ this.table = table;
2128
+ this.config = config;
2129
+ this.name = config.name;
2130
+ this.keyAsName = config.keyAsName;
2131
+ this.notNull = config.notNull;
2132
+ this.default = config.default;
2133
+ this.defaultFn = config.defaultFn;
2134
+ this.onUpdateFn = config.onUpdateFn;
2135
+ this.hasDefault = config.hasDefault;
2136
+ this.primary = config.primaryKey;
2137
+ this.isUnique = config.isUnique;
2138
+ this.uniqueName = config.uniqueName;
2139
+ this.uniqueType = config.uniqueType;
2140
+ this.dataType = config.dataType;
2141
+ this.columnType = config.columnType;
2142
+ this.generated = config.generated;
2143
+ this.generatedIdentity = config.generatedIdentity;
2144
+ }
2145
+ static [entityKind] = "Column";
2146
+ name;
2147
+ keyAsName;
2148
+ primary;
2149
+ notNull;
2150
+ default;
2151
+ defaultFn;
2152
+ onUpdateFn;
2153
+ hasDefault;
2154
+ isUnique;
2155
+ uniqueName;
2156
+ uniqueType;
2157
+ dataType;
2158
+ columnType;
2159
+ enumValues = undefined;
2160
+ generated = undefined;
2161
+ generatedIdentity = undefined;
2162
+ config;
2163
+ mapFromDriverValue(value) {
2164
+ return value;
2165
+ }
2166
+ mapToDriverValue(value) {
2167
+ return value;
2168
+ }
2169
+ shouldDisableInsert() {
2170
+ return this.config.generated !== undefined && this.config.generated.type !== "byDefault";
2500
2171
  }
2501
2172
  }
2502
2173
 
@@ -3783,9 +3454,9 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
3783
3454
 
3784
3455
  // src/cli/index.ts
3785
3456
  import { randomUUID } from "crypto";
3786
- import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
3787
- import { join as join7 } from "path";
3788
- import { homedir as homedir7 } from "os";
3457
+ import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
3458
+ import { join as join6 } from "path";
3459
+ import { homedir as homedir6 } from "os";
3789
3460
 
3790
3461
  // node_modules/drizzle-orm/bun-sqlite/driver.js
3791
3462
  import { Database } from "bun:sqlite";
@@ -6068,48 +5739,10 @@ function drizzle(...params) {
6068
5739
  drizzle2.mock = mock;
6069
5740
  })(drizzle || (drizzle = {}));
6070
5741
 
6071
- // node_modules/drizzle-orm/migrator.js
6072
- import crypto from "crypto";
6073
- import fs from "fs";
6074
- function readMigrationFiles(config) {
6075
- const migrationFolderTo = config.migrationsFolder;
6076
- const migrationQueries = [];
6077
- const journalPath = `${migrationFolderTo}/meta/_journal.json`;
6078
- if (!fs.existsSync(journalPath)) {
6079
- throw new Error(`Can't find meta/_journal.json file`);
6080
- }
6081
- const journalAsString = fs.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
6082
- const journal = JSON.parse(journalAsString);
6083
- for (const journalEntry of journal.entries) {
6084
- const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
6085
- try {
6086
- const query = fs.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
6087
- const result = query.split("--> statement-breakpoint").map((it) => {
6088
- return it;
6089
- });
6090
- migrationQueries.push({
6091
- sql: result,
6092
- bps: journalEntry.breakpoints,
6093
- folderMillis: journalEntry.when,
6094
- hash: crypto.createHash("sha256").update(query).digest("hex")
6095
- });
6096
- } catch {
6097
- throw new Error(`No file ${migrationPath} found in ${migrationFolderTo} folder`);
6098
- }
6099
- }
6100
- return migrationQueries;
6101
- }
6102
-
6103
- // node_modules/drizzle-orm/bun-sqlite/migrator.js
6104
- function migrate(db, config) {
6105
- const migrations = readMigrationFiles(config);
6106
- db.dialect.migrate(migrations, db.session, config);
6107
- }
6108
-
6109
5742
  // src/db/index.ts
6110
5743
  import { Database as Database2 } from "bun:sqlite";
6111
5744
  import { mkdirSync } from "fs";
6112
- import { dirname, join, resolve } from "path";
5745
+ import { dirname, join } from "path";
6113
5746
  import { homedir } from "os";
6114
5747
 
6115
5748
  // src/db/schema.ts
@@ -6161,55 +5794,52 @@ var DEFAULT_DB_PATH = join(homedir(), ".brains", "brains.db");
6161
5794
  function ensureDir(filePath) {
6162
5795
  mkdirSync(dirname(filePath), { recursive: true });
6163
5796
  }
5797
+ function createTables(sqlite) {
5798
+ sqlite.exec(`
5799
+ CREATE TABLE IF NOT EXISTS fine_tuned_models (
5800
+ id TEXT PRIMARY KEY,
5801
+ base_model TEXT NOT NULL,
5802
+ name TEXT NOT NULL,
5803
+ provider TEXT NOT NULL,
5804
+ status TEXT NOT NULL DEFAULT 'pending',
5805
+ fine_tune_job_id TEXT,
5806
+ display_name TEXT,
5807
+ description TEXT,
5808
+ collection TEXT,
5809
+ tags TEXT,
5810
+ created_at INTEGER NOT NULL,
5811
+ updated_at INTEGER NOT NULL
5812
+ );
5813
+
5814
+ CREATE TABLE IF NOT EXISTS training_jobs (
5815
+ id TEXT PRIMARY KEY,
5816
+ model_id TEXT NOT NULL REFERENCES fine_tuned_models(id),
5817
+ provider TEXT NOT NULL,
5818
+ status TEXT NOT NULL,
5819
+ started_at INTEGER NOT NULL,
5820
+ finished_at INTEGER,
5821
+ metrics TEXT,
5822
+ error TEXT
5823
+ );
5824
+
5825
+ CREATE TABLE IF NOT EXISTS training_datasets (
5826
+ id TEXT PRIMARY KEY,
5827
+ source TEXT NOT NULL,
5828
+ file_path TEXT NOT NULL,
5829
+ example_count INTEGER NOT NULL,
5830
+ created_at INTEGER NOT NULL,
5831
+ used_in_job_id TEXT REFERENCES training_jobs(id)
5832
+ );
5833
+ `);
5834
+ }
6164
5835
  function getDb(dbPath) {
6165
5836
  const resolvedPath = dbPath ?? DEFAULT_DB_PATH;
6166
5837
  ensureDir(resolvedPath);
6167
5838
  const sqlite = new Database2(resolvedPath);
6168
5839
  sqlite.run("PRAGMA journal_mode = WAL");
6169
5840
  sqlite.run("PRAGMA foreign_keys = ON");
6170
- const db = drizzle(sqlite, { schema: exports_schema });
6171
- try {
6172
- const migrationsFolder = resolve(import.meta.dir, "../../drizzle");
6173
- migrate(db, { migrationsFolder });
6174
- } catch {
6175
- sqlite.exec(`
6176
- CREATE TABLE IF NOT EXISTS fine_tuned_models (
6177
- id TEXT PRIMARY KEY,
6178
- base_model TEXT NOT NULL,
6179
- name TEXT NOT NULL,
6180
- provider TEXT NOT NULL,
6181
- status TEXT NOT NULL DEFAULT 'pending',
6182
- fine_tune_job_id TEXT,
6183
- display_name TEXT,
6184
- description TEXT,
6185
- collection TEXT,
6186
- tags TEXT,
6187
- created_at INTEGER NOT NULL,
6188
- updated_at INTEGER NOT NULL
6189
- );
6190
-
6191
- CREATE TABLE IF NOT EXISTS training_jobs (
6192
- id TEXT PRIMARY KEY,
6193
- model_id TEXT NOT NULL REFERENCES fine_tuned_models(id),
6194
- provider TEXT NOT NULL,
6195
- status TEXT NOT NULL,
6196
- started_at INTEGER NOT NULL,
6197
- finished_at INTEGER,
6198
- metrics TEXT,
6199
- error TEXT
6200
- );
6201
-
6202
- CREATE TABLE IF NOT EXISTS training_datasets (
6203
- id TEXT PRIMARY KEY,
6204
- source TEXT NOT NULL,
6205
- file_path TEXT NOT NULL,
6206
- example_count INTEGER NOT NULL,
6207
- created_at INTEGER NOT NULL,
6208
- used_in_job_id TEXT REFERENCES training_jobs(id)
6209
- );
6210
- `);
6211
- }
6212
- return db;
5841
+ createTables(sqlite);
5842
+ return drizzle(sqlite, { schema: exports_schema });
6213
5843
  }
6214
5844
 
6215
5845
  // node_modules/openai/internal/qs/formats.mjs
@@ -7300,8 +6930,8 @@ function _addRequestID(value, response) {
7300
6930
 
7301
6931
  class APIPromise extends Promise {
7302
6932
  constructor(responsePromise, parseResponse = defaultParseResponse) {
7303
- super((resolve2) => {
7304
- resolve2(null);
6933
+ super((resolve) => {
6934
+ resolve(null);
7305
6935
  });
7306
6936
  this.responsePromise = responsePromise;
7307
6937
  this.parseResponse = parseResponse;
@@ -7812,7 +7442,7 @@ var startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
7812
7442
  var isAbsoluteURL = (url) => {
7813
7443
  return startsWithSchemeRegexp.test(url);
7814
7444
  };
7815
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
7445
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7816
7446
  var validatePositiveInteger = (name, n) => {
7817
7447
  if (typeof n !== "number" || !Number.isInteger(n)) {
7818
7448
  throw new OpenAIError(`${name} must be an integer`);
@@ -8177,12 +7807,12 @@ class EventStream {
8177
7807
  _EventStream_errored.set(this, false);
8178
7808
  _EventStream_aborted.set(this, false);
8179
7809
  _EventStream_catchingPromiseCreated.set(this, false);
8180
- __classPrivateFieldSet3(this, _EventStream_connectedPromise, new Promise((resolve2, reject) => {
8181
- __classPrivateFieldSet3(this, _EventStream_resolveConnectedPromise, resolve2, "f");
7810
+ __classPrivateFieldSet3(this, _EventStream_connectedPromise, new Promise((resolve, reject) => {
7811
+ __classPrivateFieldSet3(this, _EventStream_resolveConnectedPromise, resolve, "f");
8182
7812
  __classPrivateFieldSet3(this, _EventStream_rejectConnectedPromise, reject, "f");
8183
7813
  }), "f");
8184
- __classPrivateFieldSet3(this, _EventStream_endPromise, new Promise((resolve2, reject) => {
8185
- __classPrivateFieldSet3(this, _EventStream_resolveEndPromise, resolve2, "f");
7814
+ __classPrivateFieldSet3(this, _EventStream_endPromise, new Promise((resolve, reject) => {
7815
+ __classPrivateFieldSet3(this, _EventStream_resolveEndPromise, resolve, "f");
8186
7816
  __classPrivateFieldSet3(this, _EventStream_rejectEndPromise, reject, "f");
8187
7817
  }), "f");
8188
7818
  __classPrivateFieldGet3(this, _EventStream_connectedPromise, "f").catch(() => {});
@@ -8234,11 +7864,11 @@ class EventStream {
8234
7864
  return this;
8235
7865
  }
8236
7866
  emitted(event) {
8237
- return new Promise((resolve2, reject) => {
7867
+ return new Promise((resolve, reject) => {
8238
7868
  __classPrivateFieldSet3(this, _EventStream_catchingPromiseCreated, true, "f");
8239
7869
  if (event !== "error")
8240
7870
  this.once("error", reject);
8241
- this.once(event, resolve2);
7871
+ this.once(event, resolve);
8242
7872
  });
8243
7873
  }
8244
7874
  async done() {
@@ -8396,7 +8026,7 @@ class AssistantStream extends EventStream {
8396
8026
  if (done) {
8397
8027
  return { value: undefined, done: true };
8398
8028
  }
8399
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
8029
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
8400
8030
  }
8401
8031
  const chunk = pushQueue.shift();
8402
8032
  return { value: chunk, done: false };
@@ -9676,11 +9306,11 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9676
9306
  }
9677
9307
  return this._addChatCompletion(__classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_endRequest).call(this));
9678
9308
  }
9679
- [(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function _ChatCompletionStream_beginRequest() {
9309
+ [(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function _ChatCompletionStream_beginRequest2() {
9680
9310
  if (this.ended)
9681
9311
  return;
9682
9312
  __classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
9683
- }, _ChatCompletionStream_getChoiceEventState = function _ChatCompletionStream_getChoiceEventState(choice) {
9313
+ }, _ChatCompletionStream_getChoiceEventState = function _ChatCompletionStream_getChoiceEventState2(choice) {
9684
9314
  let state = __classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index];
9685
9315
  if (state) {
9686
9316
  return state;
@@ -9695,7 +9325,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9695
9325
  };
9696
9326
  __classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index] = state;
9697
9327
  return state;
9698
- }, _ChatCompletionStream_addChunk = function _ChatCompletionStream_addChunk(chunk) {
9328
+ }, _ChatCompletionStream_addChunk = function _ChatCompletionStream_addChunk2(chunk) {
9699
9329
  if (this.ended)
9700
9330
  return;
9701
9331
  const completion = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_accumulateChatCompletion).call(this, chunk);
@@ -9762,7 +9392,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9762
9392
  }
9763
9393
  }
9764
9394
  }
9765
- }, _ChatCompletionStream_emitToolCallDoneEvent = function _ChatCompletionStream_emitToolCallDoneEvent(choiceSnapshot, toolCallIndex) {
9395
+ }, _ChatCompletionStream_emitToolCallDoneEvent = function _ChatCompletionStream_emitToolCallDoneEvent2(choiceSnapshot, toolCallIndex) {
9766
9396
  const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
9767
9397
  if (state.done_tool_calls.has(toolCallIndex)) {
9768
9398
  return;
@@ -9785,7 +9415,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9785
9415
  } else {
9786
9416
  assertNever2(toolCallSnapshot.type);
9787
9417
  }
9788
- }, _ChatCompletionStream_emitContentDoneEvents = function _ChatCompletionStream_emitContentDoneEvents(choiceSnapshot) {
9418
+ }, _ChatCompletionStream_emitContentDoneEvents = function _ChatCompletionStream_emitContentDoneEvents2(choiceSnapshot) {
9789
9419
  const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
9790
9420
  if (choiceSnapshot.message.content && !state.content_done) {
9791
9421
  state.content_done = true;
@@ -9807,7 +9437,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9807
9437
  state.logprobs_refusal_done = true;
9808
9438
  this._emit("logprobs.refusal.done", { refusal: choiceSnapshot.logprobs.refusal });
9809
9439
  }
9810
- }, _ChatCompletionStream_endRequest = function _ChatCompletionStream_endRequest() {
9440
+ }, _ChatCompletionStream_endRequest = function _ChatCompletionStream_endRequest2() {
9811
9441
  if (this.ended) {
9812
9442
  throw new OpenAIError(`stream has ended, this shouldn't happen`);
9813
9443
  }
@@ -9818,13 +9448,13 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9818
9448
  __classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
9819
9449
  __classPrivateFieldSet5(this, _ChatCompletionStream_choiceEventStates, [], "f");
9820
9450
  return finalizeChatCompletion(snapshot, __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f"));
9821
- }, _ChatCompletionStream_getAutoParseableResponseFormat = function _ChatCompletionStream_getAutoParseableResponseFormat() {
9451
+ }, _ChatCompletionStream_getAutoParseableResponseFormat = function _ChatCompletionStream_getAutoParseableResponseFormat2() {
9822
9452
  const responseFormat = __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f")?.response_format;
9823
9453
  if (isAutoParsableResponseFormat(responseFormat)) {
9824
9454
  return responseFormat;
9825
9455
  }
9826
9456
  return null;
9827
- }, _ChatCompletionStream_accumulateChatCompletion = function _ChatCompletionStream_accumulateChatCompletion(chunk) {
9457
+ }, _ChatCompletionStream_accumulateChatCompletion = function _ChatCompletionStream_accumulateChatCompletion2(chunk) {
9828
9458
  var _a, _b, _c, _d;
9829
9459
  let snapshot = __classPrivateFieldGet6(this, _ChatCompletionStream_currentChatCompletionSnapshot, "f");
9830
9460
  const { choices, ...rest } = chunk;
@@ -9961,7 +9591,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9961
9591
  if (done) {
9962
9592
  return { value: undefined, done: true };
9963
9593
  }
9964
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
9594
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
9965
9595
  }
9966
9596
  const chunk = pushQueue.shift();
9967
9597
  return { value: chunk, done: false };
@@ -11050,11 +10680,11 @@ class ResponseStream extends EventStream {
11050
10680
  }
11051
10681
  return __classPrivateFieldGet7(this, _ResponseStream_instances, "m", _ResponseStream_endRequest).call(this);
11052
10682
  }
11053
- [(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function _ResponseStream_beginRequest() {
10683
+ [(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function _ResponseStream_beginRequest2() {
11054
10684
  if (this.ended)
11055
10685
  return;
11056
10686
  __classPrivateFieldSet6(this, _ResponseStream_currentResponseSnapshot, undefined, "f");
11057
- }, _ResponseStream_addEvent = function _ResponseStream_addEvent(event, starting_after) {
10687
+ }, _ResponseStream_addEvent = function _ResponseStream_addEvent2(event, starting_after) {
11058
10688
  if (this.ended)
11059
10689
  return;
11060
10690
  const maybeEmit = (name, event2) => {
@@ -11102,7 +10732,7 @@ class ResponseStream extends EventStream {
11102
10732
  maybeEmit(event.type, event);
11103
10733
  break;
11104
10734
  }
11105
- }, _ResponseStream_endRequest = function _ResponseStream_endRequest() {
10735
+ }, _ResponseStream_endRequest = function _ResponseStream_endRequest2() {
11106
10736
  if (this.ended) {
11107
10737
  throw new OpenAIError(`stream has ended, this shouldn't happen`);
11108
10738
  }
@@ -11114,7 +10744,7 @@ class ResponseStream extends EventStream {
11114
10744
  const parsedResponse = finalizeResponse(snapshot, __classPrivateFieldGet7(this, _ResponseStream_params, "f"));
11115
10745
  __classPrivateFieldSet6(this, _ResponseStream_finalResponse, parsedResponse, "f");
11116
10746
  return parsedResponse;
11117
- }, _ResponseStream_accumulateResponse = function _ResponseStream_accumulateResponse(event) {
10747
+ }, _ResponseStream_accumulateResponse = function _ResponseStream_accumulateResponse2(event) {
11118
10748
  let snapshot = __classPrivateFieldGet7(this, _ResponseStream_currentResponseSnapshot, "f");
11119
10749
  if (!snapshot) {
11120
10750
  if (event.type !== "response.created") {
@@ -11210,7 +10840,7 @@ class ResponseStream extends EventStream {
11210
10840
  if (done) {
11211
10841
  return { value: undefined, done: true };
11212
10842
  }
11213
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: undefined, done: true });
10843
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: undefined, done: true });
11214
10844
  }
11215
10845
  const event = pushQueue.shift();
11216
10846
  return { value: event, done: false };
@@ -11697,183 +11327,92 @@ var _deployments_endpoints = new Set([
11697
11327
  var openai_default = OpenAI;
11698
11328
 
11699
11329
  // src/lib/providers/openai.ts
11700
- import { readFileSync as readFileSync2 } from "fs";
11701
-
11702
- // src/lib/config.ts
11703
- import { readFileSync, writeFileSync, mkdirSync as mkdirSync2, existsSync } from "fs";
11704
- import { join as join2, dirname as dirname2 } from "path";
11705
- import { homedir as homedir2 } from "os";
11706
- var CONFIG_KEYS = ["OPENAI_API_KEY", "THINKER_LABS_API_KEY", "THINKER_LABS_BASE_URL"];
11707
- var CONFIG_PATH = join2(homedir2(), ".brains", "config.json");
11708
- function readConfigFile() {
11709
- if (!existsSync(CONFIG_PATH))
11710
- return {};
11711
- try {
11712
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
11713
- } catch {
11714
- return {};
11330
+ import { readFileSync } from "fs";
11331
+ function getClient() {
11332
+ const apiKey = process.env.OPENAI_API_KEY;
11333
+ if (!apiKey) {
11334
+ throw new Error("OPENAI_API_KEY environment variable is required");
11715
11335
  }
11716
- }
11717
- function writeConfigFile(data) {
11718
- mkdirSync2(dirname2(CONFIG_PATH), { recursive: true });
11719
- writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2) + `
11720
- `, "utf-8");
11721
- }
11722
- function getConfigValue(key) {
11723
- if (process.env[key])
11724
- return process.env[key];
11725
- return readConfigFile()[key];
11726
- }
11727
- function setConfigValue(key, value) {
11728
- const data = readConfigFile();
11729
- data[key] = value;
11730
- writeConfigFile(data);
11731
- }
11732
- function listConfig() {
11733
- const file = readConfigFile();
11734
- return CONFIG_KEYS.map((key) => {
11735
- if (process.env[key])
11736
- return { key, value: process.env[key], source: "env" };
11737
- if (file[key])
11738
- return { key, value: file[key], source: "file" };
11739
- return { key, value: "", source: "unset" };
11740
- });
11741
- }
11742
- function deleteConfigValue(key) {
11743
- const data = readConfigFile();
11744
- delete data[key];
11745
- writeConfigFile(data);
11746
- }
11747
-
11748
- // src/lib/retry.ts
11749
- var RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
11750
- function isRetryableError(err) {
11751
- if (err instanceof Error) {
11752
- if (err.message.includes("fetch failed") || err.message.includes("ECONNRESET") || err.message.includes("ETIMEDOUT")) {
11753
- return true;
11754
- }
11755
- const match = err.message.match(/^(\d{3})\s/);
11756
- if (match) {
11757
- const status = parseInt(match[1], 10);
11758
- return RETRYABLE_STATUS_CODES.has(status);
11759
- }
11760
- const tlMatch = err.message.match(/API error (\d{3})/);
11761
- if (tlMatch) {
11762
- const status = parseInt(tlMatch[1], 10);
11763
- return RETRYABLE_STATUS_CODES.has(status);
11764
- }
11765
- }
11766
- return false;
11767
- }
11768
- async function withRetry(fn, options = {}) {
11769
- const { maxAttempts = 3, baseDelayMs = 1000, onRetry } = options;
11770
- let lastError = new Error("Unknown error");
11771
- for (let attempt = 1;attempt <= maxAttempts; attempt++) {
11772
- try {
11773
- return await fn();
11774
- } catch (err) {
11775
- lastError = err instanceof Error ? err : new Error(String(err));
11776
- if (attempt === maxAttempts || !isRetryableError(lastError)) {
11777
- throw lastError;
11778
- }
11779
- const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
11780
- onRetry?.(attempt, delayMs, lastError);
11781
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
11782
- }
11783
- }
11784
- throw lastError;
11785
- }
11786
-
11787
- // src/lib/providers/openai.ts
11788
- function getClient() {
11789
- const apiKey = getConfigValue("OPENAI_API_KEY");
11790
- if (!apiKey) {
11791
- throw new Error("OPENAI_API_KEY is not set. Run: brains config set OPENAI_API_KEY <key>");
11792
- }
11793
- return new openai_default({ apiKey });
11336
+ return new openai_default({ apiKey });
11794
11337
  }
11795
11338
  async function uploadTrainingFile(filePath) {
11796
- return withRetry(async () => {
11797
- const client = getClient();
11798
- const fileContent = readFileSync2(filePath);
11799
- const blob2 = new Blob([fileContent], { type: "application/jsonl" });
11800
- const file = new File([blob2], filePath.split("/").pop() ?? "training.jsonl", { type: "application/jsonl" });
11801
- const response = await client.files.create({ file, purpose: "fine-tune" });
11802
- return { fileId: response.id };
11339
+ const client = getClient();
11340
+ const fileContent = readFileSync(filePath);
11341
+ const blob2 = new Blob([fileContent], { type: "application/jsonl" });
11342
+ const file = new File([blob2], filePath.split("/").pop() ?? "training.jsonl", { type: "application/jsonl" });
11343
+ const response = await client.files.create({
11344
+ file,
11345
+ purpose: "fine-tune"
11803
11346
  });
11347
+ return { fileId: response.id };
11804
11348
  }
11805
11349
  async function createFineTuneJob(fileId, baseModel, suffix) {
11806
- return withRetry(async () => {
11807
- const client = getClient();
11808
- const params = { training_file: fileId, model: baseModel };
11809
- if (suffix)
11810
- params.suffix = suffix;
11811
- const response = await client.fineTuning.jobs.create(params);
11812
- return { jobId: response.id, status: response.status };
11813
- });
11350
+ const client = getClient();
11351
+ const params = {
11352
+ training_file: fileId,
11353
+ model: baseModel
11354
+ };
11355
+ if (suffix) {
11356
+ params.suffix = suffix;
11357
+ }
11358
+ const response = await client.fineTuning.jobs.create(params);
11359
+ return { jobId: response.id, status: response.status };
11814
11360
  }
11815
11361
  async function getFineTuneStatus(jobId) {
11816
- return withRetry(async () => {
11817
- const client = getClient();
11818
- const response = await client.fineTuning.jobs.retrieve(jobId);
11819
- return {
11820
- jobId: response.id,
11821
- status: response.status,
11822
- fineTunedModel: response.fine_tuned_model ?? undefined,
11823
- baseModel: response.model ?? undefined,
11824
- error: response.error?.message ?? undefined
11825
- };
11826
- });
11362
+ const client = getClient();
11363
+ const response = await client.fineTuning.jobs.retrieve(jobId);
11364
+ return {
11365
+ jobId: response.id,
11366
+ status: response.status,
11367
+ fineTunedModel: response.fine_tuned_model ?? undefined,
11368
+ error: response.error?.message ?? undefined
11369
+ };
11827
11370
  }
11828
11371
  async function listFineTunedModels() {
11829
- return withRetry(async () => {
11830
- const client = getClient();
11831
- const jobs = await client.fineTuning.jobs.list();
11832
- return jobs.data.map((job) => ({
11833
- id: job.id,
11834
- model: job.fine_tuned_model ?? job.model,
11835
- status: job.status,
11836
- created: job.created_at
11837
- }));
11838
- });
11372
+ const client = getClient();
11373
+ const jobs = await client.fineTuning.jobs.list();
11374
+ return jobs.data.map((job) => ({
11375
+ id: job.id,
11376
+ model: job.fine_tuned_model ?? job.model,
11377
+ status: job.status,
11378
+ created: job.created_at
11379
+ }));
11839
11380
  }
11840
11381
 
11841
11382
  // src/lib/providers/thinker-labs.ts
11842
11383
  var DEFAULT_BASE_URL = "https://api.thinkerlabs.ai/v1";
11843
11384
  function getConfig() {
11844
- const apiKey = getConfigValue("THINKER_LABS_API_KEY");
11845
- const baseUrl = getConfigValue("THINKER_LABS_BASE_URL") ?? DEFAULT_BASE_URL;
11385
+ const apiKey = process.env.THINKER_LABS_API_KEY;
11386
+ const baseUrl = process.env.THINKER_LABS_BASE_URL ?? DEFAULT_BASE_URL;
11846
11387
  if (!apiKey)
11847
- throw new Error("THINKER_LABS_API_KEY is not set. Run: brains config set THINKER_LABS_API_KEY <key>");
11388
+ throw new Error("THINKER_LABS_API_KEY environment variable is required");
11848
11389
  return { apiKey, baseUrl };
11849
11390
  }
11850
11391
  async function request(method, path, body, file) {
11851
- return withRetry(async () => {
11852
- const { apiKey, baseUrl } = getConfig();
11853
- const headers = { Authorization: `Bearer ${apiKey}` };
11854
- let fetchBody;
11855
- if (file) {
11856
- const form = new FormData;
11857
- form.append("file", new Blob([file.data], { type: "text/plain" }), file.name);
11858
- if (body) {
11859
- for (const [k, v] of Object.entries(body)) {
11860
- form.append(k, v);
11861
- }
11862
- }
11863
- fetchBody = form;
11864
- } else if (body) {
11865
- headers["Content-Type"] = "application/json";
11866
- fetchBody = JSON.stringify(body);
11867
- }
11868
- const res = await fetch(`${baseUrl}${path}`, { method, headers, body: fetchBody });
11869
- if (!res.ok) {
11870
- const text2 = await res.text();
11871
- throw new Error(`Thinker Labs API error ${res.status}: ${text2}`);
11872
- }
11873
- if (res.status === 204)
11874
- return;
11875
- return res.json();
11876
- });
11392
+ const { apiKey, baseUrl } = getConfig();
11393
+ const headers = { Authorization: `Bearer ${apiKey}` };
11394
+ let fetchBody;
11395
+ if (file) {
11396
+ const form = new FormData;
11397
+ form.append("file", new Blob([file.data], { type: "text/plain" }), file.name);
11398
+ if (body) {
11399
+ for (const [k, v] of Object.entries(body)) {
11400
+ form.append(k, v);
11401
+ }
11402
+ }
11403
+ fetchBody = form;
11404
+ } else if (body) {
11405
+ headers["Content-Type"] = "application/json";
11406
+ fetchBody = JSON.stringify(body);
11407
+ }
11408
+ const res = await fetch(`${baseUrl}${path}`, { method, headers, body: fetchBody });
11409
+ if (!res.ok) {
11410
+ const text2 = await res.text();
11411
+ throw new Error(`Thinker Labs API error ${res.status}: ${text2}`);
11412
+ }
11413
+ if (res.status === 204)
11414
+ return;
11415
+ return res.json();
11877
11416
  }
11878
11417
  async function uploadTrainingData(filePath) {
11879
11418
  const fileContent = await Bun.file(filePath).text();
@@ -11986,18 +11525,360 @@ function printInfo(message) {
11986
11525
  console.log(chalk.dim(" " + message));
11987
11526
  }
11988
11527
 
11528
+ // src/lib/gatherers/todos.ts
11529
+ import { Database as Database3 } from "bun:sqlite";
11530
+ import { homedir as homedir2 } from "os";
11531
+ import { join as join2 } from "path";
11532
+ var SYSTEM_PROMPT = "You are a task management assistant that helps users create, update, search, and manage tasks and projects.";
11533
+ function taskToCreateExample(task) {
11534
+ const userMsg = `Create a task: ${task.title}${task.description ? `
11535
+
11536
+ Description: ${task.description}` : ""}`;
11537
+ const taskDetails = {
11538
+ id: task.short_id ?? task.id,
11539
+ title: task.title,
11540
+ description: task.description ?? "",
11541
+ status: task.status,
11542
+ priority: task.priority,
11543
+ tags: JSON.parse(task.tags ?? "[]"),
11544
+ created_at: task.created_at
11545
+ };
11546
+ return {
11547
+ messages: [
11548
+ { role: "system", content: SYSTEM_PROMPT },
11549
+ { role: "user", content: userMsg },
11550
+ { role: "assistant", content: `Created task: ${JSON.stringify(taskDetails, null, 2)}` }
11551
+ ]
11552
+ };
11553
+ }
11554
+ function taskToStatusUpdateExample(task) {
11555
+ if (!task.completed_at && task.status === "pending")
11556
+ return null;
11557
+ const id = task.short_id ?? task.id;
11558
+ return {
11559
+ messages: [
11560
+ { role: "system", content: SYSTEM_PROMPT },
11561
+ { role: "user", content: `Mark task ${id} as ${task.status}` },
11562
+ { role: "assistant", content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim() }
11563
+ ]
11564
+ };
11565
+ }
11566
+ function taskToSearchExample(tasks, query) {
11567
+ const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
11568
+ return {
11569
+ messages: [
11570
+ { role: "system", content: SYSTEM_PROMPT },
11571
+ { role: "user", content: `Search tasks for: "${query}"` },
11572
+ {
11573
+ role: "assistant",
11574
+ content: matched.length > 0 ? `Found ${matched.length} task(s):
11575
+ ${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
11576
+ `)}` : `No tasks found matching "${query}".`
11577
+ }
11578
+ ]
11579
+ };
11580
+ }
11581
+ async function gatherFromTodos(options = {}) {
11582
+ const dbPath = join2(homedir2(), ".todos", "todos.db");
11583
+ const db = new Database3(dbPath, { readonly: true, create: false });
11584
+ try {
11585
+ let query = "SELECT * FROM tasks WHERE 1=1";
11586
+ const params = [];
11587
+ if (options.since) {
11588
+ query += " AND created_at >= ?";
11589
+ params.push(options.since.toISOString());
11590
+ }
11591
+ query += " ORDER BY created_at DESC";
11592
+ if (options.limit) {
11593
+ query += " LIMIT ?";
11594
+ params.push(options.limit * 2);
11595
+ }
11596
+ const tasks = db.query(query).all(...params);
11597
+ const examples = [];
11598
+ for (const task of tasks) {
11599
+ examples.push(taskToCreateExample(task));
11600
+ const statusEx = taskToStatusUpdateExample(task);
11601
+ if (statusEx)
11602
+ examples.push(statusEx);
11603
+ }
11604
+ const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
11605
+ for (const term of searchTerms) {
11606
+ examples.push(taskToSearchExample(tasks, term));
11607
+ }
11608
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11609
+ return {
11610
+ source: "todos",
11611
+ examples: finalExamples,
11612
+ count: finalExamples.length
11613
+ };
11614
+ } finally {
11615
+ db.close();
11616
+ }
11617
+ }
11618
+
11619
+ // src/lib/gatherers/mementos.ts
11620
+ import { Database as Database4 } from "bun:sqlite";
11621
+ import { homedir as homedir3 } from "os";
11622
+ import { join as join3 } from "path";
11623
+ var SYSTEM_PROMPT2 = "You are an AI assistant with persistent memory. You can remember and recall information across sessions to provide better, more personalized assistance.";
11624
+ function memoryToRecallExample(memory) {
11625
+ return {
11626
+ messages: [
11627
+ { role: "system", content: SYSTEM_PROMPT2 },
11628
+ { role: "user", content: `What do you remember about "${memory.key}"?` },
11629
+ {
11630
+ role: "assistant",
11631
+ content: memory.summary ? `${memory.value}
11632
+
11633
+ Summary: ${memory.summary}` : memory.value
11634
+ }
11635
+ ]
11636
+ };
11637
+ }
11638
+ function memoryToSaveExample(memory) {
11639
+ const tags = JSON.parse(memory.tags ?? "[]");
11640
+ return {
11641
+ messages: [
11642
+ { role: "system", content: SYSTEM_PROMPT2 },
11643
+ {
11644
+ role: "user",
11645
+ content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
11646
+ },
11647
+ {
11648
+ role: "assistant",
11649
+ content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
11650
+ }
11651
+ ]
11652
+ };
11653
+ }
11654
+ function memoryToSearchExample(memories, category) {
11655
+ const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
11656
+ return {
11657
+ messages: [
11658
+ { role: "system", content: SYSTEM_PROMPT2 },
11659
+ { role: "user", content: `What ${category} memories do you have?` },
11660
+ {
11661
+ role: "assistant",
11662
+ content: matched.length > 0 ? `Here are my ${category} memories:
11663
+ ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
11664
+ `)}` : `I don't have any ${category} memories stored yet.`
11665
+ }
11666
+ ]
11667
+ };
11668
+ }
11669
+ async function gatherFromMementos(options = {}) {
11670
+ const dbPath = join3(homedir3(), ".mementos", "mementos.db");
11671
+ const db = new Database4(dbPath, { readonly: true, create: false });
11672
+ try {
11673
+ let query = "SELECT * FROM memories WHERE status = 'active'";
11674
+ const params = [];
11675
+ if (options.since) {
11676
+ query += " AND created_at >= ?";
11677
+ params.push(options.since.toISOString());
11678
+ }
11679
+ query += " ORDER BY importance DESC, created_at DESC";
11680
+ if (options.limit) {
11681
+ query += " LIMIT ?";
11682
+ params.push(options.limit * 3);
11683
+ }
11684
+ const memories = db.query(query).all(...params);
11685
+ const examples = [];
11686
+ for (const memory of memories) {
11687
+ examples.push(memoryToRecallExample(memory));
11688
+ examples.push(memoryToSaveExample(memory));
11689
+ }
11690
+ const categories = [...new Set(memories.map((m) => m.category))];
11691
+ for (const category of categories) {
11692
+ examples.push(memoryToSearchExample(memories, category));
11693
+ }
11694
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11695
+ return {
11696
+ source: "mementos",
11697
+ examples: finalExamples,
11698
+ count: finalExamples.length
11699
+ };
11700
+ } finally {
11701
+ db.close();
11702
+ }
11703
+ }
11704
+
11705
+ // src/lib/gatherers/conversations.ts
11706
+ import { Database as Database5 } from "bun:sqlite";
11707
+ import { homedir as homedir4 } from "os";
11708
+ import { join as join4 } from "path";
11709
+ var SYSTEM_PROMPT3 = "You are a helpful AI assistant participating in multi-agent conversations. You communicate clearly and collaboratively with other agents and users.";
11710
+ function windowToExample(window2) {
11711
+ if (window2.length < 2)
11712
+ return null;
11713
+ const messages = [
11714
+ { role: "system", content: SYSTEM_PROMPT3 }
11715
+ ];
11716
+ for (let i = 0;i < window2.length - 1; i++) {
11717
+ const msg = window2[i];
11718
+ if (!msg)
11719
+ continue;
11720
+ const role = i % 2 === 0 ? "user" : "assistant";
11721
+ messages.push({
11722
+ role,
11723
+ content: `[${msg.from_agent} \u2192 ${msg.to_agent ?? msg.space ?? "all"}]: ${msg.content}`
11724
+ });
11725
+ }
11726
+ const last = window2[window2.length - 1];
11727
+ if (!last)
11728
+ return null;
11729
+ messages.push({
11730
+ role: "assistant",
11731
+ content: `[${last.from_agent} \u2192 ${last.to_agent ?? last.space ?? "all"}]: ${last.content}`
11732
+ });
11733
+ return { messages };
11734
+ }
11735
+ async function gatherFromConversations(options = {}) {
11736
+ const dbPath = join4(homedir4(), ".conversations", "messages.db");
11737
+ const db = new Database5(dbPath, { readonly: true, create: false });
11738
+ try {
11739
+ let query = "SELECT * FROM messages WHERE 1=1";
11740
+ const params = [];
11741
+ if (options.since) {
11742
+ query += " AND created_at >= ?";
11743
+ params.push(options.since.toISOString());
11744
+ }
11745
+ query += " ORDER BY session_id, created_at ASC";
11746
+ const allMessages = db.query(query).all(...params);
11747
+ const sessions = new Map;
11748
+ for (const msg of allMessages) {
11749
+ const msgs = sessions.get(msg.session_id) ?? [];
11750
+ msgs.push(msg);
11751
+ sessions.set(msg.session_id, msgs);
11752
+ }
11753
+ const examples = [];
11754
+ const windowSize = 4;
11755
+ for (const [, sessionMsgs] of sessions) {
11756
+ if (sessionMsgs.length < 2)
11757
+ continue;
11758
+ for (let start = 0;start <= sessionMsgs.length - 2; start++) {
11759
+ const end = Math.min(start + windowSize, sessionMsgs.length);
11760
+ const window2 = sessionMsgs.slice(start, end);
11761
+ const example = windowToExample(window2);
11762
+ if (example)
11763
+ examples.push(example);
11764
+ }
11765
+ }
11766
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11767
+ return {
11768
+ source: "conversations",
11769
+ examples: finalExamples,
11770
+ count: finalExamples.length
11771
+ };
11772
+ } finally {
11773
+ db.close();
11774
+ }
11775
+ }
11776
+
11777
+ // src/lib/gatherers/sessions.ts
11778
+ import { readdir, readFile, stat } from "fs/promises";
11779
+ import { existsSync } from "fs";
11780
+ import { join as join5 } from "path";
11781
+ import { homedir as homedir5 } from "os";
11782
+ var SYSTEM_PROMPT4 = "You are Claude Code, an AI assistant built by Anthropic that helps developers with coding, architecture, debugging, and software engineering tasks.";
11783
+ function extractText(content) {
11784
+ if (typeof content === "string")
11785
+ return content;
11786
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join(`
11787
+ `).trim();
11788
+ }
11789
+ async function gatherFromSessions(options = {}) {
11790
+ const { limit: limit2 = 1000 } = options;
11791
+ const examples = [];
11792
+ const claudeDir = join5(homedir5(), ".claude", "projects");
11793
+ if (!existsSync(claudeDir)) {
11794
+ return { source: "sessions", examples: [], count: 0 };
11795
+ }
11796
+ const projectDirs = await readdir(claudeDir).catch(() => []);
11797
+ for (const projectDir of projectDirs) {
11798
+ if (examples.length >= limit2)
11799
+ break;
11800
+ const projectPath = join5(claudeDir, projectDir);
11801
+ const files = await readdir(projectPath).catch(() => []);
11802
+ for (const file of files) {
11803
+ if (examples.length >= limit2)
11804
+ break;
11805
+ if (!file.endsWith(".jsonl"))
11806
+ continue;
11807
+ const filePath = join5(projectPath, file);
11808
+ if (options.since) {
11809
+ const fileStat = await stat(filePath).catch(() => null);
11810
+ if (fileStat && fileStat.mtime < options.since)
11811
+ continue;
11812
+ }
11813
+ const content = await readFile(filePath, "utf-8").catch(() => "");
11814
+ if (!content.trim())
11815
+ continue;
11816
+ const lines = content.trim().split(`
11817
+ `);
11818
+ const turns = [];
11819
+ for (const line of lines) {
11820
+ try {
11821
+ const entry = JSON.parse(line);
11822
+ if ((entry.type === "user" || entry.type === "human") && entry.message?.content) {
11823
+ const text2 = extractText(entry.message.content);
11824
+ if (text2.trim())
11825
+ turns.push({ role: "user", content: text2.trim() });
11826
+ } else if (entry.type === "assistant" && entry.message?.content) {
11827
+ const text2 = extractText(entry.message.content);
11828
+ if (text2.trim())
11829
+ turns.push({ role: "assistant", content: text2.trim() });
11830
+ }
11831
+ } catch {}
11832
+ }
11833
+ const windowSize = 6;
11834
+ for (let start = 0;start < turns.length - 1 && examples.length < limit2; start++) {
11835
+ const window2 = turns.slice(start, start + windowSize);
11836
+ if (!window2[0] || window2[0].role !== "user")
11837
+ continue;
11838
+ const lastAssistantIdx = window2.map((t) => t.role).lastIndexOf("assistant");
11839
+ if (lastAssistantIdx < 1)
11840
+ continue;
11841
+ const usedTurns = window2.slice(0, lastAssistantIdx + 1);
11842
+ examples.push({
11843
+ messages: [
11844
+ { role: "system", content: SYSTEM_PROMPT4 },
11845
+ ...usedTurns
11846
+ ]
11847
+ });
11848
+ }
11849
+ }
11850
+ }
11851
+ return { source: "sessions", examples, count: examples.length };
11852
+ }
11853
+ // src/lib/gatherers/index.ts
11854
+ var ALL_SOURCES = ["todos", "mementos", "conversations", "sessions"];
11855
+ async function gatherAll(sources, options = {}) {
11856
+ const targets = sources.includes("all") ? [...ALL_SOURCES] : sources;
11857
+ const results = await Promise.allSettled(targets.map((source) => {
11858
+ switch (source) {
11859
+ case "todos":
11860
+ return gatherFromTodos(options);
11861
+ case "mementos":
11862
+ return gatherFromMementos(options);
11863
+ case "conversations":
11864
+ return gatherFromConversations(options);
11865
+ case "sessions":
11866
+ return gatherFromSessions(options);
11867
+ default:
11868
+ return Promise.resolve({ source, examples: [], count: 0 });
11869
+ }
11870
+ }));
11871
+ return results.filter((r) => r.status === "fulfilled").map((r) => r.value);
11872
+ }
11873
+
11989
11874
  // src/cli/index.ts
11990
11875
  var program2 = new Command;
11991
11876
  program2.name("brains").description("Fine-tuned model tracker and trainer").version("0.0.1");
11992
11877
  var modelsCmd = program2.command("models").description("Manage tracked fine-tuned models");
11993
- modelsCmd.command("list").description("List all tracked fine-tuned models").option("--json", "Output as JSON").action(async (opts) => {
11878
+ modelsCmd.command("list").description("List all tracked fine-tuned models").action(async () => {
11994
11879
  try {
11995
11880
  const db = getDb();
11996
11881
  const models = await db.select().from(fineTunedModels);
11997
- if (opts.json) {
11998
- printJson(models);
11999
- return;
12000
- }
12001
11882
  if (models.length === 0) {
12002
11883
  printInfo("No models tracked yet. Use 'brains finetune start' to train one.");
12003
11884
  return;
@@ -12015,22 +11896,14 @@ modelsCmd.command("list").description("List all tracked fine-tuned models").opti
12015
11896
  process.exit(1);
12016
11897
  }
12017
11898
  });
12018
- modelsCmd.command("show <id>").description("Show details of a specific model").option("--json", "Output as JSON").action(async (id, opts) => {
11899
+ modelsCmd.command("show <id>").description("Show details of a specific model").action(async (id) => {
12019
11900
  try {
12020
11901
  const db = getDb();
12021
11902
  const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
12022
11903
  if (!model) {
12023
- if (opts.json) {
12024
- printJson({ error: `Model not found: ${id}` });
12025
- } else {
12026
- printError(`Model not found: ${id}`);
12027
- }
11904
+ printError(`Model not found: ${id}`);
12028
11905
  process.exit(1);
12029
11906
  }
12030
- if (opts.json) {
12031
- printJson(model);
12032
- return;
12033
- }
12034
11907
  console.log();
12035
11908
  const tagsList = model.tags ? JSON.parse(model.tags).join(", ") : "(none)";
12036
11909
  console.log(` ID: ${model.id}`);
@@ -12117,89 +11990,29 @@ modelsCmd.command("collection <id> <collectionName>").description("Set the colle
12117
11990
  process.exit(1);
12118
11991
  }
12119
11992
  });
12120
- modelsCmd.command("import <job-id>").description("Import an externally created fine-tuned model into local tracking").option("--provider <provider>", "Provider (openai|thinker-labs)", "openai").option("--name <name>", "Display name for the model").action(async (jobId, opts) => {
12121
- try {
12122
- let result;
12123
- if (opts.provider === "openai") {
12124
- result = await getFineTuneStatus(jobId);
12125
- } else {
12126
- const tl = new ThinkerLabsProvider;
12127
- result = await tl.getFineTuneStatus(jobId);
12128
- }
12129
- const db = getDb();
12130
- const [existing] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
12131
- if (existing) {
12132
- printInfo(`Model already tracked as: ${existing.id}`);
12133
- return;
12134
- }
12135
- const modelId = randomUUID();
12136
- const now = Date.now();
12137
- const name = opts.name ?? result.fineTunedModel ?? `imported-${jobId}`;
12138
- await db.insert(fineTunedModels).values({
12139
- id: modelId,
12140
- name,
12141
- provider: opts.provider,
12142
- baseModel: result.baseModel ?? "unknown",
12143
- status: result.status,
12144
- fineTuneJobId: jobId,
12145
- createdAt: now,
12146
- updatedAt: now
12147
- });
12148
- await db.insert(trainingJobs).values({
12149
- id: randomUUID(),
12150
- modelId,
12151
- provider: opts.provider,
12152
- status: result.status,
12153
- startedAt: now
12154
- });
12155
- printSuccess(`Model imported successfully.`);
12156
- console.log();
12157
- console.log(` Local ID: ${modelId}`);
12158
- console.log(` Job ID: ${jobId}`);
12159
- console.log(` Name: ${name}`);
12160
- console.log(` Status: ${printStatus(result.status)}`);
12161
- if (result.fineTunedModel)
12162
- console.log(` Model: ${result.fineTunedModel}`);
12163
- console.log();
12164
- } catch (err) {
12165
- printError(err instanceof Error ? err.message : String(err));
12166
- process.exit(1);
12167
- }
12168
- });
12169
11993
  var finetuneCmd = program2.command("finetune").description("Manage fine-tuning jobs");
12170
- finetuneCmd.command("start").description("Start a fine-tuning job").requiredOption("--provider <provider>", "Provider to use (openai|thinker-labs)").requiredOption("--base-model <model>", "Base model to fine-tune (e.g. gpt-4o-mini-2024-07-18)").option("--dataset <path>", "Path to the JSONL training dataset (auto-detects latest if omitted)").requiredOption("--name <name>", "Human-readable name for this fine-tuned model").action(async (opts) => {
11994
+ finetuneCmd.command("start").description("Start a fine-tuning job").requiredOption("--provider <provider>", "Provider to use (openai|thinker-labs)").requiredOption("--base-model <model>", "Base model to fine-tune (e.g. gpt-4o-mini-2024-07-18)").requiredOption("--dataset <path>", "Path to the JSONL training dataset").requiredOption("--name <name>", "Human-readable name for this fine-tuned model").action(async (opts) => {
12171
11995
  try {
12172
11996
  if (opts.provider !== "openai" && opts.provider !== "thinker-labs") {
12173
11997
  printError(`Unknown provider: ${opts.provider}. Use 'openai' or 'thinker-labs'.`);
12174
11998
  process.exit(1);
12175
11999
  }
12176
- let datasetPath = opts.dataset;
12177
- if (!datasetPath) {
12178
- const db2 = getDb();
12179
- const [latest] = await db2.select().from(trainingDatasets).orderBy(desc(trainingDatasets.createdAt)).limit(1);
12180
- if (!latest?.filePath) {
12181
- printError("No datasets found. Run 'brains data gather' first.");
12182
- process.exit(1);
12183
- }
12184
- datasetPath = latest.filePath;
12185
- printInfo(`Using latest dataset: ${datasetPath} (${latest.exampleCount} examples)`);
12186
- }
12187
- if (!existsSync3(datasetPath)) {
12188
- printError(`Dataset file not found: ${datasetPath}`);
12000
+ if (!existsSync2(opts.dataset)) {
12001
+ printError(`Dataset file not found: ${opts.dataset}`);
12189
12002
  process.exit(1);
12190
12003
  }
12191
- printInfo(`Uploading training file: ${datasetPath} \u2026`);
12004
+ printInfo(`Uploading training file: ${opts.dataset} \u2026`);
12192
12005
  let fileId;
12193
12006
  let jobId;
12194
12007
  let jobStatus;
12195
12008
  if (opts.provider === "openai") {
12196
- ({ fileId } = await uploadTrainingFile(datasetPath));
12009
+ ({ fileId } = await uploadTrainingFile(opts.dataset));
12197
12010
  printSuccess(`File uploaded. fileId = ${fileId}`);
12198
12011
  printInfo(`Creating fine-tune job on OpenAI \u2026`);
12199
12012
  ({ jobId, status: jobStatus } = await createFineTuneJob(fileId, opts.baseModel, opts.name));
12200
12013
  } else {
12201
12014
  const tl = new ThinkerLabsProvider;
12202
- ({ fileId } = await tl.uploadTrainingFile(datasetPath));
12015
+ ({ fileId } = await tl.uploadTrainingFile(opts.dataset));
12203
12016
  printSuccess(`File uploaded. fileId = ${fileId}`);
12204
12017
  printInfo(`Creating fine-tune job on Thinker Labs \u2026`);
12205
12018
  ({ jobId, status: jobStatus } = await tl.createFineTuneJob(fileId, opts.baseModel, opts.name));
@@ -12237,7 +12050,7 @@ finetuneCmd.command("start").description("Start a fine-tuning job").requiredOpti
12237
12050
  process.exit(1);
12238
12051
  }
12239
12052
  });
12240
- finetuneCmd.command("status <job-id>").description("Get the status of a fine-tuning job").option("--provider <provider>", "Provider (openai|thinker-labs)", "openai").option("--json", "Output as JSON").action(async (jobId, opts) => {
12053
+ finetuneCmd.command("status <job-id>").description("Get the status of a fine-tuning job").option("--provider <provider>", "Provider (openai|thinker-labs)", "openai").action(async (jobId, opts) => {
12241
12054
  try {
12242
12055
  let result;
12243
12056
  if (opts.provider === "openai") {
@@ -12246,20 +12059,16 @@ finetuneCmd.command("status <job-id>").description("Get the status of a fine-tun
12246
12059
  const tl = new ThinkerLabsProvider;
12247
12060
  result = await tl.getFineTuneStatus(jobId);
12248
12061
  }
12249
- if (opts.json) {
12250
- printJson(result);
12251
- } else {
12252
- console.log();
12253
- console.log(` Job ID: ${result.jobId}`);
12254
- console.log(` Status: ${printStatus(result.status)}`);
12255
- if (result.fineTunedModel) {
12256
- console.log(` Fine-tuned model: ${result.fineTunedModel}`);
12257
- }
12258
- if (result.error) {
12259
- console.log(` Error: ${result.error}`);
12260
- }
12261
- console.log();
12062
+ console.log();
12063
+ console.log(` Job ID: ${result.jobId}`);
12064
+ console.log(` Status: ${printStatus(result.status)}`);
12065
+ if (result.fineTunedModel) {
12066
+ console.log(` Fine-tuned model: ${result.fineTunedModel}`);
12262
12067
  }
12068
+ if (result.error) {
12069
+ console.log(` Error: ${result.error}`);
12070
+ }
12071
+ console.log();
12263
12072
  const db = getDb();
12264
12073
  const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
12265
12074
  if (model) {
@@ -12271,64 +12080,7 @@ finetuneCmd.command("status <job-id>").description("Get the status of a fine-tun
12271
12080
  process.exit(1);
12272
12081
  }
12273
12082
  });
12274
- finetuneCmd.command("watch <job-id>").description("Poll a fine-tuning job until it completes or fails").option("--provider <provider>", "Provider (openai|thinker-labs)", "openai").option("--interval <seconds>", "Poll interval in seconds", "30").action(async (jobId, opts) => {
12275
- const intervalMs = Math.max(5, parseInt(opts.interval, 10) || 30) * 1000;
12276
- const terminalStates = new Set(["succeeded", "failed", "cancelled"]);
12277
- printInfo(`Watching job ${jobId} (polling every ${intervalMs / 1000}s) \u2026`);
12278
- console.log();
12279
- const poll = async () => {
12280
- try {
12281
- let result;
12282
- if (opts.provider === "openai") {
12283
- result = await getFineTuneStatus(jobId);
12284
- } else {
12285
- const tl = new ThinkerLabsProvider;
12286
- result = await tl.getFineTuneStatus(jobId);
12287
- }
12288
- const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
12289
- process.stdout.write(` [${ts}] ${printStatus(result.status)}`);
12290
- if (result.fineTunedModel)
12291
- process.stdout.write(` model: ${result.fineTunedModel}`);
12292
- process.stdout.write(`
12293
- `);
12294
- const db = getDb();
12295
- const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
12296
- if (model) {
12297
- await db.update(fineTunedModels).set({ status: result.status, updatedAt: Date.now() }).where(eq(fineTunedModels.fineTuneJobId, jobId));
12298
- }
12299
- if (terminalStates.has(result.status)) {
12300
- console.log();
12301
- if (result.status === "succeeded") {
12302
- printSuccess(`Job completed successfully.`);
12303
- if (result.fineTunedModel)
12304
- printSuccess(`Fine-tuned model: ${result.fineTunedModel}`);
12305
- } else if (result.status === "failed") {
12306
- printError(`Job failed.${result.error ? " Error: " + result.error : ""}`);
12307
- } else {
12308
- printInfo(`Job ${result.status}.`);
12309
- }
12310
- return true;
12311
- }
12312
- return false;
12313
- } catch (err) {
12314
- printError(err instanceof Error ? err.message : String(err));
12315
- return false;
12316
- }
12317
- };
12318
- const done = await poll();
12319
- if (!done) {
12320
- await new Promise((resolve2) => {
12321
- const timer = setInterval(async () => {
12322
- const finished = await poll();
12323
- if (finished) {
12324
- clearInterval(timer);
12325
- resolve2();
12326
- }
12327
- }, intervalMs);
12328
- });
12329
- }
12330
- });
12331
- finetuneCmd.command("list").description("List all fine-tuning jobs").option("--provider <provider>", "Provider to query (openai|thinker-labs)", "openai").option("--json", "Output as JSON").action(async (opts) => {
12083
+ finetuneCmd.command("list").description("List all fine-tuning jobs").option("--provider <provider>", "Provider to query (openai|thinker-labs)", "openai").action(async (opts) => {
12332
12084
  try {
12333
12085
  let jobs;
12334
12086
  if (opts.provider === "openai") {
@@ -12337,10 +12089,6 @@ finetuneCmd.command("list").description("List all fine-tuning jobs").option("--p
12337
12089
  const tl = new ThinkerLabsProvider;
12338
12090
  jobs = await tl.listFineTunedModels();
12339
12091
  }
12340
- if (opts.json) {
12341
- printJson(jobs);
12342
- return;
12343
- }
12344
12092
  if (jobs.length === 0) {
12345
12093
  printInfo("No fine-tuning jobs found.");
12346
12094
  return;
@@ -12356,7 +12104,7 @@ finetuneCmd.command("list").description("List all fine-tuning jobs").option("--p
12356
12104
  process.exit(1);
12357
12105
  }
12358
12106
  });
12359
- var DEFAULT_DATASETS_DIR = join7(homedir7(), ".brains", "datasets");
12107
+ var DEFAULT_DATASETS_DIR = join6(homedir6(), ".brains", "datasets");
12360
12108
  var dataCmd = program2.command("data").description("Manage training datasets");
12361
12109
  dataCmd.command("gather").description("Gather training data from agent memory sources").option("--source <source>", "Data source: todos|mementos|conversations|sessions|all", "all").option("--output <dir>", "Output directory", DEFAULT_DATASETS_DIR).option("--limit <n>", "Maximum number of examples to gather", "500").action(async (opts) => {
12362
12110
  const validSources = ["todos", "mementos", "conversations", "sessions", "all"];
@@ -12370,59 +12118,44 @@ dataCmd.command("gather").description("Gather training data from agent memory so
12370
12118
  process.exit(1);
12371
12119
  }
12372
12120
  try {
12373
- mkdirSync3(opts.output, { recursive: true });
12121
+ mkdirSync2(opts.output, { recursive: true });
12374
12122
  const sources = opts.source === "all" ? ["todos", "mementos", "conversations", "sessions"] : [opts.source];
12375
12123
  const now = Date.now();
12376
12124
  const db = getDb();
12377
- const gathererMap = {
12378
- todos: (o) => Promise.resolve().then(() => (init_todos(), exports_todos)).then((m) => m.gatherFromTodos(o)),
12379
- mementos: (o) => Promise.resolve().then(() => (init_mementos(), exports_mementos)).then((m) => m.gatherFromMementos(o)),
12380
- conversations: (o) => Promise.resolve().then(() => (init_conversations(), exports_conversations)).then((m) => m.gatherFromConversations(o)),
12381
- sessions: (o) => Promise.resolve().then(() => (init_sessions(), exports_sessions2)).then((m) => m.gatherFromSessions(o))
12382
- };
12125
+ const results = await gatherAll(sources, { limit: limit2 });
12383
12126
  let totalExamples = 0;
12384
- let successfulSources = 0;
12385
- for (const source of sources) {
12386
- printInfo(`Gathering from ${source} \u2026`);
12387
- try {
12388
- const gatherer = gathererMap[source];
12389
- if (!gatherer) {
12390
- printError(` Unknown source: ${source}`);
12391
- continue;
12392
- }
12393
- const { examples, count } = await gatherer({ limit: limit2 });
12394
- if (count === 0) {
12395
- printInfo(` No examples found in ${source}.`);
12396
- continue;
12397
- }
12398
- const fileName = `${source}-${now}.jsonl`;
12399
- const filePath = join7(opts.output, fileName);
12400
- writeFileSync2(filePath, examples.map((e) => JSON.stringify(e)).join(`
12127
+ for (const result of results) {
12128
+ const { source, examples, count } = result;
12129
+ printInfo(`Gathered from ${source} \u2026`);
12130
+ if (count === 0) {
12131
+ printInfo(` No examples gathered from ${source}.`);
12132
+ continue;
12133
+ }
12134
+ totalExamples += count;
12135
+ const fileName = `${source}-${now}.jsonl`;
12136
+ const filePath = join6(opts.output, fileName);
12137
+ writeFileSync(filePath, examples.map((e) => JSON.stringify(e)).join(`
12401
12138
  `) + `
12402
12139
  `, "utf8");
12403
- await db.insert(trainingDatasets).values({
12404
- id: randomUUID(),
12405
- source,
12406
- filePath,
12407
- exampleCount: count,
12408
- createdAt: now
12409
- });
12410
- printSuccess(` \u2713 ${count} examples \u2192 ${filePath}`);
12411
- totalExamples += count;
12412
- successfulSources++;
12413
- } catch (sourceErr) {
12414
- printError(` \u2717 ${source}: ${sourceErr instanceof Error ? sourceErr.message : String(sourceErr)}`);
12415
- }
12140
+ const datasetId = randomUUID();
12141
+ await db.insert(trainingDatasets).values({
12142
+ id: datasetId,
12143
+ source,
12144
+ filePath,
12145
+ exampleCount: count,
12146
+ createdAt: now
12147
+ });
12148
+ printSuccess(` ${count} examples \u2192 ${filePath}`);
12416
12149
  }
12417
12150
  console.log();
12418
- printSuccess(`Total: ${totalExamples} examples from ${successfulSources} source(s)`);
12151
+ printSuccess(`Total examples gathered: ${totalExamples}`);
12419
12152
  } catch (err) {
12420
12153
  printError(err instanceof Error ? err.message : String(err));
12421
12154
  process.exit(1);
12422
12155
  }
12423
12156
  });
12424
12157
  dataCmd.command("preview <file>").description("Preview a JSONL training file").option("-n, --count <n>", "Number of examples to show", "5").action((file, opts) => {
12425
- if (!existsSync3(file)) {
12158
+ if (!existsSync2(file)) {
12426
12159
  printError(`File not found: ${file}`);
12427
12160
  process.exit(1);
12428
12161
  }
@@ -12432,7 +12165,7 @@ dataCmd.command("preview <file>").description("Preview a JSONL training file").o
12432
12165
  process.exit(1);
12433
12166
  }
12434
12167
  try {
12435
- const content = readFileSync3(file, "utf8");
12168
+ const content = readFileSync2(file, "utf8");
12436
12169
  const lines = content.trim().split(`
12437
12170
  `).filter(Boolean);
12438
12171
  const total = lines.length;
@@ -12457,63 +12190,10 @@ dataCmd.command("preview <file>").description("Preview a JSONL training file").o
12457
12190
  process.exit(1);
12458
12191
  }
12459
12192
  });
12460
- dataCmd.command("merge <files...>").description("Merge multiple JSONL datasets into one").option("--output <path>", "Output file path", join7(DEFAULT_DATASETS_DIR, `merged-${Date.now()}.jsonl`)).option("--no-dedupe", "Skip deduplication").action(async (files, opts) => {
12461
- try {
12462
- for (const f of files) {
12463
- if (!existsSync3(f)) {
12464
- printError(`File not found: ${f}`);
12465
- process.exit(1);
12466
- }
12467
- }
12468
- mkdirSync3(join7(opts.output, "..").replace(/\/\.\.$/, "") || DEFAULT_DATASETS_DIR, { recursive: true });
12469
- const allExamples = [];
12470
- for (const f of files) {
12471
- const lines = readFileSync3(f, "utf8").split(`
12472
- `).map((l) => l.trim()).filter(Boolean);
12473
- allExamples.push(...lines);
12474
- printInfo(` Read ${lines.length} examples from ${f}`);
12475
- }
12476
- let finalLines = allExamples;
12477
- let dupeCount = 0;
12478
- if (opts.dedupe) {
12479
- const seen = new Set;
12480
- finalLines = allExamples.filter((line) => {
12481
- if (seen.has(line)) {
12482
- dupeCount++;
12483
- return false;
12484
- }
12485
- seen.add(line);
12486
- return true;
12487
- });
12488
- }
12489
- writeFileSync2(opts.output, finalLines.join(`
12490
- `) + `
12491
- `, "utf8");
12492
- const db = getDb();
12493
- await db.insert(trainingDatasets).values({
12494
- id: randomUUID(),
12495
- source: "mixed",
12496
- filePath: opts.output,
12497
- exampleCount: finalLines.length,
12498
- createdAt: Date.now()
12499
- });
12500
- console.log();
12501
- printSuccess(`Merged ${files.length} files \u2014 ${finalLines.length} examples \u2192 ${opts.output}`);
12502
- if (opts.dedupe && dupeCount > 0)
12503
- printInfo(` Removed ${dupeCount} duplicate(s)`);
12504
- } catch (err) {
12505
- printError(err instanceof Error ? err.message : String(err));
12506
- process.exit(1);
12507
- }
12508
- });
12509
- dataCmd.command("list").description("List all gathered datasets").option("--json", "Output as JSON").action(async (opts) => {
12193
+ dataCmd.command("list").description("List all gathered datasets").action(async () => {
12510
12194
  try {
12511
12195
  const db = getDb();
12512
12196
  const datasets = await db.select().from(trainingDatasets);
12513
- if (opts.json) {
12514
- printJson(datasets);
12515
- return;
12516
- }
12517
12197
  if (datasets.length === 0) {
12518
12198
  printInfo("No datasets found. Use 'brains data gather' to create one.");
12519
12199
  return;
@@ -12531,10 +12211,10 @@ dataCmd.command("list").description("List all gathered datasets").option("--json
12531
12211
  }
12532
12212
  });
12533
12213
  var collectionsCmd = program2.command("collections").description("Manage model collections");
12534
- collectionsCmd.option("--json", "Output as JSON").action(async (opts) => {
12535
- await listCollections(opts.json);
12214
+ collectionsCmd.action(async () => {
12215
+ await listCollections();
12536
12216
  });
12537
- async function listCollections(json = false) {
12217
+ async function listCollections() {
12538
12218
  try {
12539
12219
  const db = getDb();
12540
12220
  const rows = await db.select({
@@ -12542,10 +12222,6 @@ async function listCollections(json = false) {
12542
12222
  count: sql`count(*)`.as("count"),
12543
12223
  names: sql`group_concat(coalesce(${fineTunedModels.displayName}, ${fineTunedModels.name}), ', ')`.as("names")
12544
12224
  }).from(fineTunedModels).groupBy(fineTunedModels.collection);
12545
- if (json) {
12546
- printJson(rows);
12547
- return;
12548
- }
12549
12225
  if (rows.length === 0) {
12550
12226
  printInfo("No collections found. Set a collection with 'brains models set-collection'.");
12551
12227
  return;
@@ -12560,8 +12236,8 @@ async function listCollections(json = false) {
12560
12236
  process.exit(1);
12561
12237
  }
12562
12238
  }
12563
- collectionsCmd.command("list").description("List all collections with model counts").option("--json", "Output as JSON").action(async (opts) => {
12564
- await listCollections(opts.json);
12239
+ collectionsCmd.command("list").description("List all collections with model counts").action(async () => {
12240
+ await listCollections();
12565
12241
  });
12566
12242
  collectionsCmd.command("show <name>").description("List all models in a collection").action(async (name) => {
12567
12243
  try {
@@ -12620,43 +12296,4 @@ program2.command("remove <id>").alias("rm").alias("uninstall").description("Remo
12620
12296
  process.exit(1);
12621
12297
  }
12622
12298
  });
12623
- var configCmd = program2.command("config").description("Manage API keys and settings");
12624
- configCmd.command("list").description("Show all config keys and their sources").action(() => {
12625
- const entries = listConfig();
12626
- console.log();
12627
- for (const { key, value, source } of entries) {
12628
- const display = source === "unset" ? "(unset)" : value.length > 8 ? value.slice(0, 4) + "****" + value.slice(-4) : "****";
12629
- const src = source === "env" ? " [env]" : source === "file" ? " [file]" : "";
12630
- console.log(` ${key.padEnd(28)} ${display}${src}`);
12631
- }
12632
- console.log();
12633
- });
12634
- configCmd.command("get <key>").description("Get a config value").action((key) => {
12635
- if (!CONFIG_KEYS.includes(key)) {
12636
- printError(`Unknown key: ${key}. Valid keys: ${CONFIG_KEYS.join(", ")}`);
12637
- process.exit(1);
12638
- }
12639
- const value = getConfigValue(key);
12640
- if (!value) {
12641
- printInfo(`${key} is not set.`);
12642
- } else {
12643
- console.log(value);
12644
- }
12645
- });
12646
- configCmd.command("set <key> <value>").description("Set a config value (stored in ~/.brains/config.json)").action((key, value) => {
12647
- if (!CONFIG_KEYS.includes(key)) {
12648
- printError(`Unknown key: ${key}. Valid keys: ${CONFIG_KEYS.join(", ")}`);
12649
- process.exit(1);
12650
- }
12651
- setConfigValue(key, value);
12652
- printSuccess(`${key} saved to ~/.brains/config.json`);
12653
- });
12654
- configCmd.command("unset <key>").description("Remove a config value from the config file").action((key) => {
12655
- if (!CONFIG_KEYS.includes(key)) {
12656
- printError(`Unknown key: ${key}. Valid keys: ${CONFIG_KEYS.join(", ")}`);
12657
- process.exit(1);
12658
- }
12659
- deleteConfigValue(key);
12660
- printSuccess(`${key} removed from config.`);
12661
- });
12662
12299
  program2.parse();