@hasna/brains 0.0.13 → 0.0.16
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/commands/cloud.d.ts +3 -0
- package/dist/cli/commands/cloud.d.ts.map +1 -0
- package/dist/cli/commands/collections.d.ts +3 -0
- package/dist/cli/commands/collections.d.ts.map +1 -0
- package/dist/cli/commands/data.d.ts +3 -0
- package/dist/cli/commands/data.d.ts.map +1 -0
- package/dist/cli/commands/finetune.d.ts +3 -0
- package/dist/cli/commands/finetune.d.ts.map +1 -0
- package/dist/cli/commands/models.d.ts +3 -0
- package/dist/cli/commands/models.d.ts.map +1 -0
- package/dist/cli/index.js +796 -789
- package/dist/index.js +17 -21
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +44 -31
- package/dist/server/index.js +41188 -150
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
8
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
9
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
10
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
11
|
for (let key of __getOwnPropNames(mod))
|
|
24
12
|
if (!__hasOwnProp.call(to, key))
|
|
25
13
|
__defProp(to, key, {
|
|
26
|
-
get:
|
|
14
|
+
get: () => mod[key],
|
|
27
15
|
enumerable: true
|
|
28
16
|
});
|
|
29
|
-
if (canCache)
|
|
30
|
-
cache.set(mod, to);
|
|
31
17
|
return to;
|
|
32
18
|
};
|
|
33
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
-
var __returnValue = (v) => v;
|
|
35
|
-
function __exportSetter(name, newValue) {
|
|
36
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
-
}
|
|
38
20
|
var __export = (target, all) => {
|
|
39
21
|
for (var name in all)
|
|
40
22
|
__defProp(target, name, {
|
|
41
23
|
get: all[name],
|
|
42
24
|
enumerable: true,
|
|
43
25
|
configurable: true,
|
|
44
|
-
set:
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
45
27
|
});
|
|
46
28
|
};
|
|
47
29
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -2176,11 +2158,11 @@ import { join as join5 } from "path";
|
|
|
2176
2158
|
import { join as join6, dirname } from "path";
|
|
2177
2159
|
import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
2178
2160
|
import { homedir as homedir5, platform } from "os";
|
|
2179
|
-
function
|
|
2161
|
+
function __accessProp(key) {
|
|
2180
2162
|
return this[key];
|
|
2181
2163
|
}
|
|
2182
|
-
function
|
|
2183
|
-
this[name] =
|
|
2164
|
+
function __exportSetter(name, newValue) {
|
|
2165
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
2184
2166
|
}
|
|
2185
2167
|
function translateSql(sql2, dialect) {
|
|
2186
2168
|
if (dialect === "sqlite")
|
|
@@ -4816,10 +4798,10 @@ function registerCloudCommands(program2, serviceName) {
|
|
|
4816
4798
|
}
|
|
4817
4799
|
});
|
|
4818
4800
|
}
|
|
4819
|
-
var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2,
|
|
4801
|
+
var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node, __toESMCache_esm, __toESM2 = (mod, isNodeMode, target) => {
|
|
4820
4802
|
var canCache = mod != null && typeof mod === "object";
|
|
4821
4803
|
if (canCache) {
|
|
4822
|
-
var cache = isNodeMode ?
|
|
4804
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
4823
4805
|
var cached = cache.get(mod);
|
|
4824
4806
|
if (cached)
|
|
4825
4807
|
return cached;
|
|
@@ -4829,19 +4811,19 @@ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __t
|
|
|
4829
4811
|
for (let key of __getOwnPropNames2(mod))
|
|
4830
4812
|
if (!__hasOwnProp2.call(to, key))
|
|
4831
4813
|
__defProp2(to, key, {
|
|
4832
|
-
get:
|
|
4814
|
+
get: __accessProp.bind(mod, key),
|
|
4833
4815
|
enumerable: true
|
|
4834
4816
|
});
|
|
4835
4817
|
if (canCache)
|
|
4836
4818
|
cache.set(mod, to);
|
|
4837
4819
|
return to;
|
|
4838
|
-
}, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports),
|
|
4820
|
+
}, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue = (v) => v, __export2 = (target, all) => {
|
|
4839
4821
|
for (var name in all)
|
|
4840
4822
|
__defProp2(target, name, {
|
|
4841
4823
|
get: all[name],
|
|
4842
4824
|
enumerable: true,
|
|
4843
4825
|
configurable: true,
|
|
4844
|
-
set:
|
|
4826
|
+
set: __exportSetter.bind(all, name)
|
|
4845
4827
|
});
|
|
4846
4828
|
}, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), __require2, require_postgres_array, require_arrayParser, require_postgres_date, require_mutable, require_postgres_interval, require_postgres_bytea, require_textParsers, require_pg_int8, require_binaryParsers, require_builtins, require_pg_types, require_defaults, require_utils, require_utils_legacy, require_utils_webcrypto, require_utils2, require_cert_signatures, require_sasl, require_type_overrides, require_pg_connection_string, require_connection_parameters, require_result, require_query, require_messages, require_buffer_writer, require_serializer, require_buffer_reader, require_parser, require_dist, require_empty, require_stream, require_connection, require_split2, require_helper, require_lib, require_client, require_pg_pool, require_query2, require_client2, require_lib2, import_lib, Client, Pool, Connection, types, Query, DatabaseError, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default, init_esm, init_adapter, util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
4847
4829
|
const t = typeof data;
|
|
@@ -13434,7 +13416,7 @@ __export(exports_sessions2, {
|
|
|
13434
13416
|
gatherFromSessions: () => gatherFromSessions
|
|
13435
13417
|
});
|
|
13436
13418
|
import { readdir, readFile, stat } from "fs/promises";
|
|
13437
|
-
import { existsSync as
|
|
13419
|
+
import { existsSync as existsSync10 } from "fs";
|
|
13438
13420
|
import { join as join12 } from "path";
|
|
13439
13421
|
import { homedir as homedir11 } from "os";
|
|
13440
13422
|
function extractText(content) {
|
|
@@ -13447,7 +13429,7 @@ async function gatherFromSessions(options = {}) {
|
|
|
13447
13429
|
const { limit: limit2 = 1000 } = options;
|
|
13448
13430
|
const examples = [];
|
|
13449
13431
|
const claudeDir = join12(homedir11(), ".claude", "projects");
|
|
13450
|
-
if (!
|
|
13432
|
+
if (!existsSync10(claudeDir)) {
|
|
13451
13433
|
return { source: "sessions", examples: [], count: 0 };
|
|
13452
13434
|
}
|
|
13453
13435
|
const projectDirs = await readdir(claudeDir).catch(() => []);
|
|
@@ -14976,10 +14958,8 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
14976
14958
|
}
|
|
14977
14959
|
|
|
14978
14960
|
// src/cli/index.ts
|
|
14979
|
-
import {
|
|
14980
|
-
import {
|
|
14981
|
-
import { join as join13 } from "path";
|
|
14982
|
-
import { homedir as homedir12 } from "os";
|
|
14961
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
14962
|
+
import { join as join14 } from "path";
|
|
14983
14963
|
|
|
14984
14964
|
// node_modules/drizzle-orm/bun-sqlite/driver.js
|
|
14985
14965
|
import { Database } from "bun:sqlite";
|
|
@@ -17402,6 +17382,57 @@ function getRawDb(dbPath) {
|
|
|
17402
17382
|
return dbPath ? new SqliteAdapter(dbPath) : createDatabase({ service: "brains" });
|
|
17403
17383
|
}
|
|
17404
17384
|
|
|
17385
|
+
// src/cli/ui.ts
|
|
17386
|
+
import chalk from "chalk";
|
|
17387
|
+
function printTable(headers, rows) {
|
|
17388
|
+
if (rows.length === 0) {
|
|
17389
|
+
console.log(chalk.dim(" (no records)"));
|
|
17390
|
+
return;
|
|
17391
|
+
}
|
|
17392
|
+
const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
17393
|
+
const separator = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
|
|
17394
|
+
const headerLine = headers.map((h, i) => ` ${chalk.bold(h.padEnd(colWidths[i] ?? 0))} `).join("\u2502");
|
|
17395
|
+
const topBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C");
|
|
17396
|
+
const midBorder = separator;
|
|
17397
|
+
const bottomBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534");
|
|
17398
|
+
console.log("\u250C" + topBorder + "\u2510");
|
|
17399
|
+
console.log("\u2502" + headerLine + "\u2502");
|
|
17400
|
+
console.log("\u251C" + midBorder + "\u2524");
|
|
17401
|
+
for (const row of rows) {
|
|
17402
|
+
const rowLine = row.map((cell, i) => ` ${(cell ?? "").padEnd(colWidths[i] ?? 0)} `).join("\u2502");
|
|
17403
|
+
console.log("\u2502" + rowLine + "\u2502");
|
|
17404
|
+
}
|
|
17405
|
+
console.log("\u2514" + bottomBorder + "\u2518");
|
|
17406
|
+
}
|
|
17407
|
+
var STATUS_COLORS = {
|
|
17408
|
+
succeeded: chalk.green,
|
|
17409
|
+
running: chalk.cyan,
|
|
17410
|
+
pending: chalk.yellow,
|
|
17411
|
+
failed: chalk.red,
|
|
17412
|
+
cancelled: chalk.dim,
|
|
17413
|
+
queued: chalk.yellow,
|
|
17414
|
+
validating_files: chalk.blue
|
|
17415
|
+
};
|
|
17416
|
+
function printStatus(status) {
|
|
17417
|
+
const colorFn = STATUS_COLORS[status] ?? chalk.white;
|
|
17418
|
+
return colorFn(`\u25CF ${status}`);
|
|
17419
|
+
}
|
|
17420
|
+
function printJson(obj) {
|
|
17421
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
17422
|
+
}
|
|
17423
|
+
function printError(message) {
|
|
17424
|
+
console.error(chalk.red("\u2717 Error: ") + message);
|
|
17425
|
+
}
|
|
17426
|
+
function printSuccess(message) {
|
|
17427
|
+
console.log(chalk.green("\u2713 ") + message);
|
|
17428
|
+
}
|
|
17429
|
+
function printInfo(message) {
|
|
17430
|
+
console.log(chalk.dim(" " + message));
|
|
17431
|
+
}
|
|
17432
|
+
|
|
17433
|
+
// src/cli/commands/models.ts
|
|
17434
|
+
import { randomUUID } from "crypto";
|
|
17435
|
+
|
|
17405
17436
|
// node_modules/openai/internal/qs/formats.mjs
|
|
17406
17437
|
var default_format = "RFC3986";
|
|
17407
17438
|
var formatters = {
|
|
@@ -20866,11 +20897,11 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
20866
20897
|
}
|
|
20867
20898
|
return this._addChatCompletion(__classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_endRequest).call(this));
|
|
20868
20899
|
}
|
|
20869
|
-
[(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function
|
|
20900
|
+
[(_ChatCompletionStream_params = new WeakMap, _ChatCompletionStream_choiceEventStates = new WeakMap, _ChatCompletionStream_currentChatCompletionSnapshot = new WeakMap, _ChatCompletionStream_instances = new WeakSet, _ChatCompletionStream_beginRequest = function _ChatCompletionStream_beginRequest() {
|
|
20870
20901
|
if (this.ended)
|
|
20871
20902
|
return;
|
|
20872
20903
|
__classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
|
|
20873
|
-
}, _ChatCompletionStream_getChoiceEventState = function
|
|
20904
|
+
}, _ChatCompletionStream_getChoiceEventState = function _ChatCompletionStream_getChoiceEventState(choice) {
|
|
20874
20905
|
let state = __classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index];
|
|
20875
20906
|
if (state) {
|
|
20876
20907
|
return state;
|
|
@@ -20885,7 +20916,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
20885
20916
|
};
|
|
20886
20917
|
__classPrivateFieldGet6(this, _ChatCompletionStream_choiceEventStates, "f")[choice.index] = state;
|
|
20887
20918
|
return state;
|
|
20888
|
-
}, _ChatCompletionStream_addChunk = function
|
|
20919
|
+
}, _ChatCompletionStream_addChunk = function _ChatCompletionStream_addChunk(chunk) {
|
|
20889
20920
|
if (this.ended)
|
|
20890
20921
|
return;
|
|
20891
20922
|
const completion = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_accumulateChatCompletion).call(this, chunk);
|
|
@@ -20952,7 +20983,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
20952
20983
|
}
|
|
20953
20984
|
}
|
|
20954
20985
|
}
|
|
20955
|
-
}, _ChatCompletionStream_emitToolCallDoneEvent = function
|
|
20986
|
+
}, _ChatCompletionStream_emitToolCallDoneEvent = function _ChatCompletionStream_emitToolCallDoneEvent(choiceSnapshot, toolCallIndex) {
|
|
20956
20987
|
const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
|
|
20957
20988
|
if (state.done_tool_calls.has(toolCallIndex)) {
|
|
20958
20989
|
return;
|
|
@@ -20975,7 +21006,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
20975
21006
|
} else {
|
|
20976
21007
|
assertNever2(toolCallSnapshot.type);
|
|
20977
21008
|
}
|
|
20978
|
-
}, _ChatCompletionStream_emitContentDoneEvents = function
|
|
21009
|
+
}, _ChatCompletionStream_emitContentDoneEvents = function _ChatCompletionStream_emitContentDoneEvents(choiceSnapshot) {
|
|
20979
21010
|
const state = __classPrivateFieldGet6(this, _ChatCompletionStream_instances, "m", _ChatCompletionStream_getChoiceEventState).call(this, choiceSnapshot);
|
|
20980
21011
|
if (choiceSnapshot.message.content && !state.content_done) {
|
|
20981
21012
|
state.content_done = true;
|
|
@@ -20997,7 +21028,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
20997
21028
|
state.logprobs_refusal_done = true;
|
|
20998
21029
|
this._emit("logprobs.refusal.done", { refusal: choiceSnapshot.logprobs.refusal });
|
|
20999
21030
|
}
|
|
21000
|
-
}, _ChatCompletionStream_endRequest = function
|
|
21031
|
+
}, _ChatCompletionStream_endRequest = function _ChatCompletionStream_endRequest() {
|
|
21001
21032
|
if (this.ended) {
|
|
21002
21033
|
throw new OpenAIError(`stream has ended, this shouldn't happen`);
|
|
21003
21034
|
}
|
|
@@ -21008,13 +21039,13 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
21008
21039
|
__classPrivateFieldSet5(this, _ChatCompletionStream_currentChatCompletionSnapshot, undefined, "f");
|
|
21009
21040
|
__classPrivateFieldSet5(this, _ChatCompletionStream_choiceEventStates, [], "f");
|
|
21010
21041
|
return finalizeChatCompletion(snapshot, __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f"));
|
|
21011
|
-
}, _ChatCompletionStream_getAutoParseableResponseFormat = function
|
|
21042
|
+
}, _ChatCompletionStream_getAutoParseableResponseFormat = function _ChatCompletionStream_getAutoParseableResponseFormat() {
|
|
21012
21043
|
const responseFormat = __classPrivateFieldGet6(this, _ChatCompletionStream_params, "f")?.response_format;
|
|
21013
21044
|
if (isAutoParsableResponseFormat(responseFormat)) {
|
|
21014
21045
|
return responseFormat;
|
|
21015
21046
|
}
|
|
21016
21047
|
return null;
|
|
21017
|
-
}, _ChatCompletionStream_accumulateChatCompletion = function
|
|
21048
|
+
}, _ChatCompletionStream_accumulateChatCompletion = function _ChatCompletionStream_accumulateChatCompletion(chunk) {
|
|
21018
21049
|
var _a, _b, _c, _d;
|
|
21019
21050
|
let snapshot = __classPrivateFieldGet6(this, _ChatCompletionStream_currentChatCompletionSnapshot, "f");
|
|
21020
21051
|
const { choices, ...rest } = chunk;
|
|
@@ -22240,11 +22271,11 @@ class ResponseStream extends EventStream {
|
|
|
22240
22271
|
}
|
|
22241
22272
|
return __classPrivateFieldGet7(this, _ResponseStream_instances, "m", _ResponseStream_endRequest).call(this);
|
|
22242
22273
|
}
|
|
22243
|
-
[(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function
|
|
22274
|
+
[(_ResponseStream_params = new WeakMap, _ResponseStream_currentResponseSnapshot = new WeakMap, _ResponseStream_finalResponse = new WeakMap, _ResponseStream_instances = new WeakSet, _ResponseStream_beginRequest = function _ResponseStream_beginRequest() {
|
|
22244
22275
|
if (this.ended)
|
|
22245
22276
|
return;
|
|
22246
22277
|
__classPrivateFieldSet6(this, _ResponseStream_currentResponseSnapshot, undefined, "f");
|
|
22247
|
-
}, _ResponseStream_addEvent = function
|
|
22278
|
+
}, _ResponseStream_addEvent = function _ResponseStream_addEvent(event, starting_after) {
|
|
22248
22279
|
if (this.ended)
|
|
22249
22280
|
return;
|
|
22250
22281
|
const maybeEmit = (name, event2) => {
|
|
@@ -22292,7 +22323,7 @@ class ResponseStream extends EventStream {
|
|
|
22292
22323
|
maybeEmit(event.type, event);
|
|
22293
22324
|
break;
|
|
22294
22325
|
}
|
|
22295
|
-
}, _ResponseStream_endRequest = function
|
|
22326
|
+
}, _ResponseStream_endRequest = function _ResponseStream_endRequest() {
|
|
22296
22327
|
if (this.ended) {
|
|
22297
22328
|
throw new OpenAIError(`stream has ended, this shouldn't happen`);
|
|
22298
22329
|
}
|
|
@@ -22304,7 +22335,7 @@ class ResponseStream extends EventStream {
|
|
|
22304
22335
|
const parsedResponse = finalizeResponse(snapshot, __classPrivateFieldGet7(this, _ResponseStream_params, "f"));
|
|
22305
22336
|
__classPrivateFieldSet6(this, _ResponseStream_finalResponse, parsedResponse, "f");
|
|
22306
22337
|
return parsedResponse;
|
|
22307
|
-
}, _ResponseStream_accumulateResponse = function
|
|
22338
|
+
}, _ResponseStream_accumulateResponse = function _ResponseStream_accumulateResponse(event) {
|
|
22308
22339
|
let snapshot = __classPrivateFieldGet7(this, _ResponseStream_currentResponseSnapshot, "f");
|
|
22309
22340
|
if (!snapshot) {
|
|
22310
22341
|
if (event.type !== "response.created") {
|
|
@@ -23149,345 +23180,263 @@ class ThinkerLabsProvider {
|
|
|
23149
23180
|
}
|
|
23150
23181
|
}
|
|
23151
23182
|
|
|
23152
|
-
// src/cli/
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
23160
|
-
const separator = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
|
|
23161
|
-
const headerLine = headers.map((h, i) => ` ${chalk.bold(h.padEnd(colWidths[i] ?? 0))} `).join("\u2502");
|
|
23162
|
-
const topBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C");
|
|
23163
|
-
const midBorder = separator;
|
|
23164
|
-
const bottomBorder = colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534");
|
|
23165
|
-
console.log("\u250C" + topBorder + "\u2510");
|
|
23166
|
-
console.log("\u2502" + headerLine + "\u2502");
|
|
23167
|
-
console.log("\u251C" + midBorder + "\u2524");
|
|
23168
|
-
for (const row of rows) {
|
|
23169
|
-
const rowLine = row.map((cell, i) => ` ${(cell ?? "").padEnd(colWidths[i] ?? 0)} `).join("\u2502");
|
|
23170
|
-
console.log("\u2502" + rowLine + "\u2502");
|
|
23171
|
-
}
|
|
23172
|
-
console.log("\u2514" + bottomBorder + "\u2518");
|
|
23173
|
-
}
|
|
23174
|
-
var STATUS_COLORS = {
|
|
23175
|
-
succeeded: chalk.green,
|
|
23176
|
-
running: chalk.cyan,
|
|
23177
|
-
pending: chalk.yellow,
|
|
23178
|
-
failed: chalk.red,
|
|
23179
|
-
cancelled: chalk.dim,
|
|
23180
|
-
queued: chalk.yellow,
|
|
23181
|
-
validating_files: chalk.blue
|
|
23182
|
-
};
|
|
23183
|
-
function printStatus(status) {
|
|
23184
|
-
const colorFn = STATUS_COLORS[status] ?? chalk.white;
|
|
23185
|
-
return colorFn(`\u25CF ${status}`);
|
|
23186
|
-
}
|
|
23187
|
-
function printJson(obj) {
|
|
23188
|
-
console.log(JSON.stringify(obj, null, 2));
|
|
23189
|
-
}
|
|
23190
|
-
function printError(message) {
|
|
23191
|
-
console.error(chalk.red("\u2717 Error: ") + message);
|
|
23192
|
-
}
|
|
23193
|
-
function printSuccess(message) {
|
|
23194
|
-
console.log(chalk.green("\u2713 ") + message);
|
|
23195
|
-
}
|
|
23196
|
-
function printInfo(message) {
|
|
23197
|
-
console.log(chalk.dim(" " + message));
|
|
23198
|
-
}
|
|
23199
|
-
|
|
23200
|
-
// src/cli/index.ts
|
|
23201
|
-
var program2 = new Command;
|
|
23202
|
-
program2.name("brains").description("Fine-tuned model tracker and trainer").version("0.0.1");
|
|
23203
|
-
var modelsCmd = program2.command("models").description("Manage tracked fine-tuned models");
|
|
23204
|
-
modelsCmd.command("list").description("List all tracked fine-tuned models").option("--json", "Output as JSON").action(async (opts) => {
|
|
23205
|
-
try {
|
|
23206
|
-
const db = getDb();
|
|
23207
|
-
const models = await db.select().from(fineTunedModels);
|
|
23208
|
-
if (opts.json) {
|
|
23209
|
-
printJson(models);
|
|
23210
|
-
return;
|
|
23211
|
-
}
|
|
23212
|
-
if (models.length === 0) {
|
|
23213
|
-
printInfo("No models tracked yet. Use 'brains finetune start' to train one.");
|
|
23214
|
-
return;
|
|
23215
|
-
}
|
|
23216
|
-
printTable(["ID", "Display Name", "Provider", "Status", "Collection", "Base Model"], models.map((m) => [
|
|
23217
|
-
m.id,
|
|
23218
|
-
m.displayName ?? m.name,
|
|
23219
|
-
m.provider,
|
|
23220
|
-
printStatus(m.status),
|
|
23221
|
-
m.collection ?? "",
|
|
23222
|
-
m.baseModel
|
|
23223
|
-
]));
|
|
23224
|
-
} catch (err) {
|
|
23225
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23226
|
-
process.exit(1);
|
|
23227
|
-
}
|
|
23228
|
-
});
|
|
23229
|
-
modelsCmd.command("show <id>").description("Show details of a specific model").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
23230
|
-
try {
|
|
23231
|
-
const db = getDb();
|
|
23232
|
-
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23233
|
-
if (!model) {
|
|
23183
|
+
// src/cli/commands/models.ts
|
|
23184
|
+
function registerModelsCommands(program2) {
|
|
23185
|
+
const modelsCmd = program2.command("models").description("Manage tracked fine-tuned models");
|
|
23186
|
+
modelsCmd.command("list").description("List all tracked fine-tuned models").option("--json", "Output as JSON").action(async (opts) => {
|
|
23187
|
+
try {
|
|
23188
|
+
const db = getDb();
|
|
23189
|
+
const models = await db.select().from(fineTunedModels);
|
|
23234
23190
|
if (opts.json) {
|
|
23235
|
-
printJson(
|
|
23236
|
-
|
|
23237
|
-
|
|
23191
|
+
printJson(models);
|
|
23192
|
+
return;
|
|
23193
|
+
}
|
|
23194
|
+
if (models.length === 0) {
|
|
23195
|
+
printInfo("No models tracked yet. Use 'brains finetune start' to train one.");
|
|
23196
|
+
return;
|
|
23238
23197
|
}
|
|
23198
|
+
printTable(["ID", "Display Name", "Provider", "Status", "Collection", "Base Model"], models.map((m) => [
|
|
23199
|
+
m.id,
|
|
23200
|
+
m.displayName ?? m.name,
|
|
23201
|
+
m.provider,
|
|
23202
|
+
printStatus(m.status),
|
|
23203
|
+
m.collection ?? "",
|
|
23204
|
+
m.baseModel
|
|
23205
|
+
]));
|
|
23206
|
+
} catch (err) {
|
|
23207
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23239
23208
|
process.exit(1);
|
|
23240
23209
|
}
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
}
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
}
|
|
23264
|
-
});
|
|
23265
|
-
|
|
23266
|
-
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
|
|
23272
|
-
|
|
23273
|
-
|
|
23274
|
-
|
|
23275
|
-
modelsCmd.command("describe <id> <description>").description("Set the description of a model").action(async (id, description) => {
|
|
23276
|
-
try {
|
|
23277
|
-
const db = getDb();
|
|
23278
|
-
await db.update(fineTunedModels).set({ description, updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23279
|
-
printSuccess(`Description updated.`);
|
|
23280
|
-
} catch (err) {
|
|
23281
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23282
|
-
process.exit(1);
|
|
23283
|
-
}
|
|
23284
|
-
});
|
|
23285
|
-
modelsCmd.command("tag <id> <tag>").description("Add a tag to a model").action(async (id, tag) => {
|
|
23286
|
-
try {
|
|
23287
|
-
const db = getDb();
|
|
23288
|
-
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23289
|
-
if (!model) {
|
|
23290
|
-
printError(`Model not found: ${id}`);
|
|
23210
|
+
});
|
|
23211
|
+
modelsCmd.command("show <id>").description("Show details of a specific model").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
23212
|
+
try {
|
|
23213
|
+
const db = getDb();
|
|
23214
|
+
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23215
|
+
if (!model) {
|
|
23216
|
+
if (opts.json) {
|
|
23217
|
+
printJson({ error: `Model not found: ${id}` });
|
|
23218
|
+
} else {
|
|
23219
|
+
printError(`Model not found: ${id}`);
|
|
23220
|
+
}
|
|
23221
|
+
process.exit(1);
|
|
23222
|
+
}
|
|
23223
|
+
if (opts.json) {
|
|
23224
|
+
printJson(model);
|
|
23225
|
+
return;
|
|
23226
|
+
}
|
|
23227
|
+
console.log();
|
|
23228
|
+
const tagsList = model.tags ? JSON.parse(model.tags).join(", ") : "(none)";
|
|
23229
|
+
console.log(` ID: ${model.id}`);
|
|
23230
|
+
console.log(` Name: ${model.name}`);
|
|
23231
|
+
console.log(` Display Name: ${model.displayName ?? "(none)"}`);
|
|
23232
|
+
console.log(` Description: ${model.description ?? "(none)"}`);
|
|
23233
|
+
console.log(` Collection: ${model.collection ?? "(none)"}`);
|
|
23234
|
+
console.log(` Tags: ${tagsList}`);
|
|
23235
|
+
console.log(` Provider: ${model.provider}`);
|
|
23236
|
+
console.log(` Status: ${printStatus(model.status)}`);
|
|
23237
|
+
console.log(` Base Model: ${model.baseModel}`);
|
|
23238
|
+
console.log(` Job ID: ${model.fineTuneJobId ?? "(none)"}`);
|
|
23239
|
+
console.log(` Created: ${new Date(model.createdAt).toISOString()}`);
|
|
23240
|
+
console.log(` Updated: ${new Date(model.updatedAt).toISOString()}`);
|
|
23241
|
+
console.log();
|
|
23242
|
+
} catch (err) {
|
|
23243
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23291
23244
|
process.exit(1);
|
|
23292
23245
|
}
|
|
23293
|
-
|
|
23294
|
-
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
process.exit(1);
|
|
23302
|
-
}
|
|
23303
|
-
});
|
|
23304
|
-
modelsCmd.command("untag <id> <tag>").description("Remove a tag from a model").action(async (id, tag) => {
|
|
23305
|
-
try {
|
|
23306
|
-
const db = getDb();
|
|
23307
|
-
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23308
|
-
if (!model) {
|
|
23309
|
-
printError(`Model not found: ${id}`);
|
|
23246
|
+
});
|
|
23247
|
+
modelsCmd.command("rename <id> <displayName>").description("Set the display name of a model").action(async (id, displayName) => {
|
|
23248
|
+
try {
|
|
23249
|
+
const db = getDb();
|
|
23250
|
+
await db.update(fineTunedModels).set({ displayName, updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23251
|
+
printSuccess(`Display name set to "${displayName}"`);
|
|
23252
|
+
} catch (err) {
|
|
23253
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23310
23254
|
process.exit(1);
|
|
23311
23255
|
}
|
|
23312
|
-
|
|
23313
|
-
|
|
23314
|
-
|
|
23315
|
-
|
|
23316
|
-
|
|
23317
|
-
|
|
23318
|
-
|
|
23319
|
-
|
|
23320
|
-
});
|
|
23321
|
-
modelsCmd.command("collection <id> <collectionName>").description("Set the collection of a model").action(async (id, collectionName) => {
|
|
23322
|
-
try {
|
|
23323
|
-
const db = getDb();
|
|
23324
|
-
await db.update(fineTunedModels).set({ collection: collectionName, updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23325
|
-
printSuccess(`Collection set to "${collectionName}"`);
|
|
23326
|
-
} catch (err) {
|
|
23327
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23328
|
-
process.exit(1);
|
|
23329
|
-
}
|
|
23330
|
-
});
|
|
23331
|
-
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) => {
|
|
23332
|
-
try {
|
|
23333
|
-
let result;
|
|
23334
|
-
if (opts.provider === "openai") {
|
|
23335
|
-
result = await getFineTuneStatus(jobId);
|
|
23336
|
-
} else {
|
|
23337
|
-
const tl = new ThinkerLabsProvider;
|
|
23338
|
-
result = await tl.getFineTuneStatus(jobId);
|
|
23339
|
-
}
|
|
23340
|
-
const db = getDb();
|
|
23341
|
-
const [existing] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23342
|
-
if (existing) {
|
|
23343
|
-
printInfo(`Model already tracked as: ${existing.id}`);
|
|
23344
|
-
return;
|
|
23345
|
-
}
|
|
23346
|
-
const modelId = randomUUID();
|
|
23347
|
-
const now = Date.now();
|
|
23348
|
-
const name = opts.name ?? result.fineTunedModel ?? `imported-${jobId}`;
|
|
23349
|
-
await db.insert(fineTunedModels).values({
|
|
23350
|
-
id: modelId,
|
|
23351
|
-
name,
|
|
23352
|
-
provider: opts.provider,
|
|
23353
|
-
baseModel: result.baseModel ?? "unknown",
|
|
23354
|
-
status: result.status,
|
|
23355
|
-
fineTuneJobId: jobId,
|
|
23356
|
-
createdAt: now,
|
|
23357
|
-
updatedAt: now
|
|
23358
|
-
});
|
|
23359
|
-
await db.insert(trainingJobs).values({
|
|
23360
|
-
id: randomUUID(),
|
|
23361
|
-
modelId,
|
|
23362
|
-
provider: opts.provider,
|
|
23363
|
-
status: result.status,
|
|
23364
|
-
startedAt: now
|
|
23365
|
-
});
|
|
23366
|
-
printSuccess(`Model imported successfully.`);
|
|
23367
|
-
console.log();
|
|
23368
|
-
console.log(` Local ID: ${modelId}`);
|
|
23369
|
-
console.log(` Job ID: ${jobId}`);
|
|
23370
|
-
console.log(` Name: ${name}`);
|
|
23371
|
-
console.log(` Status: ${printStatus(result.status)}`);
|
|
23372
|
-
if (result.fineTunedModel)
|
|
23373
|
-
console.log(` Model: ${result.fineTunedModel}`);
|
|
23374
|
-
console.log();
|
|
23375
|
-
} catch (err) {
|
|
23376
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23377
|
-
process.exit(1);
|
|
23378
|
-
}
|
|
23379
|
-
});
|
|
23380
|
-
var finetuneCmd = program2.command("finetune").description("Manage fine-tuning jobs");
|
|
23381
|
-
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) => {
|
|
23382
|
-
try {
|
|
23383
|
-
if (opts.provider !== "openai" && opts.provider !== "thinker-labs") {
|
|
23384
|
-
printError(`Unknown provider: ${opts.provider}. Use 'openai' or 'thinker-labs'.`);
|
|
23256
|
+
});
|
|
23257
|
+
modelsCmd.command("describe <id> <description>").description("Set the description of a model").action(async (id, description) => {
|
|
23258
|
+
try {
|
|
23259
|
+
const db = getDb();
|
|
23260
|
+
await db.update(fineTunedModels).set({ description, updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23261
|
+
printSuccess(`Description updated.`);
|
|
23262
|
+
} catch (err) {
|
|
23263
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23385
23264
|
process.exit(1);
|
|
23386
23265
|
}
|
|
23387
|
-
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
const
|
|
23391
|
-
|
|
23392
|
-
|
|
23266
|
+
});
|
|
23267
|
+
modelsCmd.command("tag <id> <tag>").description("Add a tag to a model").action(async (id, tag) => {
|
|
23268
|
+
try {
|
|
23269
|
+
const db = getDb();
|
|
23270
|
+
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23271
|
+
if (!model) {
|
|
23272
|
+
printError(`Model not found: ${id}`);
|
|
23393
23273
|
process.exit(1);
|
|
23394
23274
|
}
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
|
|
23275
|
+
const existing = model.tags ? JSON.parse(model.tags) : [];
|
|
23276
|
+
if (!existing.includes(tag)) {
|
|
23277
|
+
existing.push(tag);
|
|
23278
|
+
}
|
|
23279
|
+
await db.update(fineTunedModels).set({ tags: JSON.stringify(existing), updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23280
|
+
printSuccess(`Tag "${tag}" added. Tags: ${existing.join(", ")}`);
|
|
23281
|
+
} catch (err) {
|
|
23282
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23400
23283
|
process.exit(1);
|
|
23401
23284
|
}
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
const
|
|
23413
|
-
({
|
|
23414
|
-
printSuccess(`
|
|
23415
|
-
|
|
23416
|
-
(
|
|
23285
|
+
});
|
|
23286
|
+
modelsCmd.command("untag <id> <tag>").description("Remove a tag from a model").action(async (id, tag) => {
|
|
23287
|
+
try {
|
|
23288
|
+
const db = getDb();
|
|
23289
|
+
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.id, id));
|
|
23290
|
+
if (!model) {
|
|
23291
|
+
printError(`Model not found: ${id}`);
|
|
23292
|
+
process.exit(1);
|
|
23293
|
+
}
|
|
23294
|
+
const existing = model.tags ? JSON.parse(model.tags) : [];
|
|
23295
|
+
const updated = existing.filter((t) => t !== tag);
|
|
23296
|
+
await db.update(fineTunedModels).set({ tags: JSON.stringify(updated), updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23297
|
+
printSuccess(`Tag "${tag}" removed. Tags: ${updated.join(", ") || "(none)"}`);
|
|
23298
|
+
} catch (err) {
|
|
23299
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23300
|
+
process.exit(1);
|
|
23417
23301
|
}
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
|
|
23421
|
-
|
|
23422
|
-
|
|
23423
|
-
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
fineTuneJobId: jobId,
|
|
23428
|
-
createdAt: now,
|
|
23429
|
-
updatedAt: now
|
|
23430
|
-
});
|
|
23431
|
-
const trainingJobId = randomUUID();
|
|
23432
|
-
await db.insert(trainingJobs).values({
|
|
23433
|
-
id: trainingJobId,
|
|
23434
|
-
modelId,
|
|
23435
|
-
provider: opts.provider,
|
|
23436
|
-
status: jobStatus,
|
|
23437
|
-
startedAt: now
|
|
23438
|
-
});
|
|
23439
|
-
printSuccess(`Fine-tune job started!`);
|
|
23440
|
-
console.log();
|
|
23441
|
-
console.log(` Model ID: ${modelId}`);
|
|
23442
|
-
console.log(` Job ID: ${jobId}`);
|
|
23443
|
-
console.log(` Status: ${printStatus(jobStatus)}`);
|
|
23444
|
-
console.log();
|
|
23445
|
-
printInfo(`Use 'brains finetune status ${jobId}' to check progress.`);
|
|
23446
|
-
} catch (err) {
|
|
23447
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23448
|
-
process.exit(1);
|
|
23449
|
-
}
|
|
23450
|
-
});
|
|
23451
|
-
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) => {
|
|
23452
|
-
try {
|
|
23453
|
-
let result;
|
|
23454
|
-
if (opts.provider === "openai") {
|
|
23455
|
-
result = await getFineTuneStatus(jobId);
|
|
23456
|
-
} else {
|
|
23457
|
-
const tl = new ThinkerLabsProvider;
|
|
23458
|
-
result = await tl.getFineTuneStatus(jobId);
|
|
23302
|
+
});
|
|
23303
|
+
modelsCmd.command("collection <id> <collectionName>").description("Set the collection of a model").action(async (id, collectionName) => {
|
|
23304
|
+
try {
|
|
23305
|
+
const db = getDb();
|
|
23306
|
+
await db.update(fineTunedModels).set({ collection: collectionName, updatedAt: Date.now() }).where(eq(fineTunedModels.id, id));
|
|
23307
|
+
printSuccess(`Collection set to "${collectionName}"`);
|
|
23308
|
+
} catch (err) {
|
|
23309
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23310
|
+
process.exit(1);
|
|
23459
23311
|
}
|
|
23460
|
-
|
|
23461
|
-
|
|
23462
|
-
|
|
23463
|
-
|
|
23464
|
-
|
|
23465
|
-
|
|
23466
|
-
|
|
23467
|
-
|
|
23312
|
+
});
|
|
23313
|
+
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) => {
|
|
23314
|
+
try {
|
|
23315
|
+
let result;
|
|
23316
|
+
if (opts.provider === "openai") {
|
|
23317
|
+
result = await getFineTuneStatus(jobId);
|
|
23318
|
+
} else {
|
|
23319
|
+
const tl = new ThinkerLabsProvider;
|
|
23320
|
+
result = await tl.getFineTuneStatus(jobId);
|
|
23468
23321
|
}
|
|
23469
|
-
|
|
23470
|
-
|
|
23322
|
+
const db = getDb();
|
|
23323
|
+
const [existing] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23324
|
+
if (existing) {
|
|
23325
|
+
printInfo(`Model already tracked as: ${existing.id}`);
|
|
23326
|
+
return;
|
|
23471
23327
|
}
|
|
23328
|
+
const modelId = randomUUID();
|
|
23329
|
+
const now = Date.now();
|
|
23330
|
+
const name = opts.name ?? result.fineTunedModel ?? `imported-${jobId}`;
|
|
23331
|
+
await db.insert(fineTunedModels).values({
|
|
23332
|
+
id: modelId,
|
|
23333
|
+
name,
|
|
23334
|
+
provider: opts.provider,
|
|
23335
|
+
baseModel: result.baseModel ?? "unknown",
|
|
23336
|
+
status: result.status,
|
|
23337
|
+
fineTuneJobId: jobId,
|
|
23338
|
+
createdAt: now,
|
|
23339
|
+
updatedAt: now
|
|
23340
|
+
});
|
|
23341
|
+
await db.insert(trainingJobs).values({
|
|
23342
|
+
id: randomUUID(),
|
|
23343
|
+
modelId,
|
|
23344
|
+
provider: opts.provider,
|
|
23345
|
+
status: result.status,
|
|
23346
|
+
startedAt: now
|
|
23347
|
+
});
|
|
23348
|
+
printSuccess(`Model imported successfully.`);
|
|
23472
23349
|
console.log();
|
|
23350
|
+
console.log(` Local ID: ${modelId}`);
|
|
23351
|
+
console.log(` Job ID: ${jobId}`);
|
|
23352
|
+
console.log(` Name: ${name}`);
|
|
23353
|
+
console.log(` Status: ${printStatus(result.status)}`);
|
|
23354
|
+
if (result.fineTunedModel)
|
|
23355
|
+
console.log(` Model: ${result.fineTunedModel}`);
|
|
23356
|
+
console.log();
|
|
23357
|
+
} catch (err) {
|
|
23358
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23359
|
+
process.exit(1);
|
|
23473
23360
|
}
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23361
|
+
});
|
|
23362
|
+
}
|
|
23363
|
+
|
|
23364
|
+
// src/cli/commands/finetune.ts
|
|
23365
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
23366
|
+
import { existsSync as existsSync9 } from "fs";
|
|
23367
|
+
function registerFinetuneCommands(program2) {
|
|
23368
|
+
const finetuneCmd = program2.command("finetune").description("Manage fine-tuning jobs");
|
|
23369
|
+
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) => {
|
|
23370
|
+
try {
|
|
23371
|
+
if (opts.provider !== "openai" && opts.provider !== "thinker-labs") {
|
|
23372
|
+
printError(`Unknown provider: ${opts.provider}. Use 'openai' or 'thinker-labs'.`);
|
|
23373
|
+
process.exit(1);
|
|
23374
|
+
}
|
|
23375
|
+
let datasetPath = opts.dataset;
|
|
23376
|
+
if (!datasetPath) {
|
|
23377
|
+
const db2 = getDb();
|
|
23378
|
+
const [latest] = await db2.select().from(trainingDatasets).orderBy(desc(trainingDatasets.createdAt)).limit(1);
|
|
23379
|
+
if (!latest?.filePath) {
|
|
23380
|
+
printError("No datasets found. Run 'brains data gather' first.");
|
|
23381
|
+
process.exit(1);
|
|
23382
|
+
}
|
|
23383
|
+
datasetPath = latest.filePath;
|
|
23384
|
+
printInfo(`Using latest dataset: ${datasetPath} (${latest.exampleCount} examples)`);
|
|
23385
|
+
}
|
|
23386
|
+
if (!existsSync9(datasetPath)) {
|
|
23387
|
+
printError(`Dataset file not found: ${datasetPath}`);
|
|
23388
|
+
process.exit(1);
|
|
23389
|
+
}
|
|
23390
|
+
printInfo(`Uploading training file: ${datasetPath} \u2026`);
|
|
23391
|
+
let fileId;
|
|
23392
|
+
let jobId;
|
|
23393
|
+
let jobStatus;
|
|
23394
|
+
if (opts.provider === "openai") {
|
|
23395
|
+
({ fileId } = await uploadTrainingFile(datasetPath));
|
|
23396
|
+
printSuccess(`File uploaded. fileId = ${fileId}`);
|
|
23397
|
+
printInfo(`Creating fine-tune job on OpenAI \u2026`);
|
|
23398
|
+
({ jobId, status: jobStatus } = await createFineTuneJob(fileId, opts.baseModel, opts.name));
|
|
23399
|
+
} else {
|
|
23400
|
+
const tl = new ThinkerLabsProvider;
|
|
23401
|
+
({ fileId } = await tl.uploadTrainingFile(datasetPath));
|
|
23402
|
+
printSuccess(`File uploaded. fileId = ${fileId}`);
|
|
23403
|
+
printInfo(`Creating fine-tune job on Thinker Labs \u2026`);
|
|
23404
|
+
({ jobId, status: jobStatus } = await tl.createFineTuneJob(fileId, opts.baseModel, opts.name));
|
|
23405
|
+
}
|
|
23406
|
+
const db = getDb();
|
|
23407
|
+
const modelId = randomUUID2();
|
|
23408
|
+
const now = Date.now();
|
|
23409
|
+
await db.insert(fineTunedModels).values({
|
|
23410
|
+
id: modelId,
|
|
23411
|
+
name: opts.name,
|
|
23412
|
+
provider: opts.provider,
|
|
23413
|
+
baseModel: opts.baseModel,
|
|
23414
|
+
status: "running",
|
|
23415
|
+
fineTuneJobId: jobId,
|
|
23416
|
+
createdAt: now,
|
|
23417
|
+
updatedAt: now
|
|
23418
|
+
});
|
|
23419
|
+
const trainingJobId = randomUUID2();
|
|
23420
|
+
await db.insert(trainingJobs).values({
|
|
23421
|
+
id: trainingJobId,
|
|
23422
|
+
modelId,
|
|
23423
|
+
provider: opts.provider,
|
|
23424
|
+
status: jobStatus,
|
|
23425
|
+
startedAt: now
|
|
23426
|
+
});
|
|
23427
|
+
printSuccess(`Fine-tune job started!`);
|
|
23428
|
+
console.log();
|
|
23429
|
+
console.log(` Model ID: ${modelId}`);
|
|
23430
|
+
console.log(` Job ID: ${jobId}`);
|
|
23431
|
+
console.log(` Status: ${printStatus(jobStatus)}`);
|
|
23432
|
+
console.log();
|
|
23433
|
+
printInfo(`Use 'brains finetune status ${jobId}' to check progress.`);
|
|
23434
|
+
} catch (err) {
|
|
23435
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23436
|
+
process.exit(1);
|
|
23479
23437
|
}
|
|
23480
|
-
}
|
|
23481
|
-
|
|
23482
|
-
process.exit(1);
|
|
23483
|
-
}
|
|
23484
|
-
});
|
|
23485
|
-
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) => {
|
|
23486
|
-
const intervalMs = Math.max(5, parseInt(opts.interval, 10) || 30) * 1000;
|
|
23487
|
-
const terminalStates = new Set(["succeeded", "failed", "cancelled"]);
|
|
23488
|
-
printInfo(`Watching job ${jobId} (polling every ${intervalMs / 1000}s) \u2026`);
|
|
23489
|
-
console.log();
|
|
23490
|
-
const poll = async () => {
|
|
23438
|
+
});
|
|
23439
|
+
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) => {
|
|
23491
23440
|
try {
|
|
23492
23441
|
let result;
|
|
23493
23442
|
if (opts.provider === "openai") {
|
|
@@ -23496,255 +23445,299 @@ finetuneCmd.command("watch <job-id>").description("Poll a fine-tuning job until
|
|
|
23496
23445
|
const tl = new ThinkerLabsProvider;
|
|
23497
23446
|
result = await tl.getFineTuneStatus(jobId);
|
|
23498
23447
|
}
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
23503
|
-
|
|
23504
|
-
`);
|
|
23448
|
+
if (opts.json) {
|
|
23449
|
+
printJson(result);
|
|
23450
|
+
} else {
|
|
23451
|
+
console.log();
|
|
23452
|
+
console.log(` Job ID: ${result.jobId}`);
|
|
23453
|
+
console.log(` Status: ${printStatus(result.status)}`);
|
|
23454
|
+
if (result.fineTunedModel)
|
|
23455
|
+
console.log(` Fine-tuned model: ${result.fineTunedModel}`);
|
|
23456
|
+
if (result.error)
|
|
23457
|
+
console.log(` Error: ${result.error}`);
|
|
23458
|
+
console.log();
|
|
23459
|
+
}
|
|
23505
23460
|
const db = getDb();
|
|
23506
23461
|
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23507
23462
|
if (model) {
|
|
23508
|
-
|
|
23463
|
+
const status = result.status;
|
|
23464
|
+
await db.update(fineTunedModels).set({ status, updatedAt: Date.now() }).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23509
23465
|
}
|
|
23510
|
-
|
|
23511
|
-
|
|
23512
|
-
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
|
|
23516
|
-
|
|
23517
|
-
|
|
23466
|
+
} catch (err) {
|
|
23467
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23468
|
+
process.exit(1);
|
|
23469
|
+
}
|
|
23470
|
+
});
|
|
23471
|
+
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) => {
|
|
23472
|
+
const intervalMs = Math.max(5, parseInt(opts.interval, 10) || 30) * 1000;
|
|
23473
|
+
const terminalStates = new Set(["succeeded", "failed", "cancelled"]);
|
|
23474
|
+
printInfo(`Watching job ${jobId} (polling every ${intervalMs / 1000}s) \u2026`);
|
|
23475
|
+
console.log();
|
|
23476
|
+
const poll = async () => {
|
|
23477
|
+
try {
|
|
23478
|
+
let result;
|
|
23479
|
+
if (opts.provider === "openai") {
|
|
23480
|
+
result = await getFineTuneStatus(jobId);
|
|
23518
23481
|
} else {
|
|
23519
|
-
|
|
23482
|
+
const tl = new ThinkerLabsProvider;
|
|
23483
|
+
result = await tl.getFineTuneStatus(jobId);
|
|
23484
|
+
}
|
|
23485
|
+
const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
23486
|
+
process.stdout.write(` [${ts}] ${printStatus(result.status)}`);
|
|
23487
|
+
if (result.fineTunedModel)
|
|
23488
|
+
process.stdout.write(` model: ${result.fineTunedModel}`);
|
|
23489
|
+
process.stdout.write(`
|
|
23490
|
+
`);
|
|
23491
|
+
const db = getDb();
|
|
23492
|
+
const [model] = await db.select().from(fineTunedModels).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23493
|
+
if (model) {
|
|
23494
|
+
await db.update(fineTunedModels).set({ status: result.status, updatedAt: Date.now() }).where(eq(fineTunedModels.fineTuneJobId, jobId));
|
|
23495
|
+
}
|
|
23496
|
+
if (terminalStates.has(result.status)) {
|
|
23497
|
+
console.log();
|
|
23498
|
+
if (result.status === "succeeded") {
|
|
23499
|
+
printSuccess(`Job completed successfully.`);
|
|
23500
|
+
if (result.fineTunedModel)
|
|
23501
|
+
printSuccess(`Fine-tuned model: ${result.fineTunedModel}`);
|
|
23502
|
+
} else if (result.status === "failed") {
|
|
23503
|
+
printError(`Job failed.${result.error ? " Error: " + result.error : ""}`);
|
|
23504
|
+
} else {
|
|
23505
|
+
printInfo(`Job ${result.status}.`);
|
|
23506
|
+
}
|
|
23507
|
+
return true;
|
|
23520
23508
|
}
|
|
23521
|
-
return
|
|
23509
|
+
return false;
|
|
23510
|
+
} catch (err) {
|
|
23511
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23512
|
+
return false;
|
|
23522
23513
|
}
|
|
23523
|
-
|
|
23514
|
+
};
|
|
23515
|
+
const done = await poll();
|
|
23516
|
+
if (!done) {
|
|
23517
|
+
await new Promise((resolve2) => {
|
|
23518
|
+
const timer = setInterval(async () => {
|
|
23519
|
+
const finished = await poll();
|
|
23520
|
+
if (finished) {
|
|
23521
|
+
clearInterval(timer);
|
|
23522
|
+
resolve2();
|
|
23523
|
+
}
|
|
23524
|
+
}, intervalMs);
|
|
23525
|
+
});
|
|
23526
|
+
}
|
|
23527
|
+
});
|
|
23528
|
+
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) => {
|
|
23529
|
+
try {
|
|
23530
|
+
let jobs;
|
|
23531
|
+
if (opts.provider === "openai") {
|
|
23532
|
+
jobs = await listFineTunedModels();
|
|
23533
|
+
} else {
|
|
23534
|
+
const tl = new ThinkerLabsProvider;
|
|
23535
|
+
jobs = await tl.listFineTunedModels();
|
|
23536
|
+
}
|
|
23537
|
+
if (opts.json) {
|
|
23538
|
+
printJson(jobs);
|
|
23539
|
+
return;
|
|
23540
|
+
}
|
|
23541
|
+
if (jobs.length === 0) {
|
|
23542
|
+
printInfo("No fine-tuning jobs found.");
|
|
23543
|
+
return;
|
|
23544
|
+
}
|
|
23545
|
+
printTable(["Job ID", "Model", "Status", "Created"], jobs.map((j) => [
|
|
23546
|
+
j.id,
|
|
23547
|
+
j.model,
|
|
23548
|
+
printStatus(j.status),
|
|
23549
|
+
new Date(j.created * 1000).toISOString().split("T")[0] ?? ""
|
|
23550
|
+
]));
|
|
23524
23551
|
} catch (err) {
|
|
23525
23552
|
printError(err instanceof Error ? err.message : String(err));
|
|
23526
|
-
|
|
23527
|
-
}
|
|
23528
|
-
};
|
|
23529
|
-
const done = await poll();
|
|
23530
|
-
if (!done) {
|
|
23531
|
-
await new Promise((resolve2) => {
|
|
23532
|
-
const timer = setInterval(async () => {
|
|
23533
|
-
const finished = await poll();
|
|
23534
|
-
if (finished) {
|
|
23535
|
-
clearInterval(timer);
|
|
23536
|
-
resolve2();
|
|
23537
|
-
}
|
|
23538
|
-
}, intervalMs);
|
|
23539
|
-
});
|
|
23540
|
-
}
|
|
23541
|
-
});
|
|
23542
|
-
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) => {
|
|
23543
|
-
try {
|
|
23544
|
-
let jobs;
|
|
23545
|
-
if (opts.provider === "openai") {
|
|
23546
|
-
jobs = await listFineTunedModels();
|
|
23547
|
-
} else {
|
|
23548
|
-
const tl = new ThinkerLabsProvider;
|
|
23549
|
-
jobs = await tl.listFineTunedModels();
|
|
23553
|
+
process.exit(1);
|
|
23550
23554
|
}
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23555
|
+
});
|
|
23556
|
+
}
|
|
23557
|
+
|
|
23558
|
+
// src/cli/commands/data.ts
|
|
23559
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
23560
|
+
import { readFileSync as readFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
23561
|
+
import { join as join13 } from "path";
|
|
23562
|
+
import { homedir as homedir12 } from "os";
|
|
23563
|
+
var DEFAULT_DATASETS_DIR = join13(homedir12(), ".hasna", "brains", "datasets");
|
|
23564
|
+
function registerDataCommands(program2) {
|
|
23565
|
+
const dataCmd = program2.command("data").description("Manage training datasets");
|
|
23566
|
+
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) => {
|
|
23567
|
+
const validSources = ["todos", "mementos", "conversations", "sessions", "all"];
|
|
23568
|
+
if (!validSources.includes(opts.source)) {
|
|
23569
|
+
printError(`Invalid source: ${opts.source}. Choose one of: ${validSources.join(", ")}`);
|
|
23570
|
+
process.exit(1);
|
|
23554
23571
|
}
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
23572
|
+
const limit2 = parseInt(opts.limit, 10);
|
|
23573
|
+
if (isNaN(limit2) || limit2 <= 0) {
|
|
23574
|
+
printError(`Invalid --limit value: ${opts.limit}`);
|
|
23575
|
+
process.exit(1);
|
|
23558
23576
|
}
|
|
23559
|
-
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
|
|
23569
|
-
}
|
|
23570
|
-
|
|
23571
|
-
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
}
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
}
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
const gathererMap = {
|
|
23589
|
-
todos: (o) => Promise.resolve().then(() => (init_todos(), exports_todos)).then((m) => m.gatherFromTodos(o)),
|
|
23590
|
-
mementos: (o) => Promise.resolve().then(() => (init_mementos(), exports_mementos)).then((m) => m.gatherFromMementos(o)),
|
|
23591
|
-
conversations: (o) => Promise.resolve().then(() => (init_conversations(), exports_conversations)).then((m) => m.gatherFromConversations(o)),
|
|
23592
|
-
sessions: (o) => Promise.resolve().then(() => (init_sessions(), exports_sessions2)).then((m) => m.gatherFromSessions(o))
|
|
23593
|
-
};
|
|
23594
|
-
let totalExamples = 0;
|
|
23595
|
-
let successfulSources = 0;
|
|
23596
|
-
for (const source of sources) {
|
|
23597
|
-
printInfo(`Gathering from ${source} \u2026`);
|
|
23598
|
-
try {
|
|
23599
|
-
const gatherer = gathererMap[source];
|
|
23600
|
-
if (!gatherer) {
|
|
23601
|
-
printError(` Unknown source: ${source}`);
|
|
23602
|
-
continue;
|
|
23603
|
-
}
|
|
23604
|
-
const { examples, count } = await gatherer({ limit: limit2 });
|
|
23605
|
-
if (count === 0) {
|
|
23606
|
-
printInfo(` No examples found in ${source}.`);
|
|
23607
|
-
continue;
|
|
23608
|
-
}
|
|
23609
|
-
const fileName = `${source}-${now}.jsonl`;
|
|
23610
|
-
const filePath = join13(opts.output, fileName);
|
|
23611
|
-
writeFileSync5(filePath, examples.map((e) => JSON.stringify(e)).join(`
|
|
23577
|
+
try {
|
|
23578
|
+
mkdirSync6(opts.output, { recursive: true });
|
|
23579
|
+
const sources = opts.source === "all" ? ["todos", "mementos", "conversations", "sessions"] : [opts.source];
|
|
23580
|
+
const now = Date.now();
|
|
23581
|
+
const db = getDb();
|
|
23582
|
+
const gathererMap = {
|
|
23583
|
+
todos: (o) => Promise.resolve().then(() => (init_todos(), exports_todos)).then((m) => m.gatherFromTodos(o)),
|
|
23584
|
+
mementos: (o) => Promise.resolve().then(() => (init_mementos(), exports_mementos)).then((m) => m.gatherFromMementos(o)),
|
|
23585
|
+
conversations: (o) => Promise.resolve().then(() => (init_conversations(), exports_conversations)).then((m) => m.gatherFromConversations(o)),
|
|
23586
|
+
sessions: (o) => Promise.resolve().then(() => (init_sessions(), exports_sessions2)).then((m) => m.gatherFromSessions(o))
|
|
23587
|
+
};
|
|
23588
|
+
let totalExamples = 0;
|
|
23589
|
+
let successfulSources = 0;
|
|
23590
|
+
for (const source of sources) {
|
|
23591
|
+
printInfo(`Gathering from ${source} \u2026`);
|
|
23592
|
+
try {
|
|
23593
|
+
const gatherer = gathererMap[source];
|
|
23594
|
+
if (!gatherer) {
|
|
23595
|
+
printError(` Unknown source: ${source}`);
|
|
23596
|
+
continue;
|
|
23597
|
+
}
|
|
23598
|
+
const { examples, count } = await gatherer({ limit: limit2 });
|
|
23599
|
+
if (count === 0) {
|
|
23600
|
+
printInfo(` No examples found in ${source}.`);
|
|
23601
|
+
continue;
|
|
23602
|
+
}
|
|
23603
|
+
const fileName = `${source}-${now}.jsonl`;
|
|
23604
|
+
const filePath = join13(opts.output, fileName);
|
|
23605
|
+
writeFileSync5(filePath, examples.map((e) => JSON.stringify(e)).join(`
|
|
23612
23606
|
`) + `
|
|
23613
23607
|
`, "utf8");
|
|
23614
|
-
|
|
23615
|
-
|
|
23616
|
-
|
|
23617
|
-
|
|
23618
|
-
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
23625
|
-
|
|
23608
|
+
await db.insert(trainingDatasets).values({
|
|
23609
|
+
id: randomUUID3(),
|
|
23610
|
+
source,
|
|
23611
|
+
filePath,
|
|
23612
|
+
exampleCount: count,
|
|
23613
|
+
createdAt: now
|
|
23614
|
+
});
|
|
23615
|
+
printSuccess(` \u2713 ${count} examples \u2192 ${filePath}`);
|
|
23616
|
+
totalExamples += count;
|
|
23617
|
+
successfulSources++;
|
|
23618
|
+
} catch (sourceErr) {
|
|
23619
|
+
printError(` \u2717 ${source}: ${sourceErr instanceof Error ? sourceErr.message : String(sourceErr)}`);
|
|
23620
|
+
}
|
|
23626
23621
|
}
|
|
23622
|
+
console.log();
|
|
23623
|
+
printSuccess(`Total: ${totalExamples} examples from ${successfulSources} source(s)`);
|
|
23624
|
+
} catch (err) {
|
|
23625
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23626
|
+
process.exit(1);
|
|
23627
23627
|
}
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23632
|
-
|
|
23633
|
-
}
|
|
23634
|
-
});
|
|
23635
|
-
dataCmd.command("preview <file>").description("Preview a JSONL training file").option("-n, --count <n>", "Number of examples to show", "5").action((file, opts) => {
|
|
23636
|
-
if (!existsSync10(file)) {
|
|
23637
|
-
printError(`File not found: ${file}`);
|
|
23638
|
-
process.exit(1);
|
|
23639
|
-
}
|
|
23640
|
-
const n = parseInt(opts.count, 10);
|
|
23641
|
-
if (isNaN(n) || n <= 0) {
|
|
23642
|
-
printError(`Invalid --count value: ${opts.count}`);
|
|
23643
|
-
process.exit(1);
|
|
23644
|
-
}
|
|
23645
|
-
try {
|
|
23646
|
-
const content = readFileSync6(file, "utf8");
|
|
23647
|
-
const lines = content.trim().split(`
|
|
23648
|
-
`).filter(Boolean);
|
|
23649
|
-
const total = lines.length;
|
|
23650
|
-
const preview = lines.slice(0, n);
|
|
23651
|
-
console.log();
|
|
23652
|
-
printInfo(`File: ${file}`);
|
|
23653
|
-
printInfo(`Total examples: ${total}`);
|
|
23654
|
-
printInfo(`Showing first ${Math.min(n, total)}:`);
|
|
23655
|
-
console.log();
|
|
23656
|
-
preview.forEach((line, idx) => {
|
|
23657
|
-
try {
|
|
23658
|
-
const parsed = JSON.parse(line);
|
|
23659
|
-
console.log(`\u2500\u2500\u2500 Example ${idx + 1} \u2500\u2500\u2500`);
|
|
23660
|
-
printJson(parsed);
|
|
23661
|
-
console.log();
|
|
23662
|
-
} catch {
|
|
23663
|
-
printError(` Line ${idx + 1} is not valid JSON: ${line.slice(0, 80)}\u2026`);
|
|
23664
|
-
}
|
|
23665
|
-
});
|
|
23666
|
-
} catch (err) {
|
|
23667
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23668
|
-
process.exit(1);
|
|
23669
|
-
}
|
|
23670
|
-
});
|
|
23671
|
-
dataCmd.command("merge <files...>").description("Merge multiple JSONL datasets into one").option("--output <path>", "Output file path", join13(DEFAULT_DATASETS_DIR, `merged-${Date.now()}.jsonl`)).option("--no-dedupe", "Skip deduplication").action(async (files, opts) => {
|
|
23672
|
-
try {
|
|
23673
|
-
for (const f of files) {
|
|
23674
|
-
if (!existsSync10(f)) {
|
|
23675
|
-
printError(`File not found: ${f}`);
|
|
23676
|
-
process.exit(1);
|
|
23677
|
-
}
|
|
23628
|
+
});
|
|
23629
|
+
dataCmd.command("preview <file>").description("Preview a JSONL training file").option("-n, --count <n>", "Number of examples to show", "5").action((file, opts) => {
|
|
23630
|
+
if (!existsSync11(file)) {
|
|
23631
|
+
printError(`File not found: ${file}`);
|
|
23632
|
+
process.exit(1);
|
|
23678
23633
|
}
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23689
|
-
|
|
23690
|
-
|
|
23691
|
-
|
|
23692
|
-
|
|
23693
|
-
|
|
23694
|
-
|
|
23634
|
+
const n = parseInt(opts.count, 10);
|
|
23635
|
+
if (isNaN(n) || n <= 0) {
|
|
23636
|
+
printError(`Invalid --count value: ${opts.count}`);
|
|
23637
|
+
process.exit(1);
|
|
23638
|
+
}
|
|
23639
|
+
try {
|
|
23640
|
+
const content = readFileSync6(file, "utf8");
|
|
23641
|
+
const lines = content.trim().split(`
|
|
23642
|
+
`).filter(Boolean);
|
|
23643
|
+
const total = lines.length;
|
|
23644
|
+
const preview = lines.slice(0, n);
|
|
23645
|
+
console.log();
|
|
23646
|
+
printInfo(`File: ${file}`);
|
|
23647
|
+
printInfo(`Total examples: ${total}`);
|
|
23648
|
+
printInfo(`Showing first ${Math.min(n, total)}:`);
|
|
23649
|
+
console.log();
|
|
23650
|
+
preview.forEach((line, idx) => {
|
|
23651
|
+
try {
|
|
23652
|
+
const parsed = JSON.parse(line);
|
|
23653
|
+
console.log(`\u2500\u2500\u2500 Example ${idx + 1} \u2500\u2500\u2500`);
|
|
23654
|
+
printJson(parsed);
|
|
23655
|
+
console.log();
|
|
23656
|
+
} catch {
|
|
23657
|
+
printError(` Line ${idx + 1} is not valid JSON: ${line.slice(0, 80)}\u2026`);
|
|
23695
23658
|
}
|
|
23696
|
-
seen.add(line);
|
|
23697
|
-
return true;
|
|
23698
23659
|
});
|
|
23660
|
+
} catch (err) {
|
|
23661
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23662
|
+
process.exit(1);
|
|
23699
23663
|
}
|
|
23700
|
-
|
|
23664
|
+
});
|
|
23665
|
+
dataCmd.command("merge <files...>").description("Merge multiple JSONL datasets into one").option("--output <path>", "Output file path", join13(DEFAULT_DATASETS_DIR, `merged-${Date.now()}.jsonl`)).option("--no-dedupe", "Skip deduplication").action(async (files, opts) => {
|
|
23666
|
+
try {
|
|
23667
|
+
for (const f of files) {
|
|
23668
|
+
if (!existsSync11(f)) {
|
|
23669
|
+
printError(`File not found: ${f}`);
|
|
23670
|
+
process.exit(1);
|
|
23671
|
+
}
|
|
23672
|
+
}
|
|
23673
|
+
mkdirSync6(join13(opts.output, "..").replace(/\/\.\.$/, "") || DEFAULT_DATASETS_DIR, { recursive: true });
|
|
23674
|
+
const allExamples = [];
|
|
23675
|
+
for (const f of files) {
|
|
23676
|
+
const lines = readFileSync6(f, "utf8").split(`
|
|
23677
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
23678
|
+
allExamples.push(...lines);
|
|
23679
|
+
printInfo(` Read ${lines.length} examples from ${f}`);
|
|
23680
|
+
}
|
|
23681
|
+
let finalLines = allExamples;
|
|
23682
|
+
let dupeCount = 0;
|
|
23683
|
+
if (opts.dedupe) {
|
|
23684
|
+
const seen = new Set;
|
|
23685
|
+
finalLines = allExamples.filter((line) => {
|
|
23686
|
+
if (seen.has(line)) {
|
|
23687
|
+
dupeCount++;
|
|
23688
|
+
return false;
|
|
23689
|
+
}
|
|
23690
|
+
seen.add(line);
|
|
23691
|
+
return true;
|
|
23692
|
+
});
|
|
23693
|
+
}
|
|
23694
|
+
writeFileSync5(opts.output, finalLines.join(`
|
|
23701
23695
|
`) + `
|
|
23702
23696
|
`, "utf8");
|
|
23703
|
-
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
|
|
23707
|
-
|
|
23708
|
-
|
|
23709
|
-
|
|
23710
|
-
|
|
23711
|
-
|
|
23712
|
-
|
|
23713
|
-
|
|
23714
|
-
|
|
23715
|
-
|
|
23716
|
-
|
|
23717
|
-
|
|
23718
|
-
}
|
|
23719
|
-
});
|
|
23720
|
-
dataCmd.command("list").description("List all gathered datasets").option("--json", "Output as JSON").action(async (opts) => {
|
|
23721
|
-
try {
|
|
23722
|
-
const db = getDb();
|
|
23723
|
-
const datasets = await db.select().from(trainingDatasets);
|
|
23724
|
-
if (opts.json) {
|
|
23725
|
-
printJson(datasets);
|
|
23726
|
-
return;
|
|
23697
|
+
const db = getDb();
|
|
23698
|
+
await db.insert(trainingDatasets).values({
|
|
23699
|
+
id: randomUUID3(),
|
|
23700
|
+
source: "mixed",
|
|
23701
|
+
filePath: opts.output,
|
|
23702
|
+
exampleCount: finalLines.length,
|
|
23703
|
+
createdAt: Date.now()
|
|
23704
|
+
});
|
|
23705
|
+
console.log();
|
|
23706
|
+
printSuccess(`Merged ${files.length} files \u2014 ${finalLines.length} examples \u2192 ${opts.output}`);
|
|
23707
|
+
if (opts.dedupe && dupeCount > 0)
|
|
23708
|
+
printInfo(` Removed ${dupeCount} duplicate(s)`);
|
|
23709
|
+
} catch (err) {
|
|
23710
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23711
|
+
process.exit(1);
|
|
23727
23712
|
}
|
|
23728
|
-
|
|
23729
|
-
|
|
23730
|
-
|
|
23713
|
+
});
|
|
23714
|
+
dataCmd.command("list").description("List all gathered datasets").option("--json", "Output as JSON").action(async (opts) => {
|
|
23715
|
+
try {
|
|
23716
|
+
const db = getDb();
|
|
23717
|
+
const datasets = await db.select().from(trainingDatasets);
|
|
23718
|
+
if (opts.json) {
|
|
23719
|
+
printJson(datasets);
|
|
23720
|
+
return;
|
|
23721
|
+
}
|
|
23722
|
+
if (datasets.length === 0) {
|
|
23723
|
+
printInfo("No datasets found. Use 'brains data gather' to create one.");
|
|
23724
|
+
return;
|
|
23725
|
+
}
|
|
23726
|
+
printTable(["ID", "Source", "Examples", "File", "Created"], datasets.map((d) => [
|
|
23727
|
+
d.id,
|
|
23728
|
+
d.source,
|
|
23729
|
+
String(d.exampleCount),
|
|
23730
|
+
d.filePath ?? "",
|
|
23731
|
+
new Date(d.createdAt).toISOString().split("T")[0] ?? ""
|
|
23732
|
+
]));
|
|
23733
|
+
} catch (err) {
|
|
23734
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23735
|
+
process.exit(1);
|
|
23731
23736
|
}
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
d.filePath ?? "",
|
|
23737
|
-
new Date(d.createdAt).toISOString().split("T")[0] ?? ""
|
|
23738
|
-
]));
|
|
23739
|
-
} catch (err) {
|
|
23740
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
23741
|
-
process.exit(1);
|
|
23742
|
-
}
|
|
23743
|
-
});
|
|
23744
|
-
var collectionsCmd = program2.command("collections").description("Manage model collections");
|
|
23745
|
-
collectionsCmd.option("--json", "Output as JSON").action(async (opts) => {
|
|
23746
|
-
await listCollections(opts.json);
|
|
23747
|
-
});
|
|
23737
|
+
});
|
|
23738
|
+
}
|
|
23739
|
+
|
|
23740
|
+
// src/cli/commands/collections.ts
|
|
23748
23741
|
async function listCollections(json = false) {
|
|
23749
23742
|
try {
|
|
23750
23743
|
const db = getDb();
|
|
@@ -23761,45 +23754,224 @@ async function listCollections(json = false) {
|
|
|
23761
23754
|
printInfo("No collections found. Set a collection with 'brains models set-collection'.");
|
|
23762
23755
|
return;
|
|
23763
23756
|
}
|
|
23764
|
-
printTable(["Collection", "Model Count", "Models"], rows.map((r) => [
|
|
23765
|
-
r.collection ?? "(none)",
|
|
23766
|
-
String(r.count),
|
|
23767
|
-
r.names ?? ""
|
|
23768
|
-
]));
|
|
23757
|
+
printTable(["Collection", "Model Count", "Models"], rows.map((r) => [r.collection ?? "(none)", String(r.count), r.names ?? ""]));
|
|
23769
23758
|
} catch (err) {
|
|
23770
23759
|
printError(err instanceof Error ? err.message : String(err));
|
|
23771
23760
|
process.exit(1);
|
|
23772
23761
|
}
|
|
23773
23762
|
}
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
|
|
23781
|
-
|
|
23782
|
-
|
|
23783
|
-
|
|
23763
|
+
function registerCollectionsCommands(program2) {
|
|
23764
|
+
const collectionsCmd = program2.command("collections").description("Manage model collections");
|
|
23765
|
+
collectionsCmd.option("--json", "Output as JSON").action(async (opts) => {
|
|
23766
|
+
await listCollections(opts.json);
|
|
23767
|
+
});
|
|
23768
|
+
collectionsCmd.command("list").description("List all collections with model counts").option("--json", "Output as JSON").action(async (opts) => {
|
|
23769
|
+
await listCollections(opts.json);
|
|
23770
|
+
});
|
|
23771
|
+
collectionsCmd.command("show <name>").description("List all models in a collection").action(async (name) => {
|
|
23772
|
+
try {
|
|
23773
|
+
const db = getDb();
|
|
23774
|
+
const models = await db.select().from(fineTunedModels).where(eq(fineTunedModels.collection, name));
|
|
23775
|
+
if (models.length === 0) {
|
|
23776
|
+
printInfo(`No models found in collection '${name}'.`);
|
|
23777
|
+
return;
|
|
23778
|
+
}
|
|
23779
|
+
printTable(["ID", "Name", "Provider", "Status", "Base Model"], models.map((m) => [m.id, m.name, m.provider, printStatus(m.status), m.baseModel]));
|
|
23780
|
+
} catch (err) {
|
|
23781
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23782
|
+
process.exit(1);
|
|
23784
23783
|
}
|
|
23785
|
-
|
|
23786
|
-
|
|
23787
|
-
|
|
23788
|
-
|
|
23789
|
-
|
|
23790
|
-
});
|
|
23791
|
-
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23795
|
-
|
|
23796
|
-
|
|
23797
|
-
|
|
23798
|
-
|
|
23799
|
-
|
|
23800
|
-
|
|
23801
|
-
|
|
23802
|
-
|
|
23784
|
+
});
|
|
23785
|
+
collectionsCmd.command("rename <oldName> <newName>").description("Rename a collection across all models").action(async (oldName, newName) => {
|
|
23786
|
+
try {
|
|
23787
|
+
const db = getDb();
|
|
23788
|
+
const affected = await db.select({ id: fineTunedModels.id }).from(fineTunedModels).where(eq(fineTunedModels.collection, oldName));
|
|
23789
|
+
await db.update(fineTunedModels).set({ collection: newName, updatedAt: Date.now() }).where(eq(fineTunedModels.collection, oldName));
|
|
23790
|
+
printSuccess(`Renamed collection '${oldName}' \u2192 '${newName}' (${affected.length} models updated)`);
|
|
23791
|
+
} catch (err) {
|
|
23792
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
23793
|
+
process.exit(1);
|
|
23794
|
+
}
|
|
23795
|
+
});
|
|
23796
|
+
}
|
|
23797
|
+
|
|
23798
|
+
// src/cli/commands/cloud.ts
|
|
23799
|
+
function registerCloudCommands2(program2) {
|
|
23800
|
+
const cloudCmd = program2.command("cloud").description("Cloud sync commands");
|
|
23801
|
+
cloudCmd.command("status").description("Show cloud config and connection health").option("--json", "Output as JSON").action(async (opts) => {
|
|
23802
|
+
try {
|
|
23803
|
+
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, PgAdapterAsync: PgAdapterAsync2, SqliteAdapter: SqliteAdapter2, getDbPath: getDbPath2, listSqliteTables: listSqliteTables2, ensureConflictsTable: ensureConflictsTable2, listConflicts: listConflicts2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23804
|
+
const config = getCloudConfig2();
|
|
23805
|
+
const info = {
|
|
23806
|
+
mode: config.mode,
|
|
23807
|
+
service: "brains",
|
|
23808
|
+
rds_host: config.rds?.host || "(not configured)"
|
|
23809
|
+
};
|
|
23810
|
+
if (config.rds?.host && config.rds?.username) {
|
|
23811
|
+
try {
|
|
23812
|
+
const pg = new PgAdapterAsync2(getConnectionString2("postgres"));
|
|
23813
|
+
await pg.get("SELECT 1 as ok");
|
|
23814
|
+
info.postgresql = "connected";
|
|
23815
|
+
await pg.close();
|
|
23816
|
+
} catch (err) {
|
|
23817
|
+
info.postgresql = `failed \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
23818
|
+
}
|
|
23819
|
+
}
|
|
23820
|
+
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23821
|
+
const tables = listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
23822
|
+
const syncHealth = [];
|
|
23823
|
+
for (const table of tables) {
|
|
23824
|
+
try {
|
|
23825
|
+
const totalRow = local.get(`SELECT COUNT(*) as c FROM "${table}"`);
|
|
23826
|
+
const unsyncedRow = local.get(`SELECT COUNT(*) as c FROM "${table}" WHERE synced_at IS NULL`);
|
|
23827
|
+
syncHealth.push({ table, total: totalRow?.c ?? 0, unsynced: unsyncedRow?.c ?? 0 });
|
|
23828
|
+
} catch {}
|
|
23829
|
+
}
|
|
23830
|
+
info.sync_health = syncHealth.filter((s) => s.total > 0);
|
|
23831
|
+
try {
|
|
23832
|
+
ensureConflictsTable2(local);
|
|
23833
|
+
const unresolved = listConflicts2(local, { resolved: false });
|
|
23834
|
+
info.conflicts_unresolved = unresolved.length;
|
|
23835
|
+
} catch {}
|
|
23836
|
+
local.close();
|
|
23837
|
+
if (opts.json) {
|
|
23838
|
+
console.log(JSON.stringify(info, null, 2));
|
|
23839
|
+
return;
|
|
23840
|
+
}
|
|
23841
|
+
printInfo(`Mode: ${info.mode}`);
|
|
23842
|
+
printInfo(`RDS Host: ${info.rds_host}`);
|
|
23843
|
+
if (info.postgresql)
|
|
23844
|
+
printInfo(`PostgreSQL: ${info.postgresql}`);
|
|
23845
|
+
for (const s of info.sync_health) {
|
|
23846
|
+
const pct = s.total > 0 ? Math.round((s.total - s.unsynced) / s.total * 100) : 100;
|
|
23847
|
+
printInfo(` ${s.table}: ${pct}% synced (${s.unsynced} unsynced / ${s.total} total)`);
|
|
23848
|
+
}
|
|
23849
|
+
if (info.conflicts_unresolved)
|
|
23850
|
+
printInfo(`Conflicts: ${info.conflicts_unresolved} unresolved`);
|
|
23851
|
+
} catch (e) {
|
|
23852
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
23853
|
+
process.exit(1);
|
|
23854
|
+
}
|
|
23855
|
+
});
|
|
23856
|
+
cloudCmd.command("push").description("Push local data to cloud PostgreSQL").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
23857
|
+
try {
|
|
23858
|
+
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPush: syncPush2, listSqliteTables: listSqliteTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23859
|
+
const config = getCloudConfig2();
|
|
23860
|
+
if (config.mode === "local") {
|
|
23861
|
+
printError("Cloud mode not configured.");
|
|
23862
|
+
process.exit(1);
|
|
23863
|
+
}
|
|
23864
|
+
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23865
|
+
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
23866
|
+
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
23867
|
+
const results = await syncPush2(local, cloud, {
|
|
23868
|
+
tables: tableList,
|
|
23869
|
+
onProgress: (p) => {
|
|
23870
|
+
if (!opts.json && p.phase === "done")
|
|
23871
|
+
printInfo(` ${p.table}: ${p.rowsWritten} rows pushed`);
|
|
23872
|
+
}
|
|
23873
|
+
});
|
|
23874
|
+
local.close();
|
|
23875
|
+
await cloud.close();
|
|
23876
|
+
const total = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
23877
|
+
if (opts.json) {
|
|
23878
|
+
console.log(JSON.stringify({ total, tables: results }));
|
|
23879
|
+
return;
|
|
23880
|
+
}
|
|
23881
|
+
printSuccess(`Done. ${total} rows pushed.`);
|
|
23882
|
+
} catch (e) {
|
|
23883
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
23884
|
+
process.exit(1);
|
|
23885
|
+
}
|
|
23886
|
+
});
|
|
23887
|
+
cloudCmd.command("pull").description("Pull cloud data to local \u2014 merges by primary key").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
23888
|
+
try {
|
|
23889
|
+
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPull: syncPull2, listPgTables: listPgTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23890
|
+
const config = getCloudConfig2();
|
|
23891
|
+
if (config.mode === "local") {
|
|
23892
|
+
printError("Cloud mode not configured.");
|
|
23893
|
+
process.exit(1);
|
|
23894
|
+
}
|
|
23895
|
+
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23896
|
+
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
23897
|
+
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : (await listPgTables2(cloud)).filter((t) => !t.startsWith("_"));
|
|
23898
|
+
const results = await syncPull2(cloud, local, {
|
|
23899
|
+
tables: tableList,
|
|
23900
|
+
onProgress: (p) => {
|
|
23901
|
+
if (!opts.json && p.phase === "done")
|
|
23902
|
+
printInfo(` ${p.table}: ${p.rowsWritten} rows pulled`);
|
|
23903
|
+
}
|
|
23904
|
+
});
|
|
23905
|
+
local.close();
|
|
23906
|
+
await cloud.close();
|
|
23907
|
+
const total = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
23908
|
+
if (opts.json) {
|
|
23909
|
+
console.log(JSON.stringify({ total, tables: results }));
|
|
23910
|
+
return;
|
|
23911
|
+
}
|
|
23912
|
+
printSuccess(`Done. ${total} rows pulled.`);
|
|
23913
|
+
} catch (e) {
|
|
23914
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
23915
|
+
process.exit(1);
|
|
23916
|
+
}
|
|
23917
|
+
});
|
|
23918
|
+
cloudCmd.command("sync").description("Bidirectional sync \u2014 pull then push").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
23919
|
+
try {
|
|
23920
|
+
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPush: syncPush2, syncPull: syncPull2, listSqliteTables: listSqliteTables2, listPgTables: listPgTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23921
|
+
const config = getCloudConfig2();
|
|
23922
|
+
if (config.mode === "local") {
|
|
23923
|
+
printError("Cloud mode not configured.");
|
|
23924
|
+
process.exit(1);
|
|
23925
|
+
}
|
|
23926
|
+
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23927
|
+
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
23928
|
+
const localTables = listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
23929
|
+
const remoteTables = (await listPgTables2(cloud)).filter((t) => !t.startsWith("_"));
|
|
23930
|
+
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : [...new Set([...localTables, ...remoteTables])];
|
|
23931
|
+
const pullResults = await syncPull2(cloud, local, { tables: tableList.filter((t) => remoteTables.includes(t)) });
|
|
23932
|
+
const pushResults = await syncPush2(local, cloud, { tables: tableList.filter((t) => localTables.includes(t)) });
|
|
23933
|
+
local.close();
|
|
23934
|
+
await cloud.close();
|
|
23935
|
+
const pulled = pullResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
23936
|
+
const pushed = pushResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
23937
|
+
if (opts.json) {
|
|
23938
|
+
console.log(JSON.stringify({ pulled, pushed }));
|
|
23939
|
+
return;
|
|
23940
|
+
}
|
|
23941
|
+
printSuccess(`Sync done. Pulled ${pulled} rows, pushed ${pushed} rows.`);
|
|
23942
|
+
} catch (e) {
|
|
23943
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
23944
|
+
process.exit(1);
|
|
23945
|
+
}
|
|
23946
|
+
});
|
|
23947
|
+
cloudCmd.command("migrate-pg").description("Apply PostgreSQL migrations to the cloud database").requiredOption("--connection-string <connStr>", "PostgreSQL connection string").option("--json", "Output as JSON").action(async (opts) => {
|
|
23948
|
+
try {
|
|
23949
|
+
const { applyPgMigrations: applyPgMigrations3 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
|
|
23950
|
+
const result = await applyPgMigrations3(opts.connectionString);
|
|
23951
|
+
if (opts.json) {
|
|
23952
|
+
console.log(JSON.stringify(result));
|
|
23953
|
+
return;
|
|
23954
|
+
}
|
|
23955
|
+
printSuccess(`Applied ${result.applied.length} migration(s), skipped ${result.alreadyApplied.length}.`);
|
|
23956
|
+
if (result.errors.length > 0) {
|
|
23957
|
+
printError(result.errors.join(`
|
|
23958
|
+
`));
|
|
23959
|
+
process.exit(1);
|
|
23960
|
+
}
|
|
23961
|
+
} catch (e) {
|
|
23962
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
23963
|
+
process.exit(1);
|
|
23964
|
+
}
|
|
23965
|
+
});
|
|
23966
|
+
}
|
|
23967
|
+
|
|
23968
|
+
// src/cli/index.ts
|
|
23969
|
+
var program2 = new Command;
|
|
23970
|
+
program2.name("brains").description("Fine-tuned model tracker and trainer").version("0.0.1");
|
|
23971
|
+
registerModelsCommands(program2);
|
|
23972
|
+
registerFinetuneCommands(program2);
|
|
23973
|
+
registerDataCommands(program2);
|
|
23974
|
+
registerCollectionsCommands(program2);
|
|
23803
23975
|
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) => {
|
|
23804
23976
|
const db = getDb();
|
|
23805
23977
|
try {
|
|
@@ -23874,7 +24046,7 @@ var feedbackCmd = program2.command("feedback").description("Feedback commands");
|
|
|
23874
24046
|
feedbackCmd.command("send <message>").description("Send feedback about brains").option("--email <email>", "Contact email").action(async (message, opts) => {
|
|
23875
24047
|
const { sendFeedback: sendFeedback2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23876
24048
|
const rawDb = getRawDb();
|
|
23877
|
-
const pkg = JSON.parse(
|
|
24049
|
+
const pkg = JSON.parse(readFileSync7(join14(import.meta.dir, "../../package.json"), "utf8"));
|
|
23878
24050
|
const result = await sendFeedback2({ service: "brains", message, email: opts.email, version: pkg.version }, rawDb);
|
|
23879
24051
|
rawDb.close();
|
|
23880
24052
|
if (result.sent) {
|
|
@@ -23904,170 +24076,5 @@ feedbackCmd.command("list").description("List locally saved feedback").option("-
|
|
|
23904
24076
|
e.created_at ?? ""
|
|
23905
24077
|
]));
|
|
23906
24078
|
});
|
|
23907
|
-
|
|
23908
|
-
cloudCmd.command("status").description("Show cloud config and connection health").option("--json", "Output as JSON").action(async (opts) => {
|
|
23909
|
-
try {
|
|
23910
|
-
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, PgAdapterAsync: PgAdapterAsync2, SqliteAdapter: SqliteAdapter2, getDbPath: getDbPath2, listSqliteTables: listSqliteTables2, ensureConflictsTable: ensureConflictsTable2, listConflicts: listConflicts2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23911
|
-
const config = getCloudConfig2();
|
|
23912
|
-
const info = {
|
|
23913
|
-
mode: config.mode,
|
|
23914
|
-
service: "brains",
|
|
23915
|
-
rds_host: config.rds?.host || "(not configured)"
|
|
23916
|
-
};
|
|
23917
|
-
if (config.rds?.host && config.rds?.username) {
|
|
23918
|
-
try {
|
|
23919
|
-
const pg = new PgAdapterAsync2(getConnectionString2("postgres"));
|
|
23920
|
-
await pg.get("SELECT 1 as ok");
|
|
23921
|
-
info.postgresql = "connected";
|
|
23922
|
-
await pg.close();
|
|
23923
|
-
} catch (err) {
|
|
23924
|
-
info.postgresql = `failed \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
23925
|
-
}
|
|
23926
|
-
}
|
|
23927
|
-
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23928
|
-
const tables = listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
23929
|
-
const syncHealth = [];
|
|
23930
|
-
for (const table of tables) {
|
|
23931
|
-
try {
|
|
23932
|
-
const totalRow = local.get(`SELECT COUNT(*) as c FROM "${table}"`);
|
|
23933
|
-
const unsyncedRow = local.get(`SELECT COUNT(*) as c FROM "${table}" WHERE synced_at IS NULL`);
|
|
23934
|
-
syncHealth.push({ table, total: totalRow?.c ?? 0, unsynced: unsyncedRow?.c ?? 0 });
|
|
23935
|
-
} catch {}
|
|
23936
|
-
}
|
|
23937
|
-
info.sync_health = syncHealth.filter((s) => s.total > 0);
|
|
23938
|
-
try {
|
|
23939
|
-
ensureConflictsTable2(local);
|
|
23940
|
-
const unresolved = listConflicts2(local, { resolved: false });
|
|
23941
|
-
info.conflicts_unresolved = unresolved.length;
|
|
23942
|
-
} catch {}
|
|
23943
|
-
local.close();
|
|
23944
|
-
if (opts.json) {
|
|
23945
|
-
console.log(JSON.stringify(info, null, 2));
|
|
23946
|
-
return;
|
|
23947
|
-
}
|
|
23948
|
-
printInfo(`Mode: ${info.mode}`);
|
|
23949
|
-
printInfo(`RDS Host: ${info.rds_host}`);
|
|
23950
|
-
if (info.postgresql)
|
|
23951
|
-
printInfo(`PostgreSQL: ${info.postgresql}`);
|
|
23952
|
-
for (const s of info.sync_health) {
|
|
23953
|
-
const pct = s.total > 0 ? Math.round((s.total - s.unsynced) / s.total * 100) : 100;
|
|
23954
|
-
printInfo(` ${s.table}: ${pct}% synced (${s.unsynced} unsynced / ${s.total} total)`);
|
|
23955
|
-
}
|
|
23956
|
-
if (info.conflicts_unresolved)
|
|
23957
|
-
printInfo(`Conflicts: ${info.conflicts_unresolved} unresolved`);
|
|
23958
|
-
} catch (e) {
|
|
23959
|
-
printError(e instanceof Error ? e.message : String(e));
|
|
23960
|
-
process.exit(1);
|
|
23961
|
-
}
|
|
23962
|
-
});
|
|
23963
|
-
cloudCmd.command("push").description("Push local data to cloud PostgreSQL").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
23964
|
-
try {
|
|
23965
|
-
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPush: syncPush2, listSqliteTables: listSqliteTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23966
|
-
const config = getCloudConfig2();
|
|
23967
|
-
if (config.mode === "local") {
|
|
23968
|
-
printError("Cloud mode not configured.");
|
|
23969
|
-
process.exit(1);
|
|
23970
|
-
}
|
|
23971
|
-
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
23972
|
-
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
23973
|
-
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
23974
|
-
const results = await syncPush2(local, cloud, {
|
|
23975
|
-
tables: tableList,
|
|
23976
|
-
onProgress: (p) => {
|
|
23977
|
-
if (!opts.json && p.phase === "done")
|
|
23978
|
-
printInfo(` ${p.table}: ${p.rowsWritten} rows pushed`);
|
|
23979
|
-
}
|
|
23980
|
-
});
|
|
23981
|
-
local.close();
|
|
23982
|
-
await cloud.close();
|
|
23983
|
-
const total = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
23984
|
-
if (opts.json) {
|
|
23985
|
-
console.log(JSON.stringify({ total, tables: results }));
|
|
23986
|
-
return;
|
|
23987
|
-
}
|
|
23988
|
-
printSuccess(`Done. ${total} rows pushed.`);
|
|
23989
|
-
} catch (e) {
|
|
23990
|
-
printError(e instanceof Error ? e.message : String(e));
|
|
23991
|
-
process.exit(1);
|
|
23992
|
-
}
|
|
23993
|
-
});
|
|
23994
|
-
cloudCmd.command("pull").description("Pull cloud data to local \u2014 merges by primary key").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
23995
|
-
try {
|
|
23996
|
-
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPull: syncPull2, listPgTables: listPgTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
23997
|
-
const config = getCloudConfig2();
|
|
23998
|
-
if (config.mode === "local") {
|
|
23999
|
-
printError("Cloud mode not configured.");
|
|
24000
|
-
process.exit(1);
|
|
24001
|
-
}
|
|
24002
|
-
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
24003
|
-
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
24004
|
-
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : (await listPgTables2(cloud)).filter((t) => !t.startsWith("_"));
|
|
24005
|
-
const results = await syncPull2(cloud, local, {
|
|
24006
|
-
tables: tableList,
|
|
24007
|
-
onProgress: (p) => {
|
|
24008
|
-
if (!opts.json && p.phase === "done")
|
|
24009
|
-
printInfo(` ${p.table}: ${p.rowsWritten} rows pulled`);
|
|
24010
|
-
}
|
|
24011
|
-
});
|
|
24012
|
-
local.close();
|
|
24013
|
-
await cloud.close();
|
|
24014
|
-
const total = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
24015
|
-
if (opts.json) {
|
|
24016
|
-
console.log(JSON.stringify({ total, tables: results }));
|
|
24017
|
-
return;
|
|
24018
|
-
}
|
|
24019
|
-
printSuccess(`Done. ${total} rows pulled.`);
|
|
24020
|
-
} catch (e) {
|
|
24021
|
-
printError(e instanceof Error ? e.message : String(e));
|
|
24022
|
-
process.exit(1);
|
|
24023
|
-
}
|
|
24024
|
-
});
|
|
24025
|
-
cloudCmd.command("sync").description("Bidirectional sync \u2014 pull then push").option("--tables <tables>", "Comma-separated table names (default: all)").option("--json", "Output as JSON").action(async (opts) => {
|
|
24026
|
-
try {
|
|
24027
|
-
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, syncPush: syncPush2, syncPull: syncPull2, listSqliteTables: listSqliteTables2, listPgTables: listPgTables2, SqliteAdapter: SqliteAdapter2, PgAdapterAsync: PgAdapterAsync2, getDbPath: getDbPath2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
24028
|
-
const config = getCloudConfig2();
|
|
24029
|
-
if (config.mode === "local") {
|
|
24030
|
-
printError("Cloud mode not configured.");
|
|
24031
|
-
process.exit(1);
|
|
24032
|
-
}
|
|
24033
|
-
const local = new SqliteAdapter2(getDbPath2("brains"));
|
|
24034
|
-
const cloud = new PgAdapterAsync2(getConnectionString2("brains"));
|
|
24035
|
-
const localTables = listSqliteTables2(local).filter((t) => !t.startsWith("_"));
|
|
24036
|
-
const remoteTables = (await listPgTables2(cloud)).filter((t) => !t.startsWith("_"));
|
|
24037
|
-
const tableList = opts.tables ? opts.tables.split(",").map((t) => t.trim()) : [...new Set([...localTables, ...remoteTables])];
|
|
24038
|
-
const pullResults = await syncPull2(cloud, local, { tables: tableList.filter((t) => remoteTables.includes(t)) });
|
|
24039
|
-
const pushResults = await syncPush2(local, cloud, { tables: tableList.filter((t) => localTables.includes(t)) });
|
|
24040
|
-
local.close();
|
|
24041
|
-
await cloud.close();
|
|
24042
|
-
const pulled = pullResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
24043
|
-
const pushed = pushResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
24044
|
-
if (opts.json) {
|
|
24045
|
-
console.log(JSON.stringify({ pulled, pushed }));
|
|
24046
|
-
return;
|
|
24047
|
-
}
|
|
24048
|
-
printSuccess(`Sync done. Pulled ${pulled} rows, pushed ${pushed} rows.`);
|
|
24049
|
-
} catch (e) {
|
|
24050
|
-
printError(e instanceof Error ? e.message : String(e));
|
|
24051
|
-
process.exit(1);
|
|
24052
|
-
}
|
|
24053
|
-
});
|
|
24054
|
-
cloudCmd.command("migrate-pg").description("Apply PostgreSQL migrations to the cloud database").requiredOption("--connection-string <connStr>", "PostgreSQL connection string").option("--json", "Output as JSON").action(async (opts) => {
|
|
24055
|
-
try {
|
|
24056
|
-
const { applyPgMigrations: applyPgMigrations3 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
|
|
24057
|
-
const result = await applyPgMigrations3(opts.connectionString);
|
|
24058
|
-
if (opts.json) {
|
|
24059
|
-
console.log(JSON.stringify(result));
|
|
24060
|
-
return;
|
|
24061
|
-
}
|
|
24062
|
-
printSuccess(`Applied ${result.applied.length} migration(s), skipped ${result.alreadyApplied.length}.`);
|
|
24063
|
-
if (result.errors.length > 0) {
|
|
24064
|
-
printError(result.errors.join(`
|
|
24065
|
-
`));
|
|
24066
|
-
process.exit(1);
|
|
24067
|
-
}
|
|
24068
|
-
} catch (e) {
|
|
24069
|
-
printError(e instanceof Error ? e.message : String(e));
|
|
24070
|
-
process.exit(1);
|
|
24071
|
-
}
|
|
24072
|
-
});
|
|
24079
|
+
registerCloudCommands2(program2);
|
|
24073
24080
|
program2.parse();
|