@holt-os/holt 0.12.1 → 0.13.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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/cli.js +268 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -71,14 +71,27 @@ Inside `holt chat`:
71
71
  /memory memory stats. /memory <query> previews what recall would surface
72
72
  /output show or set the save format: markdown | html
73
73
  /save [name] save the last reply to a file in this folder
74
+ /allow <path> let this session read a folder outside this one (session-only)
75
+ /allowed list folders granted outside-access this session
74
76
  /setting configure brains, API brains, and your launch command
75
77
  /clear forget this session (saved memory stays)
76
78
  /help show commands
77
79
  /exit leave
78
80
  ```
79
81
 
82
+ A line that starts with `/` is treated as a command only when its first word is a real command. So a message like `/Users/me/resume.docx summarize this` (or a URL, or any sentence that opens with a slash) is sent to the brain as-is, never dropped as an "unknown command".
83
+
80
84
  The point of `/brain`: Holt owns the transcript, so you can start a thread on one model and hand it to another mid-conversation. The new brain picks up with the full context.
81
85
 
86
+ ### Reading files outside this folder
87
+
88
+ Holt runs the brain in the folder you launched it from, so by default the brain only sees this folder. If a message references an absolute path (`/...` or `~/...`) that exists **outside** the folder, Holt asks first: `Allow this session to access <dir> (outside this folder)? [y/N]`. Answer `y` and it grants the file's **containing directory** read access for the rest of this chat. Answer `n` and the message still goes through, the brain just does not get that folder.
89
+
90
+ - `/allow <path>` grants a folder up front with no prompt.
91
+ - `/allowed` lists what you have granted.
92
+
93
+ Grants are **in-memory and session-scoped**: nothing is written to disk, and they reset the next time you run `holt chat`. Access is **read-oriented** and only works with a **Claude Code** brain: Holt passes `--add-dir=<dir>` for each granted folder plus `--allowedTools=Read,Glob,Grep` so Claude Code can read (but not write) those files without an interactive permission prompt. With a Codex/Gemini or API brain, external file access is unavailable and Holt says so instead of adding flags. (If you need to tune the flags, they live in one place: `claudeAccessArgs` in `src/access.ts`.)
94
+
82
95
  ## Memory
83
96
 
84
97
  Every exchange is saved to `<folder>/.holt/memory/turns.jsonl`, private and local. On each message, Holt recalls the most relevant moments from your *past* sessions in that folder and hands them to the brain, so it remembers what you told it last week.
package/dist/cli.js CHANGED
@@ -105,11 +105,11 @@ function createReader() {
105
105
  closed = true;
106
106
  while (waiters.length) waiters.shift()(null);
107
107
  });
108
- const ask = (q) => new Promise((resolve8) => {
108
+ const ask = (q) => new Promise((resolve9) => {
109
109
  if (q) process.stdout.write(q);
110
- if (buffer.length) resolve8(buffer.shift());
111
- else if (closed) resolve8(null);
112
- else waiters.push(resolve8);
110
+ if (buffer.length) resolve9(buffer.shift());
111
+ else if (closed) resolve9(null);
112
+ else waiters.push(resolve9);
113
113
  });
114
114
  return { ask, close: () => rl.close() };
115
115
  }
@@ -307,13 +307,13 @@ function renderPrompt(history, message, memory = []) {
307
307
  parts.push("", `User: ${message}`, "", "Assistant:");
308
308
  return parts.join("\n");
309
309
  }
310
- function runBrain(brain, prompt, onChunk) {
311
- return new Promise((resolve8) => {
310
+ function runBrain(brain, prompt, onChunk, extraArgs = []) {
311
+ return new Promise((resolve9) => {
312
312
  let child;
313
313
  try {
314
- child = spawn(brain.command, [...brain.args, prompt], { stdio: ["ignore", "pipe", "pipe"] });
314
+ child = spawn(brain.command, [...brain.args, ...extraArgs, prompt], { stdio: ["ignore", "pipe", "pipe"] });
315
315
  } catch (e) {
316
- resolve8({ ok: false, text: `Could not launch "${brain.command}": ${e.message}` });
316
+ resolve9({ ok: false, text: `Could not launch "${brain.command}": ${e.message}` });
317
317
  return;
318
318
  }
319
319
  let out = "";
@@ -326,11 +326,11 @@ function runBrain(brain, prompt, onChunk) {
326
326
  child.stderr.on("data", (d) => {
327
327
  err2 += d.toString();
328
328
  });
329
- child.on("error", (e) => resolve8({ ok: false, text: `Could not run "${brain.command}": ${e.message}` }));
329
+ child.on("error", (e) => resolve9({ ok: false, text: `Could not run "${brain.command}": ${e.message}` }));
330
330
  child.on("close", (code) => {
331
331
  const text = out.trim();
332
- if (code === 0 && text) resolve8({ ok: true, text });
333
- else resolve8({ ok: false, text: err2.trim() || text || `"${brain.command}" exited with code ${code}` });
332
+ if (code === 0 && text) resolve9({ ok: true, text });
333
+ else resolve9({ ok: false, text: err2.trim() || text || `"${brain.command}" exited with code ${code}` });
334
334
  });
335
335
  });
336
336
  }
@@ -500,16 +500,16 @@ function currentAlias() {
500
500
  // src/install.ts
501
501
  import { spawn as spawn2 } from "child_process";
502
502
  function runInteractive(cmd, args) {
503
- return new Promise((resolve8) => {
503
+ return new Promise((resolve9) => {
504
504
  let child;
505
505
  try {
506
506
  child = spawn2(cmd, args, { stdio: "inherit" });
507
507
  } catch {
508
- resolve8(-1);
508
+ resolve9(-1);
509
509
  return;
510
510
  }
511
- child.on("error", () => resolve8(-1));
512
- child.on("close", (code) => resolve8(code ?? -1));
511
+ child.on("error", () => resolve9(-1));
512
+ child.on("close", (code) => resolve9(code ?? -1));
513
513
  });
514
514
  }
515
515
 
@@ -2982,6 +2982,77 @@ function saveReply(reply, format, name) {
2982
2982
  return path;
2983
2983
  }
2984
2984
 
2985
+ // src/access.ts
2986
+ import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
2987
+ import { homedir as homedir3 } from "os";
2988
+ import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2, sep } from "path";
2989
+ function expandHome(p) {
2990
+ if (p === "~") return homedir3();
2991
+ if (p.startsWith("~/")) return resolve2(homedir3(), p.slice(2));
2992
+ return p;
2993
+ }
2994
+ function isInside(child, parent) {
2995
+ if (child === parent) return true;
2996
+ const base = parent.endsWith(sep) ? parent : parent + sep;
2997
+ return child.startsWith(base);
2998
+ }
2999
+ function resolveExisting(token) {
3000
+ const expanded = expandHome(token);
3001
+ const abs = isAbsolute2(expanded) ? expanded : resolve2(process.cwd(), expanded);
3002
+ if (!existsSync9(abs)) return null;
3003
+ try {
3004
+ return realpathSync2(abs);
3005
+ } catch {
3006
+ return abs;
3007
+ }
3008
+ }
3009
+ function containingDir(absPath) {
3010
+ try {
3011
+ return statSync4(absPath).isDirectory() ? absPath : dirname3(absPath);
3012
+ } catch {
3013
+ return dirname3(absPath);
3014
+ }
3015
+ }
3016
+ function resolveGrantDir(token) {
3017
+ const expanded = expandHome(token);
3018
+ const abs = isAbsolute2(expanded) ? expanded : resolve2(process.cwd(), expanded);
3019
+ let real = abs;
3020
+ try {
3021
+ real = realpathSync2(abs);
3022
+ } catch {
3023
+ }
3024
+ return containingDir(real);
3025
+ }
3026
+ function findOutsidePaths(message, workspaceDir) {
3027
+ const ws = (() => {
3028
+ try {
3029
+ return realpathSync2(workspaceDir);
3030
+ } catch {
3031
+ return resolve2(workspaceDir);
3032
+ }
3033
+ })();
3034
+ const holt = wsHoltDir(ws);
3035
+ const tokens2 = message.match(/(?:~\/|\/|~(?=\s|$))[^\s]*/g) ?? [];
3036
+ const dirs = /* @__PURE__ */ new Set();
3037
+ for (const rawToken of tokens2) {
3038
+ const token = rawToken.replace(/[.,;:!?)"'\]]+$/, "");
3039
+ if (!token) continue;
3040
+ const abs = resolveExisting(token);
3041
+ if (!abs) continue;
3042
+ if (isInside(abs, ws)) continue;
3043
+ if (isInside(abs, holt)) continue;
3044
+ dirs.add(containingDir(abs));
3045
+ }
3046
+ return [...dirs];
3047
+ }
3048
+ function claudeAccessArgs(grantedDirs) {
3049
+ const dirs = [...grantedDirs];
3050
+ if (dirs.length === 0) return [];
3051
+ const args = dirs.map((d) => `--add-dir=${d}`);
3052
+ args.push("--allowedTools=Read,Glob,Grep");
3053
+ return args;
3054
+ }
3055
+
2985
3056
  // src/commands/chat.ts
2986
3057
  function statusLine(brainLabel, history) {
2987
3058
  const live = Math.min(history.length, MAX_REPLAY_TURNS);
@@ -2989,6 +3060,25 @@ function statusLine(brainLabel, history) {
2989
3060
  const mem = memStats().turns;
2990
3061
  return c.dim(" " + brainLabel + " ") + c.dim("[") + bar(frac) + c.dim("]") + c.dim(` recent ${live}/${MAX_REPLAY_TURNS} \xB7 ${mem} in memory`);
2991
3062
  }
3063
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
3064
+ "exit",
3065
+ "quit",
3066
+ "q",
3067
+ "help",
3068
+ "h",
3069
+ "clear",
3070
+ "memory",
3071
+ "mem",
3072
+ "output",
3073
+ "save",
3074
+ "setting",
3075
+ "settings",
3076
+ "brain",
3077
+ "skill",
3078
+ "skills",
3079
+ "allow",
3080
+ "allowed"
3081
+ ]);
2992
3082
  function help() {
2993
3083
  console.log(c.dim([
2994
3084
  " commands:",
@@ -2997,6 +3087,8 @@ function help() {
2997
3087
  ' /skill [name] [input] run a skill, or list them. "holt skill" manages them.',
2998
3088
  " /output [fmt] show or set output format: markdown | html",
2999
3089
  " /save [name] save the last reply to a file in this folder",
3090
+ " /allow [path] let this session read a folder outside this one (session-only)",
3091
+ " /allowed list folders granted outside-access this session",
3000
3092
  " /setting configure brains, API brains, and your launch command",
3001
3093
  " /clear forget this session so far (saved memory stays)",
3002
3094
  " /help this list",
@@ -3043,6 +3135,7 @@ async function chat() {
3043
3135
  const session = newSessionId();
3044
3136
  const history = [];
3045
3137
  let lastReply = "";
3138
+ const grantedDirs = /* @__PURE__ */ new Set();
3046
3139
  const sb = createStatusBar();
3047
3140
  const showStatus = () => {
3048
3141
  const line = statusLine(active.label, history);
@@ -3058,7 +3151,7 @@ async function chat() {
3058
3151
  if (embedOk && stats.withEmbeddings < stats.turns) {
3059
3152
  console.log(c.dim(` ${stats.turns - stats.withEmbeddings} older moments lack embeddings. Run "holt memory embed" to upgrade them.`));
3060
3153
  }
3061
- console.log(c.dim("Type a message. Commands: /brain /memory /output /save /setting /clear /help /exit"));
3154
+ console.log(c.dim("Type a message. Commands: /brain /memory /output /save /allow /setting /clear /help /exit"));
3062
3155
  showStatus();
3063
3156
  while (true) {
3064
3157
  const raw = await ask(c.accent("\u203A "));
@@ -3074,7 +3167,7 @@ async function chat() {
3074
3167
  }
3075
3168
  promptOverride = inv.prompt;
3076
3169
  console.log(c.dim(` running skill "${inv.skillName}"...`));
3077
- } else if (line.startsWith("/")) {
3170
+ } else if (line.startsWith("/") && KNOWN_COMMANDS.has((line.slice(1).split(/\s+/)[0] || "").toLowerCase())) {
3078
3171
  const parts = line.slice(1).split(/\s+/);
3079
3172
  const cmd = (parts[0] || "").toLowerCase();
3080
3173
  const rest = parts.slice(1).join(" ");
@@ -3169,6 +3262,24 @@ async function chat() {
3169
3262
  console.log(c.dim(" skills: " + (names.join(" ") || "none") + " usage: /skill <name> [input]"));
3170
3263
  continue;
3171
3264
  }
3265
+ if (cmd === "allowed") {
3266
+ if (grantedDirs.size === 0) console.log(c.dim(" no folders outside this one are granted this session."));
3267
+ else {
3268
+ console.log(c.dim(" granted this session (read-only):"));
3269
+ for (const d of grantedDirs) console.log(c.dim(` ${d}`));
3270
+ }
3271
+ continue;
3272
+ }
3273
+ if (cmd === "allow") {
3274
+ if (!rest) {
3275
+ console.log(c.dim(" usage: /allow <path> (grants that path's folder read access for this session)"));
3276
+ continue;
3277
+ }
3278
+ const dir = resolveGrantDir(rest);
3279
+ grantedDirs.add(dir);
3280
+ console.log(c.green(` granted this session: ${dir}`));
3281
+ continue;
3282
+ }
3172
3283
  console.log(c.dim(` unknown command: /${cmd} (try /help)`));
3173
3284
  continue;
3174
3285
  }
@@ -3180,18 +3291,39 @@ async function chat() {
3180
3291
  console.log(c.red(` ${active.label} has no API key. Use /setting to add one, or /brain to switch.`));
3181
3292
  continue;
3182
3293
  }
3294
+ if (promptOverride === null) {
3295
+ const outside = findOutsidePaths(line, workspace());
3296
+ for (const dir of outside) {
3297
+ if (grantedDirs.has(dir)) continue;
3298
+ const ans = (await ask(c.dim(` Allow this session to access ${dir} (outside this folder)? [y/N] `)) ?? "").trim().toLowerCase();
3299
+ if (ans === "y" || ans === "yes") {
3300
+ grantedDirs.add(dir);
3301
+ console.log(c.green(` granted this session: ${dir}`));
3302
+ } else {
3303
+ console.log(c.dim(" not granted."));
3304
+ }
3305
+ }
3306
+ }
3183
3307
  const remembered = await recall(line, session, 4);
3184
- const label = remembered.length ? `${active.label} is thinking (recalled ${remembered.length} moment${remembered.length === 1 ? "" : "s"})...` : `${active.label} is thinking...`;
3308
+ const label = remembered.length ? `Holt is thinking (recalled ${remembered.length} moment${remembered.length === 1 ? "" : "s"})...` : `Holt is thinking...`;
3185
3309
  console.log(c.dim(` ${label}`) + "\n");
3186
3310
  const skillsBlock = skillsPromptBlock();
3187
3311
  const base = renderPrompt(history, line, remembered);
3188
3312
  const prompt = promptOverride ?? (skillsBlock ? skillsBlock + "\n\n" + base : base);
3313
+ let extraArgs = [];
3314
+ if (grantedDirs.size > 0) {
3315
+ if (active.kind === "cli" && cfg.brains[active.id].command === "claude") {
3316
+ extraArgs = claudeAccessArgs(grantedDirs);
3317
+ } else {
3318
+ console.log(c.dim(" note: external file access is only available with a Claude Code brain; skipping granted folders this turn."));
3319
+ }
3320
+ }
3189
3321
  let streamed = false;
3190
3322
  const onChunk = (chunk2) => {
3191
3323
  streamed = true;
3192
3324
  process.stdout.write(chunk2);
3193
3325
  };
3194
- const res = active.kind === "cli" ? await runBrain(cfg.brains[active.id], prompt, onChunk) : await runApiBrain(active.brain, prompt, onChunk);
3326
+ const res = active.kind === "cli" ? await runBrain(cfg.brains[active.id], prompt, onChunk, extraArgs) : await runApiBrain(active.brain, prompt, onChunk);
3195
3327
  if (res.ok) {
3196
3328
  if (!streamed) console.log(res.text);
3197
3329
  if (!res.text.endsWith("\n")) console.log("");
@@ -3251,7 +3383,7 @@ async function login(which) {
3251
3383
  }
3252
3384
 
3253
3385
  // src/commands/memory.ts
3254
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
3386
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3255
3387
  import { basename } from "path";
3256
3388
  async function memoryCmd(sub, rest = []) {
3257
3389
  const { ask, close } = createReader();
@@ -3299,7 +3431,7 @@ async function memoryCmd(sub, rest = []) {
3299
3431
  return;
3300
3432
  }
3301
3433
  if (action === "facts") {
3302
- if (!existsSync9(factsMdPath())) {
3434
+ if (!existsSync10(factsMdPath())) {
3303
3435
  console.log(c.dim("\n No facts distilled yet. They form when you end a chat session.\n"));
3304
3436
  close();
3305
3437
  return;
@@ -3418,7 +3550,7 @@ function globalCmd(act, rest) {
3418
3550
  }
3419
3551
 
3420
3552
  // src/commands/wiki.ts
3421
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
3553
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
3422
3554
  import { join as join10 } from "path";
3423
3555
  import { spawn as spawn3 } from "child_process";
3424
3556
  function titleToSlug(title) {
@@ -3612,7 +3744,7 @@ function listCmd() {
3612
3744
  for (const slug of slugs) {
3613
3745
  const pg = readPage(slug);
3614
3746
  if (!pg) continue;
3615
- const size = existsSync10(join10(wikiDir(), slug + ".md")) ? (readFileSync11(join10(wikiDir(), slug + ".md"), "utf8").length / 1024).toFixed(1) : "0.0";
3747
+ const size = existsSync11(join10(wikiDir(), slug + ".md")) ? (readFileSync11(join10(wikiDir(), slug + ".md"), "utf8").length / 1024).toFixed(1) : "0.0";
3616
3748
  console.log(` ${c.cyan(pg.title.padEnd(28).slice(0, 28))} ${c.dim(pg.updated || " ")} ${size} KB`);
3617
3749
  }
3618
3750
  console.log("");
@@ -3700,8 +3832,8 @@ function setupCmd(cfg) {
3700
3832
  `));
3701
3833
  }
3702
3834
  function openCmd() {
3703
- const target = existsSync10(wikiIndexPath()) ? wikiIndexPath() : wikiDir();
3704
- if (!existsSync10(target)) {
3835
+ const target = existsSync11(wikiIndexPath()) ? wikiIndexPath() : wikiDir();
3836
+ if (!existsSync11(target)) {
3705
3837
  console.log(c.dim('\n No wiki yet. Run "holt wiki sync" first.\n'));
3706
3838
  return;
3707
3839
  }
@@ -3791,20 +3923,20 @@ import {
3791
3923
  mkdirSync as mkdirSync9,
3792
3924
  writeFileSync as writeFileSync10,
3793
3925
  readFileSync as readFileSync13,
3794
- existsSync as existsSync12,
3926
+ existsSync as existsSync13,
3795
3927
  readdirSync as readdirSync3,
3796
- statSync as statSync5,
3928
+ statSync as statSync6,
3797
3929
  rmSync as rmSync5,
3798
3930
  cpSync,
3799
3931
  mkdtempSync
3800
3932
  } from "fs";
3801
- import { join as join12, resolve as resolve3, sep, isAbsolute as isAbsolute3 } from "path";
3933
+ import { join as join12, resolve as resolve4, sep as sep2, isAbsolute as isAbsolute4 } from "path";
3802
3934
  import { tmpdir } from "os";
3803
3935
  import { spawnSync as spawnSync2 } from "child_process";
3804
3936
 
3805
3937
  // src/registry.ts
3806
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
3807
- import { join as join11, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
3938
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync8, statSync as statSync5 } from "fs";
3939
+ import { join as join11, isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
3808
3940
  import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
3809
3941
  var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/holt-os/registry/main/registry.json";
3810
3942
  var REGISTRY_REPO_URL = "https://github.com/holt-os/registry";
@@ -3849,7 +3981,7 @@ function parseRegistry(text) {
3849
3981
  function readCache() {
3850
3982
  try {
3851
3983
  const path = registryCachePath();
3852
- if (!existsSync11(path)) return null;
3984
+ if (!existsSync12(path)) return null;
3853
3985
  const raw = JSON.parse(readFileSync12(path, "utf8"));
3854
3986
  if (!raw || typeof raw.fetchedAt !== "number" || typeof raw.url !== "string") return null;
3855
3987
  const registry = coerceRegistry(raw.registry);
@@ -3876,12 +4008,12 @@ function localPathFor(loc) {
3876
4008
  }
3877
4009
  }
3878
4010
  if (/^https?:\/\//i.test(loc)) return null;
3879
- return isAbsolute2(loc) ? loc : resolve2(loc);
4011
+ return isAbsolute3(loc) ? loc : resolve3(loc);
3880
4012
  }
3881
4013
  var UNREACHABLE_MSG = "no registry reachable; set HOLT_REGISTRY_URL or the community registry is not live yet";
3882
4014
  function loadLocal(path) {
3883
4015
  try {
3884
- if (!existsSync11(path)) {
4016
+ if (!existsSync12(path)) {
3885
4017
  return { registry: null, fromCache: false, error: UNREACHABLE_MSG };
3886
4018
  }
3887
4019
  const registry = parseRegistry(readFileSync12(path, "utf8"));
@@ -4004,21 +4136,21 @@ function splitSubpath(source) {
4004
4136
  return { base: source.slice(0, i), subpath: source.slice(i + 1) };
4005
4137
  }
4006
4138
  function resolveSubpathDir(fetched, subpath) {
4007
- if (isAbsolute3(subpath)) return null;
4139
+ if (isAbsolute4(subpath)) return null;
4008
4140
  const segments = subpath.split(/[\\/]/);
4009
4141
  if (segments.some((s) => s === "..")) return null;
4010
- const target = resolve3(fetched, subpath);
4011
- const root = resolve3(fetched);
4012
- if (target !== root && !target.startsWith(root + sep)) return null;
4142
+ const target = resolve4(fetched, subpath);
4143
+ const root = resolve4(fetched);
4144
+ if (target !== root && !target.startsWith(root + sep2)) return null;
4013
4145
  return target;
4014
4146
  }
4015
4147
  function findSkillDir(root) {
4016
- if (existsSync12(join12(root, "SKILL.md"))) return root;
4148
+ if (existsSync13(join12(root, "SKILL.md"))) return root;
4017
4149
  let subs = [];
4018
4150
  try {
4019
4151
  subs = readdirSync3(root).filter((f) => {
4020
4152
  try {
4021
- return statSync5(join12(root, f)).isDirectory();
4153
+ return statSync6(join12(root, f)).isDirectory();
4022
4154
  } catch {
4023
4155
  return false;
4024
4156
  }
@@ -4026,7 +4158,7 @@ function findSkillDir(root) {
4026
4158
  } catch {
4027
4159
  return null;
4028
4160
  }
4029
- const withSkill = subs.filter((f) => existsSync12(join12(root, f, "SKILL.md")));
4161
+ const withSkill = subs.filter((f) => existsSync13(join12(root, f, "SKILL.md")));
4030
4162
  if (withSkill.length === 1) return join12(root, withSkill[0]);
4031
4163
  return null;
4032
4164
  }
@@ -4081,7 +4213,7 @@ function cmdCreate(name, global) {
4081
4213
  }
4082
4214
  const scope = global ? "global" : "workspace";
4083
4215
  const dir = join12(skillsRoot(scope), clean);
4084
- if (existsSync12(dir)) {
4216
+ if (existsSync13(dir)) {
4085
4217
  console.error(c.red(`
4086
4218
  A ${scope} skill named "${clean}" already exists.`));
4087
4219
  console.error(c.dim(" " + dir + "\n"));
@@ -4112,8 +4244,8 @@ function installFromSource(source, scope) {
4112
4244
  }
4113
4245
  fetched = tempDir;
4114
4246
  } else {
4115
- fetched = resolve3(base);
4116
- if (!existsSync12(fetched)) {
4247
+ fetched = resolve4(base);
4248
+ if (!existsSync13(fetched)) {
4117
4249
  console.error(c.red(`
4118
4250
  No such path: ${fetched}
4119
4251
  `));
@@ -4131,7 +4263,7 @@ function installFromSource(source, scope) {
4131
4263
  process.exitCode = 1;
4132
4264
  return false;
4133
4265
  }
4134
- if (!existsSync12(target) || !statSync5(target).isDirectory()) {
4266
+ if (!existsSync13(target) || !statSync6(target).isDirectory()) {
4135
4267
  console.error(c.red(`
4136
4268
  Subpath "${subpath}" does not exist in the source.
4137
4269
  `));
@@ -4163,7 +4295,7 @@ function installFromSource(source, scope) {
4163
4295
  return false;
4164
4296
  }
4165
4297
  const dest = join12(skillsRoot(scope), skillName);
4166
- if (existsSync12(dest)) {
4298
+ if (existsSync13(dest)) {
4167
4299
  console.error(c.red(`
4168
4300
  A ${scope} skill named "${skillName}" already exists.`));
4169
4301
  console.error(c.dim(" " + dest + "\n"));
@@ -4173,13 +4305,13 @@ function installFromSource(source, scope) {
4173
4305
  mkdirSync9(skillsRoot(scope), { recursive: true });
4174
4306
  cpSync(skillDir, dest, { recursive: true });
4175
4307
  const nestedGit = join12(dest, ".git");
4176
- if (existsSync12(nestedGit)) rmSync5(nestedGit, { recursive: true, force: true });
4308
+ if (existsSync13(nestedGit)) rmSync5(nestedGit, { recursive: true, force: true });
4177
4309
  console.log(c.green(`
4178
4310
  Installed ${scope} skill "${skillName}".`));
4179
4311
  console.log(c.dim(" " + join12(dest, "SKILL.md") + "\n"));
4180
4312
  return true;
4181
4313
  } finally {
4182
- if (tempDir && existsSync12(tempDir)) {
4314
+ if (tempDir && existsSync13(tempDir)) {
4183
4315
  try {
4184
4316
  rmSync5(tempDir, { recursive: true, force: true });
4185
4317
  } catch {
@@ -4194,7 +4326,7 @@ async function cmdAdd(source, global, refresh) {
4194
4326
  }
4195
4327
  const scope = global ? "global" : "workspace";
4196
4328
  const { base } = splitSubpath(source);
4197
- if (isGitUrl(base) || existsSync12(resolve3(base))) {
4329
+ if (isGitUrl(base) || existsSync13(resolve4(base))) {
4198
4330
  installFromSource(source, scope);
4199
4331
  return;
4200
4332
  }
@@ -4277,7 +4409,7 @@ function cmdPublish(name) {
4277
4409
  description = (data.description || loaded.skill.description || "").trim();
4278
4410
  } else {
4279
4411
  const file = join12(process.cwd(), "SKILL.md");
4280
- if (!existsSync12(file)) {
4412
+ if (!existsSync13(file)) {
4281
4413
  console.error(c.red("\n No SKILL.md in the current directory, and no skill name given."));
4282
4414
  console.error(c.dim(" Usage: holt skill publish [<name>]\n"));
4283
4415
  process.exitCode = 1;
@@ -4321,9 +4453,9 @@ async function cmdRemove(name, ask) {
4321
4453
  }
4322
4454
  const wsDir = join12(skillsRoot("workspace"), clean);
4323
4455
  const globalDir = join12(skillsRoot("global"), clean);
4324
- const target = existsSync12(wsDir) ? wsDir : existsSync12(globalDir) ? globalDir : "";
4456
+ const target = existsSync13(wsDir) ? wsDir : existsSync13(globalDir) ? globalDir : "";
4325
4457
  if (!target) {
4326
- if (existsSync12(join12(skillsRoot("builtin"), clean))) {
4458
+ if (existsSync13(join12(skillsRoot("builtin"), clean))) {
4327
4459
  console.error(c.red(`
4328
4460
  "${clean}" is a built-in skill and cannot be removed; it ships inside Holt.`));
4329
4461
  console.error(c.dim(" Override it instead: create a workspace or global skill with the same name.\n"));
@@ -4396,8 +4528,8 @@ async function skillCmd(sub, rest = []) {
4396
4528
  }
4397
4529
 
4398
4530
  // src/commands/graph.ts
4399
- import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync13 } from "fs";
4400
- import { join as join14, dirname as dirname4, resolve as resolve4 } from "path";
4531
+ import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync14 } from "fs";
4532
+ import { join as join14, dirname as dirname5, resolve as resolve5 } from "path";
4401
4533
  import { spawn as spawn4 } from "child_process";
4402
4534
 
4403
4535
  // src/communities.ts
@@ -5194,8 +5326,8 @@ function renderGraphHtml(graph2, meta) {
5194
5326
  }
5195
5327
 
5196
5328
  // src/ingest.ts
5197
- import { readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
5198
- import { join as join13, relative, sep as sep2, extname, dirname as dirname3, basename as basename2 } from "path";
5329
+ import { readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
5330
+ import { join as join13, relative, sep as sep3, extname, dirname as dirname4, basename as basename2 } from "path";
5199
5331
  var CODE_EXT = /* @__PURE__ */ new Set([
5200
5332
  "js",
5201
5333
  "jsx",
@@ -5246,7 +5378,7 @@ var MAX_FILES = 2e3;
5246
5378
  var MAX_FILE_BYTES = 400 * 1024;
5247
5379
  var MAX_TOTAL_BYTES = 64 * 1024 * 1024;
5248
5380
  function toPosix(p) {
5249
- return sep2 === "/" ? p : p.split(sep2).join("/");
5381
+ return sep3 === "/" ? p : p.split(sep3).join("/");
5250
5382
  }
5251
5383
  function ingestFolder(root, want = { code: true, docs: true }) {
5252
5384
  const files = [];
@@ -5271,7 +5403,7 @@ function ingestFolder(root, want = { code: true, docs: true }) {
5271
5403
  const full = join13(dir, name);
5272
5404
  let st;
5273
5405
  try {
5274
- st = statSync6(full);
5406
+ st = statSync7(full);
5275
5407
  } catch {
5276
5408
  skipped.unreadable++;
5277
5409
  continue;
@@ -5435,7 +5567,7 @@ function resolvePySpec(f, spec, byId) {
5435
5567
  return null;
5436
5568
  }
5437
5569
  function toPosixDir(id) {
5438
- const d = toPosix(dirname3(id));
5570
+ const d = toPosix(dirname4(id));
5439
5571
  return d === "" || d === "." ? "." : d;
5440
5572
  }
5441
5573
  var MD_LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g;
@@ -5607,7 +5739,7 @@ async function graph(args = []) {
5607
5739
  const ingestCode = opts.code;
5608
5740
  const ingestDocs = opts.docs;
5609
5741
  const turns = loadTurns();
5610
- const includeWiki = opts.wiki !== void 0 ? opts.wiki : existsSync13(wikiDir());
5742
+ const includeWiki = opts.wiki !== void 0 ? opts.wiki : existsSync14(wikiDir());
5611
5743
  const wikiPages = includeWiki ? wikiGraphPages() : [];
5612
5744
  let fileNodeCount = 0;
5613
5745
  let docNodeCount = 0;
@@ -5637,8 +5769,8 @@ async function graph(args = []) {
5637
5769
  if (codeGraph) assignCommunities(g);
5638
5770
  if (opts.report) {
5639
5771
  const md = renderReport(g, { workspace: workspace() });
5640
- const reportPath = opts.out ? resolve4(opts.out) : join14(workspace(), "GRAPH_REPORT.md");
5641
- mkdirSync10(dirname4(reportPath), { recursive: true });
5772
+ const reportPath = opts.out ? resolve5(opts.out) : join14(workspace(), "GRAPH_REPORT.md");
5773
+ mkdirSync10(dirname5(reportPath), { recursive: true });
5642
5774
  writeFileSync11(reportPath, md, "utf8");
5643
5775
  const communities = new Set(g.nodes.map((n) => n.community ?? "0")).size;
5644
5776
  console.log("\n" + c.accent("Graph report written") + c.dim(" (this folder)"));
@@ -5659,8 +5791,8 @@ async function graph(args = []) {
5659
5791
  concepts: conceptCount,
5660
5792
  edges: g.edges.length
5661
5793
  });
5662
- const outPath = opts.out ? resolve4(opts.out) : join14(wsHoltDir(), "graph.html");
5663
- mkdirSync10(dirname4(outPath), { recursive: true });
5794
+ const outPath = opts.out ? resolve5(opts.out) : join14(wsHoltDir(), "graph.html");
5795
+ mkdirSync10(dirname5(outPath), { recursive: true });
5664
5796
  writeFileSync11(outPath, html, "utf8");
5665
5797
  console.log("\n" + c.accent("Memory graph built") + c.dim(" (this folder)"));
5666
5798
  const parts = [`${turns.length} turns`, `${conceptCount} concepts`];
@@ -5773,7 +5905,7 @@ function recoverId(line) {
5773
5905
  return null;
5774
5906
  }
5775
5907
  function runMcpServer(opts) {
5776
- return new Promise((resolve8) => {
5908
+ return new Promise((resolve9) => {
5777
5909
  const stdin = process.stdin;
5778
5910
  let buffer = "";
5779
5911
  let chain = Promise.resolve();
@@ -5825,11 +5957,11 @@ function runMcpServer(opts) {
5825
5957
  processLine(buffer);
5826
5958
  buffer = "";
5827
5959
  }
5828
- chain.then(() => resolve8()).catch(() => resolve8());
5960
+ chain.then(() => resolve9()).catch(() => resolve9());
5829
5961
  });
5830
5962
  stdin.on("error", (e) => {
5831
5963
  console.error(`[mcp] stdin error: ${e.message}`);
5832
- chain.then(() => resolve8()).catch(() => resolve8());
5964
+ chain.then(() => resolve9()).catch(() => resolve9());
5833
5965
  });
5834
5966
  stdin.resume();
5835
5967
  });
@@ -5949,7 +6081,7 @@ function holtTools() {
5949
6081
  }
5950
6082
 
5951
6083
  // src/version.ts
5952
- var VERSION = "0.12.1";
6084
+ var VERSION = "0.13.0";
5953
6085
 
5954
6086
  // src/commands/mcp.ts
5955
6087
  function printSetup() {
@@ -5995,10 +6127,10 @@ async function mcp(sub, rest = []) {
5995
6127
  }
5996
6128
 
5997
6129
  // src/commands/hook.ts
5998
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync11, copyFileSync, realpathSync as realpathSync2, appendFileSync as appendFileSync2, statSync as statSync7 } from "fs";
6130
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync11, copyFileSync, realpathSync as realpathSync3, appendFileSync as appendFileSync2, statSync as statSync8 } from "fs";
5999
6131
  import { spawnSync as spawnSync3 } from "child_process";
6000
- import { join as join15, dirname as dirname5 } from "path";
6001
- import { homedir as homedir3 } from "os";
6132
+ import { join as join15, dirname as dirname6 } from "path";
6133
+ import { homedir as homedir4 } from "os";
6002
6134
  var EVENT = {
6003
6135
  inject: "UserPromptSubmit",
6004
6136
  capture: "Stop"
@@ -6008,13 +6140,13 @@ var HOOK_CMD = {
6008
6140
  capture: "holt hook capture"
6009
6141
  };
6010
6142
  function globalSettingsPath() {
6011
- return join15(homedir3(), ".claude", "settings.json");
6143
+ return join15(homedir4(), ".claude", "settings.json");
6012
6144
  }
6013
6145
  function projectSettingsPath() {
6014
6146
  return join15(process.cwd(), ".claude", "settings.json");
6015
6147
  }
6016
6148
  function readSettings(path) {
6017
- if (!existsSync14(path)) return {};
6149
+ if (!existsSync15(path)) return {};
6018
6150
  try {
6019
6151
  const parsed = JSON.parse(readFileSync15(path, "utf8"));
6020
6152
  return parsed && typeof parsed === "object" ? parsed : {};
@@ -6023,11 +6155,11 @@ function readSettings(path) {
6023
6155
  }
6024
6156
  }
6025
6157
  function writeSettings(path, settings) {
6026
- mkdirSync11(dirname5(path), { recursive: true });
6158
+ mkdirSync11(dirname6(path), { recursive: true });
6027
6159
  writeFileSync12(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
6028
6160
  }
6029
6161
  function backup(path) {
6030
- if (!existsSync14(path)) return null;
6162
+ if (!existsSync15(path)) return null;
6031
6163
  const dest = `${path}.holt-bak`;
6032
6164
  try {
6033
6165
  copyFileSync(path, dest);
@@ -6117,7 +6249,7 @@ function remove(rest) {
6117
6249
  const paths = project ? [projectSettingsPath()] : [globalSettingsPath(), projectSettingsPath()];
6118
6250
  let touchedAny = false;
6119
6251
  for (const path of paths) {
6120
- if (!existsSync14(path)) continue;
6252
+ if (!existsSync15(path)) continue;
6121
6253
  let settings;
6122
6254
  try {
6123
6255
  settings = readSettings(path);
@@ -6147,7 +6279,7 @@ function status(rest) {
6147
6279
  ];
6148
6280
  console.log(c.accent(" Holt ambient hooks"));
6149
6281
  for (const e of entries) {
6150
- if (!existsSync14(e.path)) {
6282
+ if (!existsSync15(e.path)) {
6151
6283
  console.log(c.dim(` ${e.label.padEnd(8)} ${e.path} (no settings file)`));
6152
6284
  continue;
6153
6285
  }
@@ -6167,13 +6299,13 @@ function status(rest) {
6167
6299
  console.log(c.dim(" Runtime hooks only act in trusted Holt folders that already have .holt/memory."));
6168
6300
  }
6169
6301
  function readStdin(timeoutMs) {
6170
- return new Promise((resolve8) => {
6302
+ return new Promise((resolve9) => {
6171
6303
  let input = "";
6172
6304
  let done = false;
6173
6305
  const finish = () => {
6174
6306
  if (done) return;
6175
6307
  done = true;
6176
- resolve8(input);
6308
+ resolve9(input);
6177
6309
  };
6178
6310
  try {
6179
6311
  process.stdin.setEncoding("utf8");
@@ -6197,9 +6329,9 @@ function parseHookInput(raw) {
6197
6329
  }
6198
6330
  }
6199
6331
  function enterFolder(cwd) {
6200
- if (!cwd || !existsSync14(cwd)) return null;
6332
+ if (!cwd || !existsSync15(cwd)) return null;
6201
6333
  try {
6202
- const real = realpathSync2(cwd);
6334
+ const real = realpathSync3(cwd);
6203
6335
  process.chdir(real);
6204
6336
  return real;
6205
6337
  } catch {
@@ -6208,7 +6340,7 @@ function enterFolder(cwd) {
6208
6340
  }
6209
6341
  function guardTrusted(real) {
6210
6342
  if (!isTrusted(real)) return false;
6211
- if (!existsSync14(memDir())) return false;
6343
+ if (!existsSync15(memDir())) return false;
6212
6344
  return true;
6213
6345
  }
6214
6346
  function hookLog(msg) {
@@ -6222,11 +6354,37 @@ function hookLog(msg) {
6222
6354
  function fileBytes(path) {
6223
6355
  if (!path) return 0;
6224
6356
  try {
6225
- return statSync7(path).size;
6357
+ return statSync8(path).size;
6226
6358
  } catch {
6227
6359
  return 0;
6228
6360
  }
6229
6361
  }
6362
+ var THROTTLE_MIN_NEW_EXCHANGES = 2;
6363
+ function hookStatePath() {
6364
+ return join15(GLOBAL_DIR, "hook-state.json");
6365
+ }
6366
+ function readHookState() {
6367
+ try {
6368
+ const parsed = JSON.parse(readFileSync15(hookStatePath(), "utf8"));
6369
+ if (!parsed || typeof parsed !== "object") return {};
6370
+ return parsed;
6371
+ } catch {
6372
+ return {};
6373
+ }
6374
+ }
6375
+ function lastProcessedExchanges(state, sessionId) {
6376
+ const rec = state[sessionId];
6377
+ return rec && typeof rec.lastExchanges === "number" ? rec.lastExchanges : 0;
6378
+ }
6379
+ function saveHookState(sessionId, exchanges) {
6380
+ try {
6381
+ mkdirSync11(GLOBAL_DIR, { recursive: true });
6382
+ const state = readHookState();
6383
+ state[sessionId] = { lastExchanges: exchanges };
6384
+ writeFileSync12(hookStatePath(), JSON.stringify(state, null, 2) + "\n", "utf8");
6385
+ } catch {
6386
+ }
6387
+ }
6230
6388
  async function inject() {
6231
6389
  const debug = !!process.env.HOLT_HOOK_DEBUG;
6232
6390
  const dlog = (m) => {
@@ -6325,17 +6483,17 @@ function readTranscript(path) {
6325
6483
  return out;
6326
6484
  }
6327
6485
  function resolveCommandPath(command) {
6328
- if (command.startsWith("/") && existsSync14(command)) return command;
6486
+ if (command.startsWith("/") && existsSync15(command)) return command;
6329
6487
  try {
6330
6488
  const shell = process.env.SHELL || "/bin/sh";
6331
6489
  const res = spawnSync3(shell, ["-lc", "command -v " + command], { encoding: "utf8" });
6332
6490
  if (res.status === 0) {
6333
6491
  const p = (res.stdout || "").trim().split("\n")[0]?.trim();
6334
- if (p && p.startsWith("/") && existsSync14(p)) return p;
6492
+ if (p && p.startsWith("/") && existsSync15(p)) return p;
6335
6493
  }
6336
6494
  } catch {
6337
6495
  }
6338
- const home = homedir3();
6496
+ const home = homedir4();
6339
6497
  const dirs = [
6340
6498
  join15(home, ".local", "bin"),
6341
6499
  "/opt/homebrew/bin",
@@ -6346,7 +6504,7 @@ function resolveCommandPath(command) {
6346
6504
  ];
6347
6505
  for (const dir of dirs) {
6348
6506
  const candidate = join15(dir, command);
6349
- if (existsSync14(candidate)) return candidate;
6507
+ if (existsSync15(candidate)) return candidate;
6350
6508
  }
6351
6509
  return null;
6352
6510
  }
@@ -6433,8 +6591,17 @@ async function capture() {
6433
6591
  hookLog(`capture cwd=${real} event=Stop -> only ${exchanges} exchange(s) (<${MIN_EXCHANGES_FOR_EXTRACTION}, skipped)`);
6434
6592
  return;
6435
6593
  }
6594
+ const throttleId = data.session_id || null;
6595
+ const lastProcessed = throttleId ? lastProcessedExchanges(readHookState(), throttleId) : 0;
6596
+ if (throttleId && exchanges - lastProcessed < THROTTLE_MIN_NEW_EXCHANGES) {
6597
+ hookLog(
6598
+ `capture cwd=${real} event=Stop -> throttled (exchanges=${exchanges} lastProcessed=${lastProcessed}, need +${THROTTLE_MIN_NEW_EXCHANGES})`
6599
+ );
6600
+ return;
6601
+ }
6436
6602
  const session = "hook-" + (data.session_id || newSessionId());
6437
6603
  const n = await extractAndSaveFacts(brain, cfg, history, session);
6604
+ if (throttleId) saveHookState(throttleId, exchanges);
6438
6605
  hookLog(`capture cwd=${real} event=Stop -> saved ${n} fact${n === 1 ? "" : "s"}`);
6439
6606
  if (n > 0) process.stderr.write(`holt hook capture: saved ${n} fact${n === 1 ? "" : "s"} to ${memDir()}
6440
6607
  `);
@@ -6507,7 +6674,7 @@ async function hook(sub, rest = []) {
6507
6674
 
6508
6675
  // src/commands/run.ts
6509
6676
  import { writeFileSync as writeFileSync13 } from "fs";
6510
- import { resolve as resolve5 } from "path";
6677
+ import { resolve as resolve6 } from "path";
6511
6678
  var USAGE3 = [
6512
6679
  " holt run <task> [options]",
6513
6680
  "",
@@ -6596,7 +6763,7 @@ async function run(args) {
6596
6763
  process.stdout.write("\n");
6597
6764
  }
6598
6765
  if (p.out) {
6599
- const target = resolve5(process.cwd(), p.out);
6766
+ const target = resolve6(process.cwd(), p.out);
6600
6767
  try {
6601
6768
  writeFileSync13(target, result.text, "utf8");
6602
6769
  process.stderr.write(c.dim(`saved to ${target}`) + "\n");
@@ -6609,11 +6776,11 @@ async function run(args) {
6609
6776
 
6610
6777
  // src/commands/schedule.ts
6611
6778
  import { spawnSync as spawnSync5 } from "child_process";
6612
- import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync as rmSync6, existsSync as existsSync15 } from "fs";
6613
- import { dirname as dirname6 } from "path";
6779
+ import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync as rmSync6, existsSync as existsSync16 } from "fs";
6780
+ import { dirname as dirname7 } from "path";
6614
6781
 
6615
6782
  // src/scheduler.ts
6616
- import { homedir as homedir4 } from "os";
6783
+ import { homedir as homedir5 } from "os";
6617
6784
  import { join as join16 } from "path";
6618
6785
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync14, mkdirSync as mkdirSync12 } from "fs";
6619
6786
  import { randomUUID as randomUUID4 } from "crypto";
@@ -6628,7 +6795,7 @@ function logPath(id) {
6628
6795
  return join16(logsDir(), `${id}.log`);
6629
6796
  }
6630
6797
  function plistPath(id) {
6631
- return join16(homedir4(), "Library", "LaunchAgents", `com.holt.${id}.plist`);
6798
+ return join16(homedir5(), "Library", "LaunchAgents", `com.holt.${id}.plist`);
6632
6799
  }
6633
6800
  function loadJobs() {
6634
6801
  try {
@@ -6774,7 +6941,7 @@ function parseAdd(rest) {
6774
6941
  }
6775
6942
  function installDarwin(job, holtPath) {
6776
6943
  const path = plistPath(job.id);
6777
- mkdirSync13(dirname6(path), { recursive: true });
6944
+ mkdirSync13(dirname7(path), { recursive: true });
6778
6945
  writeFileSync15(path, buildLaunchdPlist(job, holtPath), "utf8");
6779
6946
  const res = spawnSync5("launchctl", ["load", "-w", path], { encoding: "utf8" });
6780
6947
  if (res.status === 0) {
@@ -6863,7 +7030,7 @@ function listCmd2() {
6863
7030
  }
6864
7031
  function removeDarwin(id) {
6865
7032
  const path = plistPath(id);
6866
- if (existsSync15(path)) {
7033
+ if (existsSync16(path)) {
6867
7034
  spawnSync5("launchctl", ["unload", path], { encoding: "utf8" });
6868
7035
  try {
6869
7036
  rmSync6(path);
@@ -6926,8 +7093,8 @@ async function schedule(sub, rest = []) {
6926
7093
 
6927
7094
  // src/commands/routine.ts
6928
7095
  import { spawnSync as spawnSync6 } from "child_process";
6929
- import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, rmSync as rmSync7, existsSync as existsSync16 } from "fs";
6930
- import { dirname as dirname7, resolve as resolve6 } from "path";
7096
+ import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, rmSync as rmSync7, existsSync as existsSync17 } from "fs";
7097
+ import { dirname as dirname8, resolve as resolve7 } from "path";
6931
7098
 
6932
7099
  // src/routines.ts
6933
7100
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, mkdirSync as mkdirSync14 } from "fs";
@@ -7008,7 +7175,7 @@ function routineJobId(name) {
7008
7175
  }
7009
7176
  function installDarwin2(job, holtPath) {
7010
7177
  const path = plistPath(job.id);
7011
- mkdirSync15(dirname7(path), { recursive: true });
7178
+ mkdirSync15(dirname8(path), { recursive: true });
7012
7179
  writeFileSync17(path, buildLaunchdPlist(job, holtPath), "utf8");
7013
7180
  const res = spawnSync6("launchctl", ["load", "-w", path], { encoding: "utf8" });
7014
7181
  if (res.status === 0) {
@@ -7053,7 +7220,7 @@ function installSchedule(r) {
7053
7220
  }
7054
7221
  function removeScheduleDarwin(id) {
7055
7222
  const path = plistPath(id);
7056
- if (existsSync16(path)) {
7223
+ if (existsSync17(path)) {
7057
7224
  spawnSync6("launchctl", ["unload", path], { encoding: "utf8" });
7058
7225
  try {
7059
7226
  rmSync7(path);
@@ -7247,7 +7414,7 @@ async function runCmd(rest) {
7247
7414
  if (!quiet && !streamed) process.stdout.write(result.text);
7248
7415
  if (!quiet && (streamed || result.text) && !result.text.endsWith("\n")) process.stdout.write("\n");
7249
7416
  if (outTarget) {
7250
- const target = resolve6(routine2.workspace, outTarget);
7417
+ const target = resolve7(routine2.workspace, outTarget);
7251
7418
  try {
7252
7419
  writeFileSync17(target, result.text, "utf8");
7253
7420
  if (!quiet) process.stderr.write(c.dim(`saved to ${target}`) + "\n");
@@ -7632,7 +7799,7 @@ async function doctor() {
7632
7799
 
7633
7800
  // src/commands/write.ts
7634
7801
  import { writeFileSync as writeFileSync18 } from "fs";
7635
- import { resolve as resolve7 } from "path";
7802
+ import { resolve as resolve8 } from "path";
7636
7803
 
7637
7804
  // src/antiai.ts
7638
7805
  var EM_DASH = String.fromCharCode(8212);
@@ -7810,7 +7977,7 @@ async function write(args) {
7810
7977
  draft = stripEmDash(draft).trimEnd();
7811
7978
  process.stdout.write(draft + "\n");
7812
7979
  if (p.out) {
7813
- const target = resolve7(process.cwd(), p.out);
7980
+ const target = resolve8(process.cwd(), p.out);
7814
7981
  try {
7815
7982
  writeFileSync18(target, draft + "\n", "utf8");
7816
7983
  process.stderr.write(c.dim(`saved to ${target}`) + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holt-os/holt",
3
- "version": "0.12.1",
3
+ "version": "0.13.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",