@hasna/brains 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -26,6 +26,7 @@ var __export = (target, all) => {
26
26
  set: (newValue) => all[name] = () => newValue
27
27
  });
28
28
  };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
30
  var __require = import.meta.require;
30
31
 
31
32
  // node_modules/commander/lib/error.js
@@ -2062,6 +2063,352 @@ var require_commander = __commonJS((exports) => {
2062
2063
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2063
2064
  });
2064
2065
 
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 ? `
2076
+
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}".`
2118
+ }
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));
2148
+ }
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
+ }
2158
+ }
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
+
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
+
2065
2412
  // node_modules/commander/esm.mjs
2066
2413
  var import__ = __toESM(require_commander(), 1);
2067
2414
  var {
@@ -3436,9 +3783,9 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
3436
3783
 
3437
3784
  // src/cli/index.ts
3438
3785
  import { randomUUID } from "crypto";
3439
- import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
3440
- import { join as join6 } from "path";
3441
- import { homedir as homedir6 } from "os";
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";
3442
3789
 
3443
3790
  // node_modules/drizzle-orm/bun-sqlite/driver.js
3444
3791
  import { Database } from "bun:sqlite";
@@ -5721,10 +6068,48 @@ function drizzle(...params) {
5721
6068
  drizzle2.mock = mock;
5722
6069
  })(drizzle || (drizzle = {}));
5723
6070
 
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
+
5724
6109
  // src/db/index.ts
5725
6110
  import { Database as Database2 } from "bun:sqlite";
5726
6111
  import { mkdirSync } from "fs";
5727
- import { dirname, join } from "path";
6112
+ import { dirname, join, resolve } from "path";
5728
6113
  import { homedir } from "os";
5729
6114
 
5730
6115
  // src/db/schema.ts
@@ -5776,52 +6161,55 @@ var DEFAULT_DB_PATH = join(homedir(), ".brains", "brains.db");
5776
6161
  function ensureDir(filePath) {
5777
6162
  mkdirSync(dirname(filePath), { recursive: true });
5778
6163
  }
5779
- function createTables(sqlite) {
5780
- sqlite.exec(`
5781
- CREATE TABLE IF NOT EXISTS fine_tuned_models (
5782
- id TEXT PRIMARY KEY,
5783
- base_model TEXT NOT NULL,
5784
- name TEXT NOT NULL,
5785
- provider TEXT NOT NULL,
5786
- status TEXT NOT NULL DEFAULT 'pending',
5787
- fine_tune_job_id TEXT,
5788
- display_name TEXT,
5789
- description TEXT,
5790
- collection TEXT,
5791
- tags TEXT,
5792
- created_at INTEGER NOT NULL,
5793
- updated_at INTEGER NOT NULL
5794
- );
5795
-
5796
- CREATE TABLE IF NOT EXISTS training_jobs (
5797
- id TEXT PRIMARY KEY,
5798
- model_id TEXT NOT NULL REFERENCES fine_tuned_models(id),
5799
- provider TEXT NOT NULL,
5800
- status TEXT NOT NULL,
5801
- started_at INTEGER NOT NULL,
5802
- finished_at INTEGER,
5803
- metrics TEXT,
5804
- error TEXT
5805
- );
5806
-
5807
- CREATE TABLE IF NOT EXISTS training_datasets (
5808
- id TEXT PRIMARY KEY,
5809
- source TEXT NOT NULL,
5810
- file_path TEXT NOT NULL,
5811
- example_count INTEGER NOT NULL,
5812
- created_at INTEGER NOT NULL,
5813
- used_in_job_id TEXT REFERENCES training_jobs(id)
5814
- );
5815
- `);
5816
- }
5817
6164
  function getDb(dbPath) {
5818
6165
  const resolvedPath = dbPath ?? DEFAULT_DB_PATH;
5819
6166
  ensureDir(resolvedPath);
5820
6167
  const sqlite = new Database2(resolvedPath);
5821
6168
  sqlite.run("PRAGMA journal_mode = WAL");
5822
6169
  sqlite.run("PRAGMA foreign_keys = ON");
5823
- createTables(sqlite);
5824
- return drizzle(sqlite, { schema: exports_schema });
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;
5825
6213
  }
5826
6214
 
5827
6215
  // node_modules/openai/internal/qs/formats.mjs
@@ -6912,8 +7300,8 @@ function _addRequestID(value, response) {
6912
7300
 
6913
7301
  class APIPromise extends Promise {
6914
7302
  constructor(responsePromise, parseResponse = defaultParseResponse) {
6915
- super((resolve) => {
6916
- resolve(null);
7303
+ super((resolve2) => {
7304
+ resolve2(null);
6917
7305
  });
6918
7306
  this.responsePromise = responsePromise;
6919
7307
  this.parseResponse = parseResponse;
@@ -7424,7 +7812,7 @@ var startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
7424
7812
  var isAbsoluteURL = (url) => {
7425
7813
  return startsWithSchemeRegexp.test(url);
7426
7814
  };
7427
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7815
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
7428
7816
  var validatePositiveInteger = (name, n) => {
7429
7817
  if (typeof n !== "number" || !Number.isInteger(n)) {
7430
7818
  throw new OpenAIError(`${name} must be an integer`);
@@ -7789,12 +8177,12 @@ class EventStream {
7789
8177
  _EventStream_errored.set(this, false);
7790
8178
  _EventStream_aborted.set(this, false);
7791
8179
  _EventStream_catchingPromiseCreated.set(this, false);
7792
- __classPrivateFieldSet3(this, _EventStream_connectedPromise, new Promise((resolve, reject) => {
7793
- __classPrivateFieldSet3(this, _EventStream_resolveConnectedPromise, resolve, "f");
8180
+ __classPrivateFieldSet3(this, _EventStream_connectedPromise, new Promise((resolve2, reject) => {
8181
+ __classPrivateFieldSet3(this, _EventStream_resolveConnectedPromise, resolve2, "f");
7794
8182
  __classPrivateFieldSet3(this, _EventStream_rejectConnectedPromise, reject, "f");
7795
8183
  }), "f");
7796
- __classPrivateFieldSet3(this, _EventStream_endPromise, new Promise((resolve, reject) => {
7797
- __classPrivateFieldSet3(this, _EventStream_resolveEndPromise, resolve, "f");
8184
+ __classPrivateFieldSet3(this, _EventStream_endPromise, new Promise((resolve2, reject) => {
8185
+ __classPrivateFieldSet3(this, _EventStream_resolveEndPromise, resolve2, "f");
7798
8186
  __classPrivateFieldSet3(this, _EventStream_rejectEndPromise, reject, "f");
7799
8187
  }), "f");
7800
8188
  __classPrivateFieldGet3(this, _EventStream_connectedPromise, "f").catch(() => {});
@@ -7846,11 +8234,11 @@ class EventStream {
7846
8234
  return this;
7847
8235
  }
7848
8236
  emitted(event) {
7849
- return new Promise((resolve, reject) => {
8237
+ return new Promise((resolve2, reject) => {
7850
8238
  __classPrivateFieldSet3(this, _EventStream_catchingPromiseCreated, true, "f");
7851
8239
  if (event !== "error")
7852
8240
  this.once("error", reject);
7853
- this.once(event, resolve);
8241
+ this.once(event, resolve2);
7854
8242
  });
7855
8243
  }
7856
8244
  async done() {
@@ -8008,7 +8396,7 @@ class AssistantStream extends EventStream {
8008
8396
  if (done) {
8009
8397
  return { value: undefined, done: true };
8010
8398
  }
8011
- return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
8399
+ return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
8012
8400
  }
8013
8401
  const chunk = pushQueue.shift();
8014
8402
  return { value: chunk, done: false };
@@ -9288,11 +9676,11 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9288
9676
  }
9289
9677
  return this._addChatCompletion(__classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_endRequest).call(this));
9290
9678
  }
9291
- [(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function _ChatCompletionStream_beginRequest2() {
9679
+ [(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function _ChatCompletionStream_beginRequest() {
9292
9680
  if (this.ended)
9293
9681
  return;
9294
9682
  __classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
9295
- }, _ChatCompletionStream_getChoiceEventState = function _ChatCompletionStream_getChoiceEventState2(choice) {
9683
+ }, _ChatCompletionStream_getChoiceEventState = function _ChatCompletionStream_getChoiceEventState(choice) {
9296
9684
  let state = __classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index];
9297
9685
  if (state) {
9298
9686
  return state;
@@ -9307,7 +9695,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9307
9695
  };
9308
9696
  __classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index] = state;
9309
9697
  return state;
9310
- }, _ChatCompletionStream_addChunk = function _ChatCompletionStream_addChunk2(chunk) {
9698
+ }, _ChatCompletionStream_addChunk = function _ChatCompletionStream_addChunk(chunk) {
9311
9699
  if (this.ended)
9312
9700
  return;
9313
9701
  const completion = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_accumulateChatCompletion).call(this, chunk);
@@ -9374,7 +9762,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9374
9762
  }
9375
9763
  }
9376
9764
  }
9377
- }, _ChatCompletionStream_emitToolCallDoneEvent = function _ChatCompletionStream_emitToolCallDoneEvent2(choiceSnapshot, toolCallIndex) {
9765
+ }, _ChatCompletionStream_emitToolCallDoneEvent = function _ChatCompletionStream_emitToolCallDoneEvent(choiceSnapshot, toolCallIndex) {
9378
9766
  const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
9379
9767
  if (state.done_tool_calls.has(toolCallIndex)) {
9380
9768
  return;
@@ -9397,7 +9785,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9397
9785
  } else {
9398
9786
  assertNever2(toolCallSnapshot.type);
9399
9787
  }
9400
- }, _ChatCompletionStream_emitContentDoneEvents = function _ChatCompletionStream_emitContentDoneEvents2(choiceSnapshot) {
9788
+ }, _ChatCompletionStream_emitContentDoneEvents = function _ChatCompletionStream_emitContentDoneEvents(choiceSnapshot) {
9401
9789
  const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
9402
9790
  if (choiceSnapshot.message.content && !state.content_done) {
9403
9791
  state.content_done = true;
@@ -9419,7 +9807,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9419
9807
  state.logprobs_refusal_done = true;
9420
9808
  this._emit("logprobs.refusal.done", { refusal: choiceSnapshot.logprobs.refusal });
9421
9809
  }
9422
- }, _ChatCompletionStream_endRequest = function _ChatCompletionStream_endRequest2() {
9810
+ }, _ChatCompletionStream_endRequest = function _ChatCompletionStream_endRequest() {
9423
9811
  if (this.ended) {
9424
9812
  throw new OpenAIError(`stream has ended, this shouldn't happen`);
9425
9813
  }
@@ -9430,13 +9818,13 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9430
9818
  __classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
9431
9819
  __classPrivateFieldSet5(this, _ChatCompletionStream_choiceEventStates, [], "f");
9432
9820
  return finalizeChatCompletion(snapshot, __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f"));
9433
- }, _ChatCompletionStream_getAutoParseableResponseFormat = function _ChatCompletionStream_getAutoParseableResponseFormat2() {
9821
+ }, _ChatCompletionStream_getAutoParseableResponseFormat = function _ChatCompletionStream_getAutoParseableResponseFormat() {
9434
9822
  const responseFormat = __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f")?.response_format;
9435
9823
  if (isAutoParsableResponseFormat(responseFormat)) {
9436
9824
  return responseFormat;
9437
9825
  }
9438
9826
  return null;
9439
- }, _ChatCompletionStream_accumulateChatCompletion = function _ChatCompletionStream_accumulateChatCompletion2(chunk) {
9827
+ }, _ChatCompletionStream_accumulateChatCompletion = function _ChatCompletionStream_accumulateChatCompletion(chunk) {
9440
9828
  var _a, _b, _c, _d;
9441
9829
  let snapshot = __classPrivateFieldGet6(this, _ChatCompletionStream_currentChatCompletionSnapshot, "f");
9442
9830
  const { choices, ...rest } = chunk;
@@ -9573,7 +9961,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
9573
9961
  if (done) {
9574
9962
  return { value: undefined, done: true };
9575
9963
  }
9576
- return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
9964
+ return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
9577
9965
  }
9578
9966
  const chunk = pushQueue.shift();
9579
9967
  return { value: chunk, done: false };
@@ -10662,11 +11050,11 @@ class ResponseStream extends EventStream {
10662
11050
  }
10663
11051
  return __classPrivateFieldGet7(this, _ResponseStream_instances, "m", _ResponseStream_endRequest).call(this);
10664
11052
  }
10665
- [(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function _ResponseStream_beginRequest2() {
11053
+ [(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function _ResponseStream_beginRequest() {
10666
11054
  if (this.ended)
10667
11055
  return;
10668
11056
  __classPrivateFieldSet6(this, _ResponseStream_currentResponseSnapshot, undefined, "f");
10669
- }, _ResponseStream_addEvent = function _ResponseStream_addEvent2(event, starting_after) {
11057
+ }, _ResponseStream_addEvent = function _ResponseStream_addEvent(event, starting_after) {
10670
11058
  if (this.ended)
10671
11059
  return;
10672
11060
  const maybeEmit = (name, event2) => {
@@ -10714,7 +11102,7 @@ class ResponseStream extends EventStream {
10714
11102
  maybeEmit(event.type, event);
10715
11103
  break;
10716
11104
  }
10717
- }, _ResponseStream_endRequest = function _ResponseStream_endRequest2() {
11105
+ }, _ResponseStream_endRequest = function _ResponseStream_endRequest() {
10718
11106
  if (this.ended) {
10719
11107
  throw new OpenAIError(`stream has ended, this shouldn't happen`);
10720
11108
  }
@@ -10726,7 +11114,7 @@ class ResponseStream extends EventStream {
10726
11114
  const parsedResponse = finalizeResponse(snapshot, __classPrivateFieldGet7(this, _ResponseStream_params, "f"));
10727
11115
  __classPrivateFieldSet6(this, _ResponseStream_finalResponse, parsedResponse, "f");
10728
11116
  return parsedResponse;
10729
- }, _ResponseStream_accumulateResponse = function _ResponseStream_accumulateResponse2(event) {
11117
+ }, _ResponseStream_accumulateResponse = function _ResponseStream_accumulateResponse(event) {
10730
11118
  let snapshot = __classPrivateFieldGet7(this, _ResponseStream_currentResponseSnapshot, "f");
10731
11119
  if (!snapshot) {
10732
11120
  if (event.type !== "response.created") {
@@ -10822,7 +11210,7 @@ class ResponseStream extends EventStream {
10822
11210
  if (done) {
10823
11211
  return { value: undefined, done: true };
10824
11212
  }
10825
- return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: undefined, done: true });
11213
+ return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: undefined, done: true });
10826
11214
  }
10827
11215
  const event = pushQueue.shift();
10828
11216
  return { value: event, done: false };
@@ -11004,863 +11392,612 @@ class Files3 extends APIResource {
11004
11392
  case "failed":
11005
11393
  case "completed":
11006
11394
  return file;
11007
- }
11008
- }
11009
- }
11010
- async upload(vectorStoreId, file, options) {
11011
- const fileInfo = await this._client.files.create({ file, purpose: "assistants" }, options);
11012
- return this.create(vectorStoreId, { file_id: fileInfo.id }, options);
11013
- }
11014
- async uploadAndPoll(vectorStoreId, file, options) {
11015
- const fileInfo = await this.upload(vectorStoreId, file, options);
11016
- return await this.poll(vectorStoreId, fileInfo.id, options);
11017
- }
11018
- content(vectorStoreId, fileId, options) {
11019
- return this._client.getAPIList(`/vector_stores/${vectorStoreId}/files/${fileId}/content`, FileContentResponsesPage, { ...options, headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers } });
11020
- }
11021
- }
11022
-
11023
- class VectorStoreFilesPage extends CursorPage {
11024
- }
11025
-
11026
- class FileContentResponsesPage extends Page {
11027
- }
11028
- Files3.VectorStoreFilesPage = VectorStoreFilesPage;
11029
- Files3.FileContentResponsesPage = FileContentResponsesPage;
11030
-
11031
- // node_modules/openai/resources/vector-stores/file-batches.mjs
11032
- class FileBatches extends APIResource {
11033
- create(vectorStoreId, body, options) {
11034
- return this._client.post(`/vector_stores/${vectorStoreId}/file_batches`, {
11035
- body,
11036
- ...options,
11037
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11038
- });
11039
- }
11040
- retrieve(vectorStoreId, batchId, options) {
11041
- return this._client.get(`/vector_stores/${vectorStoreId}/file_batches/${batchId}`, {
11042
- ...options,
11043
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11044
- });
11045
- }
11046
- cancel(vectorStoreId, batchId, options) {
11047
- return this._client.post(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/cancel`, {
11048
- ...options,
11049
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11050
- });
11051
- }
11052
- async createAndPoll(vectorStoreId, body, options) {
11053
- const batch = await this.create(vectorStoreId, body);
11054
- return await this.poll(vectorStoreId, batch.id, options);
11055
- }
11056
- listFiles(vectorStoreId, batchId, query = {}, options) {
11057
- if (isRequestOptions(query)) {
11058
- return this.listFiles(vectorStoreId, batchId, {}, query);
11059
- }
11060
- return this._client.getAPIList(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/files`, VectorStoreFilesPage, { query, ...options, headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers } });
11061
- }
11062
- async poll(vectorStoreId, batchId, options) {
11063
- const headers = { ...options?.headers, "X-Stainless-Poll-Helper": "true" };
11064
- if (options?.pollIntervalMs) {
11065
- headers["X-Stainless-Custom-Poll-Interval"] = options.pollIntervalMs.toString();
11066
- }
11067
- while (true) {
11068
- const { data: batch, response } = await this.retrieve(vectorStoreId, batchId, {
11069
- ...options,
11070
- headers
11071
- }).withResponse();
11072
- switch (batch.status) {
11073
- case "in_progress":
11074
- let sleepInterval = 5000;
11075
- if (options?.pollIntervalMs) {
11076
- sleepInterval = options.pollIntervalMs;
11077
- } else {
11078
- const headerInterval = response.headers.get("openai-poll-after-ms");
11079
- if (headerInterval) {
11080
- const headerIntervalMs = parseInt(headerInterval);
11081
- if (!isNaN(headerIntervalMs)) {
11082
- sleepInterval = headerIntervalMs;
11083
- }
11084
- }
11085
- }
11086
- await sleep(sleepInterval);
11087
- break;
11088
- case "failed":
11089
- case "cancelled":
11090
- case "completed":
11091
- return batch;
11092
- }
11093
- }
11094
- }
11095
- async uploadAndPoll(vectorStoreId, { files, fileIds = [] }, options) {
11096
- if (files == null || files.length == 0) {
11097
- throw new Error(`No \`files\` provided to process. If you've already uploaded files you should use \`.createAndPoll()\` instead`);
11098
- }
11099
- const configuredConcurrency = options?.maxConcurrency ?? 5;
11100
- const concurrencyLimit = Math.min(configuredConcurrency, files.length);
11101
- const client = this._client;
11102
- const fileIterator = files.values();
11103
- const allFileIds = [...fileIds];
11104
- async function processFiles(iterator) {
11105
- for (let item of iterator) {
11106
- const fileObj = await client.files.create({ file: item, purpose: "assistants" }, options);
11107
- allFileIds.push(fileObj.id);
11108
- }
11109
- }
11110
- const workers = Array(concurrencyLimit).fill(fileIterator).map(processFiles);
11111
- await allSettledWithThrow(workers);
11112
- return await this.createAndPoll(vectorStoreId, {
11113
- file_ids: allFileIds
11114
- });
11115
- }
11116
- }
11117
-
11118
- // node_modules/openai/resources/vector-stores/vector-stores.mjs
11119
- class VectorStores extends APIResource {
11120
- constructor() {
11121
- super(...arguments);
11122
- this.files = new Files3(this._client);
11123
- this.fileBatches = new FileBatches(this._client);
11124
- }
11125
- create(body, options) {
11126
- return this._client.post("/vector_stores", {
11127
- body,
11128
- ...options,
11129
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11130
- });
11131
- }
11132
- retrieve(vectorStoreId, options) {
11133
- return this._client.get(`/vector_stores/${vectorStoreId}`, {
11134
- ...options,
11135
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11136
- });
11137
- }
11138
- update(vectorStoreId, body, options) {
11139
- return this._client.post(`/vector_stores/${vectorStoreId}`, {
11140
- body,
11141
- ...options,
11142
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11143
- });
11144
- }
11145
- list(query = {}, options) {
11146
- if (isRequestOptions(query)) {
11147
- return this.list({}, query);
11148
- }
11149
- return this._client.getAPIList("/vector_stores", VectorStoresPage, {
11150
- query,
11151
- ...options,
11152
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11153
- });
11154
- }
11155
- del(vectorStoreId, options) {
11156
- return this._client.delete(`/vector_stores/${vectorStoreId}`, {
11157
- ...options,
11158
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11159
- });
11160
- }
11161
- search(vectorStoreId, body, options) {
11162
- return this._client.getAPIList(`/vector_stores/${vectorStoreId}/search`, VectorStoreSearchResponsesPage, {
11163
- body,
11164
- method: "post",
11165
- ...options,
11166
- headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11167
- });
11168
- }
11169
- }
11170
-
11171
- class VectorStoresPage extends CursorPage {
11172
- }
11173
-
11174
- class VectorStoreSearchResponsesPage extends Page {
11175
- }
11176
- VectorStores.VectorStoresPage = VectorStoresPage;
11177
- VectorStores.VectorStoreSearchResponsesPage = VectorStoreSearchResponsesPage;
11178
- VectorStores.Files = Files3;
11179
- VectorStores.VectorStoreFilesPage = VectorStoreFilesPage;
11180
- VectorStores.FileContentResponsesPage = FileContentResponsesPage;
11181
- VectorStores.FileBatches = FileBatches;
11182
- // node_modules/openai/index.mjs
11183
- var _a;
11184
-
11185
- class OpenAI extends APIClient {
11186
- constructor({ baseURL = readEnv("OPENAI_BASE_URL"), apiKey = readEnv("OPENAI_API_KEY"), organization = readEnv("OPENAI_ORG_ID") ?? null, project = readEnv("OPENAI_PROJECT_ID") ?? null, ...opts } = {}) {
11187
- if (apiKey === undefined) {
11188
- throw new OpenAIError("The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'My API Key' }).");
11189
- }
11190
- const options = {
11191
- apiKey,
11192
- organization,
11193
- project,
11194
- ...opts,
11195
- baseURL: baseURL || `https://api.openai.com/v1`
11196
- };
11197
- if (!options.dangerouslyAllowBrowser && isRunningInBrowser()) {
11198
- throw new OpenAIError(`It looks like you're running in a browser-like environment.
11199
-
11200
- This is disabled by default, as it risks exposing your secret API credentials to attackers.
11201
- If you understand the risks and have appropriate mitigations in place,
11202
- you can set the \`dangerouslyAllowBrowser\` option to \`true\`, e.g.,
11203
-
11204
- new OpenAI({ apiKey, dangerouslyAllowBrowser: true });
11205
-
11206
- https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
11207
- `);
11208
- }
11209
- super({
11210
- baseURL: options.baseURL,
11211
- timeout: options.timeout ?? 600000,
11212
- httpAgent: options.httpAgent,
11213
- maxRetries: options.maxRetries,
11214
- fetch: options.fetch
11215
- });
11216
- this.completions = new Completions3(this);
11217
- this.chat = new Chat(this);
11218
- this.embeddings = new Embeddings(this);
11219
- this.files = new Files2(this);
11220
- this.images = new Images(this);
11221
- this.audio = new Audio(this);
11222
- this.moderations = new Moderations(this);
11223
- this.models = new Models(this);
11224
- this.fineTuning = new FineTuning(this);
11225
- this.graders = new Graders2(this);
11226
- this.vectorStores = new VectorStores(this);
11227
- this.beta = new Beta(this);
11228
- this.batches = new Batches(this);
11229
- this.uploads = new Uploads(this);
11230
- this.responses = new Responses(this);
11231
- this.evals = new Evals(this);
11232
- this.containers = new Containers(this);
11233
- this._options = options;
11234
- this.apiKey = apiKey;
11235
- this.organization = organization;
11236
- this.project = project;
11237
- }
11238
- defaultQuery() {
11239
- return this._options.defaultQuery;
11395
+ }
11396
+ }
11240
11397
  }
11241
- defaultHeaders(opts) {
11242
- return {
11243
- ...super.defaultHeaders(opts),
11244
- "OpenAI-Organization": this.organization,
11245
- "OpenAI-Project": this.project,
11246
- ...this._options.defaultHeaders
11247
- };
11398
+ async upload(vectorStoreId, file, options) {
11399
+ const fileInfo = await this._client.files.create({ file, purpose: "assistants" }, options);
11400
+ return this.create(vectorStoreId, { file_id: fileInfo.id }, options);
11248
11401
  }
11249
- authHeaders(opts) {
11250
- return { Authorization: `Bearer ${this.apiKey}` };
11402
+ async uploadAndPoll(vectorStoreId, file, options) {
11403
+ const fileInfo = await this.upload(vectorStoreId, file, options);
11404
+ return await this.poll(vectorStoreId, fileInfo.id, options);
11251
11405
  }
11252
- stringifyQuery(query) {
11253
- return stringify(query, { arrayFormat: "brackets" });
11406
+ content(vectorStoreId, fileId, options) {
11407
+ return this._client.getAPIList(`/vector_stores/${vectorStoreId}/files/${fileId}/content`, FileContentResponsesPage, { ...options, headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers } });
11254
11408
  }
11255
11409
  }
11256
- _a = OpenAI;
11257
- OpenAI.OpenAI = _a;
11258
- OpenAI.DEFAULT_TIMEOUT = 600000;
11259
- OpenAI.OpenAIError = OpenAIError;
11260
- OpenAI.APIError = APIError;
11261
- OpenAI.APIConnectionError = APIConnectionError;
11262
- OpenAI.APIConnectionTimeoutError = APIConnectionTimeoutError;
11263
- OpenAI.APIUserAbortError = APIUserAbortError;
11264
- OpenAI.NotFoundError = NotFoundError;
11265
- OpenAI.ConflictError = ConflictError;
11266
- OpenAI.RateLimitError = RateLimitError;
11267
- OpenAI.BadRequestError = BadRequestError;
11268
- OpenAI.AuthenticationError = AuthenticationError;
11269
- OpenAI.InternalServerError = InternalServerError;
11270
- OpenAI.PermissionDeniedError = PermissionDeniedError;
11271
- OpenAI.UnprocessableEntityError = UnprocessableEntityError;
11272
- OpenAI.toFile = toFile;
11273
- OpenAI.fileFromPath = fileFromPath;
11274
- OpenAI.Completions = Completions3;
11275
- OpenAI.Chat = Chat;
11276
- OpenAI.ChatCompletionsPage = ChatCompletionsPage;
11277
- OpenAI.Embeddings = Embeddings;
11278
- OpenAI.Files = Files2;
11279
- OpenAI.FileObjectsPage = FileObjectsPage;
11280
- OpenAI.Images = Images;
11281
- OpenAI.Audio = Audio;
11282
- OpenAI.Moderations = Moderations;
11283
- OpenAI.Models = Models;
11284
- OpenAI.ModelsPage = ModelsPage;
11285
- OpenAI.FineTuning = FineTuning;
11286
- OpenAI.Graders = Graders2;
11287
- OpenAI.VectorStores = VectorStores;
11288
- OpenAI.VectorStoresPage = VectorStoresPage;
11289
- OpenAI.VectorStoreSearchResponsesPage = VectorStoreSearchResponsesPage;
11290
- OpenAI.Beta = Beta;
11291
- OpenAI.Batches = Batches;
11292
- OpenAI.BatchesPage = BatchesPage;
11293
- OpenAI.Uploads = Uploads;
11294
- OpenAI.Responses = Responses;
11295
- OpenAI.Evals = Evals;
11296
- OpenAI.EvalListResponsesPage = EvalListResponsesPage;
11297
- OpenAI.Containers = Containers;
11298
- OpenAI.ContainerListResponsesPage = ContainerListResponsesPage;
11299
- var _deployments_endpoints = new Set([
11300
- "/completions",
11301
- "/chat/completions",
11302
- "/embeddings",
11303
- "/audio/transcriptions",
11304
- "/audio/translations",
11305
- "/audio/speech",
11306
- "/images/generations",
11307
- "/images/edits"
11308
- ]);
11309
- var openai_default = OpenAI;
11310
11410
 
11311
- // src/lib/providers/openai.ts
11312
- import { readFileSync } from "fs";
11313
- function getClient() {
11314
- const apiKey = process.env.OPENAI_API_KEY;
11315
- if (!apiKey) {
11316
- throw new Error("OPENAI_API_KEY environment variable is required");
11317
- }
11318
- return new openai_default({ apiKey });
11319
- }
11320
- async function uploadTrainingFile(filePath) {
11321
- const client = getClient();
11322
- const fileContent = readFileSync(filePath);
11323
- const blob2 = new Blob([fileContent], { type: "application/jsonl" });
11324
- const file = new File([blob2], filePath.split("/").pop() ?? "training.jsonl", { type: "application/jsonl" });
11325
- const response = await client.files.create({
11326
- file,
11327
- purpose: "fine-tune"
11328
- });
11329
- return { fileId: response.id };
11330
- }
11331
- async function createFineTuneJob(fileId, baseModel, suffix) {
11332
- const client = getClient();
11333
- const params = {
11334
- training_file: fileId,
11335
- model: baseModel
11336
- };
11337
- if (suffix) {
11338
- params.suffix = suffix;
11339
- }
11340
- const response = await client.fineTuning.jobs.create(params);
11341
- return { jobId: response.id, status: response.status };
11342
- }
11343
- async function getFineTuneStatus(jobId) {
11344
- const client = getClient();
11345
- const response = await client.fineTuning.jobs.retrieve(jobId);
11346
- return {
11347
- jobId: response.id,
11348
- status: response.status,
11349
- fineTunedModel: response.fine_tuned_model ?? undefined,
11350
- error: response.error?.message ?? undefined
11351
- };
11352
- }
11353
- async function listFineTunedModels() {
11354
- const client = getClient();
11355
- const jobs = await client.fineTuning.jobs.list();
11356
- return jobs.data.map((job) => ({
11357
- id: job.id,
11358
- model: job.fine_tuned_model ?? job.model,
11359
- status: job.status,
11360
- created: job.created_at
11361
- }));
11411
+ class VectorStoreFilesPage extends CursorPage {
11362
11412
  }
11363
11413
 
11364
- // src/lib/providers/thinker-labs.ts
11365
- var DEFAULT_BASE_URL = "https://api.thinkerlabs.ai/v1";
11366
- function getConfig() {
11367
- const apiKey = process.env.THINKER_LABS_API_KEY;
11368
- const baseUrl = process.env.THINKER_LABS_BASE_URL ?? DEFAULT_BASE_URL;
11369
- if (!apiKey)
11370
- throw new Error("THINKER_LABS_API_KEY environment variable is required");
11371
- return { apiKey, baseUrl };
11372
- }
11373
- async function request(method, path, body, file) {
11374
- const { apiKey, baseUrl } = getConfig();
11375
- const headers = { Authorization: `Bearer ${apiKey}` };
11376
- let fetchBody;
11377
- if (file) {
11378
- const form = new FormData;
11379
- form.append("file", new Blob([file.data], { type: "text/plain" }), file.name);
11380
- if (body) {
11381
- for (const [k, v] of Object.entries(body)) {
11382
- form.append(k, v);
11383
- }
11384
- }
11385
- fetchBody = form;
11386
- } else if (body) {
11387
- headers["Content-Type"] = "application/json";
11388
- fetchBody = JSON.stringify(body);
11389
- }
11390
- const res = await fetch(`${baseUrl}${path}`, { method, headers, body: fetchBody });
11391
- if (!res.ok) {
11392
- const text2 = await res.text();
11393
- throw new Error(`Thinker Labs API error ${res.status}: ${text2}`);
11394
- }
11395
- if (res.status === 204)
11396
- return;
11397
- return res.json();
11398
- }
11399
- async function uploadTrainingData(filePath) {
11400
- const fileContent = await Bun.file(filePath).text();
11401
- const fileName = filePath.split("/").pop() ?? "training.jsonl";
11402
- const result = await request("POST", "/datasets/upload", undefined, { name: fileName, data: fileContent });
11403
- return { datasetId: result.id };
11404
- }
11405
- async function startFineTune(datasetId, baseModel, name) {
11406
- const result = await request("POST", "/fine-tunes", {
11407
- dataset_id: datasetId,
11408
- base_model: baseModel,
11409
- ...name ? { name } : {}
11410
- });
11411
- return { jobId: result.id, status: result.status };
11412
- }
11413
- async function getStatus(jobId) {
11414
- const result = await request("GET", `/fine-tunes/${jobId}`);
11415
- return {
11416
- jobId: result.id,
11417
- status: result.status,
11418
- modelId: result.model_id,
11419
- error: result.error
11420
- };
11421
- }
11422
- async function listModels() {
11423
- const result = await request("GET", "/fine-tunes");
11424
- return (result.data ?? []).map((m) => ({
11425
- id: m.id,
11426
- name: m.name,
11427
- status: m.status,
11428
- baseModel: m.base_model,
11429
- createdAt: m.created_at
11430
- }));
11414
+ class FileContentResponsesPage extends Page {
11431
11415
  }
11432
- async function cancelJob(jobId) {
11433
- await request("DELETE", `/fine-tunes/${jobId}`);
11416
+ Files3.VectorStoreFilesPage = VectorStoreFilesPage;
11417
+ Files3.FileContentResponsesPage = FileContentResponsesPage;
11418
+
11419
+ // node_modules/openai/resources/vector-stores/file-batches.mjs
11420
+ class FileBatches extends APIResource {
11421
+ create(vectorStoreId, body, options) {
11422
+ return this._client.post(`/vector_stores/${vectorStoreId}/file_batches`, {
11423
+ body,
11424
+ ...options,
11425
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11426
+ });
11427
+ }
11428
+ retrieve(vectorStoreId, batchId, options) {
11429
+ return this._client.get(`/vector_stores/${vectorStoreId}/file_batches/${batchId}`, {
11430
+ ...options,
11431
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11432
+ });
11433
+ }
11434
+ cancel(vectorStoreId, batchId, options) {
11435
+ return this._client.post(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/cancel`, {
11436
+ ...options,
11437
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11438
+ });
11439
+ }
11440
+ async createAndPoll(vectorStoreId, body, options) {
11441
+ const batch = await this.create(vectorStoreId, body);
11442
+ return await this.poll(vectorStoreId, batch.id, options);
11443
+ }
11444
+ listFiles(vectorStoreId, batchId, query = {}, options) {
11445
+ if (isRequestOptions(query)) {
11446
+ return this.listFiles(vectorStoreId, batchId, {}, query);
11447
+ }
11448
+ return this._client.getAPIList(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/files`, VectorStoreFilesPage, { query, ...options, headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers } });
11449
+ }
11450
+ async poll(vectorStoreId, batchId, options) {
11451
+ const headers = { ...options?.headers, "X-Stainless-Poll-Helper": "true" };
11452
+ if (options?.pollIntervalMs) {
11453
+ headers["X-Stainless-Custom-Poll-Interval"] = options.pollIntervalMs.toString();
11454
+ }
11455
+ while (true) {
11456
+ const { data: batch, response } = await this.retrieve(vectorStoreId, batchId, {
11457
+ ...options,
11458
+ headers
11459
+ }).withResponse();
11460
+ switch (batch.status) {
11461
+ case "in_progress":
11462
+ let sleepInterval = 5000;
11463
+ if (options?.pollIntervalMs) {
11464
+ sleepInterval = options.pollIntervalMs;
11465
+ } else {
11466
+ const headerInterval = response.headers.get("openai-poll-after-ms");
11467
+ if (headerInterval) {
11468
+ const headerIntervalMs = parseInt(headerInterval);
11469
+ if (!isNaN(headerIntervalMs)) {
11470
+ sleepInterval = headerIntervalMs;
11471
+ }
11472
+ }
11473
+ }
11474
+ await sleep(sleepInterval);
11475
+ break;
11476
+ case "failed":
11477
+ case "cancelled":
11478
+ case "completed":
11479
+ return batch;
11480
+ }
11481
+ }
11482
+ }
11483
+ async uploadAndPoll(vectorStoreId, { files, fileIds = [] }, options) {
11484
+ if (files == null || files.length == 0) {
11485
+ throw new Error(`No \`files\` provided to process. If you've already uploaded files you should use \`.createAndPoll()\` instead`);
11486
+ }
11487
+ const configuredConcurrency = options?.maxConcurrency ?? 5;
11488
+ const concurrencyLimit = Math.min(configuredConcurrency, files.length);
11489
+ const client = this._client;
11490
+ const fileIterator = files.values();
11491
+ const allFileIds = [...fileIds];
11492
+ async function processFiles(iterator) {
11493
+ for (let item of iterator) {
11494
+ const fileObj = await client.files.create({ file: item, purpose: "assistants" }, options);
11495
+ allFileIds.push(fileObj.id);
11496
+ }
11497
+ }
11498
+ const workers = Array(concurrencyLimit).fill(fileIterator).map(processFiles);
11499
+ await allSettledWithThrow(workers);
11500
+ return await this.createAndPoll(vectorStoreId, {
11501
+ file_ids: allFileIds
11502
+ });
11503
+ }
11434
11504
  }
11435
11505
 
11436
- class ThinkerLabsProvider {
11437
- uploadTrainingData = uploadTrainingData;
11438
- startFineTune = startFineTune;
11439
- getStatus = getStatus;
11440
- listModels = listModels;
11441
- cancelJob = cancelJob;
11442
- async uploadTrainingFile(filePath) {
11443
- const { datasetId } = await uploadTrainingData(filePath);
11444
- return { fileId: datasetId };
11506
+ // node_modules/openai/resources/vector-stores/vector-stores.mjs
11507
+ class VectorStores extends APIResource {
11508
+ constructor() {
11509
+ super(...arguments);
11510
+ this.files = new Files3(this._client);
11511
+ this.fileBatches = new FileBatches(this._client);
11445
11512
  }
11446
- async createFineTuneJob(fileId, baseModel, suffix) {
11447
- return startFineTune(fileId, baseModel, suffix);
11513
+ create(body, options) {
11514
+ return this._client.post("/vector_stores", {
11515
+ body,
11516
+ ...options,
11517
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11518
+ });
11448
11519
  }
11449
- async getFineTuneStatus(jobId) {
11450
- const result = await getStatus(jobId);
11451
- return { jobId: result.jobId, status: result.status, fineTunedModel: result.modelId, error: result.error };
11520
+ retrieve(vectorStoreId, options) {
11521
+ return this._client.get(`/vector_stores/${vectorStoreId}`, {
11522
+ ...options,
11523
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11524
+ });
11452
11525
  }
11453
- async listFineTunedModels() {
11454
- const models = await listModels();
11455
- return models.map((m) => ({ id: m.id, model: m.baseModel, status: m.status, created: m.createdAt }));
11526
+ update(vectorStoreId, body, options) {
11527
+ return this._client.post(`/vector_stores/${vectorStoreId}`, {
11528
+ body,
11529
+ ...options,
11530
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11531
+ });
11456
11532
  }
11457
- async cancelFineTuneJob(jobId) {
11458
- return cancelJob(jobId);
11533
+ list(query = {}, options) {
11534
+ if (isRequestOptions(query)) {
11535
+ return this.list({}, query);
11536
+ }
11537
+ return this._client.getAPIList("/vector_stores", VectorStoresPage, {
11538
+ query,
11539
+ ...options,
11540
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11541
+ });
11459
11542
  }
11460
- }
11461
-
11462
- // src/cli/ui.ts
11463
- import chalk from "chalk";
11464
- function printTable(headers, rows) {
11465
- if (rows.length === 0) {
11466
- console.log(chalk.dim(" (no records)"));
11467
- return;
11543
+ del(vectorStoreId, options) {
11544
+ return this._client.delete(`/vector_stores/${vectorStoreId}`, {
11545
+ ...options,
11546
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11547
+ });
11468
11548
  }
11469
- const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
11470
- const separator = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
11471
- const headerLine = headers.map((h, i) => ` ${chalk.bold(h.padEnd(colWidths[i] ?? 0))} `).join("\u2502");
11472
- const topBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C");
11473
- const midBorder = separator;
11474
- const bottomBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534");
11475
- console.log("\u250C" + topBorder + "\u2510");
11476
- console.log("\u2502" + headerLine + "\u2502");
11477
- console.log("\u251C" + midBorder + "\u2524");
11478
- for (const row of rows) {
11479
- const rowLine = row.map((cell, i) => ` ${(cell ?? "").padEnd(colWidths[i] ?? 0)} `).join("\u2502");
11480
- console.log("\u2502" + rowLine + "\u2502");
11549
+ search(vectorStoreId, body, options) {
11550
+ return this._client.getAPIList(`/vector_stores/${vectorStoreId}/search`, VectorStoreSearchResponsesPage, {
11551
+ body,
11552
+ method: "post",
11553
+ ...options,
11554
+ headers: { "OpenAI-Beta": "assistants=v2", ...options?.headers }
11555
+ });
11481
11556
  }
11482
- console.log("\u2514" + bottomBorder + "\u2518");
11483
- }
11484
- var STATUS_COLORS = {
11485
- succeeded: chalk.green,
11486
- running: chalk.cyan,
11487
- pending: chalk.yellow,
11488
- failed: chalk.red,
11489
- cancelled: chalk.dim,
11490
- queued: chalk.yellow,
11491
- validating_files: chalk.blue
11492
- };
11493
- function printStatus(status) {
11494
- const colorFn = STATUS_COLORS[status] ?? chalk.white;
11495
- return colorFn(`\u25CF ${status}`);
11496
- }
11497
- function printJson(obj) {
11498
- console.log(JSON.stringify(obj, null, 2));
11499
- }
11500
- function printError(message) {
11501
- console.error(chalk.red("\u2717 Error: ") + message);
11502
- }
11503
- function printSuccess(message) {
11504
- console.log(chalk.green("\u2713 ") + message);
11505
- }
11506
- function printInfo(message) {
11507
- console.log(chalk.dim(" " + message));
11508
11557
  }
11509
11558
 
11510
- // src/lib/gatherers/todos.ts
11511
- import { Database as Database3 } from "bun:sqlite";
11512
- import { homedir as homedir2 } from "os";
11513
- import { join as join2 } from "path";
11514
- var SYSTEM_PROMPT = "You are a task management assistant that helps users create, update, search, and manage tasks and projects.";
11515
- function taskToCreateExample(task) {
11516
- const userMsg = `Create a task: ${task.title}${task.description ? `
11517
-
11518
- Description: ${task.description}` : ""}`;
11519
- const taskDetails = {
11520
- id: task.short_id ?? task.id,
11521
- title: task.title,
11522
- description: task.description ?? "",
11523
- status: task.status,
11524
- priority: task.priority,
11525
- tags: JSON.parse(task.tags ?? "[]"),
11526
- created_at: task.created_at
11527
- };
11528
- return {
11529
- messages: [
11530
- { role: "system", content: SYSTEM_PROMPT },
11531
- { role: "user", content: userMsg },
11532
- { role: "assistant", content: `Created task: ${JSON.stringify(taskDetails, null, 2)}` }
11533
- ]
11534
- };
11535
- }
11536
- function taskToStatusUpdateExample(task) {
11537
- if (!task.completed_at && task.status === "pending")
11538
- return null;
11539
- const id = task.short_id ?? task.id;
11540
- return {
11541
- messages: [
11542
- { role: "system", content: SYSTEM_PROMPT },
11543
- { role: "user", content: `Mark task ${id} as ${task.status}` },
11544
- { role: "assistant", content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim() }
11545
- ]
11546
- };
11559
+ class VectorStoresPage extends CursorPage {
11547
11560
  }
11548
- function taskToSearchExample(tasks, query) {
11549
- const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
11550
- return {
11551
- messages: [
11552
- { role: "system", content: SYSTEM_PROMPT },
11553
- { role: "user", content: `Search tasks for: "${query}"` },
11554
- {
11555
- role: "assistant",
11556
- content: matched.length > 0 ? `Found ${matched.length} task(s):
11557
- ${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
11558
- `)}` : `No tasks found matching "${query}".`
11559
- }
11560
- ]
11561
- };
11561
+
11562
+ class VectorStoreSearchResponsesPage extends Page {
11562
11563
  }
11563
- async function gatherFromTodos(options = {}) {
11564
- const dbPath = join2(homedir2(), ".todos", "todos.db");
11565
- const db = new Database3(dbPath, { readonly: true, create: false });
11566
- try {
11567
- let query = "SELECT * FROM tasks WHERE 1=1";
11568
- const params = [];
11569
- if (options.since) {
11570
- query += " AND created_at >= ?";
11571
- params.push(options.since.toISOString());
11572
- }
11573
- query += " ORDER BY created_at DESC";
11574
- if (options.limit) {
11575
- query += " LIMIT ?";
11576
- params.push(options.limit * 2);
11577
- }
11578
- const tasks = db.query(query).all(...params);
11579
- const examples = [];
11580
- for (const task of tasks) {
11581
- examples.push(taskToCreateExample(task));
11582
- const statusEx = taskToStatusUpdateExample(task);
11583
- if (statusEx)
11584
- examples.push(statusEx);
11564
+ VectorStores.VectorStoresPage = VectorStoresPage;
11565
+ VectorStores.VectorStoreSearchResponsesPage = VectorStoreSearchResponsesPage;
11566
+ VectorStores.Files = Files3;
11567
+ VectorStores.VectorStoreFilesPage = VectorStoreFilesPage;
11568
+ VectorStores.FileContentResponsesPage = FileContentResponsesPage;
11569
+ VectorStores.FileBatches = FileBatches;
11570
+ // node_modules/openai/index.mjs
11571
+ var _a;
11572
+
11573
+ class OpenAI extends APIClient {
11574
+ constructor({ baseURL = readEnv("OPENAI_BASE_URL"), apiKey = readEnv("OPENAI_API_KEY"), organization = readEnv("OPENAI_ORG_ID") ?? null, project = readEnv("OPENAI_PROJECT_ID") ?? null, ...opts } = {}) {
11575
+ if (apiKey === undefined) {
11576
+ throw new OpenAIError("The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'My API Key' }).");
11585
11577
  }
11586
- const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
11587
- for (const term of searchTerms) {
11588
- examples.push(taskToSearchExample(tasks, term));
11578
+ const options = {
11579
+ apiKey,
11580
+ organization,
11581
+ project,
11582
+ ...opts,
11583
+ baseURL: baseURL || `https://api.openai.com/v1`
11584
+ };
11585
+ if (!options.dangerouslyAllowBrowser && isRunningInBrowser()) {
11586
+ throw new OpenAIError(`It looks like you're running in a browser-like environment.
11587
+
11588
+ This is disabled by default, as it risks exposing your secret API credentials to attackers.
11589
+ If you understand the risks and have appropriate mitigations in place,
11590
+ you can set the \`dangerouslyAllowBrowser\` option to \`true\`, e.g.,
11591
+
11592
+ new OpenAI({ apiKey, dangerouslyAllowBrowser: true });
11593
+
11594
+ https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
11595
+ `);
11589
11596
  }
11590
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11597
+ super({
11598
+ baseURL: options.baseURL,
11599
+ timeout: options.timeout ?? 600000,
11600
+ httpAgent: options.httpAgent,
11601
+ maxRetries: options.maxRetries,
11602
+ fetch: options.fetch
11603
+ });
11604
+ this.completions = new Completions3(this);
11605
+ this.chat = new Chat(this);
11606
+ this.embeddings = new Embeddings(this);
11607
+ this.files = new Files2(this);
11608
+ this.images = new Images(this);
11609
+ this.audio = new Audio(this);
11610
+ this.moderations = new Moderations(this);
11611
+ this.models = new Models(this);
11612
+ this.fineTuning = new FineTuning(this);
11613
+ this.graders = new Graders2(this);
11614
+ this.vectorStores = new VectorStores(this);
11615
+ this.beta = new Beta(this);
11616
+ this.batches = new Batches(this);
11617
+ this.uploads = new Uploads(this);
11618
+ this.responses = new Responses(this);
11619
+ this.evals = new Evals(this);
11620
+ this.containers = new Containers(this);
11621
+ this._options = options;
11622
+ this.apiKey = apiKey;
11623
+ this.organization = organization;
11624
+ this.project = project;
11625
+ }
11626
+ defaultQuery() {
11627
+ return this._options.defaultQuery;
11628
+ }
11629
+ defaultHeaders(opts) {
11591
11630
  return {
11592
- source: "todos",
11593
- examples: finalExamples,
11594
- count: finalExamples.length
11631
+ ...super.defaultHeaders(opts),
11632
+ "OpenAI-Organization": this.organization,
11633
+ "OpenAI-Project": this.project,
11634
+ ...this._options.defaultHeaders
11595
11635
  };
11596
- } finally {
11597
- db.close();
11636
+ }
11637
+ authHeaders(opts) {
11638
+ return { Authorization: `Bearer ${this.apiKey}` };
11639
+ }
11640
+ stringifyQuery(query) {
11641
+ return stringify(query, { arrayFormat: "brackets" });
11598
11642
  }
11599
11643
  }
11644
+ _a = OpenAI;
11645
+ OpenAI.OpenAI = _a;
11646
+ OpenAI.DEFAULT_TIMEOUT = 600000;
11647
+ OpenAI.OpenAIError = OpenAIError;
11648
+ OpenAI.APIError = APIError;
11649
+ OpenAI.APIConnectionError = APIConnectionError;
11650
+ OpenAI.APIConnectionTimeoutError = APIConnectionTimeoutError;
11651
+ OpenAI.APIUserAbortError = APIUserAbortError;
11652
+ OpenAI.NotFoundError = NotFoundError;
11653
+ OpenAI.ConflictError = ConflictError;
11654
+ OpenAI.RateLimitError = RateLimitError;
11655
+ OpenAI.BadRequestError = BadRequestError;
11656
+ OpenAI.AuthenticationError = AuthenticationError;
11657
+ OpenAI.InternalServerError = InternalServerError;
11658
+ OpenAI.PermissionDeniedError = PermissionDeniedError;
11659
+ OpenAI.UnprocessableEntityError = UnprocessableEntityError;
11660
+ OpenAI.toFile = toFile;
11661
+ OpenAI.fileFromPath = fileFromPath;
11662
+ OpenAI.Completions = Completions3;
11663
+ OpenAI.Chat = Chat;
11664
+ OpenAI.ChatCompletionsPage = ChatCompletionsPage;
11665
+ OpenAI.Embeddings = Embeddings;
11666
+ OpenAI.Files = Files2;
11667
+ OpenAI.FileObjectsPage = FileObjectsPage;
11668
+ OpenAI.Images = Images;
11669
+ OpenAI.Audio = Audio;
11670
+ OpenAI.Moderations = Moderations;
11671
+ OpenAI.Models = Models;
11672
+ OpenAI.ModelsPage = ModelsPage;
11673
+ OpenAI.FineTuning = FineTuning;
11674
+ OpenAI.Graders = Graders2;
11675
+ OpenAI.VectorStores = VectorStores;
11676
+ OpenAI.VectorStoresPage = VectorStoresPage;
11677
+ OpenAI.VectorStoreSearchResponsesPage = VectorStoreSearchResponsesPage;
11678
+ OpenAI.Beta = Beta;
11679
+ OpenAI.Batches = Batches;
11680
+ OpenAI.BatchesPage = BatchesPage;
11681
+ OpenAI.Uploads = Uploads;
11682
+ OpenAI.Responses = Responses;
11683
+ OpenAI.Evals = Evals;
11684
+ OpenAI.EvalListResponsesPage = EvalListResponsesPage;
11685
+ OpenAI.Containers = Containers;
11686
+ OpenAI.ContainerListResponsesPage = ContainerListResponsesPage;
11687
+ var _deployments_endpoints = new Set([
11688
+ "/completions",
11689
+ "/chat/completions",
11690
+ "/embeddings",
11691
+ "/audio/transcriptions",
11692
+ "/audio/translations",
11693
+ "/audio/speech",
11694
+ "/images/generations",
11695
+ "/images/edits"
11696
+ ]);
11697
+ var openai_default = OpenAI;
11600
11698
 
11601
- // src/lib/gatherers/mementos.ts
11602
- import { Database as Database4 } from "bun:sqlite";
11603
- import { homedir as homedir3 } from "os";
11604
- import { join as join3 } from "path";
11605
- 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.";
11606
- function memoryToRecallExample(memory) {
11607
- return {
11608
- messages: [
11609
- { role: "system", content: SYSTEM_PROMPT2 },
11610
- { role: "user", content: `What do you remember about "${memory.key}"?` },
11611
- {
11612
- role: "assistant",
11613
- content: memory.summary ? `${memory.value}
11699
+ // src/lib/providers/openai.ts
11700
+ import { readFileSync as readFileSync2 } from "fs";
11614
11701
 
11615
- Summary: ${memory.summary}` : memory.value
11616
- }
11617
- ]
11618
- };
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 {};
11715
+ }
11619
11716
  }
11620
- function memoryToSaveExample(memory) {
11621
- const tags = JSON.parse(memory.tags ?? "[]");
11622
- return {
11623
- messages: [
11624
- { role: "system", content: SYSTEM_PROMPT2 },
11625
- {
11626
- role: "user",
11627
- content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
11628
- },
11629
- {
11630
- role: "assistant",
11631
- content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
11632
- }
11633
- ]
11634
- };
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
+ });
11635
11741
  }
11636
- function memoryToSearchExample(memories, category) {
11637
- const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
11638
- return {
11639
- messages: [
11640
- { role: "system", content: SYSTEM_PROMPT2 },
11641
- { role: "user", content: `What ${category} memories do you have?` },
11642
- {
11643
- role: "assistant",
11644
- content: matched.length > 0 ? `Here are my ${category} memories:
11645
- ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
11646
- `)}` : `I don't have any ${category} memories stored yet.`
11647
- }
11648
- ]
11649
- };
11742
+ function deleteConfigValue(key) {
11743
+ const data = readConfigFile();
11744
+ delete data[key];
11745
+ writeConfigFile(data);
11650
11746
  }
11651
- async function gatherFromMementos(options = {}) {
11652
- const dbPath = join3(homedir3(), ".mementos", "mementos.db");
11653
- const db = new Database4(dbPath, { readonly: true, create: false });
11654
- try {
11655
- let query = "SELECT * FROM memories WHERE status = 'active'";
11656
- const params = [];
11657
- if (options.since) {
11658
- query += " AND created_at >= ?";
11659
- params.push(options.since.toISOString());
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;
11660
11754
  }
11661
- query += " ORDER BY importance DESC, created_at DESC";
11662
- if (options.limit) {
11663
- query += " LIMIT ?";
11664
- params.push(options.limit * 3);
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);
11665
11759
  }
11666
- const memories = db.query(query).all(...params);
11667
- const examples = [];
11668
- for (const memory of memories) {
11669
- examples.push(memoryToRecallExample(memory));
11670
- examples.push(memoryToSaveExample(memory));
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);
11671
11764
  }
11672
- const categories = [...new Set(memories.map((m) => m.category))];
11673
- for (const category of categories) {
11674
- examples.push(memoryToSearchExample(memories, category));
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));
11675
11782
  }
11676
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11677
- return {
11678
- source: "mementos",
11679
- examples: finalExamples,
11680
- count: finalExamples.length
11681
- };
11682
- } finally {
11683
- db.close();
11684
11783
  }
11784
+ throw lastError;
11685
11785
  }
11686
11786
 
11687
- // src/lib/gatherers/conversations.ts
11688
- import { Database as Database5 } from "bun:sqlite";
11689
- import { homedir as homedir4 } from "os";
11690
- import { join as join4 } from "path";
11691
- var SYSTEM_PROMPT3 = "You are a helpful AI assistant participating in multi-agent conversations. You communicate clearly and collaboratively with other agents and users.";
11692
- function windowToExample(window2) {
11693
- if (window2.length < 2)
11694
- return null;
11695
- const messages = [
11696
- { role: "system", content: SYSTEM_PROMPT3 }
11697
- ];
11698
- for (let i = 0;i < window2.length - 1; i++) {
11699
- const msg = window2[i];
11700
- if (!msg)
11701
- continue;
11702
- const role = i % 2 === 0 ? "user" : "assistant";
11703
- messages.push({
11704
- role,
11705
- content: `[${msg.from_agent} \u2192 ${msg.to_agent ?? msg.space ?? "all"}]: ${msg.content}`
11706
- });
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>");
11707
11792
  }
11708
- const last = window2[window2.length - 1];
11709
- if (!last)
11710
- return null;
11711
- messages.push({
11712
- role: "assistant",
11713
- content: `[${last.from_agent} \u2192 ${last.to_agent ?? last.space ?? "all"}]: ${last.content}`
11793
+ return new openai_default({ apiKey });
11794
+ }
11795
+ 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 };
11714
11803
  });
11715
- return { messages };
11716
11804
  }
11717
- async function gatherFromConversations(options = {}) {
11718
- const dbPath = join4(homedir4(), ".conversations", "messages.db");
11719
- const db = new Database5(dbPath, { readonly: true, create: false });
11720
- try {
11721
- let query = "SELECT * FROM messages WHERE 1=1";
11722
- const params = [];
11723
- if (options.since) {
11724
- query += " AND created_at >= ?";
11725
- params.push(options.since.toISOString());
11726
- }
11727
- query += " ORDER BY session_id, created_at ASC";
11728
- const allMessages = db.query(query).all(...params);
11729
- const sessions = new Map;
11730
- for (const msg of allMessages) {
11731
- const msgs = sessions.get(msg.session_id) ?? [];
11732
- msgs.push(msg);
11733
- sessions.set(msg.session_id, msgs);
11734
- }
11735
- const examples = [];
11736
- const windowSize = 4;
11737
- for (const [, sessionMsgs] of sessions) {
11738
- if (sessionMsgs.length < 2)
11739
- continue;
11740
- for (let start = 0;start <= sessionMsgs.length - 2; start++) {
11741
- const end = Math.min(start + windowSize, sessionMsgs.length);
11742
- const window2 = sessionMsgs.slice(start, end);
11743
- const example = windowToExample(window2);
11744
- if (example)
11745
- examples.push(example);
11746
- }
11747
- }
11748
- const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
11805
+ 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
+ });
11814
+ }
11815
+ async function getFineTuneStatus(jobId) {
11816
+ return withRetry(async () => {
11817
+ const client = getClient();
11818
+ const response = await client.fineTuning.jobs.retrieve(jobId);
11749
11819
  return {
11750
- source: "conversations",
11751
- examples: finalExamples,
11752
- count: finalExamples.length
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
11753
11825
  };
11754
- } finally {
11755
- db.close();
11826
+ });
11827
+ }
11828
+ 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
+ });
11839
+ }
11840
+
11841
+ // src/lib/providers/thinker-labs.ts
11842
+ var DEFAULT_BASE_URL = "https://api.thinkerlabs.ai/v1";
11843
+ function getConfig() {
11844
+ const apiKey = getConfigValue("THINKER_LABS_API_KEY");
11845
+ const baseUrl = getConfigValue("THINKER_LABS_BASE_URL") ?? DEFAULT_BASE_URL;
11846
+ if (!apiKey)
11847
+ throw new Error("THINKER_LABS_API_KEY is not set. Run: brains config set THINKER_LABS_API_KEY <key>");
11848
+ return { apiKey, baseUrl };
11849
+ }
11850
+ 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
+ });
11877
+ }
11878
+ async function uploadTrainingData(filePath) {
11879
+ const fileContent = await Bun.file(filePath).text();
11880
+ const fileName = filePath.split("/").pop() ?? "training.jsonl";
11881
+ const result = await request("POST", "/datasets/upload", undefined, { name: fileName, data: fileContent });
11882
+ return { datasetId: result.id };
11883
+ }
11884
+ async function startFineTune(datasetId, baseModel, name) {
11885
+ const result = await request("POST", "/fine-tunes", {
11886
+ dataset_id: datasetId,
11887
+ base_model: baseModel,
11888
+ ...name ? { name } : {}
11889
+ });
11890
+ return { jobId: result.id, status: result.status };
11891
+ }
11892
+ async function getStatus(jobId) {
11893
+ const result = await request("GET", `/fine-tunes/${jobId}`);
11894
+ return {
11895
+ jobId: result.id,
11896
+ status: result.status,
11897
+ modelId: result.model_id,
11898
+ error: result.error
11899
+ };
11900
+ }
11901
+ async function listModels() {
11902
+ const result = await request("GET", "/fine-tunes");
11903
+ return (result.data ?? []).map((m) => ({
11904
+ id: m.id,
11905
+ name: m.name,
11906
+ status: m.status,
11907
+ baseModel: m.base_model,
11908
+ createdAt: m.created_at
11909
+ }));
11910
+ }
11911
+ async function cancelJob(jobId) {
11912
+ await request("DELETE", `/fine-tunes/${jobId}`);
11913
+ }
11914
+
11915
+ class ThinkerLabsProvider {
11916
+ uploadTrainingData = uploadTrainingData;
11917
+ startFineTune = startFineTune;
11918
+ getStatus = getStatus;
11919
+ listModels = listModels;
11920
+ cancelJob = cancelJob;
11921
+ async uploadTrainingFile(filePath) {
11922
+ const { datasetId } = await uploadTrainingData(filePath);
11923
+ return { fileId: datasetId };
11924
+ }
11925
+ async createFineTuneJob(fileId, baseModel, suffix) {
11926
+ return startFineTune(fileId, baseModel, suffix);
11927
+ }
11928
+ async getFineTuneStatus(jobId) {
11929
+ const result = await getStatus(jobId);
11930
+ return { jobId: result.jobId, status: result.status, fineTunedModel: result.modelId, error: result.error };
11931
+ }
11932
+ async listFineTunedModels() {
11933
+ const models = await listModels();
11934
+ return models.map((m) => ({ id: m.id, model: m.baseModel, status: m.status, created: m.createdAt }));
11935
+ }
11936
+ async cancelFineTuneJob(jobId) {
11937
+ return cancelJob(jobId);
11756
11938
  }
11757
11939
  }
11758
11940
 
11759
- // src/lib/gatherers/sessions.ts
11760
- import { readdir, readFile, stat } from "fs/promises";
11761
- import { existsSync } from "fs";
11762
- import { join as join5 } from "path";
11763
- import { homedir as homedir5 } from "os";
11764
- var SYSTEM_PROMPT4 = "You are Claude Code, an AI assistant built by Anthropic that helps developers with coding, architecture, debugging, and software engineering tasks.";
11765
- function extractText(content) {
11766
- if (typeof content === "string")
11767
- return content;
11768
- return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join(`
11769
- `).trim();
11770
- }
11771
- async function gatherFromSessions(options = {}) {
11772
- const { limit: limit2 = 1000 } = options;
11773
- const examples = [];
11774
- const claudeDir = join5(homedir5(), ".claude", "projects");
11775
- if (!existsSync(claudeDir)) {
11776
- return { source: "sessions", examples: [], count: 0 };
11941
+ // src/cli/ui.ts
11942
+ import chalk from "chalk";
11943
+ function printTable(headers, rows) {
11944
+ if (rows.length === 0) {
11945
+ console.log(chalk.dim(" (no records)"));
11946
+ return;
11777
11947
  }
11778
- const projectDirs = await readdir(claudeDir).catch(() => []);
11779
- for (const projectDir of projectDirs) {
11780
- if (examples.length >= limit2)
11781
- break;
11782
- const projectPath = join5(claudeDir, projectDir);
11783
- const files = await readdir(projectPath).catch(() => []);
11784
- for (const file of files) {
11785
- if (examples.length >= limit2)
11786
- break;
11787
- if (!file.endsWith(".jsonl"))
11788
- continue;
11789
- const filePath = join5(projectPath, file);
11790
- if (options.since) {
11791
- const fileStat = await stat(filePath).catch(() => null);
11792
- if (fileStat && fileStat.mtime < options.since)
11793
- continue;
11794
- }
11795
- const content = await readFile(filePath, "utf-8").catch(() => "");
11796
- if (!content.trim())
11797
- continue;
11798
- const lines = content.trim().split(`
11799
- `);
11800
- const turns = [];
11801
- for (const line of lines) {
11802
- try {
11803
- const entry = JSON.parse(line);
11804
- if ((entry.type === "user" || entry.type === "human") && entry.message?.content) {
11805
- const text2 = extractText(entry.message.content);
11806
- if (text2.trim())
11807
- turns.push({ role: "user", content: text2.trim() });
11808
- } else if (entry.type === "assistant" && entry.message?.content) {
11809
- const text2 = extractText(entry.message.content);
11810
- if (text2.trim())
11811
- turns.push({ role: "assistant", content: text2.trim() });
11812
- }
11813
- } catch {}
11814
- }
11815
- const windowSize = 6;
11816
- for (let start = 0;start < turns.length - 1 && examples.length < limit2; start++) {
11817
- const window2 = turns.slice(start, start + windowSize);
11818
- if (!window2[0] || window2[0].role !== "user")
11819
- continue;
11820
- const lastAssistantIdx = window2.map((t) => t.role).lastIndexOf("assistant");
11821
- if (lastAssistantIdx < 1)
11822
- continue;
11823
- const usedTurns = window2.slice(0, lastAssistantIdx + 1);
11824
- examples.push({
11825
- messages: [
11826
- { role: "system", content: SYSTEM_PROMPT4 },
11827
- ...usedTurns
11828
- ]
11829
- });
11830
- }
11831
- }
11948
+ const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
11949
+ const separator = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
11950
+ const headerLine = headers.map((h, i) => ` ${chalk.bold(h.padEnd(colWidths[i] ?? 0))} `).join("\u2502");
11951
+ const topBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C");
11952
+ const midBorder = separator;
11953
+ const bottomBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534");
11954
+ console.log("\u250C" + topBorder + "\u2510");
11955
+ console.log("\u2502" + headerLine + "\u2502");
11956
+ console.log("\u251C" + midBorder + "\u2524");
11957
+ for (const row of rows) {
11958
+ const rowLine = row.map((cell, i) => ` ${(cell ?? "").padEnd(colWidths[i] ?? 0)} `).join("\u2502");
11959
+ console.log("\u2502" + rowLine + "\u2502");
11832
11960
  }
11833
- return { source: "sessions", examples, count: examples.length };
11961
+ console.log("\u2514" + bottomBorder + "\u2518");
11834
11962
  }
11835
- // src/lib/gatherers/index.ts
11836
- var ALL_SOURCES = ["todos", "mementos", "conversations", "sessions"];
11837
- async function gatherAll(sources, options = {}) {
11838
- const targets = sources.includes("all") ? [...ALL_SOURCES] : sources;
11839
- const results = await Promise.allSettled(targets.map((source) => {
11840
- switch (source) {
11841
- case "todos":
11842
- return gatherFromTodos(options);
11843
- case "mementos":
11844
- return gatherFromMementos(options);
11845
- case "conversations":
11846
- return gatherFromConversations(options);
11847
- case "sessions":
11848
- return gatherFromSessions(options);
11849
- default:
11850
- return Promise.resolve({ source, examples: [], count: 0 });
11851
- }
11852
- }));
11853
- return results.filter((r) => r.status === "fulfilled").map((r) => r.value);
11963
+ var STATUS_COLORS = {
11964
+ succeeded: chalk.green,
11965
+ running: chalk.cyan,
11966
+ pending: chalk.yellow,
11967
+ failed: chalk.red,
11968
+ cancelled: chalk.dim,
11969
+ queued: chalk.yellow,
11970
+ validating_files: chalk.blue
11971
+ };
11972
+ function printStatus(status) {
11973
+ const colorFn = STATUS_COLORS[status] ?? chalk.white;
11974
+ return colorFn(`\u25CF ${status}`);
11975
+ }
11976
+ function printJson(obj) {
11977
+ console.log(JSON.stringify(obj, null, 2));
11978
+ }
11979
+ function printError(message) {
11980
+ console.error(chalk.red("\u2717 Error: ") + message);
11981
+ }
11982
+ function printSuccess(message) {
11983
+ console.log(chalk.green("\u2713 ") + message);
11984
+ }
11985
+ function printInfo(message) {
11986
+ console.log(chalk.dim(" " + message));
11854
11987
  }
11855
11988
 
11856
11989
  // src/cli/index.ts
11857
11990
  var program2 = new Command;
11858
11991
  program2.name("brains").description("Fine-tuned model tracker and trainer").version("0.0.1");
11859
11992
  var modelsCmd = program2.command("models").description("Manage tracked fine-tuned models");
11860
- modelsCmd.command("list").description("List all tracked fine-tuned models").action(async () => {
11993
+ modelsCmd.command("list").description("List all tracked fine-tuned models").option("--json", "Output as JSON").action(async (opts) => {
11861
11994
  try {
11862
11995
  const db = getDb();
11863
11996
  const models = await db.select().from(fineTunedModels);
11997
+ if (opts.json) {
11998
+ printJson(models);
11999
+ return;
12000
+ }
11864
12001
  if (models.length === 0) {
11865
12002
  printInfo("No models tracked yet. Use 'brains finetune start' to train one.");
11866
12003
  return;
@@ -11878,14 +12015,22 @@ modelsCmd.command("list").description("List all tracked fine-tuned models").acti
11878
12015
  process.exit(1);
11879
12016
  }
11880
12017
  });
11881
- modelsCmd.command("show <id>").description("Show details of a specific model").action(async (id) => {
12018
+ modelsCmd.command("show <id>").description("Show details of a specific model").option("--json", "Output as JSON").action(async (id, opts) => {
11882
12019
  try {
11883
12020
  const db = getDb();
11884
12021
  const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
11885
12022
  if (!model) {
11886
- printError(`Model not found: ${id}`);
12023
+ if (opts.json) {
12024
+ printJson({ error: `Model not found: ${id}` });
12025
+ } else {
12026
+ printError(`Model not found: ${id}`);
12027
+ }
11887
12028
  process.exit(1);
11888
12029
  }
12030
+ if (opts.json) {
12031
+ printJson(model);
12032
+ return;
12033
+ }
11889
12034
  console.log();
11890
12035
  const tagsList = model.tags ? JSON.parse(model.tags).join(", ") : "(none)";
11891
12036
  console.log(` ID: ${model.id}`);
@@ -11972,29 +12117,89 @@ modelsCmd.command("collection <id> <collectionName>").description("Set the colle
11972
12117
  process.exit(1);
11973
12118
  }
11974
12119
  });
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
+ });
11975
12169
  var finetuneCmd = program2.command("finetune").description("Manage fine-tuning jobs");
11976
- 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) => {
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) => {
11977
12171
  try {
11978
12172
  if (opts.provider !== "openai" && opts.provider !== "thinker-labs") {
11979
12173
  printError(`Unknown provider: ${opts.provider}. Use 'openai' or 'thinker-labs'.`);
11980
12174
  process.exit(1);
11981
12175
  }
11982
- if (!existsSync2(opts.dataset)) {
11983
- printError(`Dataset file not found: ${opts.dataset}`);
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}`);
11984
12189
  process.exit(1);
11985
12190
  }
11986
- printInfo(`Uploading training file: ${opts.dataset} \u2026`);
12191
+ printInfo(`Uploading training file: ${datasetPath} \u2026`);
11987
12192
  let fileId;
11988
12193
  let jobId;
11989
12194
  let jobStatus;
11990
12195
  if (opts.provider === "openai") {
11991
- ({ fileId } = await uploadTrainingFile(opts.dataset));
12196
+ ({ fileId } = await uploadTrainingFile(datasetPath));
11992
12197
  printSuccess(`File uploaded. fileId = ${fileId}`);
11993
12198
  printInfo(`Creating fine-tune job on OpenAI \u2026`);
11994
12199
  ({ jobId, status: jobStatus } = await createFineTuneJob(fileId, opts.baseModel, opts.name));
11995
12200
  } else {
11996
12201
  const tl = new ThinkerLabsProvider;
11997
- ({ fileId } = await tl.uploadTrainingFile(opts.dataset));
12202
+ ({ fileId } = await tl.uploadTrainingFile(datasetPath));
11998
12203
  printSuccess(`File uploaded. fileId = ${fileId}`);
11999
12204
  printInfo(`Creating fine-tune job on Thinker Labs \u2026`);
12000
12205
  ({ jobId, status: jobStatus } = await tl.createFineTuneJob(fileId, opts.baseModel, opts.name));
@@ -12032,7 +12237,7 @@ finetuneCmd.command("start").description("Start a fine-tuning job").requiredOpti
12032
12237
  process.exit(1);
12033
12238
  }
12034
12239
  });
12035
- 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) => {
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) => {
12036
12241
  try {
12037
12242
  let result;
12038
12243
  if (opts.provider === "openai") {
@@ -12041,16 +12246,20 @@ finetuneCmd.command("status <job-id>").description("Get the status of a fine-tun
12041
12246
  const tl = new ThinkerLabsProvider;
12042
12247
  result = await tl.getFineTuneStatus(jobId);
12043
12248
  }
12044
- console.log();
12045
- console.log(` Job ID: ${result.jobId}`);
12046
- console.log(` Status: ${printStatus(result.status)}`);
12047
- if (result.fineTunedModel) {
12048
- console.log(` Fine-tuned model: ${result.fineTunedModel}`);
12049
- }
12050
- if (result.error) {
12051
- console.log(` Error: ${result.error}`);
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();
12052
12262
  }
12053
- console.log();
12054
12263
  const db = getDb();
12055
12264
  const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
12056
12265
  if (model) {
@@ -12062,7 +12271,64 @@ finetuneCmd.command("status <job-id>").description("Get the status of a fine-tun
12062
12271
  process.exit(1);
12063
12272
  }
12064
12273
  });
12065
- finetuneCmd.command("list").description("List all fine-tuning jobs").option("--provider <provider>", "Provider to query (openai|thinker-labs)", "openai").action(async (opts) => {
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) => {
12066
12332
  try {
12067
12333
  let jobs;
12068
12334
  if (opts.provider === "openai") {
@@ -12071,6 +12337,10 @@ finetuneCmd.command("list").description("List all fine-tuning jobs").option("--p
12071
12337
  const tl = new ThinkerLabsProvider;
12072
12338
  jobs = await tl.listFineTunedModels();
12073
12339
  }
12340
+ if (opts.json) {
12341
+ printJson(jobs);
12342
+ return;
12343
+ }
12074
12344
  if (jobs.length === 0) {
12075
12345
  printInfo("No fine-tuning jobs found.");
12076
12346
  return;
@@ -12086,7 +12356,7 @@ finetuneCmd.command("list").description("List all fine-tuning jobs").option("--p
12086
12356
  process.exit(1);
12087
12357
  }
12088
12358
  });
12089
- var DEFAULT_DATASETS_DIR = join6(homedir6(), ".brains", "datasets");
12359
+ var DEFAULT_DATASETS_DIR = join7(homedir7(), ".brains", "datasets");
12090
12360
  var dataCmd = program2.command("data").description("Manage training datasets");
12091
12361
  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) => {
12092
12362
  const validSources = ["todos", "mementos", "conversations", "sessions", "all"];
@@ -12100,44 +12370,59 @@ dataCmd.command("gather").description("Gather training data from agent memory so
12100
12370
  process.exit(1);
12101
12371
  }
12102
12372
  try {
12103
- mkdirSync2(opts.output, { recursive: true });
12373
+ mkdirSync3(opts.output, { recursive: true });
12104
12374
  const sources = opts.source === "all" ? ["todos", "mementos", "conversations", "sessions"] : [opts.source];
12105
12375
  const now = Date.now();
12106
12376
  const db = getDb();
12107
- const results = await gatherAll(sources, { limit: limit2 });
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
+ };
12108
12383
  let totalExamples = 0;
12109
- for (const result of results) {
12110
- const { source, examples, count } = result;
12111
- printInfo(`Gathered from ${source} \u2026`);
12112
- if (count === 0) {
12113
- printInfo(` No examples gathered from ${source}.`);
12114
- continue;
12115
- }
12116
- totalExamples += count;
12117
- const fileName = `${source}-${now}.jsonl`;
12118
- const filePath = join6(opts.output, fileName);
12119
- writeFileSync(filePath, examples.map((e) => JSON.stringify(e)).join(`
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(`
12120
12401
  `) + `
12121
12402
  `, "utf8");
12122
- const datasetId = randomUUID();
12123
- await db.insert(trainingDatasets).values({
12124
- id: datasetId,
12125
- source,
12126
- filePath,
12127
- exampleCount: count,
12128
- createdAt: now
12129
- });
12130
- printSuccess(` ${count} examples \u2192 ${filePath}`);
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
+ }
12131
12416
  }
12132
12417
  console.log();
12133
- printSuccess(`Total examples gathered: ${totalExamples}`);
12418
+ printSuccess(`Total: ${totalExamples} examples from ${successfulSources} source(s)`);
12134
12419
  } catch (err) {
12135
12420
  printError(err instanceof Error ? err.message : String(err));
12136
12421
  process.exit(1);
12137
12422
  }
12138
12423
  });
12139
12424
  dataCmd.command("preview <file>").description("Preview a JSONL training file").option("-n, --count <n>", "Number of examples to show", "5").action((file, opts) => {
12140
- if (!existsSync2(file)) {
12425
+ if (!existsSync3(file)) {
12141
12426
  printError(`File not found: ${file}`);
12142
12427
  process.exit(1);
12143
12428
  }
@@ -12147,7 +12432,7 @@ dataCmd.command("preview <file>").description("Preview a JSONL training file").o
12147
12432
  process.exit(1);
12148
12433
  }
12149
12434
  try {
12150
- const content = readFileSync2(file, "utf8");
12435
+ const content = readFileSync3(file, "utf8");
12151
12436
  const lines = content.trim().split(`
12152
12437
  `).filter(Boolean);
12153
12438
  const total = lines.length;
@@ -12172,10 +12457,63 @@ dataCmd.command("preview <file>").description("Preview a JSONL training file").o
12172
12457
  process.exit(1);
12173
12458
  }
12174
12459
  });
12175
- dataCmd.command("list").description("List all gathered datasets").action(async () => {
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) => {
12176
12510
  try {
12177
12511
  const db = getDb();
12178
12512
  const datasets = await db.select().from(trainingDatasets);
12513
+ if (opts.json) {
12514
+ printJson(datasets);
12515
+ return;
12516
+ }
12179
12517
  if (datasets.length === 0) {
12180
12518
  printInfo("No datasets found. Use 'brains data gather' to create one.");
12181
12519
  return;
@@ -12193,10 +12531,10 @@ dataCmd.command("list").description("List all gathered datasets").action(async (
12193
12531
  }
12194
12532
  });
12195
12533
  var collectionsCmd = program2.command("collections").description("Manage model collections");
12196
- collectionsCmd.action(async () => {
12197
- await listCollections();
12534
+ collectionsCmd.option("--json", "Output as JSON").action(async (opts) => {
12535
+ await listCollections(opts.json);
12198
12536
  });
12199
- async function listCollections() {
12537
+ async function listCollections(json = false) {
12200
12538
  try {
12201
12539
  const db = getDb();
12202
12540
  const rows = await db.select({
@@ -12204,6 +12542,10 @@ async function listCollections() {
12204
12542
  count: sql`count(*)`.as("count"),
12205
12543
  names: sql`group_concat(coalesce(${fineTunedModels.displayName}, ${fineTunedModels.name}), ', ')`.as("names")
12206
12544
  }).from(fineTunedModels).groupBy(fineTunedModels.collection);
12545
+ if (json) {
12546
+ printJson(rows);
12547
+ return;
12548
+ }
12207
12549
  if (rows.length === 0) {
12208
12550
  printInfo("No collections found. Set a collection with 'brains models set-collection'.");
12209
12551
  return;
@@ -12218,8 +12560,8 @@ async function listCollections() {
12218
12560
  process.exit(1);
12219
12561
  }
12220
12562
  }
12221
- collectionsCmd.command("list").description("List all collections with model counts").action(async () => {
12222
- await listCollections();
12563
+ collectionsCmd.command("list").description("List all collections with model counts").option("--json", "Output as JSON").action(async (opts) => {
12564
+ await listCollections(opts.json);
12223
12565
  });
12224
12566
  collectionsCmd.command("show <name>").description("List all models in a collection").action(async (name) => {
12225
12567
  try {
@@ -12247,4 +12589,74 @@ collectionsCmd.command("rename <oldName> <newName>").description("Rename a colle
12247
12589
  process.exit(1);
12248
12590
  }
12249
12591
  });
12592
+ program2.command("remove <id>").alias("rm").alias("uninstall").description("Remove a fine-tuned model or training job by ID").option("--type <type>", "Type: model | job (default: auto-detect)").action(async (id, opts) => {
12593
+ const db = getDb();
12594
+ try {
12595
+ const type = opts.type?.toLowerCase();
12596
+ if (type === "job" || !type && !type) {
12597
+ const job = db.select().from(trainingJobs).where(eq(trainingJobs.id, id)).get();
12598
+ if (job || type === "job") {
12599
+ if (!job) {
12600
+ printError(`Job not found: ${id}`);
12601
+ process.exit(1);
12602
+ }
12603
+ db.delete(trainingJobs).where(eq(trainingJobs.id, id)).run();
12604
+ printSuccess(`Training job ${id} removed`);
12605
+ return;
12606
+ }
12607
+ }
12608
+ if (type === "model" || !type) {
12609
+ const model = db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id)).get();
12610
+ if (model) {
12611
+ db.delete(fineTunedModels).where(eq(fineTunedModels.id, id)).run();
12612
+ printSuccess(`Model ${id} removed`);
12613
+ return;
12614
+ }
12615
+ }
12616
+ printError(`Not found: ${id}. Use --type model|job`);
12617
+ process.exit(1);
12618
+ } catch (err) {
12619
+ printError(err instanceof Error ? err.message : String(err));
12620
+ process.exit(1);
12621
+ }
12622
+ });
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
+ });
12250
12662
  program2.parse();