@holt-os/holt 0.17.0 → 0.18.1
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 +13 -4
- package/dist/cli.js +145 -109
- package/package.json +2 -2
- package/skills/audit/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- **Any brain, one assistant.** Bare `holt` launches a real interactive brain (Claude Code, Codex, or Gemini) branded as Holt, or you connect a direct API brain with your own key. Switch mid-conversation and keep the full context.
|
|
22
22
|
- **Private memory you can see.** Per-folder semantic recall and distilled facts, an interactive knowledge graph (`holt graph`), and a self-maintaining wiki (`holt wiki`) over everything you tell it. Nothing leaves your machine.
|
|
23
23
|
- **Ambient by default.** `holt hook` wires memory into Claude Code so it recalls and remembers with no manual step, and an MCP server exposes the same memory to Cursor and Codex.
|
|
24
|
-
- **Runs your work.** Run a task once, on a schedule, or as a named routine, and reach it from your phone over Telegram.
|
|
24
|
+
- **Runs your work.** Run a task once, on a schedule, or as a named routine, and reach it from your phone over Telegram. Real OS timers on macOS, Linux, and Windows.
|
|
25
25
|
- **Useful on day one.** `/skill onboard` interviews you and fills memory in about fifteen minutes, `/skill audit` shows what is still missing, and `/skill level-up` turns one manual chore a week into something Holt does.
|
|
26
26
|
- **Extensible, zero-infra.** Thirteen built-in skills plus a git-based community registry (`holt skill search`) in the portable `SKILL.md` format.
|
|
27
27
|
- **Writes in your voice.** `holt write` drafts in a style Holt learns from you, with built-in anti-AI checks.
|
|
@@ -63,6 +63,14 @@ That is it. Bare `holt` sets the folder up the first time (trust, brain, memory
|
|
|
63
63
|
|
|
64
64
|
Prefer to step through setup on its own first? `holt init` still does that, then `holt` starts the session.
|
|
65
65
|
|
|
66
|
+
Then, in that first session, run:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
/skill onboard
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
An empty Holt gives generic answers, which is the usual reason people install it and drift away. `onboard` is a fifteen minute interview that fills memory with who you are and what you are working on, and ends by doing one real piece of your work. Everything else gets better once it knows you.
|
|
73
|
+
|
|
66
74
|
## What bare `holt` does
|
|
67
75
|
|
|
68
76
|
`holt` (with no command) is the front door: **your assistant, with memory of you.**
|
|
@@ -441,7 +449,7 @@ Options: `--brain <id>` pick a brain, `--out <file>` also write the reply to a f
|
|
|
441
449
|
|
|
442
450
|
### holt schedule
|
|
443
451
|
|
|
444
|
-
Run a task automatically on a timer, using your OS scheduler (launchd on macOS, cron on Linux). A scheduled run behaves exactly like typing `holt run` in that folder.
|
|
452
|
+
Run a task automatically on a timer, using your OS scheduler (launchd on macOS, cron on Linux, Task Scheduler on Windows). A scheduled run behaves exactly like typing `holt run` in that folder.
|
|
445
453
|
|
|
446
454
|
```bash
|
|
447
455
|
holt schedule add "summarize my inbox and flag what is urgent" 07:00 --notify
|
|
@@ -490,7 +498,7 @@ holt routine remove brief # deletes the routine and its OS timer, if any
|
|
|
490
498
|
|
|
491
499
|
Sources: give exactly one of `--skill <name>`, `--task "<prompt>"`, or `--template <t>`. A skill routine splices that skill's `SKILL.md` body into the prompt at run time; a task routine uses the prompt as-is.
|
|
492
500
|
|
|
493
|
-
Scheduling: `--at HH:MM` (24h, daily) installs an OS timer through the same scheduler `holt schedule` uses (launchd on macOS, cron on Linux) whose command is `holt routine run <name> --quiet`. Omit `--at` for a manual, run-on-demand routine. Removing the routine removes its timer.
|
|
501
|
+
Scheduling: `--at HH:MM` (24h, daily) installs an OS timer through the same scheduler `holt schedule` uses (launchd on macOS, cron on Linux, Task Scheduler on Windows) whose command is `holt routine run <name> --quiet`. Omit `--at` for a manual, run-on-demand routine. Removing the routine removes its timer.
|
|
494
502
|
|
|
495
503
|
Output routing: by default the result prints to stdout. `--out <file>` also writes it to a file (relative to the routine's workspace); `--notify` pushes it to Telegram (guarded cleanly when Telegram is not set up). Both `--out` and `--notify` can also be passed to `holt routine run` for a single run. In `--quiet` mode (what the scheduler uses) stdout is suppressed but `--out` and `--notify` still fire.
|
|
496
504
|
|
|
@@ -622,7 +630,8 @@ Built in always-shippable phases toward a full-vision v1:
|
|
|
622
630
|
4. **Knowledge graph**: see and navigate your own memory with `holt graph` *(shipped)*
|
|
623
631
|
5. **Everywhere**: MCP server so other tools read Holt's memory, plus `holt run`, scheduling, and Telegram *(shipped)*
|
|
624
632
|
6. **Knowledge wiki**: LLM-maintained, cross-linked, regenerable pages over your memory with `holt wiki` *(shipped)*
|
|
625
|
-
7. **
|
|
633
|
+
7. **Onboarding**: a day-one interview that fills memory, plus the weekly audit and level-up ritual *(shipped)*
|
|
634
|
+
8. **Next**: docs site, skill registry publishing, more channels
|
|
626
635
|
|
|
627
636
|
## Contributing
|
|
628
637
|
|
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,7 @@ function createStatusBar() {
|
|
|
46
46
|
let detached = false;
|
|
47
47
|
const realWrite = out.write.bind(out);
|
|
48
48
|
const rows = () => out.rows || 24;
|
|
49
|
-
const
|
|
49
|
+
const paint2 = () => {
|
|
50
50
|
if (detached) return;
|
|
51
51
|
const r = rows();
|
|
52
52
|
realWrite(
|
|
@@ -61,7 +61,7 @@ function createStatusBar() {
|
|
|
61
61
|
`${CSI}1;${Math.max(1, r - 1)}r${CSI}${Math.max(1, r - 1)};1H`
|
|
62
62
|
// move cursor into the region
|
|
63
63
|
);
|
|
64
|
-
|
|
64
|
+
paint2();
|
|
65
65
|
};
|
|
66
66
|
const onResize = () => {
|
|
67
67
|
if (!detached) reserve();
|
|
@@ -93,7 +93,7 @@ function createStatusBar() {
|
|
|
93
93
|
active: true,
|
|
94
94
|
set(next) {
|
|
95
95
|
text = next;
|
|
96
|
-
|
|
96
|
+
paint2();
|
|
97
97
|
},
|
|
98
98
|
detach
|
|
99
99
|
};
|
|
@@ -2515,23 +2515,37 @@ function buildCronLine(job, holtPath) {
|
|
|
2515
2515
|
const command = buildCommand(job, holtPath);
|
|
2516
2516
|
return `${minute} ${hour} * * * ${command} # ${CRON_MARKER}${job.id}`;
|
|
2517
2517
|
}
|
|
2518
|
-
function
|
|
2519
|
-
|
|
2518
|
+
function taskName(id) {
|
|
2519
|
+
return `Holt ${id}`;
|
|
2520
|
+
}
|
|
2521
|
+
function cmdScriptPath(id) {
|
|
2522
|
+
return join8(GLOBAL_DIR, "tasks", `${id}.cmd`);
|
|
2523
|
+
}
|
|
2524
|
+
function cmdQuote(s) {
|
|
2525
|
+
return /[\s&()[\]{}^=;!'+,`~|<>"]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
2526
|
+
}
|
|
2527
|
+
function buildWindowsScript(job, holtPath) {
|
|
2528
|
+
const holt = cmdQuote(holtPath);
|
|
2529
|
+
const lines = ["@echo off", `cd /d ${cmdQuote(job.workspace)}`];
|
|
2520
2530
|
if (job.runArgs && job.runArgs.length > 0) {
|
|
2521
|
-
|
|
2531
|
+
lines.push(`${holt} ${job.runArgs.map(cmdQuote).join(" ")} --quiet`);
|
|
2522
2532
|
} else {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2533
|
+
const log = cmdQuote(logPath(job.id));
|
|
2534
|
+
let run2 = `${holt} run ${cmdQuote(job.task)}`;
|
|
2535
|
+
if (job.brain) run2 += ` --brain ${cmdQuote(job.brain)}`;
|
|
2536
|
+
run2 += ` --quiet --out ${log}`;
|
|
2537
|
+
lines.push(run2);
|
|
2538
|
+
if (job.notify) {
|
|
2539
|
+
lines.push(`if errorlevel 1 exit /b 1`);
|
|
2540
|
+
lines.push(`type ${log} | ${holt} notify`);
|
|
2541
|
+
}
|
|
2526
2542
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
name: `Holt ${job.id}`
|
|
2534
|
-
};
|
|
2543
|
+
return lines.join("\r\n") + "\r\n";
|
|
2544
|
+
}
|
|
2545
|
+
function buildSchtasksArgs(job, scriptPath) {
|
|
2546
|
+
const { hour, minute } = parseWhen(job.when);
|
|
2547
|
+
const hhmm = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
2548
|
+
return ["/Create", "/F", "/SC", "DAILY", "/ST", hhmm, "/TN", taskName(job.id), "/TR", scriptPath];
|
|
2535
2549
|
}
|
|
2536
2550
|
function stripCronLines(crontab, id) {
|
|
2537
2551
|
const marker = `# ${CRON_MARKER}${id}`;
|
|
@@ -7533,7 +7547,7 @@ function holtTools() {
|
|
|
7533
7547
|
}
|
|
7534
7548
|
|
|
7535
7549
|
// src/version.ts
|
|
7536
|
-
var VERSION = "0.
|
|
7550
|
+
var VERSION = "0.18.1";
|
|
7537
7551
|
|
|
7538
7552
|
// src/commands/mcp.ts
|
|
7539
7553
|
function printSetup() {
|
|
@@ -7579,6 +7593,7 @@ async function mcp(sub, rest = []) {
|
|
|
7579
7593
|
}
|
|
7580
7594
|
|
|
7581
7595
|
// src/commands/statusline.ts
|
|
7596
|
+
import { execFileSync } from "child_process";
|
|
7582
7597
|
import { basename as basename3 } from "path";
|
|
7583
7598
|
var SEP = " \xB7 ";
|
|
7584
7599
|
function readStdin2(timeoutMs) {
|
|
@@ -7612,9 +7627,12 @@ function str(v) {
|
|
|
7612
7627
|
const t = v.trim();
|
|
7613
7628
|
return t.length ? t : void 0;
|
|
7614
7629
|
}
|
|
7615
|
-
function
|
|
7630
|
+
function dirFrom(data) {
|
|
7616
7631
|
const ws = get(data, "workspace");
|
|
7617
|
-
|
|
7632
|
+
return str(get(ws, "current_dir")) ?? str(get(ws, "project_dir")) ?? str(get(ws, "cwd")) ?? str(get(data, "cwd")) ?? str(get(data, "current_dir"));
|
|
7633
|
+
}
|
|
7634
|
+
function folderFrom(data) {
|
|
7635
|
+
const dir = dirFrom(data);
|
|
7618
7636
|
if (!dir) return void 0;
|
|
7619
7637
|
try {
|
|
7620
7638
|
const base = basename3(dir.replace(/[\\/]+$/, ""));
|
|
@@ -7628,14 +7646,63 @@ function modelFrom(data) {
|
|
|
7628
7646
|
if (typeof model === "string") return str(model);
|
|
7629
7647
|
return str(get(model, "display_name")) ?? str(get(model, "id")) ?? str(get(data, "model_display_name")) ?? str(get(data, "model_id"));
|
|
7630
7648
|
}
|
|
7631
|
-
function
|
|
7632
|
-
const
|
|
7649
|
+
function contextPctFrom(data) {
|
|
7650
|
+
const cw = get(data, "context_window");
|
|
7651
|
+
const clamp = (n) => Math.max(0, Math.min(100, Math.round(n)));
|
|
7652
|
+
const used = get(cw, "used_percentage");
|
|
7653
|
+
if (typeof used === "number" && Number.isFinite(used)) return clamp(used);
|
|
7654
|
+
if (typeof used === "string" && used.trim() && Number.isFinite(Number(used))) {
|
|
7655
|
+
return clamp(Number(used));
|
|
7656
|
+
}
|
|
7657
|
+
const size = get(cw, "context_window_size");
|
|
7658
|
+
const cu = get(cw, "current_usage");
|
|
7659
|
+
if (typeof size === "number" && size > 0 && cu && typeof cu === "object") {
|
|
7660
|
+
const n = (k) => {
|
|
7661
|
+
const v = get(cu, k);
|
|
7662
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
7663
|
+
};
|
|
7664
|
+
const tokens2 = n("input_tokens") + n("cache_creation_input_tokens") + n("cache_read_input_tokens");
|
|
7665
|
+
if (tokens2 > 0) return clamp(tokens2 / size * 100);
|
|
7666
|
+
}
|
|
7667
|
+
return void 0;
|
|
7668
|
+
}
|
|
7669
|
+
function contextBar(pct) {
|
|
7670
|
+
if (pct === void 0) return `[${"\u2591".repeat(20)}] --`;
|
|
7671
|
+
const filled = Math.floor(pct / 5);
|
|
7672
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(20 - filled)}] ${pct}%`;
|
|
7673
|
+
}
|
|
7674
|
+
function paint(code, text, color) {
|
|
7675
|
+
return color ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
7676
|
+
}
|
|
7677
|
+
function renderStatusLine(data, opts = {}) {
|
|
7678
|
+
const color = opts.color ?? false;
|
|
7679
|
+
const parts = [paint("1;35", "Holt", color)];
|
|
7633
7680
|
const folder = folderFrom(data);
|
|
7634
|
-
if (folder)
|
|
7681
|
+
if (folder) {
|
|
7682
|
+
const branch = str(opts.branch);
|
|
7683
|
+
parts.push(
|
|
7684
|
+
paint("1;34", folder, color) + (branch ? " " + paint("1;33", `(${branch})`, color) : "")
|
|
7685
|
+
);
|
|
7686
|
+
}
|
|
7635
7687
|
const model = modelFrom(data);
|
|
7636
|
-
if (model) parts.push(model);
|
|
7688
|
+
if (model) parts.push(paint("0;36", model, color));
|
|
7689
|
+
if (folder || model) parts.push(paint("0;32", contextBar(contextPctFrom(data)), color));
|
|
7637
7690
|
return parts.join(SEP);
|
|
7638
7691
|
}
|
|
7692
|
+
function gitBranch(dir) {
|
|
7693
|
+
if (!dir) return void 0;
|
|
7694
|
+
try {
|
|
7695
|
+
const out = execFileSync("git", ["-C", dir, "symbolic-ref", "--short", "HEAD"], {
|
|
7696
|
+
encoding: "utf8",
|
|
7697
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7698
|
+
timeout: 500,
|
|
7699
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
|
|
7700
|
+
});
|
|
7701
|
+
return str(out);
|
|
7702
|
+
} catch {
|
|
7703
|
+
return void 0;
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7639
7706
|
function parse(raw) {
|
|
7640
7707
|
try {
|
|
7641
7708
|
return JSON.parse(raw || "{}");
|
|
@@ -7647,7 +7714,11 @@ async function statusline() {
|
|
|
7647
7714
|
let line = "Holt";
|
|
7648
7715
|
try {
|
|
7649
7716
|
const raw = await readStdin2(1e3);
|
|
7650
|
-
|
|
7717
|
+
const data = parse(raw);
|
|
7718
|
+
line = renderStatusLine(data, {
|
|
7719
|
+
branch: gitBranch(dirFrom(data)),
|
|
7720
|
+
color: !process.env.NO_COLOR
|
|
7721
|
+
});
|
|
7651
7722
|
} catch {
|
|
7652
7723
|
line = "Holt";
|
|
7653
7724
|
}
|
|
@@ -7846,29 +7917,42 @@ async function addCmd(rest) {
|
|
|
7846
7917
|
console.log(` workspace ${job.workspace}`);
|
|
7847
7918
|
if (job.notify) console.log(` notify on (pushes output to Telegram)`);
|
|
7848
7919
|
console.log(` log ${logPath(job.id)}`);
|
|
7849
|
-
|
|
7850
|
-
else if (process.platform === "linux") installLinux(job, holtPath);
|
|
7851
|
-
else printManualInstall(job, holtPath);
|
|
7920
|
+
installForPlatform(job, holtPath);
|
|
7852
7921
|
console.log("");
|
|
7853
7922
|
}
|
|
7854
|
-
function
|
|
7855
|
-
if (process.platform
|
|
7923
|
+
function installForPlatform(job, holtPath) {
|
|
7924
|
+
if (process.platform === "darwin") installDarwin(job, holtPath);
|
|
7925
|
+
else if (process.platform === "linux") installLinux(job, holtPath);
|
|
7926
|
+
else if (process.platform === "win32") installWindows(job, holtPath);
|
|
7927
|
+
else {
|
|
7856
7928
|
console.log(c.dim("\n Your OS has no built-in installer. Install this entry yourself:"));
|
|
7857
7929
|
console.log(" " + buildCronLine(job, holtPath));
|
|
7858
|
-
return;
|
|
7859
7930
|
}
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7931
|
+
}
|
|
7932
|
+
function installWindows(job, holtPath) {
|
|
7933
|
+
const script = cmdScriptPath(job.id);
|
|
7934
|
+
mkdirSync15(dirname9(script), { recursive: true });
|
|
7935
|
+
writeFileSync17(script, buildWindowsScript(job, holtPath), "utf8");
|
|
7936
|
+
const res = spawnSync5("schtasks", buildSchtasksArgs(job, script), { encoding: "utf8" });
|
|
7937
|
+
if (res.status === 0) {
|
|
7938
|
+
console.log(c.green(" Registered with Task Scheduler."));
|
|
7939
|
+
} else {
|
|
7940
|
+
console.log(c.dim(" Wrote the task script, but schtasks did not confirm."));
|
|
7941
|
+
console.log(c.dim(" Register it yourself with:"));
|
|
7869
7942
|
console.log(
|
|
7870
|
-
c.dim(
|
|
7943
|
+
c.dim(` schtasks /Create /F /SC DAILY /ST ${job.when} /TN "${taskName(job.id)}" /TR "${script}"`)
|
|
7871
7944
|
);
|
|
7945
|
+
const why = (res.stderr || "").trim() || (res.error ? res.error.message : "");
|
|
7946
|
+
if (why) console.log(c.dim(` (${why})`));
|
|
7947
|
+
}
|
|
7948
|
+
console.log(c.dim(` task: ${taskName(job.id)}`));
|
|
7949
|
+
console.log(c.dim(` script: ${script}`));
|
|
7950
|
+
}
|
|
7951
|
+
function removeWindows(id) {
|
|
7952
|
+
spawnSync5("schtasks", ["/Delete", "/F", "/TN", taskName(id)], { encoding: "utf8" });
|
|
7953
|
+
try {
|
|
7954
|
+
rmSync7(cmdScriptPath(id));
|
|
7955
|
+
} catch {
|
|
7872
7956
|
}
|
|
7873
7957
|
}
|
|
7874
7958
|
function listCmd2() {
|
|
@@ -7902,6 +7986,11 @@ function removeLinux(id) {
|
|
|
7902
7986
|
const next = stripCronLines(body, id);
|
|
7903
7987
|
spawnSync5("crontab", ["-"], { input: next, encoding: "utf8" });
|
|
7904
7988
|
}
|
|
7989
|
+
function removeForPlatform(id) {
|
|
7990
|
+
if (process.platform === "darwin") removeDarwin(id);
|
|
7991
|
+
else if (process.platform === "linux") removeLinux(id);
|
|
7992
|
+
else if (process.platform === "win32") removeWindows(id);
|
|
7993
|
+
}
|
|
7905
7994
|
function removeCmd(rest) {
|
|
7906
7995
|
const id = rest[0];
|
|
7907
7996
|
if (!id) {
|
|
@@ -7912,8 +8001,7 @@ function removeCmd(rest) {
|
|
|
7912
8001
|
const before = loadJobs();
|
|
7913
8002
|
const existed = before.some((j) => j.id === id);
|
|
7914
8003
|
removeJob(id);
|
|
7915
|
-
|
|
7916
|
-
else if (process.platform === "linux") removeLinux(id);
|
|
8004
|
+
removeForPlatform(id);
|
|
7917
8005
|
if (existed) console.log("\n" + c.green(` Removed schedule ${id}.`) + "\n");
|
|
7918
8006
|
else {
|
|
7919
8007
|
console.error(c.dim(`
|
|
@@ -7950,9 +8038,8 @@ async function schedule(sub, rest = []) {
|
|
|
7950
8038
|
}
|
|
7951
8039
|
|
|
7952
8040
|
// src/commands/routine.ts
|
|
7953
|
-
import {
|
|
7954
|
-
import {
|
|
7955
|
-
import { dirname as dirname10, resolve as resolve7 } from "path";
|
|
8041
|
+
import { writeFileSync as writeFileSync19 } from "fs";
|
|
8042
|
+
import { resolve as resolve7 } from "path";
|
|
7956
8043
|
|
|
7957
8044
|
// src/routines.ts
|
|
7958
8045
|
import { readFileSync as readFileSync19, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16 } from "fs";
|
|
@@ -8031,70 +8118,19 @@ function jobForRoutine(r) {
|
|
|
8031
8118
|
function routineJobId(name) {
|
|
8032
8119
|
return `routine-${sanitizeRoutineName(name)}`;
|
|
8033
8120
|
}
|
|
8034
|
-
function installDarwin2(job, holtPath) {
|
|
8035
|
-
const path = plistPath(job.id);
|
|
8036
|
-
mkdirSync17(dirname10(path), { recursive: true });
|
|
8037
|
-
writeFileSync19(path, buildLaunchdPlist(job, holtPath), "utf8");
|
|
8038
|
-
const res = spawnSync6("launchctl", ["load", "-w", path], { encoding: "utf8" });
|
|
8039
|
-
if (res.status === 0) {
|
|
8040
|
-
console.log(c.green(" Loaded into launchd."));
|
|
8041
|
-
} else {
|
|
8042
|
-
console.log(c.dim(" Wrote the plist, but launchctl load did not confirm."));
|
|
8043
|
-
console.log(c.dim(` Load it yourself with: launchctl load -w ${path}`));
|
|
8044
|
-
if (res.stderr && res.stderr.trim()) console.log(c.dim(` (${res.stderr.trim()})`));
|
|
8045
|
-
}
|
|
8046
|
-
console.log(c.dim(` plist: ${path}`));
|
|
8047
|
-
}
|
|
8048
|
-
function currentCrontab2() {
|
|
8049
|
-
const res = spawnSync6("crontab", ["-l"], { encoding: "utf8" });
|
|
8050
|
-
return res.status === 0 && typeof res.stdout === "string" ? res.stdout : "";
|
|
8051
|
-
}
|
|
8052
|
-
function writeCrontab2(body) {
|
|
8053
|
-
const res = spawnSync6("crontab", ["-"], { input: body, encoding: "utf8" });
|
|
8054
|
-
return res.status === 0;
|
|
8055
|
-
}
|
|
8056
|
-
function installLinux2(job, holtPath) {
|
|
8057
|
-
const line = buildCronLine(job, holtPath);
|
|
8058
|
-
const next = appendCronLine(currentCrontab2(), line);
|
|
8059
|
-
if (writeCrontab2(next)) {
|
|
8060
|
-
console.log(c.green(" Added to your crontab."));
|
|
8061
|
-
console.log(c.dim(` line: ${line}`));
|
|
8062
|
-
} else {
|
|
8063
|
-
console.log(c.dim(" Could not update the crontab automatically. Add this line yourself:"));
|
|
8064
|
-
console.log(" " + line);
|
|
8065
|
-
}
|
|
8066
|
-
}
|
|
8067
8121
|
function installSchedule(r) {
|
|
8068
8122
|
const holtPath = resolveHoltPath();
|
|
8069
8123
|
const job = jobForRoutine(r);
|
|
8070
8124
|
removeSchedule(r.name);
|
|
8071
8125
|
addJob(job);
|
|
8072
|
-
|
|
8073
|
-
else if (process.platform === "linux") installLinux2(job, holtPath);
|
|
8074
|
-
else printManualInstall(job, holtPath);
|
|
8075
|
-
}
|
|
8076
|
-
function removeScheduleDarwin(id) {
|
|
8077
|
-
const path = plistPath(id);
|
|
8078
|
-
if (existsSync19(path)) {
|
|
8079
|
-
spawnSync6("launchctl", ["unload", path], { encoding: "utf8" });
|
|
8080
|
-
try {
|
|
8081
|
-
rmSync8(path);
|
|
8082
|
-
} catch {
|
|
8083
|
-
}
|
|
8084
|
-
}
|
|
8085
|
-
}
|
|
8086
|
-
function removeScheduleLinux(id) {
|
|
8087
|
-
const body = currentCrontab2();
|
|
8088
|
-
const next = stripCronLines(body, id);
|
|
8089
|
-
writeCrontab2(next);
|
|
8126
|
+
installForPlatform(job, holtPath);
|
|
8090
8127
|
}
|
|
8091
8128
|
function removeSchedule(name) {
|
|
8092
8129
|
const id = routineJobId(name);
|
|
8093
8130
|
const existed = loadJobs().some((j) => j.id === id);
|
|
8094
8131
|
removeJob(id);
|
|
8095
8132
|
if (!existed) return;
|
|
8096
|
-
|
|
8097
|
-
else if (process.platform === "linux") removeScheduleLinux(id);
|
|
8133
|
+
removeForPlatform(id);
|
|
8098
8134
|
}
|
|
8099
8135
|
function parseAdd2(rest) {
|
|
8100
8136
|
const opts = { notify: false };
|
|
@@ -8449,8 +8485,8 @@ function readStdin3() {
|
|
|
8449
8485
|
}
|
|
8450
8486
|
|
|
8451
8487
|
// src/commands/doctor.ts
|
|
8452
|
-
import { spawn as spawn5, spawnSync as
|
|
8453
|
-
import { existsSync as
|
|
8488
|
+
import { spawn as spawn5, spawnSync as spawnSync6 } from "child_process";
|
|
8489
|
+
import { existsSync as existsSync19 } from "fs";
|
|
8454
8490
|
|
|
8455
8491
|
// src/specs.ts
|
|
8456
8492
|
import os from "os";
|
|
@@ -8612,7 +8648,7 @@ async function fixSemanticMemory(fixed) {
|
|
|
8612
8648
|
}
|
|
8613
8649
|
if (!tags.some((t) => t.startsWith(EMBED_MODEL))) {
|
|
8614
8650
|
note(`The "${EMBED_MODEL}" model is missing. Downloading it (one-time, ~275 MB)...`);
|
|
8615
|
-
const r =
|
|
8651
|
+
const r = spawnSync6("ollama", ["pull", EMBED_MODEL], { stdio: "inherit" });
|
|
8616
8652
|
if (r.status !== 0) {
|
|
8617
8653
|
warn(`Could not download ${EMBED_MODEL}. Check your internet connection and retry.`);
|
|
8618
8654
|
return false;
|
|
@@ -8675,7 +8711,7 @@ async function doctor(args = []) {
|
|
|
8675
8711
|
}
|
|
8676
8712
|
if (embedOk) {
|
|
8677
8713
|
good(`Ollama reachable and "${EMBED_MODEL}" is present. Memory recall is semantic, private, and local.`);
|
|
8678
|
-
if (fix &&
|
|
8714
|
+
if (fix && existsSync19(wsHoltDir())) {
|
|
8679
8715
|
const missing = loadTurns().filter((t) => !Array.isArray(t.emb)).length;
|
|
8680
8716
|
if (missing > 0) {
|
|
8681
8717
|
note(`${missing} memories in this folder lack embeddings. Upgrading them...`);
|
|
@@ -8794,12 +8830,12 @@ async function tour() {
|
|
|
8794
8830
|
}
|
|
8795
8831
|
|
|
8796
8832
|
// src/commands/reset.ts
|
|
8797
|
-
import { existsSync as
|
|
8833
|
+
import { existsSync as existsSync20, rmSync as rmSync8 } from "fs";
|
|
8798
8834
|
async function reset() {
|
|
8799
8835
|
const { ask, close } = createReader();
|
|
8800
8836
|
try {
|
|
8801
8837
|
const dir = wsHoltDir();
|
|
8802
|
-
if (!
|
|
8838
|
+
if (!existsSync20(dir)) {
|
|
8803
8839
|
console.log(c.dim("\n Nothing to reset: Holt has no settings or memory in this folder.\n"));
|
|
8804
8840
|
return;
|
|
8805
8841
|
}
|
|
@@ -8818,7 +8854,7 @@ async function reset() {
|
|
|
8818
8854
|
console.log(c.dim(" Cancelled. Nothing was changed.\n"));
|
|
8819
8855
|
return;
|
|
8820
8856
|
}
|
|
8821
|
-
|
|
8857
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
8822
8858
|
console.log(c.green("\n Done. This folder is a clean slate.") + c.dim(' Run "holt" to set it up again.\n'));
|
|
8823
8859
|
} finally {
|
|
8824
8860
|
close();
|
|
@@ -8826,7 +8862,7 @@ async function reset() {
|
|
|
8826
8862
|
}
|
|
8827
8863
|
|
|
8828
8864
|
// src/commands/update.ts
|
|
8829
|
-
import { spawnSync as
|
|
8865
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
8830
8866
|
import { realpathSync as realpathSync4 } from "fs";
|
|
8831
8867
|
async function latestVersion() {
|
|
8832
8868
|
try {
|
|
@@ -8868,7 +8904,7 @@ async function update() {
|
|
|
8868
8904
|
console.log(c.dim(" Later, then. The command is: brew upgrade holt\n"));
|
|
8869
8905
|
return;
|
|
8870
8906
|
}
|
|
8871
|
-
const r2 =
|
|
8907
|
+
const r2 = spawnSync7("brew", ["upgrade", "holt"], { stdio: "inherit" });
|
|
8872
8908
|
if (r2.status === 0) console.log(c.green(`
|
|
8873
8909
|
Updated to ${latest}.`) + "\n");
|
|
8874
8910
|
else console.log(c.dim("\n Homebrew reported a problem above. You can retry with: brew upgrade holt\n"));
|
|
@@ -8878,7 +8914,7 @@ async function update() {
|
|
|
8878
8914
|
console.log(c.dim(" Later, then. The command is: npm install -g @holt-os/holt\n"));
|
|
8879
8915
|
return;
|
|
8880
8916
|
}
|
|
8881
|
-
const r =
|
|
8917
|
+
const r = spawnSync7("npm", ["install", "-g", "@holt-os/holt"], { stdio: "inherit" });
|
|
8882
8918
|
if (r.status === 0) {
|
|
8883
8919
|
console.log(c.green(`
|
|
8884
8920
|
Updated to ${latest}.`) + "\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holt-os/holt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "An open-source personal agent OS: any LLM, private memory you can see and walk.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"build": "tsup src/cli.ts --format esm --clean",
|
|
45
45
|
"start": "node dist/cli.js",
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
|
-
"test": "tsx scripts/specs.test.ts && tsx scripts/skillcompiler.test.ts",
|
|
47
|
+
"test": "tsx scripts/specs.test.ts && tsx scripts/skillcompiler.test.ts && tsx scripts/scheduler.test.ts && tsx scripts/statusline.test.ts",
|
|
48
48
|
"release": "bash scripts/release.sh"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
package/skills/audit/SKILL.md
CHANGED
|
@@ -86,9 +86,9 @@ an essay.
|
|
|
86
86
|
- **Hand-edited facts.** Facts added by editing `facts.md` directly are never
|
|
87
87
|
embedded, so they never come back through recall. They must be re-added with
|
|
88
88
|
`holt memory add`.
|
|
89
|
-
- **
|
|
90
|
-
time
|
|
91
|
-
|
|
89
|
+
- **Timers need the machine awake.** A scheduled job fires only if the computer
|
|
90
|
+
is on at that time; a missed run is skipped, not caught up. If rung four looks
|
|
91
|
+
solid but the log file is empty, check that first.
|
|
92
92
|
- **Automation above an empty memory.** If Run looks solid while Know is thin,
|
|
93
93
|
say so bluntly. That setup produces confident wrong output on a timer, and it
|
|
94
94
|
is the most expensive failure on this list.
|