@hizliemre/horse-code 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-2GPGCDX6.js → app-5FXHE7GX.js} +223 -26
- package/dist/{chunk-KKWZBZYK.js → chunk-EAF22QIG.js} +26 -4
- package/dist/chunk-G45RWL7S.js +289 -0
- package/dist/{chunk-7JMWPTJ5.js → chunk-UEWVVN5L.js} +53 -265
- package/dist/{chunk-23CLQ2KO.js → chunk-XYZVZPAY.js} +4 -4
- package/dist/{chunk-5ZV42XGJ.js → chunk-YPZP7LYL.js} +1 -1
- package/dist/cli.js +360 -66
- package/dist/{fix-JOIXQFVP.js → fix-ONLA45HD.js} +3 -2
- package/dist/{save-skills-X7U3KCPU.js → save-skills-ZW5GY6KV.js} +2 -1
- package/dist/{verify-6SC4I77M.js → verify-LC57A6H2.js} +3 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
restoreTerminal,
|
|
16
16
|
runJob,
|
|
17
17
|
sttySane
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-XYZVZPAY.js";
|
|
19
19
|
import "./chunk-M2RKCIGV.js";
|
|
20
20
|
import {
|
|
21
21
|
CODE_TEAM,
|
|
@@ -30,14 +30,14 @@ import {
|
|
|
30
30
|
SHORT_CALL_MS,
|
|
31
31
|
SPEC_TEAM,
|
|
32
32
|
WorktreeManager,
|
|
33
|
+
ZAI_MODELS,
|
|
33
34
|
buildCouncilRegistry,
|
|
34
35
|
buildTeamRegistry,
|
|
35
|
-
checkProfile,
|
|
36
36
|
cliCatalog,
|
|
37
37
|
mainWorktreeRoot,
|
|
38
|
-
|
|
38
|
+
modelsFor,
|
|
39
39
|
toSlug
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-UEWVVN5L.js";
|
|
41
41
|
import "./chunk-QF4MP6BS.js";
|
|
42
42
|
import "./chunk-UGESK765.js";
|
|
43
43
|
import "./chunk-ZSQ24YDJ.js";
|
|
@@ -48,7 +48,12 @@ import {
|
|
|
48
48
|
UNSET_MODEL,
|
|
49
49
|
loadConfig,
|
|
50
50
|
saveSkillSource
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-EAF22QIG.js";
|
|
52
|
+
import {
|
|
53
|
+
CLI_KINDS,
|
|
54
|
+
checkProfile,
|
|
55
|
+
runLogin
|
|
56
|
+
} from "./chunk-G45RWL7S.js";
|
|
52
57
|
import "./chunk-H2FDGPVW.js";
|
|
53
58
|
import {
|
|
54
59
|
buildBrief,
|
|
@@ -93,8 +98,8 @@ import {
|
|
|
93
98
|
|
|
94
99
|
// src/cli.ts
|
|
95
100
|
import { execFileSync } from "child_process";
|
|
96
|
-
import { readFileSync as
|
|
97
|
-
import { join as
|
|
101
|
+
import { readFileSync as readFileSync5, existsSync as existsSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, realpathSync, rmSync } from "fs";
|
|
102
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
98
103
|
import { pathToFileURL } from "url";
|
|
99
104
|
|
|
100
105
|
// src/agents/cli-accounts.ts
|
|
@@ -181,6 +186,26 @@ var AccountPool = class {
|
|
|
181
186
|
return kind ? this.accounts.filter((a) => a.kind === kind).length : this.accounts.length;
|
|
182
187
|
}
|
|
183
188
|
};
|
|
189
|
+
function modelsPanel(usage2, models, kinds, inUse = [], now = Date.now()) {
|
|
190
|
+
const used = new Set(inUse);
|
|
191
|
+
const rows = [];
|
|
192
|
+
for (const kind of kinds) {
|
|
193
|
+
const mine = usage2.filter((u) => u.account.kind === kind);
|
|
194
|
+
const who = mine.length ? mine.map(({ account, reading }) => {
|
|
195
|
+
const name = account.email ?? account.plan ?? account.name;
|
|
196
|
+
return `${name}${reading ? ` \xB7 ${Math.round(reading.spent * 100)}% used ${ageOf(reading.at, now)} ago` : ""}`;
|
|
197
|
+
}).join(" \xB7 ") : "_not connected \u2014 these will fail every call_";
|
|
198
|
+
rows.push(`**${kind}** \u2014 ${who}`);
|
|
199
|
+
rows.push(` ${models(kind).map((m) => `${used.has(m) ? "\u25CF" : "\xB7"} ${m}`).join(" ")}`);
|
|
200
|
+
}
|
|
201
|
+
const idle = kinds.filter((k) => usage2.some((u) => u.account.kind === k)).filter((k) => !models(k).some((m) => used.has(m)));
|
|
202
|
+
const note = idle.length ? `
|
|
203
|
+
|
|
204
|
+
_\u25CF = a role is running it. ${idle.join(" and ")} ${idle.length > 1 ? "are" : "is"} connected but in no chain \u2014 \`/roles adjust\` assigns them._` : "\n\n_\u25CF = a role is running it._";
|
|
205
|
+
return `**Models** \u2014 what your connected subscriptions serve
|
|
206
|
+
|
|
207
|
+
${rows.join("\n")}${note}`;
|
|
208
|
+
}
|
|
184
209
|
function ageOf(at, now) {
|
|
185
210
|
const mins = Math.max(0, Math.round((now - at) / 6e4));
|
|
186
211
|
if (mins < 60) return `${mins}m`;
|
|
@@ -207,9 +232,11 @@ function freshDir(kind, home, taken) {
|
|
|
207
232
|
}
|
|
208
233
|
}
|
|
209
234
|
function nameFor(kind, status, dir) {
|
|
210
|
-
|
|
235
|
+
const base = dir.split("/").pop() ?? "profile";
|
|
236
|
+
return status.email ?? (base.startsWith(`${kind}-`) ? base : `${kind}-${base}`);
|
|
211
237
|
}
|
|
212
238
|
function withAmbient(kind, existing, ambient) {
|
|
239
|
+
if (kind === "zai") return [...existing];
|
|
213
240
|
if (existing.some((a) => a.kind === kind)) return [...existing];
|
|
214
241
|
if (!ambient.loggedIn) return [...existing];
|
|
215
242
|
return [
|
|
@@ -228,11 +255,11 @@ function accountsIn(config) {
|
|
|
228
255
|
return raw.filter((a) => {
|
|
229
256
|
const e = a;
|
|
230
257
|
if (typeof a !== "object" || a === null) return false;
|
|
231
|
-
if (
|
|
258
|
+
if (!CLI_KINDS.includes(e.kind)) return false;
|
|
232
259
|
return e.configDir === void 0 || typeof e.configDir === "string";
|
|
233
260
|
});
|
|
234
261
|
}
|
|
235
|
-
function addProvider(kind, io) {
|
|
262
|
+
async function addProvider(kind, io) {
|
|
236
263
|
const config = io.readConfig();
|
|
237
264
|
const existing = accountsIn(config);
|
|
238
265
|
const accounts = withAmbient(kind, existing, io.check(kind));
|
|
@@ -242,12 +269,26 @@ function addProvider(kind, io) {
|
|
|
242
269
|
io.log(`It stays first in line, so runs keep spending it before anything added now.`);
|
|
243
270
|
}
|
|
244
271
|
const dir = freshDir(kind, io.home, accounts);
|
|
245
|
-
|
|
272
|
+
if (kind === "zai") {
|
|
273
|
+
io.log(`
|
|
274
|
+
z.ai has no sign-in \u2014 an account is an API key, and the profile at ${dir} will hold it.`);
|
|
275
|
+
io.log(`Take the key from your z.ai dashboard; it is written to that profile alone and nowhere else.
|
|
276
|
+
`);
|
|
277
|
+
} else {
|
|
278
|
+
io.log(`
|
|
246
279
|
Opening ${kind}'s own sign-in, for a NEW profile at ${dir}.`);
|
|
247
|
-
|
|
280
|
+
io.log(`Sign in with the account you want to add \u2014 not the one already connected.
|
|
248
281
|
`);
|
|
249
|
-
|
|
282
|
+
}
|
|
283
|
+
const r = await io.login(kind, dir);
|
|
250
284
|
if (!r.ok) {
|
|
285
|
+
if (kind === "zai") {
|
|
286
|
+
io.log(`
|
|
287
|
+
z.ai did not accept that key${r.error ? `: ${r.error}` : ""}. Nothing was changed.`);
|
|
288
|
+
io.log(`The key is checked by making one real call, because \`claude auth status\` reports any token as connected \u2014 valid or not.`);
|
|
289
|
+
io.log(`Nothing needs cleaning up: running this again reuses ${dir}.`);
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
251
292
|
io.log(`
|
|
252
293
|
${kind} sign-in did not complete${r.error ? `: ${r.error}` : ""}. Nothing was changed.`);
|
|
253
294
|
io.log(`If a browser page opened, its address holds the reason \u2014 an \`error=\` there is the account being refused, not this command failing.`);
|
|
@@ -277,13 +318,203 @@ That is the account already connected as "${already.name}". Nothing was changed.
|
|
|
277
318
|
const mine = [...accounts, entry].filter((a) => a.kind === kind);
|
|
278
319
|
io.log(`
|
|
279
320
|
Connected ${entry.email ?? entry.name}${entry.plan ? ` (${entry.plan})` : ""}.`);
|
|
280
|
-
io.log(`${mine.length} ${kind} account${mine.length === 1 ? "" : "s"} now connected. Runs
|
|
321
|
+
io.log(`${mine.length} ${kind} account${mine.length === 1 ? "" : "s"} now connected. Runs take turns between them.`);
|
|
322
|
+
if (kind === "claude") io.log(`One nearly out of quota steps aside for the others.`);
|
|
323
|
+
else io.log(`${kind} reports no quota figures, so a spent account is discovered by being refused, not before.`);
|
|
324
|
+
if (kind === "zai" && mine.length > 1) {
|
|
325
|
+
io.log(`z.ai reports no account identity, so this cannot be checked: if that key is one already connected, remove the duplicate from ~/.horsecode/config.json.`);
|
|
326
|
+
}
|
|
281
327
|
return 0;
|
|
282
328
|
}
|
|
283
329
|
|
|
330
|
+
// src/agents/zai-profile.ts
|
|
331
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
332
|
+
import { join as join3 } from "path";
|
|
333
|
+
var ZAI_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
334
|
+
function zaiSettings(token) {
|
|
335
|
+
return { env: { ANTHROPIC_BASE_URL: ZAI_BASE_URL, ANTHROPIC_AUTH_TOKEN: token } };
|
|
336
|
+
}
|
|
337
|
+
function settingsPath(dir) {
|
|
338
|
+
return join3(dir, "settings.json");
|
|
339
|
+
}
|
|
340
|
+
function writeZaiProfile(dir, token) {
|
|
341
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
342
|
+
writeFileSync2(settingsPath(dir), JSON.stringify(zaiSettings(token), null, 2) + "\n", { mode: 384 });
|
|
343
|
+
}
|
|
344
|
+
async function verifyZaiKey(token, model, timeoutMs = 3e4) {
|
|
345
|
+
const ctl = new AbortController();
|
|
346
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
347
|
+
try {
|
|
348
|
+
const res = await fetch(`${ZAI_BASE_URL}/v1/messages`, {
|
|
349
|
+
method: "POST",
|
|
350
|
+
signal: ctl.signal,
|
|
351
|
+
headers: {
|
|
352
|
+
"content-type": "application/json",
|
|
353
|
+
// Both spellings: z.ai accepts the bearer form, and `x-api-key` is what the Anthropic wire format
|
|
354
|
+
// names. Sending each costs nothing and removes a guess about which one this endpoint reads.
|
|
355
|
+
authorization: `Bearer ${token}`,
|
|
356
|
+
"x-api-key": token,
|
|
357
|
+
"anthropic-version": "2023-06-01"
|
|
358
|
+
},
|
|
359
|
+
body: JSON.stringify({ model, max_tokens: 16, messages: [{ role: "user", content: "Reply with: ok" }] })
|
|
360
|
+
});
|
|
361
|
+
const body = await res.json().catch(() => void 0);
|
|
362
|
+
if (!res.ok) return { ok: false, error: zaiErrorText(body) ?? `the endpoint answered HTTP ${res.status}` };
|
|
363
|
+
const served = body?.model;
|
|
364
|
+
return { ok: true, ...served ? { served } : {} };
|
|
365
|
+
} catch (e) {
|
|
366
|
+
const aborted = e instanceof Error && e.name === "AbortError";
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
error: aborted ? `${ZAI_BASE_URL} did not answer within ${Math.round(timeoutMs / 1e3)}s` : e instanceof Error ? e.message : String(e)
|
|
370
|
+
};
|
|
371
|
+
} finally {
|
|
372
|
+
clearTimeout(timer);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function zaiErrorText(body) {
|
|
376
|
+
const msg = body?.error?.message;
|
|
377
|
+
return typeof msg === "string" && msg.trim() ? msg.trim() : void 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/agents/prompt.ts
|
|
381
|
+
import { spawnSync } from "child_process";
|
|
382
|
+
import { closeSync, openSync, readSync } from "fs";
|
|
383
|
+
function promptLine() {
|
|
384
|
+
return readTty(false);
|
|
385
|
+
}
|
|
386
|
+
function promptSecret() {
|
|
387
|
+
return readTty(true);
|
|
388
|
+
}
|
|
389
|
+
function confirm(question) {
|
|
390
|
+
process.stdout.write(`${question} [y/N] `);
|
|
391
|
+
const answer = promptLine();
|
|
392
|
+
process.stdout.write("\n");
|
|
393
|
+
return /^y(es)?$/i.test((answer ?? "").trim());
|
|
394
|
+
}
|
|
395
|
+
function chooseFrom(question, options) {
|
|
396
|
+
process.stdout.write(`${question}
|
|
397
|
+
`);
|
|
398
|
+
options.forEach((o, i) => process.stdout.write(` ${i + 1}) ${o}
|
|
399
|
+
`));
|
|
400
|
+
process.stdout.write(`Number (anything else cancels): `);
|
|
401
|
+
const line = promptLine();
|
|
402
|
+
process.stdout.write("\n");
|
|
403
|
+
const n = Number((line ?? "").trim());
|
|
404
|
+
return Number.isInteger(n) && n >= 1 && n <= options.length ? n - 1 : void 0;
|
|
405
|
+
}
|
|
406
|
+
function readTty(hidden) {
|
|
407
|
+
let fd;
|
|
408
|
+
try {
|
|
409
|
+
fd = openSync("/dev/tty", "r");
|
|
410
|
+
} catch {
|
|
411
|
+
return void 0;
|
|
412
|
+
}
|
|
413
|
+
const hushed = hidden && spawnSync("stty", ["-echo"], { stdio: ["inherit", "ignore", "ignore"] }).status === 0;
|
|
414
|
+
try {
|
|
415
|
+
const buf = Buffer.alloc(1);
|
|
416
|
+
let out = "";
|
|
417
|
+
for (; ; ) {
|
|
418
|
+
let n = 0;
|
|
419
|
+
try {
|
|
420
|
+
n = readSync(fd, buf, 0, 1, null);
|
|
421
|
+
} catch {
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
if (n === 0) break;
|
|
425
|
+
const ch = buf.toString("utf8");
|
|
426
|
+
if (ch === "\n" || ch === "\r") break;
|
|
427
|
+
out += ch;
|
|
428
|
+
}
|
|
429
|
+
return out.trim() || void 0;
|
|
430
|
+
} finally {
|
|
431
|
+
if (hushed) spawnSync("stty", ["echo"], { stdio: ["inherit", "ignore", "ignore"] });
|
|
432
|
+
closeSync(fd);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/agents/remove-provider.ts
|
|
437
|
+
function ownedProfile(home, dir) {
|
|
438
|
+
if (!dir) return false;
|
|
439
|
+
const root = `${home}/.horsecode/profiles/`;
|
|
440
|
+
return dir.startsWith(root) && dir.length > root.length && !dir.slice(root.length).includes("..");
|
|
441
|
+
}
|
|
442
|
+
function describeAccount(a) {
|
|
443
|
+
const who = a.email ?? a.name;
|
|
444
|
+
return `${a.kind} ${who}${a.plan ? ` (${a.plan})` : ""}${a.configDir ? "" : " \u2014 the signed-in default"}`;
|
|
445
|
+
}
|
|
446
|
+
function pickAccount(accounts, kind, name) {
|
|
447
|
+
const mine = accounts.filter((a) => a.kind === kind);
|
|
448
|
+
if (!mine.length) return { error: `no ${kind} account is connected` };
|
|
449
|
+
if (!name) return mine.length === 1 ? { account: mine[0] } : { choices: mine };
|
|
450
|
+
const found = mine.filter((a) => a.name === name || a.email === name);
|
|
451
|
+
if (!found.length) {
|
|
452
|
+
return {
|
|
453
|
+
error: `no ${kind} account called "${name}". Connected:
|
|
454
|
+
` + mine.map((a) => ` ${a.name}${a.email && a.email !== a.name ? ` (${a.email})` : ""}`).join("\n")
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (found.length > 1) return { error: `"${name}" matches ${found.length} ${kind} accounts \u2014 remove one by its exact name` };
|
|
458
|
+
return { account: found[0] };
|
|
459
|
+
}
|
|
460
|
+
function removeProvider(kind, name, io) {
|
|
461
|
+
const config = io.readConfig();
|
|
462
|
+
const accounts = accountsIn(config);
|
|
463
|
+
const picked = pickAccount(accounts, kind, name);
|
|
464
|
+
if ("error" in picked) {
|
|
465
|
+
io.log(picked.error);
|
|
466
|
+
const others = accounts.filter((a) => a.kind !== kind);
|
|
467
|
+
if (!accounts.filter((a) => a.kind === kind).length && others.length) {
|
|
468
|
+
io.log(`
|
|
469
|
+
Connected accounts:
|
|
470
|
+
${others.map((a) => ` ${describeAccount(a)}`).join("\n")}`);
|
|
471
|
+
}
|
|
472
|
+
return { code: 1 };
|
|
473
|
+
}
|
|
474
|
+
let account;
|
|
475
|
+
if ("account" in picked) {
|
|
476
|
+
account = picked.account;
|
|
477
|
+
} else {
|
|
478
|
+
const chosen = io.choose(`Which ${kind} account should be removed?`, picked.choices.map(describeAccount));
|
|
479
|
+
if (chosen === void 0) {
|
|
480
|
+
io.log("Nothing was removed.");
|
|
481
|
+
return { code: 1 };
|
|
482
|
+
}
|
|
483
|
+
account = picked.choices[chosen];
|
|
484
|
+
}
|
|
485
|
+
const kept = accounts.filter((a) => a !== account);
|
|
486
|
+
io.writeConfig({ ...config, accounts: kept });
|
|
487
|
+
if (io.usage) {
|
|
488
|
+
const readings = io.usage.load();
|
|
489
|
+
const { [readingKey(kind, account.name)]: _dropped, ...rest } = readings;
|
|
490
|
+
io.usage.save(rest);
|
|
491
|
+
}
|
|
492
|
+
io.log(`Removed ${describeAccount(account)}.`);
|
|
493
|
+
if (!account.configDir) {
|
|
494
|
+
io.log(`That was the account you are signed into ${kind} with, not a profile horse-code created.`);
|
|
495
|
+
io.log(`It is still signed in, and a run will find it again. To stop using it, sign out of ${kind} itself.`);
|
|
496
|
+
return { code: 0, removed: account };
|
|
497
|
+
}
|
|
498
|
+
if (!ownedProfile(io.home, account.configDir)) {
|
|
499
|
+
io.log(`Its profile at ${account.configDir} was not created by horse-code, so it is left alone.`);
|
|
500
|
+
return { code: 0, removed: account };
|
|
501
|
+
}
|
|
502
|
+
const holds = kind === "zai" ? "the API key" : `the ${kind} sign-in`;
|
|
503
|
+
if (!io.confirm(`Also delete ${account.configDir}, which holds ${holds}?`)) {
|
|
504
|
+
io.log(`Left ${account.configDir} in place. Delete it by hand to remove ${holds}.`);
|
|
505
|
+
return { code: 0, removed: account };
|
|
506
|
+
}
|
|
507
|
+
io.removeDir(account.configDir);
|
|
508
|
+
io.log(`Deleted ${account.configDir}.`);
|
|
509
|
+
return { code: 0, removed: account };
|
|
510
|
+
}
|
|
511
|
+
function removeUsage(given) {
|
|
512
|
+
return `remove-provider needs a CLI to disconnect: ${CLI_KINDS.map((k) => `\`hcode remove-provider ${k}\``).join(", ")}${given ? ` (got "${given}")` : ""}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
284
515
|
// src/skills/registry.ts
|
|
285
516
|
import { readdir, readFile } from "fs/promises";
|
|
286
|
-
import { join as
|
|
517
|
+
import { join as join4 } from "path";
|
|
287
518
|
var SkillRegistry = class {
|
|
288
519
|
skills = /* @__PURE__ */ new Map();
|
|
289
520
|
register(skill) {
|
|
@@ -306,7 +537,7 @@ var SkillRegistry = class {
|
|
|
306
537
|
if (!e.isDirectory()) continue;
|
|
307
538
|
let raw;
|
|
308
539
|
try {
|
|
309
|
-
raw = await readFile(
|
|
540
|
+
raw = await readFile(join4(dir, e.name, "SKILL.md"), "utf8");
|
|
310
541
|
} catch {
|
|
311
542
|
continue;
|
|
312
543
|
}
|
|
@@ -314,14 +545,14 @@ var SkillRegistry = class {
|
|
|
314
545
|
if (!name || !description) {
|
|
315
546
|
throw new Error(`skill ${e.name}: missing frontmatter (name/description)`);
|
|
316
547
|
}
|
|
317
|
-
this.register({ name, description, content: body, dir:
|
|
548
|
+
this.register({ name, description, content: body, dir: join4(dir, e.name) });
|
|
318
549
|
}
|
|
319
550
|
}
|
|
320
551
|
};
|
|
321
552
|
|
|
322
553
|
// src/skills/builtin.ts
|
|
323
554
|
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
324
|
-
import { dirname as dirname2, join as
|
|
555
|
+
import { dirname as dirname2, join as join5, resolve } from "path";
|
|
325
556
|
import { fileURLToPath } from "url";
|
|
326
557
|
function candidates() {
|
|
327
558
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -342,14 +573,14 @@ async function registerBuiltinSkills(registry, dirs = candidates()) {
|
|
|
342
573
|
if (!e.isDirectory()) continue;
|
|
343
574
|
let raw;
|
|
344
575
|
try {
|
|
345
|
-
raw = await readFile2(
|
|
576
|
+
raw = await readFile2(join5(dir, e.name, "SKILL.md"), "utf8");
|
|
346
577
|
} catch {
|
|
347
578
|
continue;
|
|
348
579
|
}
|
|
349
580
|
const { parseFrontmatter: parseFrontmatter2 } = await import("./frontmatter-UNIPNLLO.js");
|
|
350
581
|
const { name, description, body } = parseFrontmatter2(raw);
|
|
351
582
|
if (!name || !description) continue;
|
|
352
|
-
found.push({ name, description, content: body, dir:
|
|
583
|
+
found.push({ name, description, content: body, dir: join5(dir, e.name) });
|
|
353
584
|
}
|
|
354
585
|
if (!found.length) continue;
|
|
355
586
|
for (const s of found) registry.register(s);
|
|
@@ -364,12 +595,12 @@ async function registerBuiltinSkills(registry, dirs = candidates()) {
|
|
|
364
595
|
// src/skills/external.ts
|
|
365
596
|
import { mkdir, rm, readFile as readFile3, writeFile, readdir as readdir3, cp } from "fs/promises";
|
|
366
597
|
import { existsSync } from "fs";
|
|
367
|
-
import { join as
|
|
598
|
+
import { join as join6 } from "path";
|
|
368
599
|
import { spawn } from "child_process";
|
|
369
600
|
import { tmpdir } from "os";
|
|
370
601
|
var PROVENANCE = ".horsecode-source.json";
|
|
371
602
|
function externalSkillsDir(home) {
|
|
372
|
-
return
|
|
603
|
+
return join6(home, ".horsecode", "skills");
|
|
373
604
|
}
|
|
374
605
|
function validName(name) {
|
|
375
606
|
return /^[A-Za-z0-9._-]+$/.test(name) && !name.includes("..");
|
|
@@ -413,7 +644,7 @@ async function resolveSha(src, deps) {
|
|
|
413
644
|
}
|
|
414
645
|
async function installedSource(home, name) {
|
|
415
646
|
try {
|
|
416
|
-
return JSON.parse(await readFile3(
|
|
647
|
+
return JSON.parse(await readFile3(join6(externalSkillsDir(home), name, PROVENANCE), "utf8"));
|
|
417
648
|
} catch {
|
|
418
649
|
return void 0;
|
|
419
650
|
}
|
|
@@ -423,27 +654,27 @@ async function installSkillSource(home, src, deps = {}) {
|
|
|
423
654
|
if (!validRepo(src.repo)) throw new Error(`skill source repo must be "owner/repo": ${src.repo}`);
|
|
424
655
|
if (src.path?.includes("..")) throw new Error(`skill source path may not contain "..": ${src.path}`);
|
|
425
656
|
const sha = await resolveSha(src, deps);
|
|
426
|
-
const dest =
|
|
657
|
+
const dest = join6(externalSkillsDir(home), src.name);
|
|
427
658
|
const current = await installedSource(home, src.name);
|
|
428
|
-
if (current?.sha === sha && existsSync(
|
|
659
|
+
if (current?.sha === sha && existsSync(join6(dest, "SKILL.md"))) {
|
|
429
660
|
return { name: src.name, sha, changed: false };
|
|
430
661
|
}
|
|
431
|
-
const staging =
|
|
662
|
+
const staging = join6(tmpdir(), `hc-skill-${src.name}-${sha.slice(0, 8)}`);
|
|
432
663
|
await rm(staging, { recursive: true, force: true });
|
|
433
664
|
await mkdir(staging, { recursive: true });
|
|
434
665
|
try {
|
|
435
666
|
const url = `https://codeload.github.com/${src.repo}/tar.gz/${sha}`;
|
|
436
667
|
const dl = await run("sh", ["-c", `curl -fsSL --max-time 120 ${JSON.stringify(url)} | tar xz -C ${JSON.stringify(staging)} --strip-components=1`]);
|
|
437
668
|
if (dl.code !== 0) throw new Error(`download failed for ${src.repo}@${sha.slice(0, 8)}: ${dl.err.trim().slice(0, 200)}`);
|
|
438
|
-
const from = src.path ?
|
|
439
|
-
if (!existsSync(
|
|
669
|
+
const from = src.path ? join6(staging, src.path) : staging;
|
|
670
|
+
if (!existsSync(join6(from, "SKILL.md"))) {
|
|
440
671
|
throw new Error(`${src.repo}${src.path ? `/${src.path}` : ""} has no SKILL.md`);
|
|
441
672
|
}
|
|
442
673
|
await rm(dest, { recursive: true, force: true });
|
|
443
674
|
await mkdir(dest, { recursive: true });
|
|
444
675
|
await cp(from, dest, { recursive: true });
|
|
445
676
|
const record = { ...src, sha, installedAt: (deps.now ?? Date.now)() };
|
|
446
|
-
await writeFile(
|
|
677
|
+
await writeFile(join6(dest, PROVENANCE), `${JSON.stringify(record, null, 2)}
|
|
447
678
|
`, "utf8");
|
|
448
679
|
return { name: src.name, sha, changed: true };
|
|
449
680
|
} finally {
|
|
@@ -733,8 +964,8 @@ var ProposalQueue = class {
|
|
|
733
964
|
};
|
|
734
965
|
|
|
735
966
|
// src/speckit/templates.ts
|
|
736
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
737
|
-
import { join as
|
|
967
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
968
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
738
969
|
var TEMPLATE_FILES = {
|
|
739
970
|
spec: "spec-template.md",
|
|
740
971
|
plan: "plan-template.md",
|
|
@@ -757,10 +988,10 @@ async function loadSpecKit(opts) {
|
|
|
757
988
|
);
|
|
758
989
|
}
|
|
759
990
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
760
|
-
const cacheDir =
|
|
991
|
+
const cacheDir = join7(opts.home, ".horsecode", "spec-kit", opts.version, "templates");
|
|
761
992
|
const get = async (relPath) => {
|
|
762
|
-
const cachePath =
|
|
763
|
-
if (existsSync2(cachePath)) return
|
|
993
|
+
const cachePath = join7(cacheDir, relPath);
|
|
994
|
+
if (existsSync2(cachePath)) return readFileSync3(cachePath, "utf8");
|
|
764
995
|
const url = `${RAW_BASE}/${opts.version}/templates/${relPath}`;
|
|
765
996
|
const res = await fetchFn(url);
|
|
766
997
|
if (!res.ok) {
|
|
@@ -770,8 +1001,8 @@ Check your network or set specKit.version to a valid tag.`
|
|
|
770
1001
|
);
|
|
771
1002
|
}
|
|
772
1003
|
const text = await res.text();
|
|
773
|
-
|
|
774
|
-
|
|
1004
|
+
mkdirSync3(dirname3(cachePath), { recursive: true });
|
|
1005
|
+
writeFileSync3(cachePath, text, "utf8");
|
|
775
1006
|
return text;
|
|
776
1007
|
};
|
|
777
1008
|
const templates = {};
|
|
@@ -787,7 +1018,7 @@ Check your network or set specKit.version to a valid tag.`
|
|
|
787
1018
|
|
|
788
1019
|
// src/wiring.ts
|
|
789
1020
|
import { homedir } from "os";
|
|
790
|
-
import { join as
|
|
1021
|
+
import { join as join8 } from "path";
|
|
791
1022
|
async function buildJobDeps(opts) {
|
|
792
1023
|
const { config } = opts;
|
|
793
1024
|
const roles = {};
|
|
@@ -801,7 +1032,12 @@ async function buildJobDeps(opts) {
|
|
|
801
1032
|
const roleRegistry = new RoleRegistry(roles, DEFAULT_PROMPTS, opts.skillRegistry);
|
|
802
1033
|
if (opts.accounts) {
|
|
803
1034
|
const pool = opts.accounts;
|
|
804
|
-
roleRegistry.setSourceWeights(() => ({
|
|
1035
|
+
roleRegistry.setSourceWeights(() => ({
|
|
1036
|
+
claude: pool.count("claude"),
|
|
1037
|
+
codex: pool.count("codex"),
|
|
1038
|
+
grok: pool.count("grok"),
|
|
1039
|
+
zai: pool.count("zai")
|
|
1040
|
+
}));
|
|
805
1041
|
}
|
|
806
1042
|
const fillModels = (r) => ({
|
|
807
1043
|
...r,
|
|
@@ -828,7 +1064,7 @@ async function buildJobDeps(opts) {
|
|
|
828
1064
|
const queue = new ProposalQueue();
|
|
829
1065
|
let kitPromise;
|
|
830
1066
|
const specKit = () => kitPromise ??= loadSpecKit({ version: config.specKit.version, home: opts.home, fetch: opts.fetch });
|
|
831
|
-
const fitness = new RoleFitness(
|
|
1067
|
+
const fitness = new RoleFitness(join8(opts.home ?? homedir(), ".horsecode", "model-fitness.json"));
|
|
832
1068
|
roleRegistry.setFitness(fitness);
|
|
833
1069
|
return {
|
|
834
1070
|
provider: opts.provider,
|
|
@@ -1020,10 +1256,10 @@ function redactRecord(record) {
|
|
|
1020
1256
|
}
|
|
1021
1257
|
|
|
1022
1258
|
// src/obs/sink.ts
|
|
1023
|
-
import { createWriteStream, mkdirSync as
|
|
1024
|
-
import { join as
|
|
1259
|
+
import { createWriteStream, mkdirSync as mkdirSync4 } from "fs";
|
|
1260
|
+
import { join as join9 } from "path";
|
|
1025
1261
|
function telemetryDir(home) {
|
|
1026
|
-
return
|
|
1262
|
+
return join9(home, ".horsecode", "telemetry");
|
|
1027
1263
|
}
|
|
1028
1264
|
var MAX_QUEUE = 1e4;
|
|
1029
1265
|
var FileSink = class {
|
|
@@ -1032,9 +1268,9 @@ var FileSink = class {
|
|
|
1032
1268
|
dropped = 0;
|
|
1033
1269
|
path;
|
|
1034
1270
|
constructor(home, runId) {
|
|
1035
|
-
this.path =
|
|
1271
|
+
this.path = join9(telemetryDir(home), `${runId}.jsonl`);
|
|
1036
1272
|
try {
|
|
1037
|
-
|
|
1273
|
+
mkdirSync4(telemetryDir(home), { recursive: true });
|
|
1038
1274
|
this.stream = createWriteStream(this.path, { flags: "a" });
|
|
1039
1275
|
this.stream.on("error", () => {
|
|
1040
1276
|
this.stream = void 0;
|
|
@@ -1091,13 +1327,13 @@ var FileSink = class {
|
|
|
1091
1327
|
};
|
|
1092
1328
|
|
|
1093
1329
|
// src/cli-info.ts
|
|
1094
|
-
import { readFileSync as
|
|
1095
|
-
import { dirname as dirname4, join as
|
|
1330
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1331
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
1096
1332
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1097
1333
|
function hcodeVersion() {
|
|
1098
1334
|
try {
|
|
1099
1335
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
1100
|
-
const pkg = JSON.parse(
|
|
1336
|
+
const pkg = JSON.parse(readFileSync4(join10(here, "..", "package.json"), "utf8"));
|
|
1101
1337
|
return pkg.version ?? "unknown";
|
|
1102
1338
|
} catch {
|
|
1103
1339
|
return "unknown";
|
|
@@ -1124,7 +1360,9 @@ function usage() {
|
|
|
1124
1360
|
" hcode start the REPL",
|
|
1125
1361
|
' hcode "<request>" run one request to completion, then report where the work landed',
|
|
1126
1362
|
" hcode init write .horsecode/ for this project and ask for the essentials",
|
|
1127
|
-
" hcode add-provider <cli> connect one more claude
|
|
1363
|
+
" hcode add-provider <cli> connect one more claude, codex, grok or zai subscription",
|
|
1364
|
+
" hcode remove-provider <cli> [name]",
|
|
1365
|
+
" disconnect one, and optionally delete the sign-in it holds",
|
|
1128
1366
|
"",
|
|
1129
1367
|
"OPTIONS",
|
|
1130
1368
|
" -b, --branch <name> branch the session's worktree from this ref (default: current branch)",
|
|
@@ -1152,6 +1390,18 @@ function nearestFlag(flag) {
|
|
|
1152
1390
|
|
|
1153
1391
|
// src/cli.ts
|
|
1154
1392
|
var HEAP_MB = 12288;
|
|
1393
|
+
async function connectZai(dir) {
|
|
1394
|
+
process.stdout.write("z.ai API key (not shown as you type): ");
|
|
1395
|
+
const token = promptSecret();
|
|
1396
|
+
process.stdout.write("\n");
|
|
1397
|
+
if (!token) return { ok: false, error: "no key was entered" };
|
|
1398
|
+
console.log(`Asking ${ZAI_MODELS[0]} one question, to see whether the key works\u2026`);
|
|
1399
|
+
const v = await verifyZaiKey(token, ZAI_MODELS[0]);
|
|
1400
|
+
if (!v.ok) return { ok: false, error: v.error };
|
|
1401
|
+
writeZaiProfile(dir, token);
|
|
1402
|
+
console.log(` answered by ${v.served ?? "the endpoint (which named no model)"}.`);
|
|
1403
|
+
return { ok: true };
|
|
1404
|
+
}
|
|
1155
1405
|
function parseArgs(argv) {
|
|
1156
1406
|
let fromBranch;
|
|
1157
1407
|
let jobName;
|
|
@@ -1310,14 +1560,14 @@ async function main(argv) {
|
|
|
1310
1560
|
read: read2,
|
|
1311
1561
|
readFile: (p) => {
|
|
1312
1562
|
try {
|
|
1313
|
-
return
|
|
1563
|
+
return readFileSync5(p, "utf8");
|
|
1314
1564
|
} catch {
|
|
1315
1565
|
return void 0;
|
|
1316
1566
|
}
|
|
1317
1567
|
},
|
|
1318
1568
|
writeFile: (p, c) => {
|
|
1319
|
-
|
|
1320
|
-
|
|
1569
|
+
mkdirSync5(dirname5(p), { recursive: true });
|
|
1570
|
+
writeFileSync4(p, c);
|
|
1321
1571
|
},
|
|
1322
1572
|
home: process.env.HOME ?? "",
|
|
1323
1573
|
log: (s) => console.log(s)
|
|
@@ -1329,32 +1579,74 @@ async function main(argv) {
|
|
|
1329
1579
|
}
|
|
1330
1580
|
if (argv[0] === "add-provider") {
|
|
1331
1581
|
const kind = argv[1];
|
|
1332
|
-
if (kind
|
|
1333
|
-
console.error(`add-provider needs a CLI to connect:
|
|
1582
|
+
if (!kind || !CLI_KINDS.includes(kind)) {
|
|
1583
|
+
console.error(`add-provider needs a CLI to connect: ${CLI_KINDS.map((k) => `\`hcode add-provider ${k}\``).join(", ")}${argv[1] ? ` (got "${argv[1]}")` : ""}`);
|
|
1334
1584
|
process.exitCode = 1;
|
|
1335
1585
|
return;
|
|
1336
1586
|
}
|
|
1337
1587
|
const path = `${process.env.HOME ?? ""}/.horsecode/config.json`;
|
|
1338
|
-
process.exitCode = addProvider(kind, {
|
|
1588
|
+
process.exitCode = await addProvider(kind, {
|
|
1339
1589
|
home: process.env.HOME ?? "",
|
|
1340
1590
|
readConfig: () => {
|
|
1341
1591
|
try {
|
|
1342
|
-
const parsed = JSON.parse(
|
|
1592
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
1343
1593
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1344
1594
|
} catch {
|
|
1345
1595
|
return {};
|
|
1346
1596
|
}
|
|
1347
1597
|
},
|
|
1348
1598
|
writeConfig: (c) => {
|
|
1349
|
-
|
|
1350
|
-
|
|
1599
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
1600
|
+
writeFileSync4(path, JSON.stringify(c, null, 2) + "\n");
|
|
1351
1601
|
},
|
|
1352
|
-
|
|
1602
|
+
/**
|
|
1603
|
+
* "Make this profile real" means two different things, and z.ai is the one that is not a sign-in.
|
|
1604
|
+
*
|
|
1605
|
+
* The other three hand the terminal to their own OAuth. z.ai has none — a profile is a settings file
|
|
1606
|
+
* holding an endpoint and a key — so here the step is: ask for the key, write it, and then SPEND one
|
|
1607
|
+
* call proving it works. That last part is not ceremony: `claude auth status` reports any token as
|
|
1608
|
+
* connected, valid or not, so without a real call a mistyped key would be filed as a working account
|
|
1609
|
+
* and fail every call of the next run.
|
|
1610
|
+
*/
|
|
1611
|
+
login: (k, dir) => k === "zai" ? connectZai(dir ?? "") : runLogin(k, dir),
|
|
1353
1612
|
check: checkProfile,
|
|
1354
1613
|
log: (line) => console.log(line)
|
|
1355
1614
|
});
|
|
1356
1615
|
return;
|
|
1357
1616
|
}
|
|
1617
|
+
if (argv[0] === "remove-provider") {
|
|
1618
|
+
const kind = argv[1];
|
|
1619
|
+
if (!kind || !CLI_KINDS.includes(kind)) {
|
|
1620
|
+
console.error(removeUsage(argv[1]));
|
|
1621
|
+
process.exitCode = 1;
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const path = `${process.env.HOME ?? ""}/.horsecode/config.json`;
|
|
1625
|
+
const home2 = process.env.HOME ?? "";
|
|
1626
|
+
process.exitCode = removeProvider(kind, argv[2], {
|
|
1627
|
+
home: home2,
|
|
1628
|
+
readConfig: () => {
|
|
1629
|
+
try {
|
|
1630
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
1631
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1632
|
+
} catch {
|
|
1633
|
+
return {};
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
1636
|
+
writeConfig: (c) => {
|
|
1637
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
1638
|
+
writeFileSync4(path, JSON.stringify(c, null, 2) + "\n");
|
|
1639
|
+
},
|
|
1640
|
+
removeDir: (dir) => {
|
|
1641
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1642
|
+
},
|
|
1643
|
+
confirm,
|
|
1644
|
+
choose: chooseFrom,
|
|
1645
|
+
usage: fileUsageStore(home2),
|
|
1646
|
+
log: (line) => console.log(line)
|
|
1647
|
+
}).code;
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1358
1650
|
const cwd = process.cwd();
|
|
1359
1651
|
const config = loadConfig({
|
|
1360
1652
|
cwd,
|
|
@@ -1362,7 +1654,7 @@ async function main(argv) {
|
|
|
1362
1654
|
env: process.env,
|
|
1363
1655
|
readFile: (p) => {
|
|
1364
1656
|
try {
|
|
1365
|
-
return
|
|
1657
|
+
return readFileSync5(p, "utf8");
|
|
1366
1658
|
} catch {
|
|
1367
1659
|
return void 0;
|
|
1368
1660
|
}
|
|
@@ -1370,13 +1662,13 @@ async function main(argv) {
|
|
|
1370
1662
|
});
|
|
1371
1663
|
const home = process.env.HOME ?? "";
|
|
1372
1664
|
if (!process.env.HC_HEAP_SET && !process.env.NODE_OPTIONS) {
|
|
1373
|
-
const { spawnSync } = await import("child_process");
|
|
1665
|
+
const { spawnSync: spawnSync2 } = await import("child_process");
|
|
1374
1666
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1375
1667
|
const ignore = () => {
|
|
1376
1668
|
};
|
|
1377
1669
|
const SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
1378
1670
|
for (const sig of SIGNALS) process.on(sig, ignore);
|
|
1379
|
-
const r =
|
|
1671
|
+
const r = spawnSync2(process.execPath, [`--max-old-space-size=${HEAP_MB}`, ...process.argv.slice(1)], {
|
|
1380
1672
|
stdio: "inherit",
|
|
1381
1673
|
/**
|
|
1382
1674
|
* `NODE_ENV=production` is set here because it must be set BEFORE React is imported.
|
|
@@ -1412,7 +1704,7 @@ async function main(argv) {
|
|
|
1412
1704
|
process.once("SIGTERM", flush);
|
|
1413
1705
|
}
|
|
1414
1706
|
const telemetryNote = sink ? `\u{1F4C8} Telemetry \u2192 \`${sink.path}\` (one JSON object per line)` : void 0;
|
|
1415
|
-
const ambientLogins =
|
|
1707
|
+
const ambientLogins = CLI_KINDS.filter((k) => k !== "zai" && !config.accounts.some((a) => a.kind === k)).map((kind) => ({ kind, status: checkProfile(kind) }));
|
|
1416
1708
|
const accounts = new AccountPool(
|
|
1417
1709
|
[
|
|
1418
1710
|
...config.accounts,
|
|
@@ -1427,12 +1719,13 @@ async function main(argv) {
|
|
|
1427
1719
|
);
|
|
1428
1720
|
const notSignedIn = ambientLogins.filter((a) => !a.status.loggedIn).map((a) => a.kind);
|
|
1429
1721
|
const accountsNote = () => accountsLine(accounts.usage(), notSignedIn);
|
|
1722
|
+
const modelsPanelNote = (inUse) => modelsPanel(accounts.usage(), modelsFor, CLI_KINDS, inUse);
|
|
1430
1723
|
const raw = new CliProvider({ readOnly: false, accounts });
|
|
1431
1724
|
const provider = config.telemetry ? telemetryProvider(raw, telemetry()) : raw;
|
|
1432
1725
|
const skillRegistry = new SkillRegistry();
|
|
1433
1726
|
await registerBuiltinSkills(skillRegistry);
|
|
1434
1727
|
await skillRegistry.loadFromDir(externalSkillsDir(home));
|
|
1435
|
-
const skillsDir =
|
|
1728
|
+
const skillsDir = join11(cwd, ".horsecode", "skills");
|
|
1436
1729
|
if (existsSync3(skillsDir)) await skillRegistry.loadFromDir(skillsDir);
|
|
1437
1730
|
const worktreeHome = await mainWorktreeRoot(defaultGitRunner, cwd);
|
|
1438
1731
|
if (worktreeHome !== cwd) {
|
|
@@ -1465,7 +1758,7 @@ async function main(argv) {
|
|
|
1465
1758
|
const useTui = shouldUseTui(!!process.stdin.isTTY, !!process.stdout.isTTY, !!args.noTui);
|
|
1466
1759
|
if (!args.prompt) {
|
|
1467
1760
|
if (useTui) {
|
|
1468
|
-
const { runTuiRepl } = await import("./app-
|
|
1761
|
+
const { runTuiRepl } = await import("./app-5FXHE7GX.js");
|
|
1469
1762
|
const { fetchCatalog, makeProbe, discoverSources } = await import("./discover-G2Z6XC3O.js");
|
|
1470
1763
|
const { loadSourceCache, saveSourceCache } = await import("./source-cache-XEK5WN7I.js");
|
|
1471
1764
|
const manualSources = config.modelSources.length > 0;
|
|
@@ -1516,7 +1809,7 @@ _Expected something like \`https://github.com/owner/repo\` or a link to the dire
|
|
|
1516
1809
|
_Discoverable by every agent. \`/roles adjust\` assigns it to the roles it fits; \`/skills update\` re-installs it from upstream._`;
|
|
1517
1810
|
};
|
|
1518
1811
|
const reloadProjectSkills = async () => {
|
|
1519
|
-
const dir =
|
|
1812
|
+
const dir = join11(cwd, ".horsecode", "skills");
|
|
1520
1813
|
if (existsSync3(dir)) await skillRegistry.loadFromDir(dir);
|
|
1521
1814
|
};
|
|
1522
1815
|
const updateSkills = async () => {
|
|
@@ -1607,6 +1900,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
|
|
|
1607
1900
|
buildDeps,
|
|
1608
1901
|
memStore,
|
|
1609
1902
|
accountsNote,
|
|
1903
|
+
modelsPanel: modelsPanelNote,
|
|
1610
1904
|
listSkills,
|
|
1611
1905
|
updateSkills,
|
|
1612
1906
|
addSkill,
|
|
@@ -1644,7 +1938,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
|
|
|
1644
1938
|
...args.revisionRounds !== void 0 && { revisionRounds: args.revisionRounds }
|
|
1645
1939
|
};
|
|
1646
1940
|
if (useTui) {
|
|
1647
|
-
const { runTui } = await import("./app-
|
|
1941
|
+
const { runTui } = await import("./app-5FXHE7GX.js");
|
|
1648
1942
|
const res = await runTui({ buildDeps, job });
|
|
1649
1943
|
console.log(renderResult(res));
|
|
1650
1944
|
return;
|