@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 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. **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
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 paint = () => {
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
- paint();
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
- paint();
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 buildWindowsTaskFields(job, holtPath) {
2519
- let args;
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
- args = [...job.runArgs, "--quiet"];
2531
+ lines.push(`${holt} ${job.runArgs.map(cmdQuote).join(" ")} --quiet`);
2522
2532
  } else {
2523
- args = ["run", job.task];
2524
- if (job.brain) args.push("--brain", job.brain);
2525
- args.push("--quiet", "--out", logPath(job.id));
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
- const quoted = args.map((a) => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
2528
- return {
2529
- program: holtPath,
2530
- args: quoted.join(" "),
2531
- startIn: job.workspace,
2532
- when: job.when,
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.17.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 folderFrom(data) {
7630
+ function dirFrom(data) {
7616
7631
  const ws = get(data, "workspace");
7617
- 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"));
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 renderStatusLine(data) {
7632
- const parts = ["Holt"];
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) parts.push(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
- line = renderStatusLine(parse(raw));
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
- if (process.platform === "darwin") installDarwin(job, holtPath);
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 printManualInstall(job, holtPath) {
7855
- if (process.platform !== "win32") {
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
- const f = buildWindowsTaskFields(job, holtPath);
7861
- console.log(c.dim("\n Saved, but not started: Holt cannot install timers on Windows yet."));
7862
- console.log(c.dim(' Open Task Scheduler, choose "Create Basic Task", and fill in:'));
7863
- console.log(` Name ${f.name}`);
7864
- console.log(` Trigger Daily at ${f.when}`);
7865
- console.log(` Program ${f.program}`);
7866
- console.log(` Arguments ${f.args}`);
7867
- console.log(` Start in ${f.startIn}`);
7868
- if (job.notify) {
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("\n Note: --notify does not carry over to Task Scheduler. Read the log file instead.")
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
- if (process.platform === "darwin") removeDarwin(id);
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 { spawnSync as spawnSync6 } from "child_process";
7954
- import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, rmSync as rmSync8, existsSync as existsSync19 } from "fs";
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
- if (process.platform === "darwin") installDarwin2(job, holtPath);
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
- if (process.platform === "darwin") removeScheduleDarwin(id);
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 spawnSync7 } from "child_process";
8453
- import { existsSync as existsSync20 } from "fs";
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 = spawnSync7("ollama", ["pull", EMBED_MODEL], { stdio: "inherit" });
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 && existsSync20(wsHoltDir())) {
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 existsSync21, rmSync as rmSync9 } from "fs";
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 (!existsSync21(dir)) {
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
- rmSync9(dir, { recursive: true, force: true });
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 spawnSync8 } from "child_process";
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 = spawnSync8("brew", ["upgrade", "holt"], { stdio: "inherit" });
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 = spawnSync8("npm", ["install", "-g", "@holt-os/holt"], { stdio: "inherit" });
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.17.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": {
@@ -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
- - **Windows and timers.** On Windows, `holt schedule` and a routine's `--at`
90
- time do not install anything. Anything on rung four there has to run another
91
- way, so check the platform before calling rung four solid.
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.