@holt-os/holt 0.14.1 → 0.14.3
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/README.md +51 -13
- package/dist/cli.js +532 -194
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -2220,9 +2220,9 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2220
2220
|
}
|
|
2221
2221
|
|
|
2222
2222
|
// src/commands/launch.ts
|
|
2223
|
-
import { readFileSync as
|
|
2224
|
-
import { homedir as
|
|
2225
|
-
import { join as
|
|
2223
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, existsSync as existsSync12, mkdirSync as mkdirSync11, copyFileSync as copyFileSync2 } from "fs";
|
|
2224
|
+
import { homedir as homedir7 } from "os";
|
|
2225
|
+
import { join as join13, dirname as dirname6 } from "path";
|
|
2226
2226
|
|
|
2227
2227
|
// src/scheduler.ts
|
|
2228
2228
|
import { homedir as homedir3 } from "os";
|
|
@@ -2363,11 +2363,340 @@ function ensureLogsDir() {
|
|
|
2363
2363
|
mkdirSync7(logsDir(), { recursive: true });
|
|
2364
2364
|
}
|
|
2365
2365
|
|
|
2366
|
+
// src/skillcompiler.ts
|
|
2367
|
+
import {
|
|
2368
|
+
readFileSync as readFileSync10,
|
|
2369
|
+
writeFileSync as writeFileSync8,
|
|
2370
|
+
existsSync as existsSync8,
|
|
2371
|
+
mkdirSync as mkdirSync8,
|
|
2372
|
+
readdirSync as readdirSync2,
|
|
2373
|
+
statSync as statSync3,
|
|
2374
|
+
rmSync as rmSync4,
|
|
2375
|
+
cpSync
|
|
2376
|
+
} from "fs";
|
|
2377
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
2378
|
+
import { homedir as homedir4 } from "os";
|
|
2379
|
+
import { createHash as createHash2 } from "crypto";
|
|
2380
|
+
var MARKER = "holt-managed skill";
|
|
2381
|
+
var CODEX_PREFIX = "holt-";
|
|
2382
|
+
function manifestPath(ws = workspace()) {
|
|
2383
|
+
return join9(wsHoltDir(ws), "skill-commands.json");
|
|
2384
|
+
}
|
|
2385
|
+
function emptyManifest() {
|
|
2386
|
+
return { version: 1, brains: {} };
|
|
2387
|
+
}
|
|
2388
|
+
function readManifest(ws = workspace()) {
|
|
2389
|
+
const p = manifestPath(ws);
|
|
2390
|
+
if (!existsSync8(p)) return emptyManifest();
|
|
2391
|
+
try {
|
|
2392
|
+
const parsed = JSON.parse(readFileSync10(p, "utf8"));
|
|
2393
|
+
const brains = parsed.brains && typeof parsed.brains === "object" ? parsed.brains : {};
|
|
2394
|
+
return { version: 1, brains };
|
|
2395
|
+
} catch {
|
|
2396
|
+
return emptyManifest();
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
function writeManifest(m, ws = workspace()) {
|
|
2400
|
+
mkdirSync8(wsHoltDir(ws), { recursive: true });
|
|
2401
|
+
writeFileSync8(manifestPath(ws), JSON.stringify(m, null, 2) + "\n", "utf8");
|
|
2402
|
+
}
|
|
2403
|
+
function hashString(s) {
|
|
2404
|
+
return createHash2("sha256").update(s).digest("hex");
|
|
2405
|
+
}
|
|
2406
|
+
function hashDir(dir) {
|
|
2407
|
+
const h = createHash2("sha256");
|
|
2408
|
+
const walk = (rel) => {
|
|
2409
|
+
const abs = join9(dir, rel);
|
|
2410
|
+
let entries;
|
|
2411
|
+
try {
|
|
2412
|
+
entries = readdirSync2(abs).sort();
|
|
2413
|
+
} catch {
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
for (const name of entries) {
|
|
2417
|
+
const childRel = rel ? join9(rel, name) : name;
|
|
2418
|
+
const childAbs = join9(dir, childRel);
|
|
2419
|
+
let st;
|
|
2420
|
+
try {
|
|
2421
|
+
st = statSync3(childAbs);
|
|
2422
|
+
} catch {
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
if (st.isDirectory()) {
|
|
2426
|
+
h.update("D:" + childRel + "\n");
|
|
2427
|
+
walk(childRel);
|
|
2428
|
+
} else {
|
|
2429
|
+
h.update("F:" + childRel + "\n");
|
|
2430
|
+
try {
|
|
2431
|
+
h.update(readFileSync10(childAbs));
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
walk("");
|
|
2438
|
+
return h.digest("hex");
|
|
2439
|
+
}
|
|
2440
|
+
function readSkillSource(skill) {
|
|
2441
|
+
const file = join9(skill.dir, "SKILL.md");
|
|
2442
|
+
let raw = "";
|
|
2443
|
+
try {
|
|
2444
|
+
raw = readFileSync10(file, "utf8");
|
|
2445
|
+
} catch {
|
|
2446
|
+
return null;
|
|
2447
|
+
}
|
|
2448
|
+
const { data, body } = parseFrontmatter(raw);
|
|
2449
|
+
const description = (data.description || skill.description || "").trim();
|
|
2450
|
+
return { name: skill.name, description, body: body.trim() };
|
|
2451
|
+
}
|
|
2452
|
+
function tomlBasicString(s) {
|
|
2453
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ").trim();
|
|
2454
|
+
}
|
|
2455
|
+
function tomlMultiline(s) {
|
|
2456
|
+
return s.replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"');
|
|
2457
|
+
}
|
|
2458
|
+
function renderGeminiToml(src) {
|
|
2459
|
+
const instructions = src.body || src.description;
|
|
2460
|
+
if (!instructions.trim()) return null;
|
|
2461
|
+
const prompt = [
|
|
2462
|
+
instructions,
|
|
2463
|
+
"",
|
|
2464
|
+
"Apply the skill above to this request:",
|
|
2465
|
+
"{{args}}"
|
|
2466
|
+
].join("\n");
|
|
2467
|
+
const lines = [
|
|
2468
|
+
`# ${MARKER}: ${src.name}`,
|
|
2469
|
+
"# Generated by Holt from a SKILL.md. Edit the skill, not this file: Holt",
|
|
2470
|
+
'# will overwrite it on the next launch. Delete via "holt skill remove".'
|
|
2471
|
+
];
|
|
2472
|
+
if (src.description) lines.push(`description = "${tomlBasicString(src.description)}"`);
|
|
2473
|
+
lines.push('prompt = """');
|
|
2474
|
+
lines.push(tomlMultiline(prompt));
|
|
2475
|
+
lines.push('"""');
|
|
2476
|
+
return lines.join("\n") + "\n";
|
|
2477
|
+
}
|
|
2478
|
+
function renderCodexPrompt(src) {
|
|
2479
|
+
const instructions = src.body || src.description;
|
|
2480
|
+
if (!instructions.trim()) return null;
|
|
2481
|
+
const front = ["---"];
|
|
2482
|
+
if (src.description) front.push(`description: ${src.description.replace(/\n/g, " ").trim()}`);
|
|
2483
|
+
front.push("argument-hint: [request]");
|
|
2484
|
+
front.push("---");
|
|
2485
|
+
const parts = [
|
|
2486
|
+
front.join("\n"),
|
|
2487
|
+
"",
|
|
2488
|
+
`<!-- ${MARKER}: ${src.name} (generated by Holt from a SKILL.md; do not edit) -->`,
|
|
2489
|
+
"",
|
|
2490
|
+
instructions,
|
|
2491
|
+
"",
|
|
2492
|
+
"Apply the skill above to this request:",
|
|
2493
|
+
"",
|
|
2494
|
+
"$ARGUMENTS",
|
|
2495
|
+
""
|
|
2496
|
+
];
|
|
2497
|
+
return parts.join("\n");
|
|
2498
|
+
}
|
|
2499
|
+
function claudeSkillsDir(ws) {
|
|
2500
|
+
return join9(ws, ".claude", "skills");
|
|
2501
|
+
}
|
|
2502
|
+
function geminiCommandsDir(ws) {
|
|
2503
|
+
return join9(ws, ".gemini", "commands");
|
|
2504
|
+
}
|
|
2505
|
+
function codexPromptsDir() {
|
|
2506
|
+
return join9(homedir4(), ".codex", "prompts");
|
|
2507
|
+
}
|
|
2508
|
+
function fileIsOurs(path) {
|
|
2509
|
+
try {
|
|
2510
|
+
return readFileSync10(path, "utf8").includes(MARKER);
|
|
2511
|
+
} catch {
|
|
2512
|
+
return false;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function emptyResult() {
|
|
2516
|
+
return { written: [], skippedExisting: [], skippedUnexpressible: [], removed: [] };
|
|
2517
|
+
}
|
|
2518
|
+
function priorEntry(m, brain, path) {
|
|
2519
|
+
return (m.brains[brain] || []).find((e) => e.path === path);
|
|
2520
|
+
}
|
|
2521
|
+
function compileForBrain(brain, ws = workspace()) {
|
|
2522
|
+
const res = emptyResult();
|
|
2523
|
+
const manifest = readManifest(ws);
|
|
2524
|
+
const prior = manifest.brains[brain] || [];
|
|
2525
|
+
const nextEntries = [];
|
|
2526
|
+
let skills;
|
|
2527
|
+
try {
|
|
2528
|
+
skills = listSkills();
|
|
2529
|
+
} catch {
|
|
2530
|
+
return res;
|
|
2531
|
+
}
|
|
2532
|
+
const enabledNames = new Set(skills.map((s) => s.name));
|
|
2533
|
+
for (const skill of skills) {
|
|
2534
|
+
try {
|
|
2535
|
+
if (brain === "claude") {
|
|
2536
|
+
compileClaude(skill, ws, manifest, res, nextEntries);
|
|
2537
|
+
} else if (brain === "gemini") {
|
|
2538
|
+
compileGemini(skill, ws, manifest, res, nextEntries);
|
|
2539
|
+
} else {
|
|
2540
|
+
compileCodex(skill, manifest, res, nextEntries);
|
|
2541
|
+
}
|
|
2542
|
+
} catch {
|
|
2543
|
+
res.skippedUnexpressible.push(skill.name);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
for (const e of prior) {
|
|
2547
|
+
if (enabledNames.has(e.skill) && nextEntries.some((n) => n.path === e.path)) continue;
|
|
2548
|
+
if (removeArtifact(e.path)) res.removed.push(e.path);
|
|
2549
|
+
}
|
|
2550
|
+
manifest.brains[brain] = nextEntries;
|
|
2551
|
+
try {
|
|
2552
|
+
writeManifest(manifest, ws);
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
return res;
|
|
2556
|
+
}
|
|
2557
|
+
function compileClaude(skill, ws, manifest, res, next) {
|
|
2558
|
+
const dest = join9(claudeSkillsDir(ws), skill.name);
|
|
2559
|
+
const known = priorEntry(manifest, "claude", dest);
|
|
2560
|
+
if (existsSync8(dest) && !known) {
|
|
2561
|
+
res.skippedExisting.push(dest);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
const srcHash = hashDir(skill.dir);
|
|
2565
|
+
if (known && known.hash === srcHash && existsSync8(dest)) {
|
|
2566
|
+
next.push(known);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
2570
|
+
mkdirSync8(dirname3(dest), { recursive: true });
|
|
2571
|
+
cpSync(skill.dir, dest, { recursive: true });
|
|
2572
|
+
next.push({ skill: skill.name, path: dest, hash: hashDir(dest) });
|
|
2573
|
+
res.written.push(dest);
|
|
2574
|
+
}
|
|
2575
|
+
function compileGemini(skill, ws, manifest, res, next) {
|
|
2576
|
+
const src = readSkillSource(skill);
|
|
2577
|
+
if (!src) {
|
|
2578
|
+
res.skippedUnexpressible.push(skill.name);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
const content = renderGeminiToml(src);
|
|
2582
|
+
if (content === null) {
|
|
2583
|
+
res.skippedUnexpressible.push(skill.name);
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
const dest = join9(geminiCommandsDir(ws), `${skill.name}.toml`);
|
|
2587
|
+
writeTextArtifact("gemini", skill.name, dest, content, manifest, res, next);
|
|
2588
|
+
}
|
|
2589
|
+
function compileCodex(skill, manifest, res, next) {
|
|
2590
|
+
const src = readSkillSource(skill);
|
|
2591
|
+
if (!src) {
|
|
2592
|
+
res.skippedUnexpressible.push(skill.name);
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
const content = renderCodexPrompt(src);
|
|
2596
|
+
if (content === null) {
|
|
2597
|
+
res.skippedUnexpressible.push(skill.name);
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
const dest = join9(codexPromptsDir(), `${CODEX_PREFIX}${skill.name}.md`);
|
|
2601
|
+
writeTextArtifact("codex", skill.name, dest, content, manifest, res, next);
|
|
2602
|
+
}
|
|
2603
|
+
function writeTextArtifact(brain, skillName, dest, content, manifest, res, next) {
|
|
2604
|
+
const known = priorEntry(manifest, brain, dest);
|
|
2605
|
+
if (existsSync8(dest) && !known && !fileIsOurs(dest)) {
|
|
2606
|
+
res.skippedExisting.push(dest);
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
const hash = hashString(content);
|
|
2610
|
+
if (existsSync8(dest)) {
|
|
2611
|
+
let cur = "";
|
|
2612
|
+
try {
|
|
2613
|
+
cur = readFileSync10(dest, "utf8");
|
|
2614
|
+
} catch {
|
|
2615
|
+
cur = "";
|
|
2616
|
+
}
|
|
2617
|
+
if (hashString(cur) === hash) {
|
|
2618
|
+
next.push({ skill: skillName, path: dest, hash });
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
mkdirSync8(dirname3(dest), { recursive: true });
|
|
2623
|
+
writeFileSync8(dest, content, "utf8");
|
|
2624
|
+
next.push({ skill: skillName, path: dest, hash });
|
|
2625
|
+
res.written.push(dest);
|
|
2626
|
+
}
|
|
2627
|
+
function removeArtifact(path) {
|
|
2628
|
+
try {
|
|
2629
|
+
if (!existsSync8(path)) return false;
|
|
2630
|
+
rmSync4(path, { recursive: true, force: true });
|
|
2631
|
+
return true;
|
|
2632
|
+
} catch {
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
function syncSkillCommands(brain, ws = workspace()) {
|
|
2637
|
+
let res;
|
|
2638
|
+
try {
|
|
2639
|
+
res = compileForBrain(brain, ws);
|
|
2640
|
+
} catch {
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
reportQuiet(brain, res);
|
|
2644
|
+
}
|
|
2645
|
+
function syncAllBrains(enabled, ws = workspace()) {
|
|
2646
|
+
for (const brain of enabled) {
|
|
2647
|
+
try {
|
|
2648
|
+
const res = compileForBrain(brain, ws);
|
|
2649
|
+
reportQuiet(brain, res);
|
|
2650
|
+
} catch {
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function removeSkillArtifacts(skillName, ws = workspace()) {
|
|
2655
|
+
const manifest = readManifest(ws);
|
|
2656
|
+
let removed = 0;
|
|
2657
|
+
for (const brain of Object.keys(manifest.brains)) {
|
|
2658
|
+
const entries = manifest.brains[brain] || [];
|
|
2659
|
+
const keep = [];
|
|
2660
|
+
for (const e of entries) {
|
|
2661
|
+
if (e.skill === skillName) {
|
|
2662
|
+
if (removeArtifact(e.path)) removed++;
|
|
2663
|
+
} else {
|
|
2664
|
+
keep.push(e);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
manifest.brains[brain] = keep;
|
|
2668
|
+
}
|
|
2669
|
+
try {
|
|
2670
|
+
writeManifest(manifest, ws);
|
|
2671
|
+
} catch {
|
|
2672
|
+
}
|
|
2673
|
+
return removed;
|
|
2674
|
+
}
|
|
2675
|
+
var BRAIN_LABEL = {
|
|
2676
|
+
claude: "Claude Code",
|
|
2677
|
+
gemini: "Gemini CLI",
|
|
2678
|
+
codex: "Codex"
|
|
2679
|
+
};
|
|
2680
|
+
function reportQuiet(brain, res) {
|
|
2681
|
+
const changed = res.written.length + res.removed.length;
|
|
2682
|
+
if (changed === 0 && res.skippedExisting.length === 0) return;
|
|
2683
|
+
const label = BRAIN_LABEL[brain];
|
|
2684
|
+
const parts = [];
|
|
2685
|
+
if (res.written.length) parts.push(`${res.written.length} synced`);
|
|
2686
|
+
if (res.removed.length) parts.push(`${res.removed.length} removed`);
|
|
2687
|
+
if (parts.length) {
|
|
2688
|
+
console.log(c.dim(` Skills as ${label} commands: ${parts.join(", ")}.`));
|
|
2689
|
+
}
|
|
2690
|
+
for (const p of res.skippedExisting) {
|
|
2691
|
+
console.log(c.dim(` Skipped ${p} (already exists and not written by Holt).`));
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2366
2695
|
// src/commands/hook.ts
|
|
2367
|
-
import { readFileSync as
|
|
2696
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync10, copyFileSync, realpathSync as realpathSync2, appendFileSync as appendFileSync2, statSync as statSync5 } from "fs";
|
|
2368
2697
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
2369
|
-
import { join as
|
|
2370
|
-
import { homedir as
|
|
2698
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
2699
|
+
import { homedir as homedir5 } from "os";
|
|
2371
2700
|
|
|
2372
2701
|
// src/facts.ts
|
|
2373
2702
|
var MIN_EXCHANGES_FOR_EXTRACTION = 3;
|
|
@@ -2483,15 +2812,15 @@ async function extractAndSaveFacts(active, cfg, history, session) {
|
|
|
2483
2812
|
|
|
2484
2813
|
// src/wiki.ts
|
|
2485
2814
|
import {
|
|
2486
|
-
readFileSync as
|
|
2487
|
-
writeFileSync as
|
|
2488
|
-
existsSync as
|
|
2489
|
-
mkdirSync as
|
|
2490
|
-
rmSync as
|
|
2491
|
-
readdirSync as
|
|
2492
|
-
statSync as
|
|
2815
|
+
readFileSync as readFileSync11,
|
|
2816
|
+
writeFileSync as writeFileSync9,
|
|
2817
|
+
existsSync as existsSync9,
|
|
2818
|
+
mkdirSync as mkdirSync9,
|
|
2819
|
+
rmSync as rmSync5,
|
|
2820
|
+
readdirSync as readdirSync3,
|
|
2821
|
+
statSync as statSync4
|
|
2493
2822
|
} from "fs";
|
|
2494
|
-
import { join as
|
|
2823
|
+
import { join as join10 } from "path";
|
|
2495
2824
|
import { totalmem } from "os";
|
|
2496
2825
|
|
|
2497
2826
|
// src/localmodel.ts
|
|
@@ -2546,28 +2875,28 @@ function pullHint(model) {
|
|
|
2546
2875
|
|
|
2547
2876
|
// src/wiki.ts
|
|
2548
2877
|
function wikiDir() {
|
|
2549
|
-
return
|
|
2878
|
+
return join10(wsHoltDir(), "wiki");
|
|
2550
2879
|
}
|
|
2551
2880
|
function wikiStatePath() {
|
|
2552
|
-
return
|
|
2881
|
+
return join10(wikiDir(), ".state.json");
|
|
2553
2882
|
}
|
|
2554
2883
|
function wikiIndexPath() {
|
|
2555
|
-
return
|
|
2884
|
+
return join10(wikiDir(), "index.md");
|
|
2556
2885
|
}
|
|
2557
2886
|
function pagePath(slug) {
|
|
2558
|
-
return
|
|
2887
|
+
return join10(wikiDir(), `${slug}.md`);
|
|
2559
2888
|
}
|
|
2560
2889
|
function loadState() {
|
|
2561
2890
|
try {
|
|
2562
|
-
const s = JSON.parse(
|
|
2891
|
+
const s = JSON.parse(readFileSync11(wikiStatePath(), "utf8"));
|
|
2563
2892
|
return { lastSyncTs: typeof s.lastSyncTs === "number" ? s.lastSyncTs : 0, updatedAt: s.updatedAt ?? 0 };
|
|
2564
2893
|
} catch {
|
|
2565
2894
|
return { lastSyncTs: 0, updatedAt: 0 };
|
|
2566
2895
|
}
|
|
2567
2896
|
}
|
|
2568
2897
|
function saveState(s) {
|
|
2569
|
-
|
|
2570
|
-
|
|
2898
|
+
mkdirSync9(wikiDir(), { recursive: true });
|
|
2899
|
+
writeFileSync9(wikiStatePath(), JSON.stringify(s, null, 2) + "\n", "utf8");
|
|
2571
2900
|
}
|
|
2572
2901
|
function saveSyncMarker(ts) {
|
|
2573
2902
|
saveState({ lastSyncTs: ts, updatedAt: Date.now() });
|
|
@@ -2577,19 +2906,19 @@ function isPageFile(name) {
|
|
|
2577
2906
|
}
|
|
2578
2907
|
function listPageSlugs() {
|
|
2579
2908
|
const dir = wikiDir();
|
|
2580
|
-
if (!
|
|
2909
|
+
if (!existsSync9(dir)) return [];
|
|
2581
2910
|
try {
|
|
2582
|
-
return
|
|
2911
|
+
return readdirSync3(dir).filter(isPageFile).map((f) => f.slice(0, -3)).sort();
|
|
2583
2912
|
} catch {
|
|
2584
2913
|
return [];
|
|
2585
2914
|
}
|
|
2586
2915
|
}
|
|
2587
2916
|
function readPage(slug) {
|
|
2588
2917
|
const p = pagePath(slug);
|
|
2589
|
-
if (!
|
|
2918
|
+
if (!existsSync9(p)) return null;
|
|
2590
2919
|
let raw = "";
|
|
2591
2920
|
try {
|
|
2592
|
-
raw =
|
|
2921
|
+
raw = readFileSync11(p, "utf8");
|
|
2593
2922
|
} catch {
|
|
2594
2923
|
return null;
|
|
2595
2924
|
}
|
|
@@ -2631,8 +2960,8 @@ function renderPage(pg) {
|
|
|
2631
2960
|
return fm;
|
|
2632
2961
|
}
|
|
2633
2962
|
function writePage(pg) {
|
|
2634
|
-
|
|
2635
|
-
|
|
2963
|
+
mkdirSync9(wikiDir(), { recursive: true });
|
|
2964
|
+
writeFileSync9(pagePath(pg.slug), renderPage(pg), "utf8");
|
|
2636
2965
|
}
|
|
2637
2966
|
var WIKILINK_RE = /\[\[([^\]]+)\]\]/g;
|
|
2638
2967
|
function extractLinks(body) {
|
|
@@ -2909,8 +3238,8 @@ function renderIndex(pages) {
|
|
|
2909
3238
|
return lines.join("\n");
|
|
2910
3239
|
}
|
|
2911
3240
|
function writeIndex(pages) {
|
|
2912
|
-
|
|
2913
|
-
|
|
3241
|
+
mkdirSync9(wikiDir(), { recursive: true });
|
|
3242
|
+
writeFileSync9(wikiIndexPath(), renderIndex(pages), "utf8");
|
|
2914
3243
|
}
|
|
2915
3244
|
var WIKI_SESSION = "wiki";
|
|
2916
3245
|
function wikiRowId(slug) {
|
|
@@ -2925,9 +3254,9 @@ async function indexPagesForRecall(pages) {
|
|
|
2925
3254
|
if (pages.length === 0) return;
|
|
2926
3255
|
const slugs = new Set(pages.map((p) => sanitizeName(p.slug).slice(0, 24)));
|
|
2927
3256
|
const kept = dropWikiRows(slugs);
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3257
|
+
mkdirSync9(join10(wsHoltDir(), "memory"), { recursive: true });
|
|
3258
|
+
writeFileSync9(
|
|
3259
|
+
join10(wsHoltDir(), "memory", "turns.jsonl"),
|
|
2931
3260
|
kept.map((t) => JSON.stringify(t)).join("\n") + (kept.length ? "\n" : ""),
|
|
2932
3261
|
"utf8"
|
|
2933
3262
|
);
|
|
@@ -2949,9 +3278,9 @@ ${pg.body}`.slice(0, 4e3);
|
|
|
2949
3278
|
function clearWikiRecallRows() {
|
|
2950
3279
|
try {
|
|
2951
3280
|
const kept = loadTurns().filter((t) => t.session !== WIKI_SESSION);
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
3281
|
+
mkdirSync9(join10(wsHoltDir(), "memory"), { recursive: true });
|
|
3282
|
+
writeFileSync9(
|
|
3283
|
+
join10(wsHoltDir(), "memory", "turns.jsonl"),
|
|
2955
3284
|
kept.map((t) => JSON.stringify(t)).join("\n") + (kept.length ? "\n" : ""),
|
|
2956
3285
|
"utf8"
|
|
2957
3286
|
);
|
|
@@ -2959,14 +3288,14 @@ function clearWikiRecallRows() {
|
|
|
2959
3288
|
}
|
|
2960
3289
|
}
|
|
2961
3290
|
function wipeWiki() {
|
|
2962
|
-
if (
|
|
3291
|
+
if (existsSync9(wikiDir())) rmSync5(wikiDir(), { recursive: true, force: true });
|
|
2963
3292
|
}
|
|
2964
3293
|
function wikiStats() {
|
|
2965
3294
|
const slugs = listPageSlugs();
|
|
2966
3295
|
let bytes = 0;
|
|
2967
3296
|
for (const slug of slugs) {
|
|
2968
3297
|
try {
|
|
2969
|
-
bytes +=
|
|
3298
|
+
bytes += statSync4(pagePath(slug)).size;
|
|
2970
3299
|
} catch {
|
|
2971
3300
|
}
|
|
2972
3301
|
}
|
|
@@ -3005,26 +3334,26 @@ var HOOK_CMD = {
|
|
|
3005
3334
|
capture: "holt hook capture"
|
|
3006
3335
|
};
|
|
3007
3336
|
function globalSettingsPath() {
|
|
3008
|
-
return
|
|
3337
|
+
return join11(homedir5(), ".claude", "settings.json");
|
|
3009
3338
|
}
|
|
3010
3339
|
function projectSettingsPath() {
|
|
3011
|
-
return
|
|
3340
|
+
return join11(process.cwd(), ".claude", "settings.json");
|
|
3012
3341
|
}
|
|
3013
3342
|
function readSettings(path) {
|
|
3014
|
-
if (!
|
|
3343
|
+
if (!existsSync10(path)) return {};
|
|
3015
3344
|
try {
|
|
3016
|
-
const parsed = JSON.parse(
|
|
3345
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
3017
3346
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
3018
3347
|
} catch {
|
|
3019
3348
|
throw new Error(`could not parse ${path} (not valid JSON) - leaving it untouched`);
|
|
3020
3349
|
}
|
|
3021
3350
|
}
|
|
3022
3351
|
function writeSettings(path, settings) {
|
|
3023
|
-
|
|
3024
|
-
|
|
3352
|
+
mkdirSync10(dirname4(path), { recursive: true });
|
|
3353
|
+
writeFileSync10(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
3025
3354
|
}
|
|
3026
3355
|
function backup(path) {
|
|
3027
|
-
if (!
|
|
3356
|
+
if (!existsSync10(path)) return null;
|
|
3028
3357
|
const dest = `${path}.holt-bak`;
|
|
3029
3358
|
try {
|
|
3030
3359
|
copyFileSync(path, dest);
|
|
@@ -3114,7 +3443,7 @@ function remove(rest) {
|
|
|
3114
3443
|
const paths = project ? [projectSettingsPath()] : [globalSettingsPath(), projectSettingsPath()];
|
|
3115
3444
|
let touchedAny = false;
|
|
3116
3445
|
for (const path of paths) {
|
|
3117
|
-
if (!
|
|
3446
|
+
if (!existsSync10(path)) continue;
|
|
3118
3447
|
let settings;
|
|
3119
3448
|
try {
|
|
3120
3449
|
settings = readSettings(path);
|
|
@@ -3144,7 +3473,7 @@ function status(rest) {
|
|
|
3144
3473
|
];
|
|
3145
3474
|
console.log(c.accent(" Holt ambient hooks"));
|
|
3146
3475
|
for (const e of entries) {
|
|
3147
|
-
if (!
|
|
3476
|
+
if (!existsSync10(e.path)) {
|
|
3148
3477
|
console.log(c.dim(` ${e.label.padEnd(8)} ${e.path} (no settings file)`));
|
|
3149
3478
|
continue;
|
|
3150
3479
|
}
|
|
@@ -3194,7 +3523,7 @@ function parseHookInput(raw) {
|
|
|
3194
3523
|
}
|
|
3195
3524
|
}
|
|
3196
3525
|
function enterFolder(cwd) {
|
|
3197
|
-
if (!cwd || !
|
|
3526
|
+
if (!cwd || !existsSync10(cwd)) return null;
|
|
3198
3527
|
try {
|
|
3199
3528
|
const real = realpathSync2(cwd);
|
|
3200
3529
|
process.chdir(real);
|
|
@@ -3205,13 +3534,13 @@ function enterFolder(cwd) {
|
|
|
3205
3534
|
}
|
|
3206
3535
|
function guardTrusted(real) {
|
|
3207
3536
|
if (!isTrusted(real)) return false;
|
|
3208
|
-
if (!
|
|
3537
|
+
if (!existsSync10(memDir())) return false;
|
|
3209
3538
|
return true;
|
|
3210
3539
|
}
|
|
3211
3540
|
function hookLog(msg) {
|
|
3212
3541
|
try {
|
|
3213
|
-
|
|
3214
|
-
appendFileSync2(
|
|
3542
|
+
mkdirSync10(GLOBAL_DIR, { recursive: true });
|
|
3543
|
+
appendFileSync2(join11(GLOBAL_DIR, "hooks.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3215
3544
|
`, "utf8");
|
|
3216
3545
|
} catch {
|
|
3217
3546
|
}
|
|
@@ -3219,18 +3548,18 @@ function hookLog(msg) {
|
|
|
3219
3548
|
function fileBytes(path) {
|
|
3220
3549
|
if (!path) return 0;
|
|
3221
3550
|
try {
|
|
3222
|
-
return
|
|
3551
|
+
return statSync5(path).size;
|
|
3223
3552
|
} catch {
|
|
3224
3553
|
return 0;
|
|
3225
3554
|
}
|
|
3226
3555
|
}
|
|
3227
3556
|
var THROTTLE_MIN_NEW_EXCHANGES = 2;
|
|
3228
3557
|
function hookStatePath() {
|
|
3229
|
-
return
|
|
3558
|
+
return join11(GLOBAL_DIR, "hook-state.json");
|
|
3230
3559
|
}
|
|
3231
3560
|
function readHookState() {
|
|
3232
3561
|
try {
|
|
3233
|
-
const parsed = JSON.parse(
|
|
3562
|
+
const parsed = JSON.parse(readFileSync12(hookStatePath(), "utf8"));
|
|
3234
3563
|
if (!parsed || typeof parsed !== "object") return {};
|
|
3235
3564
|
return parsed;
|
|
3236
3565
|
} catch {
|
|
@@ -3243,10 +3572,10 @@ function lastProcessedExchanges(state, sessionId) {
|
|
|
3243
3572
|
}
|
|
3244
3573
|
function saveHookState(sessionId, exchanges) {
|
|
3245
3574
|
try {
|
|
3246
|
-
|
|
3575
|
+
mkdirSync10(GLOBAL_DIR, { recursive: true });
|
|
3247
3576
|
const state = readHookState();
|
|
3248
3577
|
state[sessionId] = { lastExchanges: exchanges };
|
|
3249
|
-
|
|
3578
|
+
writeFileSync10(hookStatePath(), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
3250
3579
|
} catch {
|
|
3251
3580
|
}
|
|
3252
3581
|
}
|
|
@@ -3320,7 +3649,7 @@ function readTranscript(path) {
|
|
|
3320
3649
|
const out = [];
|
|
3321
3650
|
let raw;
|
|
3322
3651
|
try {
|
|
3323
|
-
raw =
|
|
3652
|
+
raw = readFileSync12(path, "utf8");
|
|
3324
3653
|
} catch {
|
|
3325
3654
|
return out;
|
|
3326
3655
|
}
|
|
@@ -3348,28 +3677,28 @@ function readTranscript(path) {
|
|
|
3348
3677
|
return out;
|
|
3349
3678
|
}
|
|
3350
3679
|
function resolveCommandPath(command) {
|
|
3351
|
-
if (command.startsWith("/") &&
|
|
3680
|
+
if (command.startsWith("/") && existsSync10(command)) return command;
|
|
3352
3681
|
try {
|
|
3353
3682
|
const shell = process.env.SHELL || "/bin/sh";
|
|
3354
3683
|
const res = spawnSync3(shell, ["-lc", "command -v " + command], { encoding: "utf8" });
|
|
3355
3684
|
if (res.status === 0) {
|
|
3356
3685
|
const p = (res.stdout || "").trim().split("\n")[0]?.trim();
|
|
3357
|
-
if (p && p.startsWith("/") &&
|
|
3686
|
+
if (p && p.startsWith("/") && existsSync10(p)) return p;
|
|
3358
3687
|
}
|
|
3359
3688
|
} catch {
|
|
3360
3689
|
}
|
|
3361
|
-
const home =
|
|
3690
|
+
const home = homedir5();
|
|
3362
3691
|
const dirs = [
|
|
3363
|
-
|
|
3692
|
+
join11(home, ".local", "bin"),
|
|
3364
3693
|
"/opt/homebrew/bin",
|
|
3365
3694
|
"/usr/local/bin",
|
|
3366
|
-
|
|
3695
|
+
join11(home, ".npm-global", "bin"),
|
|
3367
3696
|
"/usr/bin",
|
|
3368
3697
|
"/bin"
|
|
3369
3698
|
];
|
|
3370
3699
|
for (const dir of dirs) {
|
|
3371
|
-
const candidate =
|
|
3372
|
-
if (
|
|
3700
|
+
const candidate = join11(dir, command);
|
|
3701
|
+
if (existsSync10(candidate)) return candidate;
|
|
3373
3702
|
}
|
|
3374
3703
|
return null;
|
|
3375
3704
|
}
|
|
@@ -3443,7 +3772,7 @@ async function capture() {
|
|
|
3443
3772
|
let bytes = 0;
|
|
3444
3773
|
let lines = 0;
|
|
3445
3774
|
try {
|
|
3446
|
-
const rawT =
|
|
3775
|
+
const rawT = readFileSync12(data.transcript_path, "utf8");
|
|
3447
3776
|
bytes = Buffer.byteLength(rawT, "utf8");
|
|
3448
3777
|
lines = countLines(rawT);
|
|
3449
3778
|
} catch {
|
|
@@ -3541,8 +3870,8 @@ async function hook(sub, rest = []) {
|
|
|
3541
3870
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3542
3871
|
|
|
3543
3872
|
// src/output.ts
|
|
3544
|
-
import { writeFileSync as
|
|
3545
|
-
import { join as
|
|
3873
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
3874
|
+
import { join as join12, isAbsolute } from "path";
|
|
3546
3875
|
function escapeHtml(s) {
|
|
3547
3876
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3548
3877
|
}
|
|
@@ -3682,8 +4011,8 @@ function saveReply(reply, format, name) {
|
|
|
3682
4011
|
throw new Error('name must be a plain file name in this folder (no "/", "\\", or "..").');
|
|
3683
4012
|
}
|
|
3684
4013
|
if (!/\.[a-z0-9]+$/i.test(base)) base += ext;
|
|
3685
|
-
const path =
|
|
3686
|
-
|
|
4014
|
+
const path = join12(process.cwd(), base);
|
|
4015
|
+
writeFileSync11(path, renderReply(reply, format), "utf8");
|
|
3687
4016
|
return path;
|
|
3688
4017
|
}
|
|
3689
4018
|
|
|
@@ -3711,12 +4040,12 @@ async function login(which) {
|
|
|
3711
4040
|
}
|
|
3712
4041
|
|
|
3713
4042
|
// src/access.ts
|
|
3714
|
-
import { existsSync as
|
|
3715
|
-
import { homedir as
|
|
3716
|
-
import { dirname as
|
|
4043
|
+
import { existsSync as existsSync11, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4044
|
+
import { homedir as homedir6 } from "os";
|
|
4045
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, resolve as resolve2, sep } from "path";
|
|
3717
4046
|
function expandHome(p) {
|
|
3718
|
-
if (p === "~") return
|
|
3719
|
-
if (p.startsWith("~/")) return resolve2(
|
|
4047
|
+
if (p === "~") return homedir6();
|
|
4048
|
+
if (p.startsWith("~/")) return resolve2(homedir6(), p.slice(2));
|
|
3720
4049
|
return p;
|
|
3721
4050
|
}
|
|
3722
4051
|
function isInside(child, parent) {
|
|
@@ -3727,7 +4056,7 @@ function isInside(child, parent) {
|
|
|
3727
4056
|
function resolveExisting(token) {
|
|
3728
4057
|
const expanded = expandHome(token);
|
|
3729
4058
|
const abs = isAbsolute2(expanded) ? expanded : resolve2(process.cwd(), expanded);
|
|
3730
|
-
if (!
|
|
4059
|
+
if (!existsSync11(abs)) return null;
|
|
3731
4060
|
try {
|
|
3732
4061
|
return realpathSync3(abs);
|
|
3733
4062
|
} catch {
|
|
@@ -3736,9 +4065,9 @@ function resolveExisting(token) {
|
|
|
3736
4065
|
}
|
|
3737
4066
|
function containingDir(absPath) {
|
|
3738
4067
|
try {
|
|
3739
|
-
return
|
|
4068
|
+
return statSync6(absPath).isDirectory() ? absPath : dirname5(absPath);
|
|
3740
4069
|
} catch {
|
|
3741
|
-
return
|
|
4070
|
+
return dirname5(absPath);
|
|
3742
4071
|
}
|
|
3743
4072
|
}
|
|
3744
4073
|
function resolveGrantDir(token) {
|
|
@@ -4166,13 +4495,13 @@ var CONTEXT_MARKER = "<!-- holt-managed identity -->";
|
|
|
4166
4495
|
function brandContextFile(id) {
|
|
4167
4496
|
const name = CONTEXT_FILE[id];
|
|
4168
4497
|
if (!name) return null;
|
|
4169
|
-
const path =
|
|
4498
|
+
const path = join13(workspace(), name);
|
|
4170
4499
|
try {
|
|
4171
|
-
if (
|
|
4172
|
-
const cur =
|
|
4500
|
+
if (existsSync12(path)) {
|
|
4501
|
+
const cur = readFileSync13(path, "utf8");
|
|
4173
4502
|
if (!cur.includes(CONTEXT_MARKER)) return null;
|
|
4174
4503
|
}
|
|
4175
|
-
|
|
4504
|
+
writeFileSync12(path, `${CONTEXT_MARKER}
|
|
4176
4505
|
# Holt
|
|
4177
4506
|
|
|
4178
4507
|
${HOLT_IDENTITY}
|
|
@@ -4183,11 +4512,11 @@ ${HOLT_IDENTITY}
|
|
|
4183
4512
|
}
|
|
4184
4513
|
}
|
|
4185
4514
|
function brandStatusLine() {
|
|
4186
|
-
const path =
|
|
4515
|
+
const path = join13(workspace(), ".claude", "settings.json");
|
|
4187
4516
|
let settings = {};
|
|
4188
4517
|
try {
|
|
4189
|
-
if (
|
|
4190
|
-
const parsed = JSON.parse(
|
|
4518
|
+
if (existsSync12(path)) {
|
|
4519
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
4191
4520
|
if (parsed && typeof parsed === "object") settings = parsed;
|
|
4192
4521
|
}
|
|
4193
4522
|
} catch {
|
|
@@ -4195,13 +4524,13 @@ function brandStatusLine() {
|
|
|
4195
4524
|
}
|
|
4196
4525
|
if (settings.statusLine) return false;
|
|
4197
4526
|
try {
|
|
4198
|
-
if (
|
|
4527
|
+
if (existsSync12(path)) copyFileSync2(path, `${path}.holt-bak`);
|
|
4199
4528
|
settings.statusLine = {
|
|
4200
4529
|
type: "command",
|
|
4201
4530
|
command: `${resolveHoltPath()} statusline`
|
|
4202
4531
|
};
|
|
4203
|
-
|
|
4204
|
-
|
|
4532
|
+
mkdirSync11(dirname6(path), { recursive: true });
|
|
4533
|
+
writeFileSync12(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
4205
4534
|
return true;
|
|
4206
4535
|
} catch {
|
|
4207
4536
|
return false;
|
|
@@ -4223,7 +4552,7 @@ function resolveTarget(cfg) {
|
|
|
4223
4552
|
async function ensureSetup() {
|
|
4224
4553
|
const ws = workspace();
|
|
4225
4554
|
if (!isTrusted(ws)) trustDir(ws);
|
|
4226
|
-
|
|
4555
|
+
mkdirSync11(memDir(), { recursive: true });
|
|
4227
4556
|
if (!hooksAlreadyInstalled()) {
|
|
4228
4557
|
try {
|
|
4229
4558
|
await hook("install", []);
|
|
@@ -4233,8 +4562,8 @@ async function ensureSetup() {
|
|
|
4233
4562
|
}
|
|
4234
4563
|
function hooksAlreadyInstalled() {
|
|
4235
4564
|
try {
|
|
4236
|
-
const p =
|
|
4237
|
-
return
|
|
4565
|
+
const p = join13(homedir7(), ".claude", "settings.json");
|
|
4566
|
+
return existsSync12(p) && readFileSync13(p, "utf8").includes("holt hook");
|
|
4238
4567
|
} catch {
|
|
4239
4568
|
return false;
|
|
4240
4569
|
}
|
|
@@ -4283,6 +4612,7 @@ async function launch() {
|
|
|
4283
4612
|
if (ctx) console.log(c.dim(` Holt identity: ${ctx}`));
|
|
4284
4613
|
}
|
|
4285
4614
|
if (target.id === "claude") brandStatusLine();
|
|
4615
|
+
syncSkillCommands(target.id);
|
|
4286
4616
|
const interactive = INTERACTIVE_ARGS[target.id];
|
|
4287
4617
|
const args = [...interactive, ...flags];
|
|
4288
4618
|
console.log(c.dim(' Starting your Holt session. Type "exit" to leave.\n'));
|
|
@@ -4291,7 +4621,7 @@ async function launch() {
|
|
|
4291
4621
|
}
|
|
4292
4622
|
|
|
4293
4623
|
// src/commands/memory.ts
|
|
4294
|
-
import { existsSync as
|
|
4624
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4295
4625
|
import { basename } from "path";
|
|
4296
4626
|
async function memoryCmd(sub, rest = []) {
|
|
4297
4627
|
const { ask, close } = createReader();
|
|
@@ -4339,12 +4669,12 @@ async function memoryCmd(sub, rest = []) {
|
|
|
4339
4669
|
return;
|
|
4340
4670
|
}
|
|
4341
4671
|
if (action === "facts") {
|
|
4342
|
-
if (!
|
|
4672
|
+
if (!existsSync13(factsMdPath())) {
|
|
4343
4673
|
console.log(c.dim("\n No facts distilled yet. They form when you end a chat session.\n"));
|
|
4344
4674
|
close();
|
|
4345
4675
|
return;
|
|
4346
4676
|
}
|
|
4347
|
-
const body =
|
|
4677
|
+
const body = readFileSync14(factsMdPath(), "utf8").trim();
|
|
4348
4678
|
console.log("\n" + body + "\n");
|
|
4349
4679
|
close();
|
|
4350
4680
|
return;
|
|
@@ -4458,8 +4788,8 @@ function globalCmd(act, rest) {
|
|
|
4458
4788
|
}
|
|
4459
4789
|
|
|
4460
4790
|
// src/commands/wiki.ts
|
|
4461
|
-
import { existsSync as
|
|
4462
|
-
import { join as
|
|
4791
|
+
import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
|
|
4792
|
+
import { join as join14 } from "path";
|
|
4463
4793
|
import { spawn as spawn3 } from "child_process";
|
|
4464
4794
|
function titleToSlug(title) {
|
|
4465
4795
|
return proposeSlug(title);
|
|
@@ -4652,7 +4982,7 @@ function listCmd() {
|
|
|
4652
4982
|
for (const slug of slugs) {
|
|
4653
4983
|
const pg = readPage(slug);
|
|
4654
4984
|
if (!pg) continue;
|
|
4655
|
-
const size =
|
|
4985
|
+
const size = existsSync14(join14(wikiDir(), slug + ".md")) ? (readFileSync15(join14(wikiDir(), slug + ".md"), "utf8").length / 1024).toFixed(1) : "0.0";
|
|
4656
4986
|
console.log(` ${c.cyan(pg.title.padEnd(28).slice(0, 28))} ${c.dim(pg.updated || " ")} ${size} KB`);
|
|
4657
4987
|
}
|
|
4658
4988
|
console.log("");
|
|
@@ -4670,7 +5000,7 @@ function showCmd(name) {
|
|
|
4670
5000
|
`));
|
|
4671
5001
|
return;
|
|
4672
5002
|
}
|
|
4673
|
-
const raw =
|
|
5003
|
+
const raw = readFileSync15(join14(wikiDir(), slug + ".md"), "utf8");
|
|
4674
5004
|
console.log("\n" + raw.trim() + "\n");
|
|
4675
5005
|
}
|
|
4676
5006
|
async function statusCmd(cfg) {
|
|
@@ -4740,8 +5070,8 @@ function setupCmd(cfg) {
|
|
|
4740
5070
|
`));
|
|
4741
5071
|
}
|
|
4742
5072
|
function openCmd() {
|
|
4743
|
-
const target =
|
|
4744
|
-
if (!
|
|
5073
|
+
const target = existsSync14(wikiIndexPath()) ? wikiIndexPath() : wikiDir();
|
|
5074
|
+
if (!existsSync14(target)) {
|
|
4745
5075
|
console.log(c.dim('\n No wiki yet. Run "holt wiki sync" first.\n'));
|
|
4746
5076
|
return;
|
|
4747
5077
|
}
|
|
@@ -4828,29 +5158,29 @@ async function wikiCmd(sub, rest = []) {
|
|
|
4828
5158
|
|
|
4829
5159
|
// src/commands/skill.ts
|
|
4830
5160
|
import {
|
|
4831
|
-
mkdirSync as
|
|
4832
|
-
writeFileSync as
|
|
4833
|
-
readFileSync as
|
|
4834
|
-
existsSync as
|
|
4835
|
-
readdirSync as
|
|
4836
|
-
statSync as
|
|
4837
|
-
rmSync as
|
|
4838
|
-
cpSync,
|
|
5161
|
+
mkdirSync as mkdirSync13,
|
|
5162
|
+
writeFileSync as writeFileSync14,
|
|
5163
|
+
readFileSync as readFileSync17,
|
|
5164
|
+
existsSync as existsSync16,
|
|
5165
|
+
readdirSync as readdirSync4,
|
|
5166
|
+
statSync as statSync8,
|
|
5167
|
+
rmSync as rmSync6,
|
|
5168
|
+
cpSync as cpSync2,
|
|
4839
5169
|
mkdtempSync
|
|
4840
5170
|
} from "fs";
|
|
4841
|
-
import { join as
|
|
5171
|
+
import { join as join16, resolve as resolve4, sep as sep2, isAbsolute as isAbsolute4 } from "path";
|
|
4842
5172
|
import { tmpdir } from "os";
|
|
4843
5173
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4844
5174
|
|
|
4845
5175
|
// src/registry.ts
|
|
4846
|
-
import { readFileSync as
|
|
4847
|
-
import { join as
|
|
5176
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync13, existsSync as existsSync15, mkdirSync as mkdirSync12, statSync as statSync7 } from "fs";
|
|
5177
|
+
import { join as join15, isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
|
|
4848
5178
|
import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
|
|
4849
5179
|
var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/holt-os/registry/main/registry.json";
|
|
4850
5180
|
var REGISTRY_REPO_URL = "https://github.com/holt-os/registry";
|
|
4851
5181
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
4852
5182
|
function registryCachePath() {
|
|
4853
|
-
return
|
|
5183
|
+
return join15(GLOBAL_DIR, "registry-cache.json");
|
|
4854
5184
|
}
|
|
4855
5185
|
function registryUrl() {
|
|
4856
5186
|
const env = (process.env.HOLT_REGISTRY_URL || "").trim();
|
|
@@ -4889,8 +5219,8 @@ function parseRegistry(text) {
|
|
|
4889
5219
|
function readCache() {
|
|
4890
5220
|
try {
|
|
4891
5221
|
const path = registryCachePath();
|
|
4892
|
-
if (!
|
|
4893
|
-
const raw = JSON.parse(
|
|
5222
|
+
if (!existsSync15(path)) return null;
|
|
5223
|
+
const raw = JSON.parse(readFileSync16(path, "utf8"));
|
|
4894
5224
|
if (!raw || typeof raw.fetchedAt !== "number" || typeof raw.url !== "string") return null;
|
|
4895
5225
|
const registry = coerceRegistry(raw.registry);
|
|
4896
5226
|
if (!registry) return null;
|
|
@@ -4901,9 +5231,9 @@ function readCache() {
|
|
|
4901
5231
|
}
|
|
4902
5232
|
function writeCache(url, registry) {
|
|
4903
5233
|
try {
|
|
4904
|
-
|
|
5234
|
+
mkdirSync12(GLOBAL_DIR, { recursive: true });
|
|
4905
5235
|
const body = { fetchedAt: Date.now(), url, registry };
|
|
4906
|
-
|
|
5236
|
+
writeFileSync13(registryCachePath(), JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
4907
5237
|
} catch {
|
|
4908
5238
|
}
|
|
4909
5239
|
}
|
|
@@ -4921,10 +5251,10 @@ function localPathFor(loc) {
|
|
|
4921
5251
|
var UNREACHABLE_MSG = "no registry reachable; set HOLT_REGISTRY_URL or the community registry is not live yet";
|
|
4922
5252
|
function loadLocal(path) {
|
|
4923
5253
|
try {
|
|
4924
|
-
if (!
|
|
5254
|
+
if (!existsSync15(path)) {
|
|
4925
5255
|
return { registry: null, fromCache: false, error: UNREACHABLE_MSG };
|
|
4926
5256
|
}
|
|
4927
|
-
const registry = parseRegistry(
|
|
5257
|
+
const registry = parseRegistry(readFileSync16(path, "utf8"));
|
|
4928
5258
|
if (!registry) {
|
|
4929
5259
|
return { registry: null, fromCache: false, error: "registry file is not valid JSON" };
|
|
4930
5260
|
}
|
|
@@ -4993,6 +5323,12 @@ function buildPublishEntry(input) {
|
|
|
4993
5323
|
}
|
|
4994
5324
|
|
|
4995
5325
|
// src/commands/skill.ts
|
|
5326
|
+
function enabledBrains() {
|
|
5327
|
+
const cfg = loadConfig();
|
|
5328
|
+
if (!cfg) return [...BRAIN_IDS];
|
|
5329
|
+
const on2 = BRAIN_IDS.filter((b) => cfg.brains[b].enabled);
|
|
5330
|
+
return on2.length ? on2 : [...BRAIN_IDS];
|
|
5331
|
+
}
|
|
4996
5332
|
function usage() {
|
|
4997
5333
|
console.log(c.dim([
|
|
4998
5334
|
"",
|
|
@@ -5053,12 +5389,12 @@ function resolveSubpathDir(fetched, subpath) {
|
|
|
5053
5389
|
return target;
|
|
5054
5390
|
}
|
|
5055
5391
|
function findSkillDir(root) {
|
|
5056
|
-
if (
|
|
5392
|
+
if (existsSync16(join16(root, "SKILL.md"))) return root;
|
|
5057
5393
|
let subs = [];
|
|
5058
5394
|
try {
|
|
5059
|
-
subs =
|
|
5395
|
+
subs = readdirSync4(root).filter((f) => {
|
|
5060
5396
|
try {
|
|
5061
|
-
return
|
|
5397
|
+
return statSync8(join16(root, f)).isDirectory();
|
|
5062
5398
|
} catch {
|
|
5063
5399
|
return false;
|
|
5064
5400
|
}
|
|
@@ -5066,8 +5402,8 @@ function findSkillDir(root) {
|
|
|
5066
5402
|
} catch {
|
|
5067
5403
|
return null;
|
|
5068
5404
|
}
|
|
5069
|
-
const withSkill = subs.filter((f) =>
|
|
5070
|
-
if (withSkill.length === 1) return
|
|
5405
|
+
const withSkill = subs.filter((f) => existsSync16(join16(root, f, "SKILL.md")));
|
|
5406
|
+
if (withSkill.length === 1) return join16(root, withSkill[0]);
|
|
5071
5407
|
return null;
|
|
5072
5408
|
}
|
|
5073
5409
|
function cmdList() {
|
|
@@ -5108,7 +5444,7 @@ function cmdShow(name) {
|
|
|
5108
5444
|
const { skill, body } = loaded;
|
|
5109
5445
|
console.log("\n" + c.accent(skill.name) + c.dim(` (${skill.scope})`));
|
|
5110
5446
|
if (skill.description) console.log(" " + skill.description);
|
|
5111
|
-
console.log(c.dim(" " +
|
|
5447
|
+
console.log(c.dim(" " + join16(skill.dir, "SKILL.md")));
|
|
5112
5448
|
console.log("");
|
|
5113
5449
|
console.log(c.dim(body));
|
|
5114
5450
|
console.log("");
|
|
@@ -5120,19 +5456,19 @@ function cmdCreate(name, global) {
|
|
|
5120
5456
|
return;
|
|
5121
5457
|
}
|
|
5122
5458
|
const scope = global ? "global" : "workspace";
|
|
5123
|
-
const dir =
|
|
5124
|
-
if (
|
|
5459
|
+
const dir = join16(skillsRoot(scope), clean);
|
|
5460
|
+
if (existsSync16(dir)) {
|
|
5125
5461
|
console.error(c.red(`
|
|
5126
5462
|
A ${scope} skill named "${clean}" already exists.`));
|
|
5127
5463
|
console.error(c.dim(" " + dir + "\n"));
|
|
5128
5464
|
process.exitCode = 1;
|
|
5129
5465
|
return;
|
|
5130
5466
|
}
|
|
5131
|
-
|
|
5132
|
-
|
|
5467
|
+
mkdirSync13(dir, { recursive: true });
|
|
5468
|
+
writeFileSync14(join16(dir, "SKILL.md"), SKILL_TEMPLATE(clean), "utf8");
|
|
5133
5469
|
console.log(c.green(`
|
|
5134
5470
|
Created ${scope} skill "${clean}".`));
|
|
5135
|
-
console.log(c.dim(" " +
|
|
5471
|
+
console.log(c.dim(" " + join16(dir, "SKILL.md")));
|
|
5136
5472
|
console.log(c.dim(' Edit it, then run it in chat with "/skill ' + clean + '".\n'));
|
|
5137
5473
|
}
|
|
5138
5474
|
function installFromSource(source, scope) {
|
|
@@ -5141,7 +5477,7 @@ function installFromSource(source, scope) {
|
|
|
5141
5477
|
let tempDir = "";
|
|
5142
5478
|
try {
|
|
5143
5479
|
if (isGitUrl(base)) {
|
|
5144
|
-
tempDir = mkdtempSync(
|
|
5480
|
+
tempDir = mkdtempSync(join16(tmpdir(), "holt-skill-"));
|
|
5145
5481
|
console.log(c.dim(`
|
|
5146
5482
|
Cloning ${base} ...`));
|
|
5147
5483
|
const res = spawnSync4("git", ["clone", "--depth", "1", base, tempDir], { stdio: "ignore" });
|
|
@@ -5153,7 +5489,7 @@ function installFromSource(source, scope) {
|
|
|
5153
5489
|
fetched = tempDir;
|
|
5154
5490
|
} else {
|
|
5155
5491
|
fetched = resolve4(base);
|
|
5156
|
-
if (!
|
|
5492
|
+
if (!existsSync16(fetched)) {
|
|
5157
5493
|
console.error(c.red(`
|
|
5158
5494
|
No such path: ${fetched}
|
|
5159
5495
|
`));
|
|
@@ -5171,7 +5507,7 @@ function installFromSource(source, scope) {
|
|
|
5171
5507
|
process.exitCode = 1;
|
|
5172
5508
|
return false;
|
|
5173
5509
|
}
|
|
5174
|
-
if (!
|
|
5510
|
+
if (!existsSync16(target) || !statSync8(target).isDirectory()) {
|
|
5175
5511
|
console.error(c.red(`
|
|
5176
5512
|
Subpath "${subpath}" does not exist in the source.
|
|
5177
5513
|
`));
|
|
@@ -5191,7 +5527,7 @@ function installFromSource(source, scope) {
|
|
|
5191
5527
|
}
|
|
5192
5528
|
let skillName = "";
|
|
5193
5529
|
try {
|
|
5194
|
-
const { data } = parseFrontmatter(
|
|
5530
|
+
const { data } = parseFrontmatter(readFileSync17(join16(skillDir, "SKILL.md"), "utf8"));
|
|
5195
5531
|
skillName = sanitizeName(data.name || "");
|
|
5196
5532
|
} catch {
|
|
5197
5533
|
skillName = "";
|
|
@@ -5202,26 +5538,26 @@ function installFromSource(source, scope) {
|
|
|
5202
5538
|
process.exitCode = 1;
|
|
5203
5539
|
return false;
|
|
5204
5540
|
}
|
|
5205
|
-
const dest =
|
|
5206
|
-
if (
|
|
5541
|
+
const dest = join16(skillsRoot(scope), skillName);
|
|
5542
|
+
if (existsSync16(dest)) {
|
|
5207
5543
|
console.error(c.red(`
|
|
5208
5544
|
A ${scope} skill named "${skillName}" already exists.`));
|
|
5209
5545
|
console.error(c.dim(" " + dest + "\n"));
|
|
5210
5546
|
process.exitCode = 1;
|
|
5211
5547
|
return false;
|
|
5212
5548
|
}
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
const nestedGit =
|
|
5216
|
-
if (
|
|
5549
|
+
mkdirSync13(skillsRoot(scope), { recursive: true });
|
|
5550
|
+
cpSync2(skillDir, dest, { recursive: true });
|
|
5551
|
+
const nestedGit = join16(dest, ".git");
|
|
5552
|
+
if (existsSync16(nestedGit)) rmSync6(nestedGit, { recursive: true, force: true });
|
|
5217
5553
|
console.log(c.green(`
|
|
5218
5554
|
Installed ${scope} skill "${skillName}".`));
|
|
5219
|
-
console.log(c.dim(" " +
|
|
5555
|
+
console.log(c.dim(" " + join16(dest, "SKILL.md") + "\n"));
|
|
5220
5556
|
return true;
|
|
5221
5557
|
} finally {
|
|
5222
|
-
if (tempDir &&
|
|
5558
|
+
if (tempDir && existsSync16(tempDir)) {
|
|
5223
5559
|
try {
|
|
5224
|
-
|
|
5560
|
+
rmSync6(tempDir, { recursive: true, force: true });
|
|
5225
5561
|
} catch {
|
|
5226
5562
|
}
|
|
5227
5563
|
}
|
|
@@ -5234,8 +5570,8 @@ async function cmdAdd(source, global, refresh) {
|
|
|
5234
5570
|
}
|
|
5235
5571
|
const scope = global ? "global" : "workspace";
|
|
5236
5572
|
const { base } = splitSubpath(source);
|
|
5237
|
-
if (isGitUrl(base) ||
|
|
5238
|
-
installFromSource(source, scope);
|
|
5573
|
+
if (isGitUrl(base) || existsSync16(resolve4(base))) {
|
|
5574
|
+
if (installFromSource(source, scope)) syncAllBrains(enabledBrains());
|
|
5239
5575
|
return;
|
|
5240
5576
|
}
|
|
5241
5577
|
const { registry, error } = await loadRegistry({ refresh });
|
|
@@ -5256,7 +5592,7 @@ async function cmdAdd(source, global, refresh) {
|
|
|
5256
5592
|
}
|
|
5257
5593
|
console.log(c.dim(`
|
|
5258
5594
|
Resolved "${hit.name}" -> ${hit.source}`));
|
|
5259
|
-
installFromSource(hit.source, scope);
|
|
5595
|
+
if (installFromSource(hit.source, scope)) syncAllBrains(enabledBrains());
|
|
5260
5596
|
}
|
|
5261
5597
|
async function cmdSearch(query, refresh) {
|
|
5262
5598
|
const { registry, error, fromCache } = await loadRegistry({ refresh });
|
|
@@ -5306,7 +5642,7 @@ function cmdPublish(name) {
|
|
|
5306
5642
|
}
|
|
5307
5643
|
let raw = "";
|
|
5308
5644
|
try {
|
|
5309
|
-
raw =
|
|
5645
|
+
raw = readFileSync17(join16(loaded.skill.dir, "SKILL.md"), "utf8");
|
|
5310
5646
|
} catch {
|
|
5311
5647
|
console.error(c.red("\n Could not read the skill's SKILL.md.\n"));
|
|
5312
5648
|
process.exitCode = 1;
|
|
@@ -5316,14 +5652,14 @@ function cmdPublish(name) {
|
|
|
5316
5652
|
skillName = sanitizeName(data.name || loaded.skill.name);
|
|
5317
5653
|
description = (data.description || loaded.skill.description || "").trim();
|
|
5318
5654
|
} else {
|
|
5319
|
-
const file =
|
|
5320
|
-
if (!
|
|
5655
|
+
const file = join16(process.cwd(), "SKILL.md");
|
|
5656
|
+
if (!existsSync16(file)) {
|
|
5321
5657
|
console.error(c.red("\n No SKILL.md in the current directory, and no skill name given."));
|
|
5322
5658
|
console.error(c.dim(" Usage: holt skill publish [<name>]\n"));
|
|
5323
5659
|
process.exitCode = 1;
|
|
5324
5660
|
return;
|
|
5325
5661
|
}
|
|
5326
|
-
const { data } = parseFrontmatter(
|
|
5662
|
+
const { data } = parseFrontmatter(readFileSync17(file, "utf8"));
|
|
5327
5663
|
skillName = sanitizeName(data.name || process.cwd().split(/[\\/]/).pop() || "");
|
|
5328
5664
|
description = (data.description || "").trim();
|
|
5329
5665
|
sourceHint = process.cwd();
|
|
@@ -5359,11 +5695,11 @@ async function cmdRemove(name, ask) {
|
|
|
5359
5695
|
console.log(c.dim("\n Usage: holt skill remove <name>\n"));
|
|
5360
5696
|
return;
|
|
5361
5697
|
}
|
|
5362
|
-
const wsDir =
|
|
5363
|
-
const globalDir =
|
|
5364
|
-
const target =
|
|
5698
|
+
const wsDir = join16(skillsRoot("workspace"), clean);
|
|
5699
|
+
const globalDir = join16(skillsRoot("global"), clean);
|
|
5700
|
+
const target = existsSync16(wsDir) ? wsDir : existsSync16(globalDir) ? globalDir : "";
|
|
5365
5701
|
if (!target) {
|
|
5366
|
-
if (
|
|
5702
|
+
if (existsSync16(join16(skillsRoot("builtin"), clean))) {
|
|
5367
5703
|
console.error(c.red(`
|
|
5368
5704
|
"${clean}" is a built-in skill and cannot be removed; it ships inside Holt.`));
|
|
5369
5705
|
console.error(c.dim(" Override it instead: create a workspace or global skill with the same name.\n"));
|
|
@@ -5380,7 +5716,9 @@ async function cmdRemove(name, ask) {
|
|
|
5380
5716
|
const a = (await ask(`
|
|
5381
5717
|
Delete ${scope} skill "${clean}"? [y/N] `) ?? "").trim().toLowerCase();
|
|
5382
5718
|
if (a === "y" || a === "yes") {
|
|
5383
|
-
|
|
5719
|
+
rmSync6(target, { recursive: true, force: true });
|
|
5720
|
+
removeSkillArtifacts(clean);
|
|
5721
|
+
syncAllBrains(enabledBrains());
|
|
5384
5722
|
console.log(c.green(" Removed.\n"));
|
|
5385
5723
|
} else {
|
|
5386
5724
|
console.log(c.dim(" Kept.\n"));
|
|
@@ -5436,8 +5774,8 @@ async function skillCmd(sub, rest = []) {
|
|
|
5436
5774
|
}
|
|
5437
5775
|
|
|
5438
5776
|
// src/commands/graph.ts
|
|
5439
|
-
import { writeFileSync as
|
|
5440
|
-
import { join as
|
|
5777
|
+
import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, existsSync as existsSync17 } from "fs";
|
|
5778
|
+
import { join as join18, dirname as dirname8, resolve as resolve5 } from "path";
|
|
5441
5779
|
import { spawn as spawn4 } from "child_process";
|
|
5442
5780
|
|
|
5443
5781
|
// src/communities.ts
|
|
@@ -6234,8 +6572,8 @@ function renderGraphHtml(graph2, meta) {
|
|
|
6234
6572
|
}
|
|
6235
6573
|
|
|
6236
6574
|
// src/ingest.ts
|
|
6237
|
-
import { readdirSync as
|
|
6238
|
-
import { join as
|
|
6575
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
6576
|
+
import { join as join17, relative, sep as sep3, extname, dirname as dirname7, basename as basename2 } from "path";
|
|
6239
6577
|
var CODE_EXT = /* @__PURE__ */ new Set([
|
|
6240
6578
|
"js",
|
|
6241
6579
|
"jsx",
|
|
@@ -6297,7 +6635,7 @@ function ingestFolder(root, want = { code: true, docs: true }) {
|
|
|
6297
6635
|
const dir = stack.pop();
|
|
6298
6636
|
let entries;
|
|
6299
6637
|
try {
|
|
6300
|
-
entries =
|
|
6638
|
+
entries = readdirSync5(dir).sort((a, b) => a.localeCompare(b));
|
|
6301
6639
|
} catch {
|
|
6302
6640
|
skipped.unreadable++;
|
|
6303
6641
|
continue;
|
|
@@ -6308,10 +6646,10 @@ function ingestFolder(root, want = { code: true, docs: true }) {
|
|
|
6308
6646
|
skipped.hitFileCap = true;
|
|
6309
6647
|
break;
|
|
6310
6648
|
}
|
|
6311
|
-
const full =
|
|
6649
|
+
const full = join17(dir, name);
|
|
6312
6650
|
let st;
|
|
6313
6651
|
try {
|
|
6314
|
-
st =
|
|
6652
|
+
st = statSync9(full);
|
|
6315
6653
|
} catch {
|
|
6316
6654
|
skipped.unreadable++;
|
|
6317
6655
|
continue;
|
|
@@ -6341,7 +6679,7 @@ function ingestFolder(root, want = { code: true, docs: true }) {
|
|
|
6341
6679
|
}
|
|
6342
6680
|
let content;
|
|
6343
6681
|
try {
|
|
6344
|
-
content =
|
|
6682
|
+
content = readFileSync18(full, "utf8");
|
|
6345
6683
|
} catch {
|
|
6346
6684
|
skipped.unreadable++;
|
|
6347
6685
|
continue;
|
|
@@ -6475,7 +6813,7 @@ function resolvePySpec(f, spec, byId) {
|
|
|
6475
6813
|
return null;
|
|
6476
6814
|
}
|
|
6477
6815
|
function toPosixDir(id) {
|
|
6478
|
-
const d = toPosix(
|
|
6816
|
+
const d = toPosix(dirname7(id));
|
|
6479
6817
|
return d === "" || d === "." ? "." : d;
|
|
6480
6818
|
}
|
|
6481
6819
|
var MD_LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g;
|
|
@@ -6647,7 +6985,7 @@ async function graph(args = []) {
|
|
|
6647
6985
|
const ingestCode = opts.code;
|
|
6648
6986
|
const ingestDocs = opts.docs;
|
|
6649
6987
|
const turns = loadTurns();
|
|
6650
|
-
const includeWiki = opts.wiki !== void 0 ? opts.wiki :
|
|
6988
|
+
const includeWiki = opts.wiki !== void 0 ? opts.wiki : existsSync17(wikiDir());
|
|
6651
6989
|
const wikiPages = includeWiki ? wikiGraphPages() : [];
|
|
6652
6990
|
let fileNodeCount = 0;
|
|
6653
6991
|
let docNodeCount = 0;
|
|
@@ -6677,9 +7015,9 @@ async function graph(args = []) {
|
|
|
6677
7015
|
if (codeGraph) assignCommunities(g);
|
|
6678
7016
|
if (opts.report) {
|
|
6679
7017
|
const md = renderReport(g, { workspace: workspace() });
|
|
6680
|
-
const reportPath = opts.out ? resolve5(opts.out) :
|
|
6681
|
-
|
|
6682
|
-
|
|
7018
|
+
const reportPath = opts.out ? resolve5(opts.out) : join18(workspace(), "GRAPH_REPORT.md");
|
|
7019
|
+
mkdirSync14(dirname8(reportPath), { recursive: true });
|
|
7020
|
+
writeFileSync15(reportPath, md, "utf8");
|
|
6683
7021
|
const communities = new Set(g.nodes.map((n) => n.community ?? "0")).size;
|
|
6684
7022
|
console.log("\n" + c.accent("Graph report written") + c.dim(" (this folder)"));
|
|
6685
7023
|
console.log(` nodes ${g.nodes.length} (${fileNodeCount} files, ${docNodeCount} docs)`);
|
|
@@ -6699,9 +7037,9 @@ async function graph(args = []) {
|
|
|
6699
7037
|
concepts: conceptCount,
|
|
6700
7038
|
edges: g.edges.length
|
|
6701
7039
|
});
|
|
6702
|
-
const outPath = opts.out ? resolve5(opts.out) :
|
|
6703
|
-
|
|
6704
|
-
|
|
7040
|
+
const outPath = opts.out ? resolve5(opts.out) : join18(wsHoltDir(), "graph.html");
|
|
7041
|
+
mkdirSync14(dirname8(outPath), { recursive: true });
|
|
7042
|
+
writeFileSync15(outPath, html, "utf8");
|
|
6705
7043
|
console.log("\n" + c.accent("Memory graph built") + c.dim(" (this folder)"));
|
|
6706
7044
|
const parts = [`${turns.length} turns`, `${conceptCount} concepts`];
|
|
6707
7045
|
if (wikiCount) parts.push(`${wikiCount} wiki pages`);
|
|
@@ -6989,7 +7327,7 @@ function holtTools() {
|
|
|
6989
7327
|
}
|
|
6990
7328
|
|
|
6991
7329
|
// src/version.ts
|
|
6992
|
-
var VERSION = "0.14.
|
|
7330
|
+
var VERSION = "0.14.3";
|
|
6993
7331
|
|
|
6994
7332
|
// src/commands/mcp.ts
|
|
6995
7333
|
function printSetup() {
|
|
@@ -7111,7 +7449,7 @@ async function statusline() {
|
|
|
7111
7449
|
}
|
|
7112
7450
|
|
|
7113
7451
|
// src/commands/run.ts
|
|
7114
|
-
import { writeFileSync as
|
|
7452
|
+
import { writeFileSync as writeFileSync16 } from "fs";
|
|
7115
7453
|
import { resolve as resolve6 } from "path";
|
|
7116
7454
|
var USAGE3 = [
|
|
7117
7455
|
" holt run <task> [options]",
|
|
@@ -7203,7 +7541,7 @@ async function run(args) {
|
|
|
7203
7541
|
if (p.out) {
|
|
7204
7542
|
const target = resolve6(process.cwd(), p.out);
|
|
7205
7543
|
try {
|
|
7206
|
-
|
|
7544
|
+
writeFileSync16(target, result.text, "utf8");
|
|
7207
7545
|
process.stderr.write(c.dim(`saved to ${target}`) + "\n");
|
|
7208
7546
|
} catch (e) {
|
|
7209
7547
|
process.stderr.write(c.red(`Could not write ${target}: ${e.message}`) + "\n");
|
|
@@ -7214,8 +7552,8 @@ async function run(args) {
|
|
|
7214
7552
|
|
|
7215
7553
|
// src/commands/schedule.ts
|
|
7216
7554
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
7217
|
-
import { writeFileSync as
|
|
7218
|
-
import { dirname as
|
|
7555
|
+
import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, rmSync as rmSync7, existsSync as existsSync18 } from "fs";
|
|
7556
|
+
import { dirname as dirname9 } from "path";
|
|
7219
7557
|
function parseAdd(rest) {
|
|
7220
7558
|
const positional = [];
|
|
7221
7559
|
const opts = { notify: false };
|
|
@@ -7238,8 +7576,8 @@ function parseAdd(rest) {
|
|
|
7238
7576
|
}
|
|
7239
7577
|
function installDarwin(job, holtPath) {
|
|
7240
7578
|
const path = plistPath(job.id);
|
|
7241
|
-
|
|
7242
|
-
|
|
7579
|
+
mkdirSync15(dirname9(path), { recursive: true });
|
|
7580
|
+
writeFileSync17(path, buildLaunchdPlist(job, holtPath), "utf8");
|
|
7243
7581
|
const res = spawnSync5("launchctl", ["load", "-w", path], { encoding: "utf8" });
|
|
7244
7582
|
if (res.status === 0) {
|
|
7245
7583
|
console.log(c.green(" Loaded into launchd."));
|
|
@@ -7327,10 +7665,10 @@ function listCmd2() {
|
|
|
7327
7665
|
}
|
|
7328
7666
|
function removeDarwin(id) {
|
|
7329
7667
|
const path = plistPath(id);
|
|
7330
|
-
if (
|
|
7668
|
+
if (existsSync18(path)) {
|
|
7331
7669
|
spawnSync5("launchctl", ["unload", path], { encoding: "utf8" });
|
|
7332
7670
|
try {
|
|
7333
|
-
|
|
7671
|
+
rmSync7(path);
|
|
7334
7672
|
} catch {
|
|
7335
7673
|
}
|
|
7336
7674
|
}
|
|
@@ -7390,29 +7728,29 @@ async function schedule(sub, rest = []) {
|
|
|
7390
7728
|
|
|
7391
7729
|
// src/commands/routine.ts
|
|
7392
7730
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7393
|
-
import { writeFileSync as
|
|
7394
|
-
import { dirname as
|
|
7731
|
+
import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, rmSync as rmSync8, existsSync as existsSync19 } from "fs";
|
|
7732
|
+
import { dirname as dirname10, resolve as resolve7 } from "path";
|
|
7395
7733
|
|
|
7396
7734
|
// src/routines.ts
|
|
7397
|
-
import { readFileSync as
|
|
7398
|
-
import { join as
|
|
7735
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16 } from "fs";
|
|
7736
|
+
import { join as join19 } from "path";
|
|
7399
7737
|
function sanitizeRoutineName(name) {
|
|
7400
7738
|
return (name || "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
7401
7739
|
}
|
|
7402
7740
|
function routinesPath() {
|
|
7403
|
-
return
|
|
7741
|
+
return join19(GLOBAL_DIR, "routines.json");
|
|
7404
7742
|
}
|
|
7405
7743
|
function loadRoutines() {
|
|
7406
7744
|
try {
|
|
7407
|
-
const raw = JSON.parse(
|
|
7745
|
+
const raw = JSON.parse(readFileSync19(routinesPath(), "utf8"));
|
|
7408
7746
|
return Array.isArray(raw) ? raw : [];
|
|
7409
7747
|
} catch {
|
|
7410
7748
|
return [];
|
|
7411
7749
|
}
|
|
7412
7750
|
}
|
|
7413
7751
|
function saveRoutines(routines) {
|
|
7414
|
-
|
|
7415
|
-
|
|
7752
|
+
mkdirSync16(GLOBAL_DIR, { recursive: true });
|
|
7753
|
+
writeFileSync18(routinesPath(), JSON.stringify(routines, null, 2) + "\n", "utf8");
|
|
7416
7754
|
}
|
|
7417
7755
|
function findRoutine(name) {
|
|
7418
7756
|
const want = sanitizeRoutineName(name);
|
|
@@ -7472,8 +7810,8 @@ function routineJobId(name) {
|
|
|
7472
7810
|
}
|
|
7473
7811
|
function installDarwin2(job, holtPath) {
|
|
7474
7812
|
const path = plistPath(job.id);
|
|
7475
|
-
|
|
7476
|
-
|
|
7813
|
+
mkdirSync17(dirname10(path), { recursive: true });
|
|
7814
|
+
writeFileSync19(path, buildLaunchdPlist(job, holtPath), "utf8");
|
|
7477
7815
|
const res = spawnSync6("launchctl", ["load", "-w", path], { encoding: "utf8" });
|
|
7478
7816
|
if (res.status === 0) {
|
|
7479
7817
|
console.log(c.green(" Loaded into launchd."));
|
|
@@ -7517,10 +7855,10 @@ function installSchedule(r) {
|
|
|
7517
7855
|
}
|
|
7518
7856
|
function removeScheduleDarwin(id) {
|
|
7519
7857
|
const path = plistPath(id);
|
|
7520
|
-
if (
|
|
7858
|
+
if (existsSync19(path)) {
|
|
7521
7859
|
spawnSync6("launchctl", ["unload", path], { encoding: "utf8" });
|
|
7522
7860
|
try {
|
|
7523
|
-
|
|
7861
|
+
rmSync8(path);
|
|
7524
7862
|
} catch {
|
|
7525
7863
|
}
|
|
7526
7864
|
}
|
|
@@ -7713,7 +8051,7 @@ async function runCmd(rest) {
|
|
|
7713
8051
|
if (outTarget) {
|
|
7714
8052
|
const target = resolve7(routine2.workspace, outTarget);
|
|
7715
8053
|
try {
|
|
7716
|
-
|
|
8054
|
+
writeFileSync19(target, result.text, "utf8");
|
|
7717
8055
|
if (!quiet) process.stderr.write(c.dim(`saved to ${target}`) + "\n");
|
|
7718
8056
|
} catch (e) {
|
|
7719
8057
|
process.stderr.write(c.red(`Could not write ${target}: ${e.message}`) + "\n");
|
|
@@ -7851,7 +8189,7 @@ async function routine(sub, rest = []) {
|
|
|
7851
8189
|
}
|
|
7852
8190
|
|
|
7853
8191
|
// src/commands/notify.ts
|
|
7854
|
-
import { readFileSync as
|
|
8192
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
7855
8193
|
async function notify(args) {
|
|
7856
8194
|
const nonFlag = args.filter((a) => a !== "-" && !a.startsWith("--"));
|
|
7857
8195
|
const wantsStdin = args.includes("-") || !process.stdin.isTTY;
|
|
@@ -7884,7 +8222,7 @@ async function notify(args) {
|
|
|
7884
8222
|
}
|
|
7885
8223
|
function readStdin3() {
|
|
7886
8224
|
try {
|
|
7887
|
-
return
|
|
8225
|
+
return readFileSync20(0, "utf8");
|
|
7888
8226
|
} catch {
|
|
7889
8227
|
return "";
|
|
7890
8228
|
}
|
|
@@ -8095,7 +8433,7 @@ async function doctor() {
|
|
|
8095
8433
|
}
|
|
8096
8434
|
|
|
8097
8435
|
// src/commands/write.ts
|
|
8098
|
-
import { writeFileSync as
|
|
8436
|
+
import { writeFileSync as writeFileSync20 } from "fs";
|
|
8099
8437
|
import { resolve as resolve8 } from "path";
|
|
8100
8438
|
|
|
8101
8439
|
// src/antiai.ts
|
|
@@ -8276,7 +8614,7 @@ async function write(args) {
|
|
|
8276
8614
|
if (p.out) {
|
|
8277
8615
|
const target = resolve8(process.cwd(), p.out);
|
|
8278
8616
|
try {
|
|
8279
|
-
|
|
8617
|
+
writeFileSync20(target, draft + "\n", "utf8");
|
|
8280
8618
|
process.stderr.write(c.dim(`saved to ${target}`) + "\n");
|
|
8281
8619
|
} catch (e) {
|
|
8282
8620
|
process.stderr.write(c.red(`Could not write ${target}: ${e.message}`) + "\n");
|