@holt-os/holt 0.15.0 → 0.15.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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.js +28 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -494,6 +494,14 @@ It reports, section by section:
494
494
  - **Always-on / Telegram**: whether a Telegram bot is set up, and a note on hosting the bot on a low-power always-on machine while keeping heavy local models on a bigger one.
495
495
  - **Recommended next steps**: a short checklist built from whatever gaps it found.
496
496
 
497
+ And when you would rather not do the steps yourself:
498
+
499
+ ```bash
500
+ holt doctor --fix
501
+ ```
502
+
503
+ `--fix` repairs the common problems in one go: starts Ollama when it is installed but not running, downloads the missing embed model, upgrades any memories saved while embeddings were off, and reports a damaged-config repair if one happened. It ends with a "Repairs" section listing exactly what it did (or "Nothing needed repair"). Every Holt error message points here, so the answer to "something is off" is always the same one command.
504
+
497
505
  It is read-only advice: it changes nothing, needs no trust, and always exits cleanly even when a probe fails.
498
506
 
499
507
  The RAM-to-model table lives in `src/specs.ts` (`LOCAL_MODEL_RECS` / `recommendLocalModel`), the single source of truth the wiki maintainer reads too.
package/dist/cli.js CHANGED
@@ -98,6 +98,18 @@ function createStatusBar() {
98
98
  detach
99
99
  };
100
100
  }
101
+ async function askYesNo(ask, question, def) {
102
+ for (let tries = 0; tries < 3; tries++) {
103
+ const raw = await ask(question);
104
+ if (raw === null) return def;
105
+ const a = raw.trim().toLowerCase();
106
+ if (a === "") return def;
107
+ if (a === "y" || a === "yes") return true;
108
+ if (a === "n" || a === "no") return false;
109
+ console.log(c.dim(` Just "y" or "n" please (enter = ${def ? "yes" : "no"}). You typed "${raw.trim()}".`));
110
+ }
111
+ return def;
112
+ }
101
113
  function createReader() {
102
114
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
103
115
  const buffer = [];
@@ -155,8 +167,7 @@ async function ensureTrusted(ask) {
155
167
  console.log("\n" + c.accent("Trust this folder?"));
156
168
  console.log(" " + ws);
157
169
  console.log(c.dim(" Holt will read and write here: its config, memory, and any files you ask a brain to touch."));
158
- const ans = (await ask(" Trust and continue? [y/N] ") ?? "").trim().toLowerCase();
159
- if (ans === "y" || ans === "yes") {
170
+ if (await askYesNo(ask, " Trust and continue? [y/N] ", false)) {
160
171
  trustDir(ws);
161
172
  console.log(c.green(" Trusted.") + c.dim(" (remembered for next time)") + "\n");
162
173
  return true;
@@ -1369,9 +1380,9 @@ async function setupTelegram(ask) {
1369
1380
  console.log(c.dim(" Option A: open your new bot in Telegram and send it any message,"));
1370
1381
  console.log(c.dim(" then I will auto-detect the chat id."));
1371
1382
  console.log(c.dim(" Option B: paste a numeric chat id yourself."));
1372
- const mode = (await ask("\n Auto-detect from a message you just sent? [Y/n] ") ?? "").trim().toLowerCase();
1383
+ const autoDetect = await askYesNo(ask, "\n Auto-detect from a message you just sent? [Y/n] ", true);
1373
1384
  let allowedChatId = null;
1374
- if (mode === "" || mode === "y" || mode === "yes") {
1385
+ if (autoDetect) {
1375
1386
  console.log(c.dim("\n Checking for a recent message..."));
1376
1387
  const updates = await getUpdates(0, token);
1377
1388
  allowedChatId = latestChatId(updates);
@@ -1912,8 +1923,7 @@ async function runQuestions(ask, depth) {
1912
1923
  return out;
1913
1924
  }
1914
1925
  async function askKeepExcerpt(ask) {
1915
- const a = (await ask(" Store a short excerpt so Holt can match your tone directly? [y/N] ") ?? "").trim().toLowerCase();
1916
- return a === "y" || a === "yes";
1926
+ return askYesNo(ask, " Store a short excerpt so Holt can match your tone directly? [y/N] ", false);
1917
1927
  }
1918
1928
  async function addFileSample(ask, file, v) {
1919
1929
  const path = resolve(process.cwd(), file);
@@ -2161,12 +2171,10 @@ async function init() {
2161
2171
  const toInstall = chosen.filter((id) => !isInstalled(BRAIN_DEFS[id].command));
2162
2172
  const loginWanted = /* @__PURE__ */ new Set();
2163
2173
  for (const id of toInstall) {
2164
- const a = (await ask(` ${BRAIN_DEFS[id].label} is not installed. Sign in after install? [Y/n] `) ?? "").trim().toLowerCase();
2165
- if (a !== "n" && a !== "no") loginWanted.add(id);
2174
+ if (await askYesNo(ask, ` ${BRAIN_DEFS[id].label} is not installed. Sign in after install? [Y/n] `, true)) loginWanted.add(id);
2166
2175
  }
2167
2176
  const connectedApiBrains = [];
2168
- const apiAns = (await ask("\nAlso connect a direct API brain (raw key, no CLI needed)? [y/N] ") ?? "").trim().toLowerCase();
2169
- if (apiAns === "y" || apiAns === "yes") {
2177
+ if (await askYesNo(ask, "\nAlso connect a direct API brain (raw key, no CLI needed)? [y/N] ", false)) {
2170
2178
  const holder = defaultConfig();
2171
2179
  const brain = await connectApiBrain(ask, holder);
2172
2180
  if (brain) connectedApiBrains.push(brain);
@@ -2195,13 +2203,11 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
2195
2203
  } else {
2196
2204
  const ollamaHere = isInstalled("ollama");
2197
2205
  const q = ollamaHere ? `Semantic memory needs a local embed model. Pull ${EMBED_MODEL} with Ollama now? [Y/n] ` : "Enable private semantic memory? Installs Ollama plus a small local embed model. Everything stays on your machine. [Y/n] ";
2198
- const a = (await ask("\n" + q) ?? "").trim().toLowerCase();
2199
- wantMemorySetup = a !== "n" && a !== "no";
2206
+ wantMemorySetup = await askYesNo(ask, "\n" + q, true);
2200
2207
  if (!wantMemorySetup) console.log(c.dim(' Okay. Memory still works with keyword recall; run "holt init" again anytime.'));
2201
2208
  }
2202
2209
  if (!loadTelegramConfig()) {
2203
- const tgAns = (await ask("\nChat with Holt from your phone over Telegram? [y/N] ") ?? "").trim().toLowerCase();
2204
- if (tgAns === "y" || tgAns === "yes") await setupTelegram(ask);
2210
+ if (await askYesNo(ask, "\nChat with Holt from your phone over Telegram? [y/N] ", false)) await setupTelegram(ask);
2205
2211
  }
2206
2212
  close();
2207
2213
  for (const id of toInstall) {
@@ -2261,9 +2267,9 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
2261
2267
  console.log(c.dim('No brain is ready yet. Install one, then run "holt init" again.\n'));
2262
2268
  }
2263
2269
  const voiceReader = createReader();
2264
- const voiceAns = (await voiceReader.ask("Want Holt to learn how you write, so it can draft in your voice? [y/N] ") ?? "").trim().toLowerCase();
2270
+ const voiceYes = await askYesNo(voiceReader.ask, "Want Holt to learn how you write, so it can draft in your voice? [y/N] ", false);
2265
2271
  voiceReader.close();
2266
- if (voiceAns === "y" || voiceAns === "yes") {
2272
+ if (voiceYes) {
2267
2273
  await voiceInterview();
2268
2274
  } else {
2269
2275
  console.log(c.dim(' Skipped. Run "holt voice" anytime to set up your writing voice.\n'));
@@ -4222,9 +4228,9 @@ async function chat() {
4222
4228
  }
4223
4229
  let cfg = loadConfig();
4224
4230
  if (!cfg || !cfg.defaultBrain) {
4225
- const a = (await ask(c.dim("No Holt setup in this folder. Set it up now? [Y/n] ")) ?? "").trim().toLowerCase();
4231
+ const setupYes = await askYesNo(ask, c.dim("No Holt setup in this folder. Set it up now? [Y/n] "), true);
4226
4232
  close();
4227
- if (a === "n" || a === "no") {
4233
+ if (!setupYes) {
4228
4234
  console.log(c.dim(' Run "holt init" here when ready.\n'));
4229
4235
  return;
4230
4236
  }
@@ -4426,8 +4432,7 @@ async function chat() {
4426
4432
  const outside = findOutsidePaths(line, workspace());
4427
4433
  for (const dir of outside) {
4428
4434
  if (grantedDirs.has(dir)) continue;
4429
- const ans = (await ask(c.dim(` Allow this session to access ${dir} (outside this folder)? [y/N] `)) ?? "").trim().toLowerCase();
4430
- if (ans === "y" || ans === "yes") {
4435
+ if (await askYesNo(ask, c.dim(` Allow this session to access ${dir} (outside this folder)? [y/N] `), false)) {
4431
4436
  grantedDirs.add(dir);
4432
4437
  console.log(c.green(` granted this session: ${dir}`));
4433
4438
  } else {
@@ -7384,7 +7389,7 @@ function holtTools() {
7384
7389
  }
7385
7390
 
7386
7391
  // src/version.ts
7387
- var VERSION = "0.15.0";
7392
+ var VERSION = "0.15.1";
7388
7393
 
7389
7394
  // src/commands/mcp.ts
7390
7395
  function printSetup() {
@@ -8695,8 +8700,7 @@ async function update() {
8695
8700
  }
8696
8701
  console.log(` Newer version available: ${c.bold(latest)}`);
8697
8702
  if (isBrewInstall()) {
8698
- const a2 = (await ask(" Update now via Homebrew? [Y/n] ") ?? "").trim().toLowerCase();
8699
- if (a2 === "n" || a2 === "no") {
8703
+ if (!await askYesNo(ask, " Update now via Homebrew? [Y/n] ", true)) {
8700
8704
  console.log(c.dim(" Later, then. The command is: brew upgrade holt\n"));
8701
8705
  return;
8702
8706
  }
@@ -8706,8 +8710,7 @@ async function update() {
8706
8710
  else console.log(c.dim("\n Homebrew reported a problem above. You can retry with: brew upgrade holt\n"));
8707
8711
  return;
8708
8712
  }
8709
- const a = (await ask(" Update now? [Y/n] ") ?? "").trim().toLowerCase();
8710
- if (a === "n" || a === "no") {
8713
+ if (!await askYesNo(ask, " Update now? [Y/n] ", true)) {
8711
8714
  console.log(c.dim(" Later, then. The command is: npm install -g @holt-os/holt\n"));
8712
8715
  return;
8713
8716
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holt-os/holt",
3
- "version": "0.15.0",
3
+ "version": "0.15.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",