@holt-os/holt 0.14.0 → 0.14.2
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 +1 -1
- package/dist/cli.js +397 -286
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Holt is an open-source, self-hosted personal agent OS. Clone it, pick your skill
|
|
|
8
8
|
|
|
9
9
|
> **Status: shipping and usable today.** Working now:
|
|
10
10
|
>
|
|
11
|
-
> - `holt
|
|
11
|
+
> - Bare `holt` launches the real interactive brain (Claude Code, Codex, or Gemini) branded as Holt, setting the folder up the first time. `holt chat` stays as a lightweight REPL for direct API brains, with mid-conversation brain switching that keeps context.
|
|
12
12
|
> - Per-folder memory with semantic recall and distilled facts, ambient memory for Claude Code (`holt hook`), and opt-in memory shared across folders (`holt memory global`).
|
|
13
13
|
> - A self-maintaining knowledge wiki (`holt wiki`) and an interactive knowledge graph (`holt graph`) that can also ingest your code and docs into communities.
|
|
14
14
|
> - Run a task once, on a schedule, or as a named routine (`holt run` / `schedule` / `routine`), and reach it from your phone over Telegram.
|
package/dist/cli.js
CHANGED
|
@@ -1737,7 +1737,7 @@ function parseStyleProfile(raw) {
|
|
|
1737
1737
|
}
|
|
1738
1738
|
if (!obj || typeof obj !== "object") return null;
|
|
1739
1739
|
const o = obj;
|
|
1740
|
-
const
|
|
1740
|
+
const str2 = (x) => typeof x === "string" && x.trim() ? x.trim() : void 0;
|
|
1741
1741
|
const arr = (x) => {
|
|
1742
1742
|
if (!Array.isArray(x)) return void 0;
|
|
1743
1743
|
const out = x.filter((i) => typeof i === "string" && i.trim().length > 0).map((i) => i.trim());
|
|
@@ -1748,17 +1748,17 @@ function parseStyleProfile(raw) {
|
|
|
1748
1748
|
return Number.isFinite(n) ? Math.max(1, Math.min(5, Math.round(n))) : void 0;
|
|
1749
1749
|
};
|
|
1750
1750
|
const profile = {
|
|
1751
|
-
tone:
|
|
1751
|
+
tone: str2(o["tone"]),
|
|
1752
1752
|
formality: num(o["formality"]),
|
|
1753
|
-
avgSentenceLength:
|
|
1754
|
-
person:
|
|
1755
|
-
emoji:
|
|
1756
|
-
formatting:
|
|
1753
|
+
avgSentenceLength: str2(o["avgSentenceLength"]),
|
|
1754
|
+
person: str2(o["person"]),
|
|
1755
|
+
emoji: str2(o["emoji"]),
|
|
1756
|
+
formatting: str2(o["formatting"]),
|
|
1757
1757
|
signatureMoves: arr(o["signatureMoves"]),
|
|
1758
1758
|
bannedWords: arr(o["bannedWords"]),
|
|
1759
1759
|
targetAudiences: arr(o["targetAudiences"]),
|
|
1760
|
-
soundsLike:
|
|
1761
|
-
doesNotSoundLike:
|
|
1760
|
+
soundsLike: str2(o["soundsLike"]),
|
|
1761
|
+
doesNotSoundLike: str2(o["doesNotSoundLike"])
|
|
1762
1762
|
};
|
|
1763
1763
|
const any = Object.values(profile).some((v) => v !== void 0);
|
|
1764
1764
|
return any ? profile : null;
|
|
@@ -2220,14 +2220,154 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2220
2220
|
}
|
|
2221
2221
|
|
|
2222
2222
|
// src/commands/launch.ts
|
|
2223
|
-
import { readFileSync as
|
|
2224
|
-
import {
|
|
2223
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync11, existsSync as existsSync11, mkdirSync as mkdirSync10, copyFileSync as copyFileSync2 } from "fs";
|
|
2224
|
+
import { homedir as homedir6 } from "os";
|
|
2225
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
2225
2226
|
|
|
2226
|
-
// src/
|
|
2227
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync8, copyFileSync, realpathSync as realpathSync2, appendFileSync as appendFileSync2, statSync as statSync4 } from "fs";
|
|
2228
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
2229
|
-
import { join as join9, dirname as dirname3 } from "path";
|
|
2227
|
+
// src/scheduler.ts
|
|
2230
2228
|
import { homedir as homedir3 } from "os";
|
|
2229
|
+
import { join as join8 } from "path";
|
|
2230
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
2231
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2232
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2233
|
+
function schedulesPath() {
|
|
2234
|
+
return join8(GLOBAL_DIR, "schedules.json");
|
|
2235
|
+
}
|
|
2236
|
+
function logsDir() {
|
|
2237
|
+
return join8(GLOBAL_DIR, "logs");
|
|
2238
|
+
}
|
|
2239
|
+
function logPath(id) {
|
|
2240
|
+
return join8(logsDir(), `${id}.log`);
|
|
2241
|
+
}
|
|
2242
|
+
function plistPath(id) {
|
|
2243
|
+
return join8(homedir3(), "Library", "LaunchAgents", `com.holt.${id}.plist`);
|
|
2244
|
+
}
|
|
2245
|
+
function loadJobs() {
|
|
2246
|
+
try {
|
|
2247
|
+
const raw = JSON.parse(readFileSync9(schedulesPath(), "utf8"));
|
|
2248
|
+
return Array.isArray(raw) ? raw : [];
|
|
2249
|
+
} catch {
|
|
2250
|
+
return [];
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
function saveJobs(jobs) {
|
|
2254
|
+
mkdirSync7(GLOBAL_DIR, { recursive: true });
|
|
2255
|
+
writeFileSync7(schedulesPath(), JSON.stringify(jobs, null, 2) + "\n", "utf8");
|
|
2256
|
+
}
|
|
2257
|
+
function addJob(job) {
|
|
2258
|
+
const jobs = loadJobs();
|
|
2259
|
+
jobs.push(job);
|
|
2260
|
+
saveJobs(jobs);
|
|
2261
|
+
return jobs;
|
|
2262
|
+
}
|
|
2263
|
+
function removeJob(id) {
|
|
2264
|
+
const jobs = loadJobs().filter((j) => j.id !== id);
|
|
2265
|
+
saveJobs(jobs);
|
|
2266
|
+
return jobs;
|
|
2267
|
+
}
|
|
2268
|
+
function newJobId() {
|
|
2269
|
+
return randomUUID3().slice(0, 8);
|
|
2270
|
+
}
|
|
2271
|
+
function parseWhen(when) {
|
|
2272
|
+
const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(when.trim());
|
|
2273
|
+
if (!m) {
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
`Could not read time "${when}". Use 24h HH:MM for a daily run, e.g. 07:00 or 23:30.`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
const hour = Number(m[1]);
|
|
2279
|
+
const minute = Number(m[2]);
|
|
2280
|
+
return { hour, minute };
|
|
2281
|
+
}
|
|
2282
|
+
function shQuote(s) {
|
|
2283
|
+
return `'` + s.replace(/'/g, `'\\''`) + `'`;
|
|
2284
|
+
}
|
|
2285
|
+
function resolveHoltPath() {
|
|
2286
|
+
try {
|
|
2287
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
2288
|
+
const res = spawnSync2(finder, ["holt"], { encoding: "utf8" });
|
|
2289
|
+
if (res.status === 0 && typeof res.stdout === "string") {
|
|
2290
|
+
const first = res.stdout.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
2291
|
+
if (first) return first;
|
|
2292
|
+
}
|
|
2293
|
+
} catch {
|
|
2294
|
+
}
|
|
2295
|
+
return "holt";
|
|
2296
|
+
}
|
|
2297
|
+
function buildCommand(job, holtPath) {
|
|
2298
|
+
const holt = shQuote(holtPath);
|
|
2299
|
+
const ws = shQuote(job.workspace);
|
|
2300
|
+
if (job.runArgs && job.runArgs.length > 0) {
|
|
2301
|
+
const parts = job.runArgs.map(shQuote).join(" ");
|
|
2302
|
+
return `cd ${ws} && ${holt} ${parts} --quiet`;
|
|
2303
|
+
}
|
|
2304
|
+
const log = shQuote(logPath(job.id));
|
|
2305
|
+
let cmd = `cd ${ws} && ${holt} run ${shQuote(job.task)}`;
|
|
2306
|
+
if (job.brain) cmd += ` --brain ${shQuote(job.brain)}`;
|
|
2307
|
+
cmd += ` --quiet --out ${log}`;
|
|
2308
|
+
if (job.notify) cmd += ` && ${holt} notify "$(cat ${log})"`;
|
|
2309
|
+
return cmd;
|
|
2310
|
+
}
|
|
2311
|
+
function xmlEscape(s) {
|
|
2312
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2313
|
+
}
|
|
2314
|
+
function buildLaunchdPlist(job, holtPath) {
|
|
2315
|
+
const { hour, minute } = parseWhen(job.when);
|
|
2316
|
+
const command = buildCommand(job, holtPath);
|
|
2317
|
+
const log = logPath(job.id);
|
|
2318
|
+
const args = ["/bin/sh", "-lc", command];
|
|
2319
|
+
const argXml = args.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
|
|
2320
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2321
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2322
|
+
<plist version="1.0">
|
|
2323
|
+
<dict>
|
|
2324
|
+
<key>Label</key>
|
|
2325
|
+
<string>com.holt.${xmlEscape(job.id)}</string>
|
|
2326
|
+
<key>ProgramArguments</key>
|
|
2327
|
+
<array>
|
|
2328
|
+
${argXml}
|
|
2329
|
+
</array>
|
|
2330
|
+
<key>StartCalendarInterval</key>
|
|
2331
|
+
<dict>
|
|
2332
|
+
<key>Hour</key>
|
|
2333
|
+
<integer>${hour}</integer>
|
|
2334
|
+
<key>Minute</key>
|
|
2335
|
+
<integer>${minute}</integer>
|
|
2336
|
+
</dict>
|
|
2337
|
+
<key>RunAtLoad</key>
|
|
2338
|
+
<false/>
|
|
2339
|
+
<key>StandardOutPath</key>
|
|
2340
|
+
<string>${xmlEscape(log)}</string>
|
|
2341
|
+
<key>StandardErrorPath</key>
|
|
2342
|
+
<string>${xmlEscape(log)}</string>
|
|
2343
|
+
</dict>
|
|
2344
|
+
</plist>
|
|
2345
|
+
`;
|
|
2346
|
+
}
|
|
2347
|
+
var CRON_MARKER = "holt:";
|
|
2348
|
+
function buildCronLine(job, holtPath) {
|
|
2349
|
+
const { hour, minute } = parseWhen(job.when);
|
|
2350
|
+
const command = buildCommand(job, holtPath);
|
|
2351
|
+
return `${minute} ${hour} * * * ${command} # ${CRON_MARKER}${job.id}`;
|
|
2352
|
+
}
|
|
2353
|
+
function stripCronLines(crontab, id) {
|
|
2354
|
+
const marker = `# ${CRON_MARKER}${id}`;
|
|
2355
|
+
const kept = crontab.split("\n").filter((line) => !line.includes(marker));
|
|
2356
|
+
return kept.join("\n").replace(/\n+$/, "\n");
|
|
2357
|
+
}
|
|
2358
|
+
function appendCronLine(crontab, line) {
|
|
2359
|
+
const base = crontab.replace(/\s*$/, "");
|
|
2360
|
+
return (base ? base + "\n" : "") + line + "\n";
|
|
2361
|
+
}
|
|
2362
|
+
function ensureLogsDir() {
|
|
2363
|
+
mkdirSync7(logsDir(), { recursive: true });
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// src/commands/hook.ts
|
|
2367
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync9, copyFileSync, realpathSync as realpathSync2, appendFileSync as appendFileSync2, statSync as statSync4 } from "fs";
|
|
2368
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2369
|
+
import { join as join10, dirname as dirname3 } from "path";
|
|
2370
|
+
import { homedir as homedir4 } from "os";
|
|
2231
2371
|
|
|
2232
2372
|
// src/facts.ts
|
|
2233
2373
|
var MIN_EXCHANGES_FOR_EXTRACTION = 3;
|
|
@@ -2343,15 +2483,15 @@ async function extractAndSaveFacts(active, cfg, history, session) {
|
|
|
2343
2483
|
|
|
2344
2484
|
// src/wiki.ts
|
|
2345
2485
|
import {
|
|
2346
|
-
readFileSync as
|
|
2347
|
-
writeFileSync as
|
|
2486
|
+
readFileSync as readFileSync10,
|
|
2487
|
+
writeFileSync as writeFileSync8,
|
|
2348
2488
|
existsSync as existsSync8,
|
|
2349
|
-
mkdirSync as
|
|
2489
|
+
mkdirSync as mkdirSync8,
|
|
2350
2490
|
rmSync as rmSync4,
|
|
2351
2491
|
readdirSync as readdirSync2,
|
|
2352
2492
|
statSync as statSync3
|
|
2353
2493
|
} from "fs";
|
|
2354
|
-
import { join as
|
|
2494
|
+
import { join as join9 } from "path";
|
|
2355
2495
|
import { totalmem } from "os";
|
|
2356
2496
|
|
|
2357
2497
|
// src/localmodel.ts
|
|
@@ -2406,28 +2546,28 @@ function pullHint(model) {
|
|
|
2406
2546
|
|
|
2407
2547
|
// src/wiki.ts
|
|
2408
2548
|
function wikiDir() {
|
|
2409
|
-
return
|
|
2549
|
+
return join9(wsHoltDir(), "wiki");
|
|
2410
2550
|
}
|
|
2411
2551
|
function wikiStatePath() {
|
|
2412
|
-
return
|
|
2552
|
+
return join9(wikiDir(), ".state.json");
|
|
2413
2553
|
}
|
|
2414
2554
|
function wikiIndexPath() {
|
|
2415
|
-
return
|
|
2555
|
+
return join9(wikiDir(), "index.md");
|
|
2416
2556
|
}
|
|
2417
2557
|
function pagePath(slug) {
|
|
2418
|
-
return
|
|
2558
|
+
return join9(wikiDir(), `${slug}.md`);
|
|
2419
2559
|
}
|
|
2420
2560
|
function loadState() {
|
|
2421
2561
|
try {
|
|
2422
|
-
const s = JSON.parse(
|
|
2562
|
+
const s = JSON.parse(readFileSync10(wikiStatePath(), "utf8"));
|
|
2423
2563
|
return { lastSyncTs: typeof s.lastSyncTs === "number" ? s.lastSyncTs : 0, updatedAt: s.updatedAt ?? 0 };
|
|
2424
2564
|
} catch {
|
|
2425
2565
|
return { lastSyncTs: 0, updatedAt: 0 };
|
|
2426
2566
|
}
|
|
2427
2567
|
}
|
|
2428
2568
|
function saveState(s) {
|
|
2429
|
-
|
|
2430
|
-
|
|
2569
|
+
mkdirSync8(wikiDir(), { recursive: true });
|
|
2570
|
+
writeFileSync8(wikiStatePath(), JSON.stringify(s, null, 2) + "\n", "utf8");
|
|
2431
2571
|
}
|
|
2432
2572
|
function saveSyncMarker(ts) {
|
|
2433
2573
|
saveState({ lastSyncTs: ts, updatedAt: Date.now() });
|
|
@@ -2449,7 +2589,7 @@ function readPage(slug) {
|
|
|
2449
2589
|
if (!existsSync8(p)) return null;
|
|
2450
2590
|
let raw = "";
|
|
2451
2591
|
try {
|
|
2452
|
-
raw =
|
|
2592
|
+
raw = readFileSync10(p, "utf8");
|
|
2453
2593
|
} catch {
|
|
2454
2594
|
return null;
|
|
2455
2595
|
}
|
|
@@ -2491,8 +2631,8 @@ function renderPage(pg) {
|
|
|
2491
2631
|
return fm;
|
|
2492
2632
|
}
|
|
2493
2633
|
function writePage(pg) {
|
|
2494
|
-
|
|
2495
|
-
|
|
2634
|
+
mkdirSync8(wikiDir(), { recursive: true });
|
|
2635
|
+
writeFileSync8(pagePath(pg.slug), renderPage(pg), "utf8");
|
|
2496
2636
|
}
|
|
2497
2637
|
var WIKILINK_RE = /\[\[([^\]]+)\]\]/g;
|
|
2498
2638
|
function extractLinks(body) {
|
|
@@ -2769,8 +2909,8 @@ function renderIndex(pages) {
|
|
|
2769
2909
|
return lines.join("\n");
|
|
2770
2910
|
}
|
|
2771
2911
|
function writeIndex(pages) {
|
|
2772
|
-
|
|
2773
|
-
|
|
2912
|
+
mkdirSync8(wikiDir(), { recursive: true });
|
|
2913
|
+
writeFileSync8(wikiIndexPath(), renderIndex(pages), "utf8");
|
|
2774
2914
|
}
|
|
2775
2915
|
var WIKI_SESSION = "wiki";
|
|
2776
2916
|
function wikiRowId(slug) {
|
|
@@ -2785,9 +2925,9 @@ async function indexPagesForRecall(pages) {
|
|
|
2785
2925
|
if (pages.length === 0) return;
|
|
2786
2926
|
const slugs = new Set(pages.map((p) => sanitizeName(p.slug).slice(0, 24)));
|
|
2787
2927
|
const kept = dropWikiRows(slugs);
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2928
|
+
mkdirSync8(join9(wsHoltDir(), "memory"), { recursive: true });
|
|
2929
|
+
writeFileSync8(
|
|
2930
|
+
join9(wsHoltDir(), "memory", "turns.jsonl"),
|
|
2791
2931
|
kept.map((t) => JSON.stringify(t)).join("\n") + (kept.length ? "\n" : ""),
|
|
2792
2932
|
"utf8"
|
|
2793
2933
|
);
|
|
@@ -2809,9 +2949,9 @@ ${pg.body}`.slice(0, 4e3);
|
|
|
2809
2949
|
function clearWikiRecallRows() {
|
|
2810
2950
|
try {
|
|
2811
2951
|
const kept = loadTurns().filter((t) => t.session !== WIKI_SESSION);
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2952
|
+
mkdirSync8(join9(wsHoltDir(), "memory"), { recursive: true });
|
|
2953
|
+
writeFileSync8(
|
|
2954
|
+
join9(wsHoltDir(), "memory", "turns.jsonl"),
|
|
2815
2955
|
kept.map((t) => JSON.stringify(t)).join("\n") + (kept.length ? "\n" : ""),
|
|
2816
2956
|
"utf8"
|
|
2817
2957
|
);
|
|
@@ -2865,23 +3005,23 @@ var HOOK_CMD = {
|
|
|
2865
3005
|
capture: "holt hook capture"
|
|
2866
3006
|
};
|
|
2867
3007
|
function globalSettingsPath() {
|
|
2868
|
-
return
|
|
3008
|
+
return join10(homedir4(), ".claude", "settings.json");
|
|
2869
3009
|
}
|
|
2870
3010
|
function projectSettingsPath() {
|
|
2871
|
-
return
|
|
3011
|
+
return join10(process.cwd(), ".claude", "settings.json");
|
|
2872
3012
|
}
|
|
2873
3013
|
function readSettings(path) {
|
|
2874
3014
|
if (!existsSync9(path)) return {};
|
|
2875
3015
|
try {
|
|
2876
|
-
const parsed = JSON.parse(
|
|
3016
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
2877
3017
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
2878
3018
|
} catch {
|
|
2879
3019
|
throw new Error(`could not parse ${path} (not valid JSON) - leaving it untouched`);
|
|
2880
3020
|
}
|
|
2881
3021
|
}
|
|
2882
3022
|
function writeSettings(path, settings) {
|
|
2883
|
-
|
|
2884
|
-
|
|
3023
|
+
mkdirSync9(dirname3(path), { recursive: true });
|
|
3024
|
+
writeFileSync9(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
2885
3025
|
}
|
|
2886
3026
|
function backup(path) {
|
|
2887
3027
|
if (!existsSync9(path)) return null;
|
|
@@ -3070,8 +3210,8 @@ function guardTrusted(real) {
|
|
|
3070
3210
|
}
|
|
3071
3211
|
function hookLog(msg) {
|
|
3072
3212
|
try {
|
|
3073
|
-
|
|
3074
|
-
appendFileSync2(
|
|
3213
|
+
mkdirSync9(GLOBAL_DIR, { recursive: true });
|
|
3214
|
+
appendFileSync2(join10(GLOBAL_DIR, "hooks.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3075
3215
|
`, "utf8");
|
|
3076
3216
|
} catch {
|
|
3077
3217
|
}
|
|
@@ -3086,11 +3226,11 @@ function fileBytes(path) {
|
|
|
3086
3226
|
}
|
|
3087
3227
|
var THROTTLE_MIN_NEW_EXCHANGES = 2;
|
|
3088
3228
|
function hookStatePath() {
|
|
3089
|
-
return
|
|
3229
|
+
return join10(GLOBAL_DIR, "hook-state.json");
|
|
3090
3230
|
}
|
|
3091
3231
|
function readHookState() {
|
|
3092
3232
|
try {
|
|
3093
|
-
const parsed = JSON.parse(
|
|
3233
|
+
const parsed = JSON.parse(readFileSync11(hookStatePath(), "utf8"));
|
|
3094
3234
|
if (!parsed || typeof parsed !== "object") return {};
|
|
3095
3235
|
return parsed;
|
|
3096
3236
|
} catch {
|
|
@@ -3103,10 +3243,10 @@ function lastProcessedExchanges(state, sessionId) {
|
|
|
3103
3243
|
}
|
|
3104
3244
|
function saveHookState(sessionId, exchanges) {
|
|
3105
3245
|
try {
|
|
3106
|
-
|
|
3246
|
+
mkdirSync9(GLOBAL_DIR, { recursive: true });
|
|
3107
3247
|
const state = readHookState();
|
|
3108
3248
|
state[sessionId] = { lastExchanges: exchanges };
|
|
3109
|
-
|
|
3249
|
+
writeFileSync9(hookStatePath(), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
3110
3250
|
} catch {
|
|
3111
3251
|
}
|
|
3112
3252
|
}
|
|
@@ -3180,7 +3320,7 @@ function readTranscript(path) {
|
|
|
3180
3320
|
const out = [];
|
|
3181
3321
|
let raw;
|
|
3182
3322
|
try {
|
|
3183
|
-
raw =
|
|
3323
|
+
raw = readFileSync11(path, "utf8");
|
|
3184
3324
|
} catch {
|
|
3185
3325
|
return out;
|
|
3186
3326
|
}
|
|
@@ -3211,24 +3351,24 @@ function resolveCommandPath(command) {
|
|
|
3211
3351
|
if (command.startsWith("/") && existsSync9(command)) return command;
|
|
3212
3352
|
try {
|
|
3213
3353
|
const shell = process.env.SHELL || "/bin/sh";
|
|
3214
|
-
const res =
|
|
3354
|
+
const res = spawnSync3(shell, ["-lc", "command -v " + command], { encoding: "utf8" });
|
|
3215
3355
|
if (res.status === 0) {
|
|
3216
3356
|
const p = (res.stdout || "").trim().split("\n")[0]?.trim();
|
|
3217
3357
|
if (p && p.startsWith("/") && existsSync9(p)) return p;
|
|
3218
3358
|
}
|
|
3219
3359
|
} catch {
|
|
3220
3360
|
}
|
|
3221
|
-
const home =
|
|
3361
|
+
const home = homedir4();
|
|
3222
3362
|
const dirs = [
|
|
3223
|
-
|
|
3363
|
+
join10(home, ".local", "bin"),
|
|
3224
3364
|
"/opt/homebrew/bin",
|
|
3225
3365
|
"/usr/local/bin",
|
|
3226
|
-
|
|
3366
|
+
join10(home, ".npm-global", "bin"),
|
|
3227
3367
|
"/usr/bin",
|
|
3228
3368
|
"/bin"
|
|
3229
3369
|
];
|
|
3230
3370
|
for (const dir of dirs) {
|
|
3231
|
-
const candidate =
|
|
3371
|
+
const candidate = join10(dir, command);
|
|
3232
3372
|
if (existsSync9(candidate)) return candidate;
|
|
3233
3373
|
}
|
|
3234
3374
|
return null;
|
|
@@ -3303,7 +3443,7 @@ async function capture() {
|
|
|
3303
3443
|
let bytes = 0;
|
|
3304
3444
|
let lines = 0;
|
|
3305
3445
|
try {
|
|
3306
|
-
const rawT =
|
|
3446
|
+
const rawT = readFileSync11(data.transcript_path, "utf8");
|
|
3307
3447
|
bytes = Buffer.byteLength(rawT, "utf8");
|
|
3308
3448
|
lines = countLines(rawT);
|
|
3309
3449
|
} catch {
|
|
@@ -3398,11 +3538,11 @@ async function hook(sub, rest = []) {
|
|
|
3398
3538
|
}
|
|
3399
3539
|
|
|
3400
3540
|
// src/commands/chat.ts
|
|
3401
|
-
import { randomUUID as
|
|
3541
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3402
3542
|
|
|
3403
3543
|
// src/output.ts
|
|
3404
|
-
import { writeFileSync as
|
|
3405
|
-
import { join as
|
|
3544
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
3545
|
+
import { join as join11, isAbsolute } from "path";
|
|
3406
3546
|
function escapeHtml(s) {
|
|
3407
3547
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3408
3548
|
}
|
|
@@ -3542,8 +3682,8 @@ function saveReply(reply, format, name) {
|
|
|
3542
3682
|
throw new Error('name must be a plain file name in this folder (no "/", "\\", or "..").');
|
|
3543
3683
|
}
|
|
3544
3684
|
if (!/\.[a-z0-9]+$/i.test(base)) base += ext;
|
|
3545
|
-
const path =
|
|
3546
|
-
|
|
3685
|
+
const path = join11(process.cwd(), base);
|
|
3686
|
+
writeFileSync10(path, renderReply(reply, format), "utf8");
|
|
3547
3687
|
return path;
|
|
3548
3688
|
}
|
|
3549
3689
|
|
|
@@ -3572,11 +3712,11 @@ async function login(which) {
|
|
|
3572
3712
|
|
|
3573
3713
|
// src/access.ts
|
|
3574
3714
|
import { existsSync as existsSync10, realpathSync as realpathSync3, statSync as statSync5 } from "fs";
|
|
3575
|
-
import { homedir as
|
|
3715
|
+
import { homedir as homedir5 } from "os";
|
|
3576
3716
|
import { dirname as dirname4, isAbsolute as isAbsolute2, resolve as resolve2, sep } from "path";
|
|
3577
3717
|
function expandHome(p) {
|
|
3578
|
-
if (p === "~") return
|
|
3579
|
-
if (p.startsWith("~/")) return resolve2(
|
|
3718
|
+
if (p === "~") return homedir5();
|
|
3719
|
+
if (p.startsWith("~/")) return resolve2(homedir5(), p.slice(2));
|
|
3580
3720
|
return p;
|
|
3581
3721
|
}
|
|
3582
3722
|
function isInside(child, parent) {
|
|
@@ -3947,8 +4087,8 @@ async function chat() {
|
|
|
3947
4087
|
history.push({ role: "user", content: line });
|
|
3948
4088
|
history.push({ role: "assistant", content: res.text });
|
|
3949
4089
|
const now = Date.now();
|
|
3950
|
-
appendTurn({ id:
|
|
3951
|
-
appendTurn({ id:
|
|
4090
|
+
appendTurn({ id: randomUUID4().slice(0, 8), ts: now, session, role: "user", content: line, emb: await embed(line) ?? void 0 });
|
|
4091
|
+
appendTurn({ id: randomUUID4().slice(0, 8), ts: now, session, role: "assistant", content: res.text, emb: await embed(res.text) ?? void 0 });
|
|
3952
4092
|
showStatus();
|
|
3953
4093
|
} else {
|
|
3954
4094
|
console.log(c.red("\n " + res.text + "\n"));
|
|
@@ -3975,15 +4115,39 @@ async function chat() {
|
|
|
3975
4115
|
}
|
|
3976
4116
|
|
|
3977
4117
|
// src/commands/launch.ts
|
|
3978
|
-
var
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
4118
|
+
var TAGLINE = "Holt: your assistant, with memory that lasts.";
|
|
4119
|
+
var TIPS = [
|
|
4120
|
+
"Just talk. I remember what matters across our sessions.",
|
|
4121
|
+
"Ask me to recall something we discussed before.",
|
|
4122
|
+
"If I ever say I am signed out, type /login.",
|
|
4123
|
+
"Everything stays on your machine."
|
|
4124
|
+
];
|
|
4125
|
+
function renderIntroBox() {
|
|
4126
|
+
const wordmark = "HOLT";
|
|
4127
|
+
const context = "Your assistant is ready. Memory is on for this folder.";
|
|
4128
|
+
const bullets = TIPS.map((t) => `\u2022 ${t}`);
|
|
4129
|
+
const content = [wordmark, TAGLINE, "", context, "", ...bullets];
|
|
4130
|
+
const pad = 2;
|
|
4131
|
+
const inner = Math.max(...content.map((l) => l.length));
|
|
4132
|
+
const span = inner + pad * 2;
|
|
4133
|
+
const top = "\u250C" + "\u2500".repeat(span) + "\u2510";
|
|
4134
|
+
const bottom = "\u2514" + "\u2500".repeat(span) + "\u2518";
|
|
4135
|
+
const row2 = (plain, colorize = (s) => s) => {
|
|
4136
|
+
const right = " ".repeat(inner - plain.length);
|
|
4137
|
+
const body = " ".repeat(pad) + colorize(plain) + right + " ".repeat(pad);
|
|
4138
|
+
return c.dim("\u2502") + body + c.dim("\u2502");
|
|
4139
|
+
};
|
|
4140
|
+
const lines = [];
|
|
4141
|
+
lines.push(c.dim(top));
|
|
4142
|
+
lines.push(row2(wordmark, c.accent));
|
|
4143
|
+
lines.push(row2(TAGLINE, c.dim));
|
|
4144
|
+
lines.push(row2(""));
|
|
4145
|
+
lines.push(row2(context, c.dim));
|
|
4146
|
+
lines.push(row2(""));
|
|
4147
|
+
for (const b of bullets) lines.push(row2(b, c.dim));
|
|
4148
|
+
lines.push(c.dim(bottom));
|
|
4149
|
+
return "\n" + lines.join("\n") + "\n";
|
|
4150
|
+
}
|
|
3987
4151
|
var HOLT_IDENTITY = "You are Holt, the user's personal assistant. You have persistent memory of this user across sessions, provided by the Holt tool you run inside (it recalls relevant notes before each prompt and remembers new facts when the session ends). Act as their assistant. Do not mention the underlying model or that you are looking anything up; just use what you know naturally.";
|
|
3988
4152
|
var INTERACTIVE_ARGS = {
|
|
3989
4153
|
claude: [],
|
|
@@ -4002,13 +4166,13 @@ var CONTEXT_MARKER = "<!-- holt-managed identity -->";
|
|
|
4002
4166
|
function brandContextFile(id) {
|
|
4003
4167
|
const name = CONTEXT_FILE[id];
|
|
4004
4168
|
if (!name) return null;
|
|
4005
|
-
const path =
|
|
4169
|
+
const path = join12(workspace(), name);
|
|
4006
4170
|
try {
|
|
4007
4171
|
if (existsSync11(path)) {
|
|
4008
|
-
const cur =
|
|
4172
|
+
const cur = readFileSync12(path, "utf8");
|
|
4009
4173
|
if (!cur.includes(CONTEXT_MARKER)) return null;
|
|
4010
4174
|
}
|
|
4011
|
-
|
|
4175
|
+
writeFileSync11(path, `${CONTEXT_MARKER}
|
|
4012
4176
|
# Holt
|
|
4013
4177
|
|
|
4014
4178
|
${HOLT_IDENTITY}
|
|
@@ -4019,11 +4183,11 @@ ${HOLT_IDENTITY}
|
|
|
4019
4183
|
}
|
|
4020
4184
|
}
|
|
4021
4185
|
function brandStatusLine() {
|
|
4022
|
-
const path =
|
|
4186
|
+
const path = join12(workspace(), ".claude", "settings.json");
|
|
4023
4187
|
let settings = {};
|
|
4024
4188
|
try {
|
|
4025
4189
|
if (existsSync11(path)) {
|
|
4026
|
-
const parsed = JSON.parse(
|
|
4190
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
4027
4191
|
if (parsed && typeof parsed === "object") settings = parsed;
|
|
4028
4192
|
}
|
|
4029
4193
|
} catch {
|
|
@@ -4034,10 +4198,10 @@ function brandStatusLine() {
|
|
|
4034
4198
|
if (existsSync11(path)) copyFileSync2(path, `${path}.holt-bak`);
|
|
4035
4199
|
settings.statusLine = {
|
|
4036
4200
|
type: "command",
|
|
4037
|
-
command:
|
|
4201
|
+
command: `${resolveHoltPath()} statusline`
|
|
4038
4202
|
};
|
|
4039
|
-
|
|
4040
|
-
|
|
4203
|
+
mkdirSync10(dirname5(path), { recursive: true });
|
|
4204
|
+
writeFileSync11(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
4041
4205
|
return true;
|
|
4042
4206
|
} catch {
|
|
4043
4207
|
return false;
|
|
@@ -4059,10 +4223,20 @@ function resolveTarget(cfg) {
|
|
|
4059
4223
|
async function ensureSetup() {
|
|
4060
4224
|
const ws = workspace();
|
|
4061
4225
|
if (!isTrusted(ws)) trustDir(ws);
|
|
4062
|
-
|
|
4226
|
+
mkdirSync10(memDir(), { recursive: true });
|
|
4227
|
+
if (!hooksAlreadyInstalled()) {
|
|
4228
|
+
try {
|
|
4229
|
+
await hook("install", []);
|
|
4230
|
+
} catch {
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
function hooksAlreadyInstalled() {
|
|
4063
4235
|
try {
|
|
4064
|
-
|
|
4236
|
+
const p = join12(homedir6(), ".claude", "settings.json");
|
|
4237
|
+
return existsSync11(p) && readFileSync12(p, "utf8").includes("holt hook");
|
|
4065
4238
|
} catch {
|
|
4239
|
+
return false;
|
|
4066
4240
|
}
|
|
4067
4241
|
}
|
|
4068
4242
|
async function launch() {
|
|
@@ -4102,7 +4276,7 @@ async function launch() {
|
|
|
4102
4276
|
);
|
|
4103
4277
|
return;
|
|
4104
4278
|
}
|
|
4105
|
-
console.log(
|
|
4279
|
+
console.log(renderIntroBox());
|
|
4106
4280
|
const flags = [...brandingFlags(target.id)];
|
|
4107
4281
|
if (flags.length === 0) {
|
|
4108
4282
|
const ctx = brandContextFile(target.id);
|
|
@@ -4111,14 +4285,13 @@ async function launch() {
|
|
|
4111
4285
|
if (target.id === "claude") brandStatusLine();
|
|
4112
4286
|
const interactive = INTERACTIVE_ARGS[target.id];
|
|
4113
4287
|
const args = [...interactive, ...flags];
|
|
4114
|
-
console.log(c.dim(
|
|
4115
|
-
`));
|
|
4288
|
+
console.log(c.dim(' Starting your Holt session. Type "exit" to leave.\n'));
|
|
4116
4289
|
await runInteractive(cfg.brains[target.id].command, args);
|
|
4117
4290
|
console.log(c.dim("\nBye. Holt kept what mattered from this session.\n"));
|
|
4118
4291
|
}
|
|
4119
4292
|
|
|
4120
4293
|
// src/commands/memory.ts
|
|
4121
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4294
|
+
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
|
|
4122
4295
|
import { basename } from "path";
|
|
4123
4296
|
async function memoryCmd(sub, rest = []) {
|
|
4124
4297
|
const { ask, close } = createReader();
|
|
@@ -4171,7 +4344,7 @@ async function memoryCmd(sub, rest = []) {
|
|
|
4171
4344
|
close();
|
|
4172
4345
|
return;
|
|
4173
4346
|
}
|
|
4174
|
-
const body =
|
|
4347
|
+
const body = readFileSync13(factsMdPath(), "utf8").trim();
|
|
4175
4348
|
console.log("\n" + body + "\n");
|
|
4176
4349
|
close();
|
|
4177
4350
|
return;
|
|
@@ -4285,8 +4458,8 @@ function globalCmd(act, rest) {
|
|
|
4285
4458
|
}
|
|
4286
4459
|
|
|
4287
4460
|
// src/commands/wiki.ts
|
|
4288
|
-
import { existsSync as existsSync13, readFileSync as
|
|
4289
|
-
import { join as
|
|
4461
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4462
|
+
import { join as join13 } from "path";
|
|
4290
4463
|
import { spawn as spawn3 } from "child_process";
|
|
4291
4464
|
function titleToSlug(title) {
|
|
4292
4465
|
return proposeSlug(title);
|
|
@@ -4479,7 +4652,7 @@ function listCmd() {
|
|
|
4479
4652
|
for (const slug of slugs) {
|
|
4480
4653
|
const pg = readPage(slug);
|
|
4481
4654
|
if (!pg) continue;
|
|
4482
|
-
const size = existsSync13(
|
|
4655
|
+
const size = existsSync13(join13(wikiDir(), slug + ".md")) ? (readFileSync14(join13(wikiDir(), slug + ".md"), "utf8").length / 1024).toFixed(1) : "0.0";
|
|
4483
4656
|
console.log(` ${c.cyan(pg.title.padEnd(28).slice(0, 28))} ${c.dim(pg.updated || " ")} ${size} KB`);
|
|
4484
4657
|
}
|
|
4485
4658
|
console.log("");
|
|
@@ -4497,7 +4670,7 @@ function showCmd(name) {
|
|
|
4497
4670
|
`));
|
|
4498
4671
|
return;
|
|
4499
4672
|
}
|
|
4500
|
-
const raw =
|
|
4673
|
+
const raw = readFileSync14(join13(wikiDir(), slug + ".md"), "utf8");
|
|
4501
4674
|
console.log("\n" + raw.trim() + "\n");
|
|
4502
4675
|
}
|
|
4503
4676
|
async function statusCmd(cfg) {
|
|
@@ -4655,9 +4828,9 @@ async function wikiCmd(sub, rest = []) {
|
|
|
4655
4828
|
|
|
4656
4829
|
// src/commands/skill.ts
|
|
4657
4830
|
import {
|
|
4658
|
-
mkdirSync as
|
|
4659
|
-
writeFileSync as
|
|
4660
|
-
readFileSync as
|
|
4831
|
+
mkdirSync as mkdirSync12,
|
|
4832
|
+
writeFileSync as writeFileSync13,
|
|
4833
|
+
readFileSync as readFileSync16,
|
|
4661
4834
|
existsSync as existsSync15,
|
|
4662
4835
|
readdirSync as readdirSync3,
|
|
4663
4836
|
statSync as statSync7,
|
|
@@ -4665,19 +4838,19 @@ import {
|
|
|
4665
4838
|
cpSync,
|
|
4666
4839
|
mkdtempSync
|
|
4667
4840
|
} from "fs";
|
|
4668
|
-
import { join as
|
|
4841
|
+
import { join as join15, resolve as resolve4, sep as sep2, isAbsolute as isAbsolute4 } from "path";
|
|
4669
4842
|
import { tmpdir } from "os";
|
|
4670
|
-
import { spawnSync as
|
|
4843
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4671
4844
|
|
|
4672
4845
|
// src/registry.ts
|
|
4673
|
-
import { readFileSync as
|
|
4674
|
-
import { join as
|
|
4846
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync11, statSync as statSync6 } from "fs";
|
|
4847
|
+
import { join as join14, isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
|
|
4675
4848
|
import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
|
|
4676
4849
|
var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/holt-os/registry/main/registry.json";
|
|
4677
4850
|
var REGISTRY_REPO_URL = "https://github.com/holt-os/registry";
|
|
4678
4851
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
4679
4852
|
function registryCachePath() {
|
|
4680
|
-
return
|
|
4853
|
+
return join14(GLOBAL_DIR, "registry-cache.json");
|
|
4681
4854
|
}
|
|
4682
4855
|
function registryUrl() {
|
|
4683
4856
|
const env = (process.env.HOLT_REGISTRY_URL || "").trim();
|
|
@@ -4717,7 +4890,7 @@ function readCache() {
|
|
|
4717
4890
|
try {
|
|
4718
4891
|
const path = registryCachePath();
|
|
4719
4892
|
if (!existsSync14(path)) return null;
|
|
4720
|
-
const raw = JSON.parse(
|
|
4893
|
+
const raw = JSON.parse(readFileSync15(path, "utf8"));
|
|
4721
4894
|
if (!raw || typeof raw.fetchedAt !== "number" || typeof raw.url !== "string") return null;
|
|
4722
4895
|
const registry = coerceRegistry(raw.registry);
|
|
4723
4896
|
if (!registry) return null;
|
|
@@ -4728,9 +4901,9 @@ function readCache() {
|
|
|
4728
4901
|
}
|
|
4729
4902
|
function writeCache(url, registry) {
|
|
4730
4903
|
try {
|
|
4731
|
-
|
|
4904
|
+
mkdirSync11(GLOBAL_DIR, { recursive: true });
|
|
4732
4905
|
const body = { fetchedAt: Date.now(), url, registry };
|
|
4733
|
-
|
|
4906
|
+
writeFileSync12(registryCachePath(), JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
4734
4907
|
} catch {
|
|
4735
4908
|
}
|
|
4736
4909
|
}
|
|
@@ -4751,7 +4924,7 @@ function loadLocal(path) {
|
|
|
4751
4924
|
if (!existsSync14(path)) {
|
|
4752
4925
|
return { registry: null, fromCache: false, error: UNREACHABLE_MSG };
|
|
4753
4926
|
}
|
|
4754
|
-
const registry = parseRegistry(
|
|
4927
|
+
const registry = parseRegistry(readFileSync15(path, "utf8"));
|
|
4755
4928
|
if (!registry) {
|
|
4756
4929
|
return { registry: null, fromCache: false, error: "registry file is not valid JSON" };
|
|
4757
4930
|
}
|
|
@@ -4880,12 +5053,12 @@ function resolveSubpathDir(fetched, subpath) {
|
|
|
4880
5053
|
return target;
|
|
4881
5054
|
}
|
|
4882
5055
|
function findSkillDir(root) {
|
|
4883
|
-
if (existsSync15(
|
|
5056
|
+
if (existsSync15(join15(root, "SKILL.md"))) return root;
|
|
4884
5057
|
let subs = [];
|
|
4885
5058
|
try {
|
|
4886
5059
|
subs = readdirSync3(root).filter((f) => {
|
|
4887
5060
|
try {
|
|
4888
|
-
return statSync7(
|
|
5061
|
+
return statSync7(join15(root, f)).isDirectory();
|
|
4889
5062
|
} catch {
|
|
4890
5063
|
return false;
|
|
4891
5064
|
}
|
|
@@ -4893,8 +5066,8 @@ function findSkillDir(root) {
|
|
|
4893
5066
|
} catch {
|
|
4894
5067
|
return null;
|
|
4895
5068
|
}
|
|
4896
|
-
const withSkill = subs.filter((f) => existsSync15(
|
|
4897
|
-
if (withSkill.length === 1) return
|
|
5069
|
+
const withSkill = subs.filter((f) => existsSync15(join15(root, f, "SKILL.md")));
|
|
5070
|
+
if (withSkill.length === 1) return join15(root, withSkill[0]);
|
|
4898
5071
|
return null;
|
|
4899
5072
|
}
|
|
4900
5073
|
function cmdList() {
|
|
@@ -4935,7 +5108,7 @@ function cmdShow(name) {
|
|
|
4935
5108
|
const { skill, body } = loaded;
|
|
4936
5109
|
console.log("\n" + c.accent(skill.name) + c.dim(` (${skill.scope})`));
|
|
4937
5110
|
if (skill.description) console.log(" " + skill.description);
|
|
4938
|
-
console.log(c.dim(" " +
|
|
5111
|
+
console.log(c.dim(" " + join15(skill.dir, "SKILL.md")));
|
|
4939
5112
|
console.log("");
|
|
4940
5113
|
console.log(c.dim(body));
|
|
4941
5114
|
console.log("");
|
|
@@ -4947,7 +5120,7 @@ function cmdCreate(name, global) {
|
|
|
4947
5120
|
return;
|
|
4948
5121
|
}
|
|
4949
5122
|
const scope = global ? "global" : "workspace";
|
|
4950
|
-
const dir =
|
|
5123
|
+
const dir = join15(skillsRoot(scope), clean);
|
|
4951
5124
|
if (existsSync15(dir)) {
|
|
4952
5125
|
console.error(c.red(`
|
|
4953
5126
|
A ${scope} skill named "${clean}" already exists.`));
|
|
@@ -4955,11 +5128,11 @@ function cmdCreate(name, global) {
|
|
|
4955
5128
|
process.exitCode = 1;
|
|
4956
5129
|
return;
|
|
4957
5130
|
}
|
|
4958
|
-
|
|
4959
|
-
|
|
5131
|
+
mkdirSync12(dir, { recursive: true });
|
|
5132
|
+
writeFileSync13(join15(dir, "SKILL.md"), SKILL_TEMPLATE(clean), "utf8");
|
|
4960
5133
|
console.log(c.green(`
|
|
4961
5134
|
Created ${scope} skill "${clean}".`));
|
|
4962
|
-
console.log(c.dim(" " +
|
|
5135
|
+
console.log(c.dim(" " + join15(dir, "SKILL.md")));
|
|
4963
5136
|
console.log(c.dim(' Edit it, then run it in chat with "/skill ' + clean + '".\n'));
|
|
4964
5137
|
}
|
|
4965
5138
|
function installFromSource(source, scope) {
|
|
@@ -4968,10 +5141,10 @@ function installFromSource(source, scope) {
|
|
|
4968
5141
|
let tempDir = "";
|
|
4969
5142
|
try {
|
|
4970
5143
|
if (isGitUrl(base)) {
|
|
4971
|
-
tempDir = mkdtempSync(
|
|
5144
|
+
tempDir = mkdtempSync(join15(tmpdir(), "holt-skill-"));
|
|
4972
5145
|
console.log(c.dim(`
|
|
4973
5146
|
Cloning ${base} ...`));
|
|
4974
|
-
const res =
|
|
5147
|
+
const res = spawnSync4("git", ["clone", "--depth", "1", base, tempDir], { stdio: "ignore" });
|
|
4975
5148
|
if (res.status !== 0) {
|
|
4976
5149
|
console.error(c.red(" Clone failed. Check the URL and that git is installed.\n"));
|
|
4977
5150
|
process.exitCode = 1;
|
|
@@ -5018,7 +5191,7 @@ function installFromSource(source, scope) {
|
|
|
5018
5191
|
}
|
|
5019
5192
|
let skillName = "";
|
|
5020
5193
|
try {
|
|
5021
|
-
const { data } = parseFrontmatter(
|
|
5194
|
+
const { data } = parseFrontmatter(readFileSync16(join15(skillDir, "SKILL.md"), "utf8"));
|
|
5022
5195
|
skillName = sanitizeName(data.name || "");
|
|
5023
5196
|
} catch {
|
|
5024
5197
|
skillName = "";
|
|
@@ -5029,7 +5202,7 @@ function installFromSource(source, scope) {
|
|
|
5029
5202
|
process.exitCode = 1;
|
|
5030
5203
|
return false;
|
|
5031
5204
|
}
|
|
5032
|
-
const dest =
|
|
5205
|
+
const dest = join15(skillsRoot(scope), skillName);
|
|
5033
5206
|
if (existsSync15(dest)) {
|
|
5034
5207
|
console.error(c.red(`
|
|
5035
5208
|
A ${scope} skill named "${skillName}" already exists.`));
|
|
@@ -5037,13 +5210,13 @@ function installFromSource(source, scope) {
|
|
|
5037
5210
|
process.exitCode = 1;
|
|
5038
5211
|
return false;
|
|
5039
5212
|
}
|
|
5040
|
-
|
|
5213
|
+
mkdirSync12(skillsRoot(scope), { recursive: true });
|
|
5041
5214
|
cpSync(skillDir, dest, { recursive: true });
|
|
5042
|
-
const nestedGit =
|
|
5215
|
+
const nestedGit = join15(dest, ".git");
|
|
5043
5216
|
if (existsSync15(nestedGit)) rmSync5(nestedGit, { recursive: true, force: true });
|
|
5044
5217
|
console.log(c.green(`
|
|
5045
5218
|
Installed ${scope} skill "${skillName}".`));
|
|
5046
|
-
console.log(c.dim(" " +
|
|
5219
|
+
console.log(c.dim(" " + join15(dest, "SKILL.md") + "\n"));
|
|
5047
5220
|
return true;
|
|
5048
5221
|
} finally {
|
|
5049
5222
|
if (tempDir && existsSync15(tempDir)) {
|
|
@@ -5133,7 +5306,7 @@ function cmdPublish(name) {
|
|
|
5133
5306
|
}
|
|
5134
5307
|
let raw = "";
|
|
5135
5308
|
try {
|
|
5136
|
-
raw =
|
|
5309
|
+
raw = readFileSync16(join15(loaded.skill.dir, "SKILL.md"), "utf8");
|
|
5137
5310
|
} catch {
|
|
5138
5311
|
console.error(c.red("\n Could not read the skill's SKILL.md.\n"));
|
|
5139
5312
|
process.exitCode = 1;
|
|
@@ -5143,14 +5316,14 @@ function cmdPublish(name) {
|
|
|
5143
5316
|
skillName = sanitizeName(data.name || loaded.skill.name);
|
|
5144
5317
|
description = (data.description || loaded.skill.description || "").trim();
|
|
5145
5318
|
} else {
|
|
5146
|
-
const file =
|
|
5319
|
+
const file = join15(process.cwd(), "SKILL.md");
|
|
5147
5320
|
if (!existsSync15(file)) {
|
|
5148
5321
|
console.error(c.red("\n No SKILL.md in the current directory, and no skill name given."));
|
|
5149
5322
|
console.error(c.dim(" Usage: holt skill publish [<name>]\n"));
|
|
5150
5323
|
process.exitCode = 1;
|
|
5151
5324
|
return;
|
|
5152
5325
|
}
|
|
5153
|
-
const { data } = parseFrontmatter(
|
|
5326
|
+
const { data } = parseFrontmatter(readFileSync16(file, "utf8"));
|
|
5154
5327
|
skillName = sanitizeName(data.name || process.cwd().split(/[\\/]/).pop() || "");
|
|
5155
5328
|
description = (data.description || "").trim();
|
|
5156
5329
|
sourceHint = process.cwd();
|
|
@@ -5186,11 +5359,11 @@ async function cmdRemove(name, ask) {
|
|
|
5186
5359
|
console.log(c.dim("\n Usage: holt skill remove <name>\n"));
|
|
5187
5360
|
return;
|
|
5188
5361
|
}
|
|
5189
|
-
const wsDir =
|
|
5190
|
-
const globalDir =
|
|
5362
|
+
const wsDir = join15(skillsRoot("workspace"), clean);
|
|
5363
|
+
const globalDir = join15(skillsRoot("global"), clean);
|
|
5191
5364
|
const target = existsSync15(wsDir) ? wsDir : existsSync15(globalDir) ? globalDir : "";
|
|
5192
5365
|
if (!target) {
|
|
5193
|
-
if (existsSync15(
|
|
5366
|
+
if (existsSync15(join15(skillsRoot("builtin"), clean))) {
|
|
5194
5367
|
console.error(c.red(`
|
|
5195
5368
|
"${clean}" is a built-in skill and cannot be removed; it ships inside Holt.`));
|
|
5196
5369
|
console.error(c.dim(" Override it instead: create a workspace or global skill with the same name.\n"));
|
|
@@ -5263,8 +5436,8 @@ async function skillCmd(sub, rest = []) {
|
|
|
5263
5436
|
}
|
|
5264
5437
|
|
|
5265
5438
|
// src/commands/graph.ts
|
|
5266
|
-
import { writeFileSync as
|
|
5267
|
-
import { join as
|
|
5439
|
+
import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, existsSync as existsSync16 } from "fs";
|
|
5440
|
+
import { join as join17, dirname as dirname7, resolve as resolve5 } from "path";
|
|
5268
5441
|
import { spawn as spawn4 } from "child_process";
|
|
5269
5442
|
|
|
5270
5443
|
// src/communities.ts
|
|
@@ -6061,8 +6234,8 @@ function renderGraphHtml(graph2, meta) {
|
|
|
6061
6234
|
}
|
|
6062
6235
|
|
|
6063
6236
|
// src/ingest.ts
|
|
6064
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
6065
|
-
import { join as
|
|
6237
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync17, statSync as statSync8 } from "fs";
|
|
6238
|
+
import { join as join16, relative, sep as sep3, extname, dirname as dirname6, basename as basename2 } from "path";
|
|
6066
6239
|
var CODE_EXT = /* @__PURE__ */ new Set([
|
|
6067
6240
|
"js",
|
|
6068
6241
|
"jsx",
|
|
@@ -6135,7 +6308,7 @@ function ingestFolder(root, want = { code: true, docs: true }) {
|
|
|
6135
6308
|
skipped.hitFileCap = true;
|
|
6136
6309
|
break;
|
|
6137
6310
|
}
|
|
6138
|
-
const full =
|
|
6311
|
+
const full = join16(dir, name);
|
|
6139
6312
|
let st;
|
|
6140
6313
|
try {
|
|
6141
6314
|
st = statSync8(full);
|
|
@@ -6168,7 +6341,7 @@ function ingestFolder(root, want = { code: true, docs: true }) {
|
|
|
6168
6341
|
}
|
|
6169
6342
|
let content;
|
|
6170
6343
|
try {
|
|
6171
|
-
content =
|
|
6344
|
+
content = readFileSync17(full, "utf8");
|
|
6172
6345
|
} catch {
|
|
6173
6346
|
skipped.unreadable++;
|
|
6174
6347
|
continue;
|
|
@@ -6504,9 +6677,9 @@ async function graph(args = []) {
|
|
|
6504
6677
|
if (codeGraph) assignCommunities(g);
|
|
6505
6678
|
if (opts.report) {
|
|
6506
6679
|
const md = renderReport(g, { workspace: workspace() });
|
|
6507
|
-
const reportPath = opts.out ? resolve5(opts.out) :
|
|
6508
|
-
|
|
6509
|
-
|
|
6680
|
+
const reportPath = opts.out ? resolve5(opts.out) : join17(workspace(), "GRAPH_REPORT.md");
|
|
6681
|
+
mkdirSync13(dirname7(reportPath), { recursive: true });
|
|
6682
|
+
writeFileSync14(reportPath, md, "utf8");
|
|
6510
6683
|
const communities = new Set(g.nodes.map((n) => n.community ?? "0")).size;
|
|
6511
6684
|
console.log("\n" + c.accent("Graph report written") + c.dim(" (this folder)"));
|
|
6512
6685
|
console.log(` nodes ${g.nodes.length} (${fileNodeCount} files, ${docNodeCount} docs)`);
|
|
@@ -6526,9 +6699,9 @@ async function graph(args = []) {
|
|
|
6526
6699
|
concepts: conceptCount,
|
|
6527
6700
|
edges: g.edges.length
|
|
6528
6701
|
});
|
|
6529
|
-
const outPath = opts.out ? resolve5(opts.out) :
|
|
6530
|
-
|
|
6531
|
-
|
|
6702
|
+
const outPath = opts.out ? resolve5(opts.out) : join17(wsHoltDir(), "graph.html");
|
|
6703
|
+
mkdirSync13(dirname7(outPath), { recursive: true });
|
|
6704
|
+
writeFileSync14(outPath, html, "utf8");
|
|
6532
6705
|
console.log("\n" + c.accent("Memory graph built") + c.dim(" (this folder)"));
|
|
6533
6706
|
const parts = [`${turns.length} turns`, `${conceptCount} concepts`];
|
|
6534
6707
|
if (wikiCount) parts.push(`${wikiCount} wiki pages`);
|
|
@@ -6635,8 +6808,8 @@ function recoverId(line) {
|
|
|
6635
6808
|
const n = Number(num[1]);
|
|
6636
6809
|
if (Number.isFinite(n)) return n;
|
|
6637
6810
|
}
|
|
6638
|
-
const
|
|
6639
|
-
if (
|
|
6811
|
+
const str2 = line.match(/"id"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
6812
|
+
if (str2 && str2[1] !== void 0) return str2[1];
|
|
6640
6813
|
return null;
|
|
6641
6814
|
}
|
|
6642
6815
|
function runMcpServer(opts) {
|
|
@@ -6816,7 +6989,7 @@ function holtTools() {
|
|
|
6816
6989
|
}
|
|
6817
6990
|
|
|
6818
6991
|
// src/version.ts
|
|
6819
|
-
var VERSION = "0.14.
|
|
6992
|
+
var VERSION = "0.14.2";
|
|
6820
6993
|
|
|
6821
6994
|
// src/commands/mcp.ts
|
|
6822
6995
|
function printSetup() {
|
|
@@ -6861,8 +7034,84 @@ async function mcp(sub, rest = []) {
|
|
|
6861
7034
|
}
|
|
6862
7035
|
}
|
|
6863
7036
|
|
|
7037
|
+
// src/commands/statusline.ts
|
|
7038
|
+
import { basename as basename3 } from "path";
|
|
7039
|
+
var SEP = " \xB7 ";
|
|
7040
|
+
function readStdin2(timeoutMs) {
|
|
7041
|
+
return new Promise((resolve9) => {
|
|
7042
|
+
let input = "";
|
|
7043
|
+
let done = false;
|
|
7044
|
+
const finish = () => {
|
|
7045
|
+
if (done) return;
|
|
7046
|
+
done = true;
|
|
7047
|
+
resolve9(input);
|
|
7048
|
+
};
|
|
7049
|
+
try {
|
|
7050
|
+
process.stdin.setEncoding("utf8");
|
|
7051
|
+
process.stdin.on("data", (chunk2) => {
|
|
7052
|
+
input += chunk2;
|
|
7053
|
+
});
|
|
7054
|
+
process.stdin.on("end", finish);
|
|
7055
|
+
process.stdin.on("error", finish);
|
|
7056
|
+
} catch {
|
|
7057
|
+
finish();
|
|
7058
|
+
}
|
|
7059
|
+
setTimeout(finish, timeoutMs);
|
|
7060
|
+
});
|
|
7061
|
+
}
|
|
7062
|
+
function get(obj, key) {
|
|
7063
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
7064
|
+
return obj[key];
|
|
7065
|
+
}
|
|
7066
|
+
function str(v) {
|
|
7067
|
+
if (typeof v !== "string") return void 0;
|
|
7068
|
+
const t = v.trim();
|
|
7069
|
+
return t.length ? t : void 0;
|
|
7070
|
+
}
|
|
7071
|
+
function folderFrom(data) {
|
|
7072
|
+
const ws = get(data, "workspace");
|
|
7073
|
+
const dir = str(get(ws, "current_dir")) ?? str(get(ws, "project_dir")) ?? str(get(ws, "cwd")) ?? str(get(data, "cwd")) ?? str(get(data, "current_dir"));
|
|
7074
|
+
if (!dir) return void 0;
|
|
7075
|
+
try {
|
|
7076
|
+
const base = basename3(dir.replace(/[\\/]+$/, ""));
|
|
7077
|
+
return base.length ? base : void 0;
|
|
7078
|
+
} catch {
|
|
7079
|
+
return void 0;
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
function modelFrom(data) {
|
|
7083
|
+
const model = get(data, "model");
|
|
7084
|
+
if (typeof model === "string") return str(model);
|
|
7085
|
+
return str(get(model, "display_name")) ?? str(get(model, "id")) ?? str(get(data, "model_display_name")) ?? str(get(data, "model_id"));
|
|
7086
|
+
}
|
|
7087
|
+
function renderStatusLine(data) {
|
|
7088
|
+
const parts = ["Holt"];
|
|
7089
|
+
const folder = folderFrom(data);
|
|
7090
|
+
if (folder) parts.push(folder);
|
|
7091
|
+
const model = modelFrom(data);
|
|
7092
|
+
if (model) parts.push(model);
|
|
7093
|
+
return parts.join(SEP);
|
|
7094
|
+
}
|
|
7095
|
+
function parse(raw) {
|
|
7096
|
+
try {
|
|
7097
|
+
return JSON.parse(raw || "{}");
|
|
7098
|
+
} catch {
|
|
7099
|
+
return {};
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
async function statusline() {
|
|
7103
|
+
let line = "Holt";
|
|
7104
|
+
try {
|
|
7105
|
+
const raw = await readStdin2(1e3);
|
|
7106
|
+
line = renderStatusLine(parse(raw));
|
|
7107
|
+
} catch {
|
|
7108
|
+
line = "Holt";
|
|
7109
|
+
}
|
|
7110
|
+
process.stdout.write(line + "\n");
|
|
7111
|
+
}
|
|
7112
|
+
|
|
6864
7113
|
// src/commands/run.ts
|
|
6865
|
-
import { writeFileSync as
|
|
7114
|
+
import { writeFileSync as writeFileSync15 } from "fs";
|
|
6866
7115
|
import { resolve as resolve6 } from "path";
|
|
6867
7116
|
var USAGE3 = [
|
|
6868
7117
|
" holt run <task> [options]",
|
|
@@ -6954,7 +7203,7 @@ async function run(args) {
|
|
|
6954
7203
|
if (p.out) {
|
|
6955
7204
|
const target = resolve6(process.cwd(), p.out);
|
|
6956
7205
|
try {
|
|
6957
|
-
|
|
7206
|
+
writeFileSync15(target, result.text, "utf8");
|
|
6958
7207
|
process.stderr.write(c.dim(`saved to ${target}`) + "\n");
|
|
6959
7208
|
} catch (e) {
|
|
6960
7209
|
process.stderr.write(c.red(`Could not write ${target}: ${e.message}`) + "\n");
|
|
@@ -6967,147 +7216,6 @@ async function run(args) {
|
|
|
6967
7216
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
6968
7217
|
import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync14, rmSync as rmSync6, existsSync as existsSync17 } from "fs";
|
|
6969
7218
|
import { dirname as dirname8 } from "path";
|
|
6970
|
-
|
|
6971
|
-
// src/scheduler.ts
|
|
6972
|
-
import { homedir as homedir5 } from "os";
|
|
6973
|
-
import { join as join17 } from "path";
|
|
6974
|
-
import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13 } from "fs";
|
|
6975
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
6976
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
6977
|
-
function schedulesPath() {
|
|
6978
|
-
return join17(GLOBAL_DIR, "schedules.json");
|
|
6979
|
-
}
|
|
6980
|
-
function logsDir() {
|
|
6981
|
-
return join17(GLOBAL_DIR, "logs");
|
|
6982
|
-
}
|
|
6983
|
-
function logPath(id) {
|
|
6984
|
-
return join17(logsDir(), `${id}.log`);
|
|
6985
|
-
}
|
|
6986
|
-
function plistPath(id) {
|
|
6987
|
-
return join17(homedir5(), "Library", "LaunchAgents", `com.holt.${id}.plist`);
|
|
6988
|
-
}
|
|
6989
|
-
function loadJobs() {
|
|
6990
|
-
try {
|
|
6991
|
-
const raw = JSON.parse(readFileSync17(schedulesPath(), "utf8"));
|
|
6992
|
-
return Array.isArray(raw) ? raw : [];
|
|
6993
|
-
} catch {
|
|
6994
|
-
return [];
|
|
6995
|
-
}
|
|
6996
|
-
}
|
|
6997
|
-
function saveJobs(jobs) {
|
|
6998
|
-
mkdirSync13(GLOBAL_DIR, { recursive: true });
|
|
6999
|
-
writeFileSync15(schedulesPath(), JSON.stringify(jobs, null, 2) + "\n", "utf8");
|
|
7000
|
-
}
|
|
7001
|
-
function addJob(job) {
|
|
7002
|
-
const jobs = loadJobs();
|
|
7003
|
-
jobs.push(job);
|
|
7004
|
-
saveJobs(jobs);
|
|
7005
|
-
return jobs;
|
|
7006
|
-
}
|
|
7007
|
-
function removeJob(id) {
|
|
7008
|
-
const jobs = loadJobs().filter((j) => j.id !== id);
|
|
7009
|
-
saveJobs(jobs);
|
|
7010
|
-
return jobs;
|
|
7011
|
-
}
|
|
7012
|
-
function newJobId() {
|
|
7013
|
-
return randomUUID4().slice(0, 8);
|
|
7014
|
-
}
|
|
7015
|
-
function parseWhen(when) {
|
|
7016
|
-
const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(when.trim());
|
|
7017
|
-
if (!m) {
|
|
7018
|
-
throw new Error(
|
|
7019
|
-
`Could not read time "${when}". Use 24h HH:MM for a daily run, e.g. 07:00 or 23:30.`
|
|
7020
|
-
);
|
|
7021
|
-
}
|
|
7022
|
-
const hour = Number(m[1]);
|
|
7023
|
-
const minute = Number(m[2]);
|
|
7024
|
-
return { hour, minute };
|
|
7025
|
-
}
|
|
7026
|
-
function shQuote(s) {
|
|
7027
|
-
return `'` + s.replace(/'/g, `'\\''`) + `'`;
|
|
7028
|
-
}
|
|
7029
|
-
function resolveHoltPath() {
|
|
7030
|
-
try {
|
|
7031
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
7032
|
-
const res = spawnSync4(finder, ["holt"], { encoding: "utf8" });
|
|
7033
|
-
if (res.status === 0 && typeof res.stdout === "string") {
|
|
7034
|
-
const first = res.stdout.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
7035
|
-
if (first) return first;
|
|
7036
|
-
}
|
|
7037
|
-
} catch {
|
|
7038
|
-
}
|
|
7039
|
-
return "holt";
|
|
7040
|
-
}
|
|
7041
|
-
function buildCommand(job, holtPath) {
|
|
7042
|
-
const holt = shQuote(holtPath);
|
|
7043
|
-
const ws = shQuote(job.workspace);
|
|
7044
|
-
if (job.runArgs && job.runArgs.length > 0) {
|
|
7045
|
-
const parts = job.runArgs.map(shQuote).join(" ");
|
|
7046
|
-
return `cd ${ws} && ${holt} ${parts} --quiet`;
|
|
7047
|
-
}
|
|
7048
|
-
const log = shQuote(logPath(job.id));
|
|
7049
|
-
let cmd = `cd ${ws} && ${holt} run ${shQuote(job.task)}`;
|
|
7050
|
-
if (job.brain) cmd += ` --brain ${shQuote(job.brain)}`;
|
|
7051
|
-
cmd += ` --quiet --out ${log}`;
|
|
7052
|
-
if (job.notify) cmd += ` && ${holt} notify "$(cat ${log})"`;
|
|
7053
|
-
return cmd;
|
|
7054
|
-
}
|
|
7055
|
-
function xmlEscape(s) {
|
|
7056
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7057
|
-
}
|
|
7058
|
-
function buildLaunchdPlist(job, holtPath) {
|
|
7059
|
-
const { hour, minute } = parseWhen(job.when);
|
|
7060
|
-
const command = buildCommand(job, holtPath);
|
|
7061
|
-
const log = logPath(job.id);
|
|
7062
|
-
const args = ["/bin/sh", "-lc", command];
|
|
7063
|
-
const argXml = args.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
|
|
7064
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7065
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7066
|
-
<plist version="1.0">
|
|
7067
|
-
<dict>
|
|
7068
|
-
<key>Label</key>
|
|
7069
|
-
<string>com.holt.${xmlEscape(job.id)}</string>
|
|
7070
|
-
<key>ProgramArguments</key>
|
|
7071
|
-
<array>
|
|
7072
|
-
${argXml}
|
|
7073
|
-
</array>
|
|
7074
|
-
<key>StartCalendarInterval</key>
|
|
7075
|
-
<dict>
|
|
7076
|
-
<key>Hour</key>
|
|
7077
|
-
<integer>${hour}</integer>
|
|
7078
|
-
<key>Minute</key>
|
|
7079
|
-
<integer>${minute}</integer>
|
|
7080
|
-
</dict>
|
|
7081
|
-
<key>RunAtLoad</key>
|
|
7082
|
-
<false/>
|
|
7083
|
-
<key>StandardOutPath</key>
|
|
7084
|
-
<string>${xmlEscape(log)}</string>
|
|
7085
|
-
<key>StandardErrorPath</key>
|
|
7086
|
-
<string>${xmlEscape(log)}</string>
|
|
7087
|
-
</dict>
|
|
7088
|
-
</plist>
|
|
7089
|
-
`;
|
|
7090
|
-
}
|
|
7091
|
-
var CRON_MARKER = "holt:";
|
|
7092
|
-
function buildCronLine(job, holtPath) {
|
|
7093
|
-
const { hour, minute } = parseWhen(job.when);
|
|
7094
|
-
const command = buildCommand(job, holtPath);
|
|
7095
|
-
return `${minute} ${hour} * * * ${command} # ${CRON_MARKER}${job.id}`;
|
|
7096
|
-
}
|
|
7097
|
-
function stripCronLines(crontab, id) {
|
|
7098
|
-
const marker = `# ${CRON_MARKER}${id}`;
|
|
7099
|
-
const kept = crontab.split("\n").filter((line) => !line.includes(marker));
|
|
7100
|
-
return kept.join("\n").replace(/\n+$/, "\n");
|
|
7101
|
-
}
|
|
7102
|
-
function appendCronLine(crontab, line) {
|
|
7103
|
-
const base = crontab.replace(/\s*$/, "");
|
|
7104
|
-
return (base ? base + "\n" : "") + line + "\n";
|
|
7105
|
-
}
|
|
7106
|
-
function ensureLogsDir() {
|
|
7107
|
-
mkdirSync13(logsDir(), { recursive: true });
|
|
7108
|
-
}
|
|
7109
|
-
|
|
7110
|
-
// src/commands/schedule.ts
|
|
7111
7219
|
function parseAdd(rest) {
|
|
7112
7220
|
const positional = [];
|
|
7113
7221
|
const opts = { notify: false };
|
|
@@ -7751,7 +7859,7 @@ async function notify(args) {
|
|
|
7751
7859
|
if (nonFlag.length > 0 && !args.includes("-")) {
|
|
7752
7860
|
message = nonFlag.join(" ").trim();
|
|
7753
7861
|
} else if (wantsStdin) {
|
|
7754
|
-
message =
|
|
7862
|
+
message = readStdin3().trim();
|
|
7755
7863
|
} else if (nonFlag.length > 0) {
|
|
7756
7864
|
message = nonFlag.join(" ").trim();
|
|
7757
7865
|
}
|
|
@@ -7774,7 +7882,7 @@ async function notify(args) {
|
|
|
7774
7882
|
process.exitCode = 1;
|
|
7775
7883
|
}
|
|
7776
7884
|
}
|
|
7777
|
-
function
|
|
7885
|
+
function readStdin3() {
|
|
7778
7886
|
try {
|
|
7779
7887
|
return readFileSync19(0, "utf8");
|
|
7780
7888
|
} catch {
|
|
@@ -8273,6 +8381,9 @@ async function main() {
|
|
|
8273
8381
|
case "hook":
|
|
8274
8382
|
await hook(process.argv[3], process.argv.slice(4));
|
|
8275
8383
|
break;
|
|
8384
|
+
case "statusline":
|
|
8385
|
+
await statusline();
|
|
8386
|
+
break;
|
|
8276
8387
|
case "run":
|
|
8277
8388
|
await run(process.argv.slice(3));
|
|
8278
8389
|
break;
|