@holt-os/holt 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,8 +21,9 @@
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.
25
- - **Extensible, zero-infra.** Ten built-in skills plus a git-based community registry (`holt skill search`) in the portable `SKILL.md` format.
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
+ - **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
+ - **Extensible, zero-infra.** Thirteen built-in skills plus a git-based community registry (`holt skill search`) in the portable `SKILL.md` format.
26
27
  - **Writes in your voice.** `holt write` drafts in a style Holt learns from you, with built-in anti-AI checks.
27
28
 
28
29
  > **Status:** shipping and usable today. Every capability above works now.
@@ -43,6 +44,14 @@ Or with npm, if you already have Node:
43
44
  npm install -g @holt-os/holt
44
45
  ```
45
46
 
47
+ **Not comfortable in a terminal?** Install [Claude Code](https://claude.com/claude-code), open it, and paste this:
48
+
49
+ ```
50
+ Set up Holt for me: read https://raw.githubusercontent.com/holt-os/holt/main/docs/INSTALL.md and follow it exactly.
51
+ ```
52
+
53
+ It installs Holt, checks its own work, and fixes what breaks. Full detail, system requirements, and troubleshooting: [docs/INSTALL.md](./docs/INSTALL.md).
54
+
46
55
  ## Quickstart
47
56
 
48
57
  ```bash
@@ -54,6 +63,14 @@ That is it. Bare `holt` sets the folder up the first time (trust, brain, memory
54
63
 
55
64
  Prefer to step through setup on its own first? `holt init` still does that, then `holt` starts the session.
56
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
+
57
74
  ## What bare `holt` does
58
75
 
59
76
  `holt` (with no command) is the front door: **your assistant, with memory of you.**
@@ -139,6 +156,8 @@ Every exchange is saved to `<folder>/.holt/memory/turns.jsonl`, private and loca
139
156
 
140
157
  When you end a chat, Holt asks the brain to distill 1 to 5 durable facts from the session (decisions, preferences, key names, numbers) and writes them to a human-readable `<folder>/.holt/memory/facts.md` you can read and edit. Those facts are also embedded and ranked slightly higher than raw turns during recall, so the signal rises to the top over time. See them with `holt memory facts`. Turn it off by setting `memory.extractFacts` to `false` in `config.json`.
141
158
 
159
+ You do not have to wait for the end of a session. `holt memory add "<fact>"` saves one immediately, and duplicates are ignored, so it is safe to run twice. Piping in several facts, one per line, works too. Prefer this over editing `facts.md` by hand: a fact typed straight into that file is never embedded, so semantic recall will not find it.
160
+
142
161
  Two recall modes, picked automatically:
143
162
 
144
163
  - **Semantic** (best): a local [Ollama](https://ollama.com) with an embedding model, which `holt init` offers to set up for you. Recall matches by meaning: asking "who owns my apartment" finds "my landlord is called Pieter". No API keys, nothing leaves your machine.
@@ -148,6 +167,7 @@ Inspect it any time:
148
167
 
149
168
  ```bash
150
169
  holt memory # stats for this folder
170
+ holt memory add "<fact>" # teach it something durable right now
151
171
  holt memory facts # show the distilled facts (facts.md)
152
172
  holt memory search <query> # find remembered moments
153
173
  holt memory embed # embed older moments for semantic recall
@@ -315,6 +335,11 @@ Holt ships with a small, curated set of general-purpose skills, available in eve
315
335
  - **plan**: break a goal into a concrete, ordered set of steps.
316
336
  - **rewrite**: tighten and clarify a piece of text without changing its meaning.
317
337
  - **decide**: weigh a decision and give a clear recommendation.
338
+ - **onboard**: a guided first-day interview that fills memory with who you are and what you are working on.
339
+ - **audit**: a read-only health check across the four rungs, naming the gaps and the single next fix.
340
+ - **level-up**: a weekly session that turns one recurring manual chore into something Holt does, and ships it.
341
+
342
+ The last three are the setup loop. `onboard` on day one, then `audit` and `level-up` weekly. Holt gets useful in four rungs, in order: **Know** (it holds facts about you), **Reach** (it can see your real material), **Do** (it runs your repeated work), **Run** (it acts while you are away). Automating on top of an empty memory just produces confident nonsense on a timer, so the order matters.
318
343
 
319
344
  ### Skill registry (`holt skill search` / `publish`)
320
345
 
@@ -424,7 +449,7 @@ Options: `--brain <id>` pick a brain, `--out <file>` also write the reply to a f
424
449
 
425
450
  ### holt schedule
426
451
 
427
- 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.
428
453
 
429
454
  ```bash
430
455
  holt schedule add "summarize my inbox and flag what is urgent" 07:00 --notify
@@ -473,7 +498,7 @@ holt routine remove brief # deletes the routine and its OS timer, if any
473
498
 
474
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.
475
500
 
476
- 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.
477
502
 
478
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.
479
504
 
@@ -578,7 +603,7 @@ holt graph see your memory as an interactive knowledge graph
578
603
  holt mcp run an MCP server so other tools use this folder's memory (holt mcp setup)
579
604
  holt hook ambient memory for Claude Code: install | remove | status
580
605
  holt skill manage skills: list | show | create | add | remove
581
- holt memory inspect memory: holt memory [search <query> | facts | embed | clear]
606
+ holt memory inspect memory: holt memory [add <fact> | search <query> | facts | embed | clear]
582
607
  holt wiki derived knowledge wiki: holt wiki [sync | auto | rebuild | lint | list | show | status]
583
608
  holt setting configure brains, API brains, and launch command
584
609
  holt login <brain> sign in to claude, codex, or gemini
@@ -605,7 +630,8 @@ Built in always-shippable phases toward a full-vision v1:
605
630
  4. **Knowledge graph**: see and navigate your own memory with `holt graph` *(shipped)*
606
631
  5. **Everywhere**: MCP server so other tools read Holt's memory, plus `holt run`, scheduling, and Telegram *(shipped)*
607
632
  6. **Knowledge wiki**: LLM-maintained, cross-linked, regenerable pages over your memory with `holt wiki` *(shipped)*
608
- 7. **Next**: docs site, skill registry publishing, more channels
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
609
635
 
610
636
  ## Contributing
611
637
 
package/dist/cli.js CHANGED
@@ -677,7 +677,7 @@ function clearMemory() {
677
677
  function normalizeFact(s) {
678
678
  return (s || "").toLowerCase().replace(/\s+/g, " ").trim().replace(/[.,;:!?]+$/, "");
679
679
  }
680
- var FACTS_HEADER = "# Holt facts\n\nDistilled memories for this folder. Safe to edit by hand.\n";
680
+ var FACTS_HEADER = '# Holt facts\n\nDistilled memories for this folder. Edit or delete lines freely.\nTo ADD one, use `holt memory add "<fact>"`: a line typed in here by hand is\nnever embedded, so semantic recall will not find it.\n';
681
681
  async function saveFact(content, session) {
682
682
  const clean = content.trim();
683
683
  if (!clean) return false;
@@ -2515,6 +2515,38 @@ 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 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)}`];
2530
+ if (job.runArgs && job.runArgs.length > 0) {
2531
+ lines.push(`${holt} ${job.runArgs.map(cmdQuote).join(" ")} --quiet`);
2532
+ } else {
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
+ }
2542
+ }
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];
2549
+ }
2518
2550
  function stripCronLines(crontab, id) {
2519
2551
  const marker = `# ${CRON_MARKER}${id}`;
2520
2552
  const kept = crontab.split("\n").filter((line) => !line.includes(marker));
@@ -4824,6 +4856,34 @@ async function memoryCmd(sub, rest = []) {
4824
4856
  close();
4825
4857
  return;
4826
4858
  }
4859
+ if (action === "add" || action === "remember") {
4860
+ let items = [];
4861
+ const inline = rest.join(" ").trim();
4862
+ if (inline) items = [inline];
4863
+ else if (!process.stdin.isTTY) {
4864
+ items = readFileSync14(0, "utf8").split("\n").map((l) => l.replace(/^\s*[-*]\s+/, "").trim()).filter(Boolean);
4865
+ }
4866
+ if (items.length === 0) {
4867
+ console.log(c.dim('\n Usage: holt memory add "<fact>" (or pipe one fact per line on stdin)\n'));
4868
+ close();
4869
+ return;
4870
+ }
4871
+ const session = newSessionId();
4872
+ let saved = 0;
4873
+ let dupes = 0;
4874
+ for (const item of items) {
4875
+ if (await saveFact(item, session)) saved++;
4876
+ else dupes++;
4877
+ }
4878
+ const dupNote = dupes ? c.dim(` (${dupes} already known)`) : "";
4879
+ console.log("\n" + c.green(` Remembered ${saved} fact${saved === 1 ? "" : "s"}.`) + dupNote);
4880
+ if (saved && !await embeddingsAvailable()) {
4881
+ console.log(c.dim(' Saved in keyword mode. Run "holt doctor --fix" for semantic recall.'));
4882
+ }
4883
+ console.log("");
4884
+ close();
4885
+ return;
4886
+ }
4827
4887
  if (action === "facts") {
4828
4888
  if (!existsSync13(factsMdPath())) {
4829
4889
  console.log(c.dim("\n No facts distilled yet. They form when you end a chat session.\n"));
@@ -4864,9 +4924,9 @@ async function memoryCmd(sub, rest = []) {
4864
4924
  close();
4865
4925
  return;
4866
4926
  }
4867
- if (action && !["clear", "embed", "facts", "search", "global"].includes(action)) {
4927
+ if (action && !["add", "remember", "clear", "embed", "facts", "search", "global"].includes(action)) {
4868
4928
  console.error(`
4869
- Unknown memory subcommand: "${sub}". Use: search <query> | facts | embed | global | clear
4929
+ Unknown memory subcommand: "${sub}". Use: add <fact> | search <query> | facts | embed | global | clear
4870
4930
  `);
4871
4931
  process.exitCode = 1;
4872
4932
  close();
@@ -4888,7 +4948,8 @@ async function memoryCmd(sub, rest = []) {
4888
4948
  }
4889
4949
  const gEnabled = isGlobalEnabled(workspace());
4890
4950
  console.log(` global ${gEnabled ? c.green("on") + c.dim(" (contributes + reads shared facts)") : c.dim("off (this folder is isolated, the default)")}`);
4891
- console.log(c.dim("\n holt memory search <query> find remembered moments"));
4951
+ console.log(c.dim('\n holt memory add "<fact>" teach it something durable right now'));
4952
+ console.log(c.dim(" holt memory search <query> find remembered moments"));
4892
4953
  console.log(c.dim(" holt memory facts show distilled facts (facts.md)"));
4893
4954
  console.log(c.dim(" holt memory embed embed older memories for semantic recall"));
4894
4955
  console.log(c.dim(" holt memory global share high-value facts across your folders"));
@@ -7486,7 +7547,7 @@ function holtTools() {
7486
7547
  }
7487
7548
 
7488
7549
  // src/version.ts
7489
- var VERSION = "0.16.0";
7550
+ var VERSION = "0.18.0";
7490
7551
 
7491
7552
  // src/commands/mcp.ts
7492
7553
  function printSetup() {
@@ -7799,13 +7860,43 @@ async function addCmd(rest) {
7799
7860
  console.log(` workspace ${job.workspace}`);
7800
7861
  if (job.notify) console.log(` notify on (pushes output to Telegram)`);
7801
7862
  console.log(` log ${logPath(job.id)}`);
7863
+ installForPlatform(job, holtPath);
7864
+ console.log("");
7865
+ }
7866
+ function installForPlatform(job, holtPath) {
7802
7867
  if (process.platform === "darwin") installDarwin(job, holtPath);
7803
7868
  else if (process.platform === "linux") installLinux(job, holtPath);
7869
+ else if (process.platform === "win32") installWindows(job, holtPath);
7804
7870
  else {
7805
7871
  console.log(c.dim("\n Your OS has no built-in installer. Install this entry yourself:"));
7806
7872
  console.log(" " + buildCronLine(job, holtPath));
7807
7873
  }
7808
- console.log("");
7874
+ }
7875
+ function installWindows(job, holtPath) {
7876
+ const script = cmdScriptPath(job.id);
7877
+ mkdirSync15(dirname9(script), { recursive: true });
7878
+ writeFileSync17(script, buildWindowsScript(job, holtPath), "utf8");
7879
+ const res = spawnSync5("schtasks", buildSchtasksArgs(job, script), { encoding: "utf8" });
7880
+ if (res.status === 0) {
7881
+ console.log(c.green(" Registered with Task Scheduler."));
7882
+ } else {
7883
+ console.log(c.dim(" Wrote the task script, but schtasks did not confirm."));
7884
+ console.log(c.dim(" Register it yourself with:"));
7885
+ console.log(
7886
+ c.dim(` schtasks /Create /F /SC DAILY /ST ${job.when} /TN "${taskName(job.id)}" /TR "${script}"`)
7887
+ );
7888
+ const why = (res.stderr || "").trim() || (res.error ? res.error.message : "");
7889
+ if (why) console.log(c.dim(` (${why})`));
7890
+ }
7891
+ console.log(c.dim(` task: ${taskName(job.id)}`));
7892
+ console.log(c.dim(` script: ${script}`));
7893
+ }
7894
+ function removeWindows(id) {
7895
+ spawnSync5("schtasks", ["/Delete", "/F", "/TN", taskName(id)], { encoding: "utf8" });
7896
+ try {
7897
+ rmSync7(cmdScriptPath(id));
7898
+ } catch {
7899
+ }
7809
7900
  }
7810
7901
  function listCmd2() {
7811
7902
  const jobs = loadJobs();
@@ -7838,6 +7929,11 @@ function removeLinux(id) {
7838
7929
  const next = stripCronLines(body, id);
7839
7930
  spawnSync5("crontab", ["-"], { input: next, encoding: "utf8" });
7840
7931
  }
7932
+ function removeForPlatform(id) {
7933
+ if (process.platform === "darwin") removeDarwin(id);
7934
+ else if (process.platform === "linux") removeLinux(id);
7935
+ else if (process.platform === "win32") removeWindows(id);
7936
+ }
7841
7937
  function removeCmd(rest) {
7842
7938
  const id = rest[0];
7843
7939
  if (!id) {
@@ -7848,8 +7944,7 @@ function removeCmd(rest) {
7848
7944
  const before = loadJobs();
7849
7945
  const existed = before.some((j) => j.id === id);
7850
7946
  removeJob(id);
7851
- if (process.platform === "darwin") removeDarwin(id);
7852
- else if (process.platform === "linux") removeLinux(id);
7947
+ removeForPlatform(id);
7853
7948
  if (existed) console.log("\n" + c.green(` Removed schedule ${id}.`) + "\n");
7854
7949
  else {
7855
7950
  console.error(c.dim(`
@@ -7886,9 +7981,8 @@ async function schedule(sub, rest = []) {
7886
7981
  }
7887
7982
 
7888
7983
  // src/commands/routine.ts
7889
- import { spawnSync as spawnSync6 } from "child_process";
7890
- import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, rmSync as rmSync8, existsSync as existsSync19 } from "fs";
7891
- import { dirname as dirname10, resolve as resolve7 } from "path";
7984
+ import { writeFileSync as writeFileSync19 } from "fs";
7985
+ import { resolve as resolve7 } from "path";
7892
7986
 
7893
7987
  // src/routines.ts
7894
7988
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16 } from "fs";
@@ -7967,73 +8061,19 @@ function jobForRoutine(r) {
7967
8061
  function routineJobId(name) {
7968
8062
  return `routine-${sanitizeRoutineName(name)}`;
7969
8063
  }
7970
- function installDarwin2(job, holtPath) {
7971
- const path = plistPath(job.id);
7972
- mkdirSync17(dirname10(path), { recursive: true });
7973
- writeFileSync19(path, buildLaunchdPlist(job, holtPath), "utf8");
7974
- const res = spawnSync6("launchctl", ["load", "-w", path], { encoding: "utf8" });
7975
- if (res.status === 0) {
7976
- console.log(c.green(" Loaded into launchd."));
7977
- } else {
7978
- console.log(c.dim(" Wrote the plist, but launchctl load did not confirm."));
7979
- console.log(c.dim(` Load it yourself with: launchctl load -w ${path}`));
7980
- if (res.stderr && res.stderr.trim()) console.log(c.dim(` (${res.stderr.trim()})`));
7981
- }
7982
- console.log(c.dim(` plist: ${path}`));
7983
- }
7984
- function currentCrontab2() {
7985
- const res = spawnSync6("crontab", ["-l"], { encoding: "utf8" });
7986
- return res.status === 0 && typeof res.stdout === "string" ? res.stdout : "";
7987
- }
7988
- function writeCrontab2(body) {
7989
- const res = spawnSync6("crontab", ["-"], { input: body, encoding: "utf8" });
7990
- return res.status === 0;
7991
- }
7992
- function installLinux2(job, holtPath) {
7993
- const line = buildCronLine(job, holtPath);
7994
- const next = appendCronLine(currentCrontab2(), line);
7995
- if (writeCrontab2(next)) {
7996
- console.log(c.green(" Added to your crontab."));
7997
- console.log(c.dim(` line: ${line}`));
7998
- } else {
7999
- console.log(c.dim(" Could not update the crontab automatically. Add this line yourself:"));
8000
- console.log(" " + line);
8001
- }
8002
- }
8003
8064
  function installSchedule(r) {
8004
8065
  const holtPath = resolveHoltPath();
8005
8066
  const job = jobForRoutine(r);
8006
8067
  removeSchedule(r.name);
8007
8068
  addJob(job);
8008
- if (process.platform === "darwin") installDarwin2(job, holtPath);
8009
- else if (process.platform === "linux") installLinux2(job, holtPath);
8010
- else {
8011
- console.log(c.dim("\n Your OS has no built-in installer. Install this entry yourself:"));
8012
- console.log(" " + buildCronLine(job, holtPath));
8013
- }
8014
- }
8015
- function removeScheduleDarwin(id) {
8016
- const path = plistPath(id);
8017
- if (existsSync19(path)) {
8018
- spawnSync6("launchctl", ["unload", path], { encoding: "utf8" });
8019
- try {
8020
- rmSync8(path);
8021
- } catch {
8022
- }
8023
- }
8024
- }
8025
- function removeScheduleLinux(id) {
8026
- const body = currentCrontab2();
8027
- const next = stripCronLines(body, id);
8028
- writeCrontab2(next);
8069
+ installForPlatform(job, holtPath);
8029
8070
  }
8030
8071
  function removeSchedule(name) {
8031
8072
  const id = routineJobId(name);
8032
8073
  const existed = loadJobs().some((j) => j.id === id);
8033
8074
  removeJob(id);
8034
8075
  if (!existed) return;
8035
- if (process.platform === "darwin") removeScheduleDarwin(id);
8036
- else if (process.platform === "linux") removeScheduleLinux(id);
8076
+ removeForPlatform(id);
8037
8077
  }
8038
8078
  function parseAdd2(rest) {
8039
8079
  const opts = { notify: false };
@@ -8388,8 +8428,8 @@ function readStdin3() {
8388
8428
  }
8389
8429
 
8390
8430
  // src/commands/doctor.ts
8391
- import { spawn as spawn5, spawnSync as spawnSync7 } from "child_process";
8392
- import { existsSync as existsSync20 } from "fs";
8431
+ import { spawn as spawn5, spawnSync as spawnSync6 } from "child_process";
8432
+ import { existsSync as existsSync19 } from "fs";
8393
8433
 
8394
8434
  // src/specs.ts
8395
8435
  import os from "os";
@@ -8551,7 +8591,7 @@ async function fixSemanticMemory(fixed) {
8551
8591
  }
8552
8592
  if (!tags.some((t) => t.startsWith(EMBED_MODEL))) {
8553
8593
  note(`The "${EMBED_MODEL}" model is missing. Downloading it (one-time, ~275 MB)...`);
8554
- const r = spawnSync7("ollama", ["pull", EMBED_MODEL], { stdio: "inherit" });
8594
+ const r = spawnSync6("ollama", ["pull", EMBED_MODEL], { stdio: "inherit" });
8555
8595
  if (r.status !== 0) {
8556
8596
  warn(`Could not download ${EMBED_MODEL}. Check your internet connection and retry.`);
8557
8597
  return false;
@@ -8614,7 +8654,7 @@ async function doctor(args = []) {
8614
8654
  }
8615
8655
  if (embedOk) {
8616
8656
  good(`Ollama reachable and "${EMBED_MODEL}" is present. Memory recall is semantic, private, and local.`);
8617
- if (fix && existsSync20(wsHoltDir())) {
8657
+ if (fix && existsSync19(wsHoltDir())) {
8618
8658
  const missing = loadTurns().filter((t) => !Array.isArray(t.emb)).length;
8619
8659
  if (missing > 0) {
8620
8660
  note(`${missing} memories in this folder lack embeddings. Upgrading them...`);
@@ -8733,12 +8773,12 @@ async function tour() {
8733
8773
  }
8734
8774
 
8735
8775
  // src/commands/reset.ts
8736
- import { existsSync as existsSync21, rmSync as rmSync9 } from "fs";
8776
+ import { existsSync as existsSync20, rmSync as rmSync8 } from "fs";
8737
8777
  async function reset() {
8738
8778
  const { ask, close } = createReader();
8739
8779
  try {
8740
8780
  const dir = wsHoltDir();
8741
- if (!existsSync21(dir)) {
8781
+ if (!existsSync20(dir)) {
8742
8782
  console.log(c.dim("\n Nothing to reset: Holt has no settings or memory in this folder.\n"));
8743
8783
  return;
8744
8784
  }
@@ -8757,7 +8797,7 @@ async function reset() {
8757
8797
  console.log(c.dim(" Cancelled. Nothing was changed.\n"));
8758
8798
  return;
8759
8799
  }
8760
- rmSync9(dir, { recursive: true, force: true });
8800
+ rmSync8(dir, { recursive: true, force: true });
8761
8801
  console.log(c.green("\n Done. This folder is a clean slate.") + c.dim(' Run "holt" to set it up again.\n'));
8762
8802
  } finally {
8763
8803
  close();
@@ -8765,7 +8805,7 @@ async function reset() {
8765
8805
  }
8766
8806
 
8767
8807
  // src/commands/update.ts
8768
- import { spawnSync as spawnSync8 } from "child_process";
8808
+ import { spawnSync as spawnSync7 } from "child_process";
8769
8809
  import { realpathSync as realpathSync4 } from "fs";
8770
8810
  async function latestVersion() {
8771
8811
  try {
@@ -8807,7 +8847,7 @@ async function update() {
8807
8847
  console.log(c.dim(" Later, then. The command is: brew upgrade holt\n"));
8808
8848
  return;
8809
8849
  }
8810
- const r2 = spawnSync8("brew", ["upgrade", "holt"], { stdio: "inherit" });
8850
+ const r2 = spawnSync7("brew", ["upgrade", "holt"], { stdio: "inherit" });
8811
8851
  if (r2.status === 0) console.log(c.green(`
8812
8852
  Updated to ${latest}.`) + "\n");
8813
8853
  else console.log(c.dim("\n Homebrew reported a problem above. You can retry with: brew upgrade holt\n"));
@@ -8817,7 +8857,7 @@ async function update() {
8817
8857
  console.log(c.dim(" Later, then. The command is: npm install -g @holt-os/holt\n"));
8818
8858
  return;
8819
8859
  }
8820
- const r = spawnSync8("npm", ["install", "-g", "@holt-os/holt"], { stdio: "inherit" });
8860
+ const r = spawnSync7("npm", ["install", "-g", "@holt-os/holt"], { stdio: "inherit" });
8821
8861
  if (r.status === 0) {
8822
8862
  console.log(c.green(`
8823
8863
  Updated to ${latest}.`) + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holt-os/holt",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
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",
48
48
  "release": "bash scripts/release.sh"
49
49
  },
50
50
  "devDependencies": {
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: audit
3
+ description: A read-only health check of your Holt setup across the four rungs (Know, Reach, Do, Run), naming the gaps and the single next thing to fix. Use weekly, or when Holt feels less useful than expected.
4
+ ---
5
+
6
+ # audit
7
+
8
+ ## When to use
9
+
10
+ The user wants to know whether Holt is actually set up well, or why it feels
11
+ thin. This skill inspects the real state of the folder and reports back. It
12
+ changes nothing: no files written, no settings altered, no facts saved. If a fix
13
+ is obvious, name the command and let the user run it.
14
+
15
+ ## The four rungs
16
+
17
+ Each rung depends on the one below it. A gap low down makes everything above it
18
+ worse, so always report the lowest gap first.
19
+
20
+ 1. **Know**: it holds facts about the user and their work.
21
+ 2. **Reach**: it can see the real material, not just what is typed at it.
22
+ 3. **Do**: it runs their repeated work as skills.
23
+ 4. **Run**: it acts without the user being present.
24
+
25
+ ## Instructions
26
+
27
+ ### Gather the state
28
+
29
+ Run these and read the output. If you cannot run commands in this session, ask
30
+ the user to paste the output of the first three rather than guessing.
31
+
32
+ ```
33
+ holt memory facts count, embedded count, recall mode
34
+ holt memory facts what it actually knows
35
+ holt voice show is there a writing profile
36
+ holt hook status ambient memory in Claude Code
37
+ holt skill list which skills exist, and which are built in
38
+ holt routine list named jobs
39
+ holt schedule list timers
40
+ holt doctor machine level problems
41
+ holt wiki status derived knowledge, if used
42
+ ```
43
+
44
+ ### Score each rung
45
+
46
+ Be strict. A generous audit is a useless one. Mark each rung **solid**, **thin**,
47
+ or **missing**, and say what the evidence was.
48
+
49
+ - **Know** is solid when memory holds a few dozen facts that are about the
50
+ user's actual work, and recall is running on embeddings rather than keyword
51
+ match. It is thin when the facts are mostly setup trivia, or when
52
+ `holt memory` shows moments that were never embedded. Read the facts, do not
53
+ just count them: fifty facts about one afternoon is still thin.
54
+ - **Reach** is solid when Holt sees material without the user pasting it: the
55
+ hook is installed, or MCP is wired into the tools they use, or the folder
56
+ holds the real files. It is missing when every session starts by pasting
57
+ context in by hand.
58
+ - **Do** is solid when at least one skill exists that the user or their
59
+ registry provided, tuned to their work. Built-in skills alone do not count;
60
+ everyone has those. Note which ones are actually being used.
61
+ - **Run** is solid when something has produced output while they were away, and
62
+ they read it. A routine that exists but has never run is thin, not solid.
63
+
64
+ ### Report
65
+
66
+ 1. Name the rung they are on: the highest one that is solid, with everything
67
+ below it also solid. Say it in one line.
68
+ 2. List the gaps, lowest rung first. For each, state what is missing and the
69
+ command that would close it.
70
+ 3. Give **one** next action. Not a menu, not a ranked list of five. The single
71
+ thing that unblocks the most.
72
+ 4. Flag anything actively broken separately from anything merely absent. A
73
+ scheduled routine that fails every night is worse than no routine, and a
74
+ memory in keyword mode is quietly degrading every answer.
75
+
76
+ Keep the whole report short enough to read in a minute. It is a dashboard, not
77
+ an essay.
78
+
79
+ ### Known traps to check for
80
+
81
+ - **Keyword mode.** If `holt memory` says recall is keyword rather than
82
+ embeddings, semantic recall is off and every answer is worse. `holt doctor
83
+ --fix` usually repairs it.
84
+ - **Unembedded moments.** If embedded count is below total, older memories are
85
+ invisible to semantic recall. `holt memory embed` fixes it in one pass.
86
+ - **Hand-edited facts.** Facts added by editing `facts.md` directly are never
87
+ embedded, so they never come back through recall. They must be re-added with
88
+ `holt memory add`.
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
+ - **Automation above an empty memory.** If Run looks solid while Know is thin,
93
+ say so bluntly. That setup produces confident wrong output on a timer, and it
94
+ is the most expensive failure on this list.
95
+
96
+ ## Example
97
+
98
+ You are on rung two. Know and Reach are solid, Do is thin.
99
+
100
+ Know: solid. 41 facts covering the agro project, the job search, and family
101
+ health. Recall is on embeddings.
102
+ Reach: solid. Hook installed, so Claude Code sessions recall automatically.
103
+ Do: thin. Ten skills listed, all built in. Nothing tuned to your work.
104
+ Run: missing. No routines, no schedules.
105
+
106
+ Broken: 12 of 340 moments have no embeddings, so a slice of last month is
107
+ invisible to recall.
108
+
109
+ Next action: run `/skill level-up` and turn the weekly client update into a
110
+ skill. Rung four is not worth touching until something on rung three has earned
111
+ its place.
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: level-up
3
+ description: A weekly working session that turns one piece of recurring manual work into something Holt does for you, and ships it before the session ends. Use as a standing ritual once memory has real facts in it.
4
+ ---
5
+
6
+ # level-up
7
+
8
+ ## When to use
9
+
10
+ The user has been using Holt and wants it to carry more. This is the ritual that
11
+ compounds: once a week, one drain becomes one shipped capability. Run it after
12
+ memory holds real facts about their work; on an empty folder, run `onboard`
13
+ first.
14
+
15
+ The session fails if it ends in a plan. It succeeds if something exists that did
16
+ not exist an hour ago.
17
+
18
+ ## Instructions
19
+
20
+ ### 1. Find the constraint
21
+
22
+ Read what memory already knows. Relevant past notes are injected above; also run
23
+ `holt memory facts` and search for the work they complain about:
24
+
25
+ ```
26
+ holt memory search <what they keep doing by hand>
27
+ ```
28
+
29
+ Then ask what ate their time this week, and what they did more than once. Pick
30
+ **one**. The best candidate is frequent, rule-shaped, and low-stakes if it comes
31
+ out slightly wrong. Say plainly why you picked it over the others.
32
+
33
+ If several are tempting, choose the boring one. A dull thing that runs every
34
+ week beats an ambitious thing that runs once.
35
+
36
+ ### 2. Decide what to do with it
37
+
38
+ Three options, in this order:
39
+
40
+ - **Drop it.** Does this task need to exist? Work that only continues out of
41
+ habit should be stopped, not automated. Ask this first and mean it; the
42
+ cheapest automation is the one you do not build.
43
+ - **Automate it.** Rule-shaped and repeatable, with a checkable output.
44
+ - **Hand it off.** It needs judgement, relationships, or accountability a
45
+ machine should not hold. Say so and move to the next candidate.
46
+
47
+ Automating something that should have been dropped makes the waste permanent
48
+ and harder to see. Push back before building.
49
+
50
+ ### 3. Map it before building
51
+
52
+ Walk the task through in the user's own words: what starts it, what information
53
+ it needs, what it produces, and who sees the output. Write the steps down. If
54
+ you cannot state the steps in plain sentences, it is not ready to build.
55
+
56
+ Then set how much rope it gets:
57
+
58
+ - **Drafts, you send.** Anything that leaves the house: email, posts, messages.
59
+ - **Runs, you review.** Produces a file or a note you read afterwards.
60
+ - **Runs unattended.** Only for work that is reversible and already proven by
61
+ hand for a few cycles.
62
+
63
+ Start one level more cautious than feels necessary. Moving up later is easy;
64
+ un-sending is not.
65
+
66
+ ### 4. Build it now
67
+
68
+ Pick the lightest thing that works:
69
+
70
+ - A **skill**, when it is a repeatable way of thinking or writing:
71
+ `holt skill create <name>`, then write the instructions into its `SKILL.md`.
72
+ Check the registry first with `holt skill search <topic>`, because the work
73
+ may be done already.
74
+ - A **routine**, when it is a task worth naming and rerunning:
75
+ `holt routine add <name> "<task>"`, run it with `holt routine run <name>`.
76
+ - A **schedule**, when it should happen without being asked:
77
+ `holt schedule add`, or a scheduled routine. Only after it has produced good
78
+ output by hand at least twice.
79
+ - A **connection**, when the blocker is that Holt cannot see the material:
80
+ `holt hook` for ambient memory in Claude Code, `holt mcp setup` for other
81
+ tools. Sometimes the whole win is that it can finally read the thing.
82
+
83
+ Prefer a fixed sequence of steps over an open-ended agent. Predictable and
84
+ slightly dumb beats clever and unrepeatable.
85
+
86
+ ### 5. Test it on something real
87
+
88
+ Run it once, now, on genuine input, and read the output with the user. If it is
89
+ wrong, fix it in this session. A capability that has never run on real data is
90
+ not shipped.
91
+
92
+ Then agree how it gets switched off: which command removes it, or which file to
93
+ delete. Anything that runs on a timer needs a way to stop.
94
+
95
+ ### 6. Close the loop
96
+
97
+ 1. Save what was built and why, so next week starts informed:
98
+ ```
99
+ holt memory add "Built the <name> routine on <date>: <what it does, what triggers it>"
100
+ ```
101
+ 2. Note what to watch for: the failure mode most likely to show up first.
102
+ 3. Name the single next candidate from the list you did not pick, so the next
103
+ session starts with a running head start.
104
+
105
+ ## Example
106
+
107
+ This week: forty minutes lost to writing the same client update every Friday.
108
+
109
+ Not dropped, it is genuinely wanted. Not handed off, the facts live here.
110
+ Automate it, at drafts-you-send, because it goes to a client.
111
+
112
+ Built as a routine over the folder's memory, run once against this week's real
113
+ work. First draft named the wrong milestone; instructions tightened, second
114
+ draft was sendable. Off switch: `holt routine remove friday-update`.
115
+
116
+ Watch for: it will keep sounding confident about numbers it has not been given.
117
+
118
+ Next week's candidate: the invoice chase.
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: onboard
3
+ description: A guided first-day interview that fills Holt's memory with who you are and what you are working on, so recall is useful from day one instead of after weeks of use. Use on a fresh folder, or to top up a thin memory.
4
+ ---
5
+
6
+ # onboard
7
+
8
+ ## When to use
9
+
10
+ Holt is installed but it does not know the user yet. An empty memory makes every
11
+ answer generic, which is the most common reason people install Holt and drift
12
+ away. This skill front-loads the knowledge that recall needs, in about fifteen
13
+ minutes, and ends with one real piece of work.
14
+
15
+ Also use it when `holt memory` shows very few facts and answers still feel
16
+ generic, or when the user starts a new folder for a different part of their life.
17
+
18
+ ## The ladder
19
+
20
+ Holt gets useful in four rungs, in order. Onboarding is rung one.
21
+
22
+ 1. **Know**: it holds facts about you and your work. (`holt memory`)
23
+ 2. **Reach**: it can see your real material: files, notes, other tools. (`holt hook`, `holt mcp`)
24
+ 3. **Do**: it runs your repeated work as skills. (`holt skill`)
25
+ 4. **Run**: it acts without you being there. (`holt routine`, `holt schedule`)
26
+
27
+ Do not skip ahead. Automation on top of an empty memory produces confident
28
+ nonsense on a timer.
29
+
30
+ ## Instructions
31
+
32
+ ### Before you start
33
+
34
+ 1. Check what is already known: run `holt memory` and, if any facts exist,
35
+ `holt memory facts`. If the folder already holds real facts, say so and offer
36
+ a top-up on the thin areas instead of the full interview. Never re-ask
37
+ something already in memory.
38
+ 2. Set expectations in two lines: seven questions, about fifteen minutes, and
39
+ they can stop at any point because you save as you go.
40
+
41
+ ### Running the interview
42
+
43
+ Ask **one question at a time.** Wait for the answer before moving on. Never
44
+ present the whole list at once; a wall of questions is the main reason people
45
+ abandon setup. Keep your own words short between questions.
46
+
47
+ Ask for the real thing, never a description of it. "Paste the last update you
48
+ sent your team" beats "how would you describe your writing style". Concrete
49
+ input is what makes recall work later.
50
+
51
+ If an answer is thin or abstract, ask one follow-up for a specific name, number,
52
+ or example, then move on. Two follow-ups on one question is too many.
53
+
54
+ The seven questions:
55
+
56
+ 1. **Who you are.** What is your work, and what does a normal day actually
57
+ involve? Get the role, and what they are responsible for.
58
+ 2. **What is live.** The three to five things genuinely in flight right now, and
59
+ the state of each. Push for status, not just titles.
60
+ 3. **Who matters.** The people, clients, teams, or companies that come up
61
+ regularly, and how each relates to them. Names matter here.
62
+ 4. **Standing rules.** Decisions and preferences you should never re-ask:
63
+ how they want to be spoken to, what is off limits, choices already made.
64
+ 5. **Where the work lives.** Which folders, tools, and accounts hold the real
65
+ material. This becomes the map for rung two, so note what is not reachable
66
+ yet rather than pretending it is.
67
+ 6. **What drains the time.** The recurring work they resent doing by hand. Ask
68
+ for the last three times it happened. These are the candidates the `level-up`
69
+ skill will draw from later.
70
+ 7. **Their voice.** Ask them to paste two or three samples of their real
71
+ writing: a sent email, a message, a published post. Then run
72
+ `holt voice add <file>` if the samples are in files, or tell them the command
73
+ so their drafts sound like them rather than like a model.
74
+
75
+ ### Saving as you go
76
+
77
+ After every two questions, write what you learned to memory with:
78
+
79
+ ```
80
+ holt memory add "<one durable fact>"
81
+ ```
82
+
83
+ One fact per call. Several facts can be piped in at once, one per line.
84
+
85
+ Write facts that will still be true in a month and that a future session could
86
+ not guess. "Runs product for a fintech in Bhubaneswar, nine years in" is a fact.
87
+ "Wants to be more productive" is noise. Include names, numbers, and dates. Write
88
+ the fact so it stands alone, because recall will surface it without this
89
+ conversation around it.
90
+
91
+ If you cannot run commands in this session, write the facts out as a block of
92
+ `holt memory add` lines and ask the user to paste them into their terminal.
93
+ Do not hand-edit `facts.md`: facts added that way are never embedded, so
94
+ semantic recall will not find them.
95
+
96
+ ### Finishing
97
+
98
+ 1. Show what was captured: run `holt memory facts` so they see their own words
99
+ stored in a file they own and can edit.
100
+ 2. Prove it works. Ask one question whose answer depends on what they just told
101
+ you, and answer it from memory. This is the moment the tool becomes real, so
102
+ do not skip it.
103
+ 3. Do one real thing now. Pick the smallest useful task from what they said
104
+ drains their time and complete it in this session. An artifact beats a plan.
105
+ 4. Name rung two. Tell them the single next step, usually `holt hook` so Claude
106
+ Code remembers automatically, or `holt mcp setup` to reach their other tools.
107
+ One step, not a menu.
108
+ 5. Point at the ritual: `/skill level-up` weekly turns one drain into one
109
+ automation, and `/skill audit` shows which rung they are on.
110
+
111
+ ## Example
112
+
113
+ Not: "Tell me about your business, your projects, your clients, your
114
+ preferences, and your writing style."
115
+
116
+ Instead: "First one. What is your work, and what does a normal Tuesday actually
117
+ look like?" then, after the answer: "Got it. Now the live list: what are the three to five
118
+ things genuinely in flight this week?"
119
+
120
+ Then, quietly, between questions:
121
+
122
+ ```
123
+ holt memory add "Senior product manager, nine years, currently between roles and job-hunting in the Netherlands"
124
+ holt memory add "Runs Sasyam Agro with an uncle in Bargarh; demo plots are on the uncle's land"
125
+ ```