@holt-os/holt 0.15.0 → 0.16.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 +19 -10
- package/dist/cli.js +185 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ cd ~/where-you-want-to-work
|
|
|
50
50
|
holt # start your assistant (or use your custom command, e.g. `ai`)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
That is it. Bare `holt` sets the folder up the first time (trust, brain, memory hooks) and then launches the **real interactive brain** (Claude Code, Codex, or Gemini), branded as Holt. Run `holt help` for the full command list
|
|
53
|
+
That is it. Bare `holt` sets the folder up the first time (trust, brain, memory hooks) and then launches the **real interactive brain** (Claude Code, Codex, or Gemini), branded as Holt. Run `holt help` for the full command list.
|
|
54
54
|
|
|
55
55
|
Prefer to step through setup on its own first? `holt init` still does that, then `holt` starts the session.
|
|
56
56
|
|
|
@@ -59,10 +59,12 @@ Prefer to step through setup on its own first? `holt init` still does that, then
|
|
|
59
59
|
`holt` (with no command) is the front door: **your assistant, with memory of you.**
|
|
60
60
|
|
|
61
61
|
1. **Sets up if needed.** In a fresh folder it runs onboarding (trust, pick a brain, memory). In an already-configured folder it just makes sure the folder is trusted, the memory directory exists, and the [ambient memory hooks](#ambient-memory-for-claude-code-holt-hook) are installed.
|
|
62
|
-
|
|
62
|
+
> **Gemini note.** Personal Google accounts can no longer sign in to Gemini CLI (Google retired that login on 18 June 2026 in favour of Antigravity). Gemini still works in Holt with an API key from <https://aistudio.google.com/apikey>; `holt login gemini` walks you through connecting it.
|
|
63
|
+
|
|
64
|
+
2. **Launches the real interactive brain.** It hands the terminal to the brain's own interactive session (`claude`, `codex`, or `gemini` with no `-p` and no prompt), so you keep the brain's full power: agentic edits, tool use, the permission UI, MCP. That is different from Holt's own built-in REPL, which shells out to the brain once per turn and is used for API brains that have no TUI of their own.
|
|
63
65
|
3. **Brands the session as Holt.** See [Branding](#branding-holt-forward) below.
|
|
64
66
|
|
|
65
|
-
If your default brain is a **direct API brain** (no interactive TUI), bare `holt`
|
|
67
|
+
If your default brain is a **direct API brain** (no interactive TUI of its own), bare `holt` opens Holt's own built-in REPL instead, branded the same way. Same command, same intro, no second thing to learn.
|
|
66
68
|
|
|
67
69
|
### Branding (Holt-forward)
|
|
68
70
|
|
|
@@ -96,7 +98,7 @@ During `holt init` you:
|
|
|
96
98
|
|
|
97
99
|
## Using it
|
|
98
100
|
|
|
99
|
-
Inside
|
|
101
|
+
Inside a Holt session:
|
|
100
102
|
|
|
101
103
|
```
|
|
102
104
|
/brain list your brains and see which is active
|
|
@@ -127,7 +129,7 @@ Holt runs the brain in the folder you launched it from, so by default the brain
|
|
|
127
129
|
- `/allow <path>` grants a folder up front with no prompt.
|
|
128
130
|
- `/allowed` lists what you have granted.
|
|
129
131
|
|
|
130
|
-
Grants are **in-memory and session-scoped**: nothing is written to disk, and they reset the next time you run `holt
|
|
132
|
+
Grants are **in-memory and session-scoped**: nothing is written to disk, and they reset the next time you run `holt`. 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`.)
|
|
131
133
|
|
|
132
134
|
***
|
|
133
135
|
|
|
@@ -248,7 +250,7 @@ holt wiki setup # recommend a local model for this machine's RAM
|
|
|
248
250
|
|
|
249
251
|
**How sync works.** `holt wiki sync` gathers facts added since the last sync (a marker in `.holt/wiki/.state.json`), embeds each one locally, and routes it to the nearest existing page by cosine similarity. Related facts land on the same page; a fact that matches nothing new starts a fresh page. Facts are grouped by target page and the maintainer is called **once per changed page** (not once per fact), so it only rewrites the few pages that actually changed. Routing is always local and free, which is what keeps the maintainer's cost minimal. `holt wiki rebuild` folds every fact from scratch.
|
|
250
252
|
|
|
251
|
-
**Self-maintaining (auto-sync).** Turn on `holt wiki auto on` (persists `wiki.autoSync: true` for the folder) and the wiki keeps itself current with no manual command. It syncs **after fact distillation** at the end of a
|
|
253
|
+
**Self-maintaining (auto-sync).** Turn on `holt wiki auto on` (persists `wiki.autoSync: true` for the folder) and the wiki keeps itself current with no manual command. It syncs **after fact distillation** at the end of a Holt session, and ambiently when the Claude Code **Stop** hook fires (`holt hook capture`), so newly captured facts fold straight into pages. Auto-sync is silent, best-effort, and never blocks exit: it reuses the exact same sync engine as `holt wiki sync`, so there is no behavior drift. Left off (the default), nothing changes and you sync by hand. `holt wiki auto` with no argument shows the current state; `holt wiki status` surfaces it too.
|
|
252
254
|
|
|
253
255
|
**Lint that can fix.** `holt wiki lint` audits pages for contradictions, duplicates, and gaps and prints a report only (files untouched). Add `--fix` and Holt asks the maintainer to return corrected page bodies and **applies them**, rewriting the affected pages (provenance/`sources` is preserved). Because pages are derived and regenerable, applying is safe: a bad `--fix` is fully recoverable with `holt wiki rebuild`, and the command prints a git/backup nudge before writing. Only pages that actually change are rewritten.
|
|
254
256
|
|
|
@@ -378,7 +380,7 @@ Point the client at the folder whose memory you want it to use; Holt serves what
|
|
|
378
380
|
|
|
379
381
|
## Ambient memory for Claude Code (`holt hook`)
|
|
380
382
|
|
|
381
|
-
The MCP server above lets Claude Code recall and remember **when it decides to**. `holt hook` goes further: it makes Holt's per-folder memory work **ambiently**, with no
|
|
383
|
+
The MCP server above lets Claude Code recall and remember **when it decides to**. `holt hook` goes further: it makes Holt's per-folder memory work **ambiently**, with no Holt session open and no manual tool call. Two directions, both wired as [Claude Code hooks](https://docs.claude.com/en/docs/claude-code/hooks):
|
|
382
384
|
|
|
383
385
|
- **Inject** (`UserPromptSubmit`): before each prompt, Holt quietly recalls the most relevant remembered notes for the current folder and adds them to the model's context.
|
|
384
386
|
- **Capture** (`Stop`): when a session ends, Holt distills durable facts from the transcript (via this folder's configured brain) and saves them to the folder's memory.
|
|
@@ -394,7 +396,7 @@ holt hook remove # remove ONLY Holt's hooks, leaving everything
|
|
|
394
396
|
|
|
395
397
|
Install **merges** into any existing `hooks` config, never clobbers unrelated settings or other hooks, is **idempotent** (re-running does not duplicate), and backs the file up to `settings.json.holt-bak` before writing. `remove` deletes only the entries whose command is `holt hook inject` / `holt hook capture`.
|
|
396
398
|
|
|
397
|
-
**Trusted-folder guard (important).** The two runtime hooks (`holt hook inject` and `holt hook capture`, invoked by Claude Code, not you) no-op **silently** unless the folder is a trusted Holt workspace with an existing `.holt/memory`. So Holt never injects your private notes into an unrelated project, and never creates memory in a folder you never set up. To make a folder ambient, run `holt init`
|
|
399
|
+
**Trusted-folder guard (important).** The two runtime hooks (`holt hook inject` and `holt hook capture`, invoked by Claude Code, not you) no-op **silently** unless the folder is a trusted Holt workspace with an existing `.holt/memory`. So Holt never injects your private notes into an unrelated project, and never creates memory in a folder you never set up. To make a folder ambient, run `holt init` there once so it becomes trusted and gets its `.holt/memory`. The inject hook keeps stdout clean (only the context block is printed, since Claude Code injects it verbatim); all diagnostics go to stderr, and both hooks always exit 0 so a memory step never blocks or slows your prompt.
|
|
398
400
|
|
|
399
401
|
**Seeing what the hooks did (`~/.holt/hooks.log`).** Since the hooks stay silent, every `holt hook capture` run appends **one** line to `~/.holt/hooks.log` with the outcome or the exact skip reason (untrusted folder, no config, `extractFacts` off, no usable brain, missing or empty transcript, too few exchanges, or `saved N facts`), preceded by a line of the raw hook fields it received. Check this file first if capture seems to save nothing. `inject` uses the same log but stays quiet by default; set `HOLT_HOOK_DEBUG=1` to make it log too.
|
|
400
402
|
|
|
@@ -418,7 +420,7 @@ Run a single task non-interactively. It recalls relevant memory, injects your sk
|
|
|
418
420
|
holt run "summarize the open items in this folder"
|
|
419
421
|
```
|
|
420
422
|
|
|
421
|
-
Options: `--brain <id>` pick a brain, `--out <file>` also write the reply to a file, `--no-store` skip saving, `--no-recall` skip memory, `--quiet` suppress streaming. The folder must be trusted first (`holt init`
|
|
423
|
+
Options: `--brain <id>` pick a brain, `--out <file>` also write the reply to a file, `--no-store` skip saving, `--no-recall` skip memory, `--quiet` suppress streaming. The folder must be trusted first (`holt init` once); in a non-interactive context an untrusted folder exits non-zero instead of prompting.
|
|
422
424
|
|
|
423
425
|
### holt schedule
|
|
424
426
|
|
|
@@ -494,6 +496,14 @@ It reports, section by section:
|
|
|
494
496
|
- **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
497
|
- **Recommended next steps**: a short checklist built from whatever gaps it found.
|
|
496
498
|
|
|
499
|
+
And when you would rather not do the steps yourself:
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
holt doctor --fix
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
`--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.
|
|
506
|
+
|
|
497
507
|
It is read-only advice: it changes nothing, needs no trust, and always exits cleanly even when a probe fails.
|
|
498
508
|
|
|
499
509
|
The RAM-to-model table lives in `src/specs.ts` (`LOCAL_MODEL_RECS` / `recommendLocalModel`), the single source of truth the wiki maintainer reads too.
|
|
@@ -549,7 +559,6 @@ holt start your assistant: set up if needed, then launch the rea
|
|
|
549
559
|
interactive brain (Claude Code/Codex/Gemini), branded as Holt
|
|
550
560
|
holt launch same as bare "holt"
|
|
551
561
|
holt init set up (trust, brains, sign-in, defaults) for this folder
|
|
552
|
-
holt chat lightweight REPL that remembers past ones (used for API brains)
|
|
553
562
|
holt run <task> run one task non-interactively (recall, brain, remember)
|
|
554
563
|
holt schedule run a task on a timer: add | list | remove
|
|
555
564
|
holt routine named, reusable, scheduled jobs: add | run | list | show | remove
|
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
|
-
|
|
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;
|
|
@@ -194,12 +205,22 @@ var BRAIN_SETUP = {
|
|
|
194
205
|
codex: { install: ["npm", "install", "-g", "@openai/codex"], login: ["codex", "login"] },
|
|
195
206
|
gemini: { install: ["npm", "install", "-g", "@google/gemini-cli"], login: ["gemini"] }
|
|
196
207
|
};
|
|
208
|
+
var BRAIN_LOGIN_UNAVAILABLE = {
|
|
209
|
+
gemini: {
|
|
210
|
+
reason: 'Google ended "Login with Google" for Gemini Code Assist individual accounts on 18 June 2026. Personal Google accounts (free, AI Pro, AI Ultra) can no longer sign in to Gemini CLI.',
|
|
211
|
+
provider: "gemini",
|
|
212
|
+
keyUrl: "https://aistudio.google.com/apikey"
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function loginUnavailable(id) {
|
|
216
|
+
return BRAIN_LOGIN_UNAVAILABLE[id] ?? null;
|
|
217
|
+
}
|
|
197
218
|
var BRAIN_IDS = ["claude", "codex", "gemini"];
|
|
198
219
|
var PROVIDERS = ["anthropic", "openai", "gemini", "kimi", "grok"];
|
|
199
220
|
var PROVIDER_MODEL_SUGGESTION = {
|
|
200
|
-
anthropic: "claude-sonnet-
|
|
221
|
+
anthropic: "claude-sonnet-5",
|
|
201
222
|
openai: "gpt-5",
|
|
202
|
-
gemini: "gemini-
|
|
223
|
+
gemini: "gemini-flash-latest",
|
|
203
224
|
kimi: "kimi-k2-turbo-preview",
|
|
204
225
|
grok: "grok-4"
|
|
205
226
|
};
|
|
@@ -210,6 +231,11 @@ var PROVIDER_ENV = {
|
|
|
210
231
|
kimi: "MOONSHOT_API_KEY",
|
|
211
232
|
grok: "XAI_API_KEY"
|
|
212
233
|
};
|
|
234
|
+
function cliBrainUsable(id) {
|
|
235
|
+
const gone = loginUnavailable(id);
|
|
236
|
+
if (!gone) return true;
|
|
237
|
+
return Boolean(process.env[PROVIDER_ENV[gone.provider]]);
|
|
238
|
+
}
|
|
213
239
|
function defaultConfig() {
|
|
214
240
|
const brains = {};
|
|
215
241
|
for (const id of BRAIN_IDS) {
|
|
@@ -1369,9 +1395,9 @@ async function setupTelegram(ask) {
|
|
|
1369
1395
|
console.log(c.dim(" Option A: open your new bot in Telegram and send it any message,"));
|
|
1370
1396
|
console.log(c.dim(" then I will auto-detect the chat id."));
|
|
1371
1397
|
console.log(c.dim(" Option B: paste a numeric chat id yourself."));
|
|
1372
|
-
const
|
|
1398
|
+
const autoDetect = await askYesNo(ask, "\n Auto-detect from a message you just sent? [Y/n] ", true);
|
|
1373
1399
|
let allowedChatId = null;
|
|
1374
|
-
if (
|
|
1400
|
+
if (autoDetect) {
|
|
1375
1401
|
console.log(c.dim("\n Checking for a recent message..."));
|
|
1376
1402
|
const updates = await getUpdates(0, token);
|
|
1377
1403
|
allowedChatId = latestChatId(updates);
|
|
@@ -1498,7 +1524,10 @@ function printStatus(cfg) {
|
|
|
1498
1524
|
const b = cfg.brains[id];
|
|
1499
1525
|
const tags = [
|
|
1500
1526
|
b.enabled ? c.green("enabled") : isInstalled(b.command) ? c.dim("installed, off") : c.dim("not installed"),
|
|
1501
|
-
cfg.defaultBrain === id ? c.accent("default") : ""
|
|
1527
|
+
cfg.defaultBrain === id ? c.accent("default") : "",
|
|
1528
|
+
// A retired sign-in with no key in the environment is the real reason a
|
|
1529
|
+
// brain will not answer, so name it here rather than at first failure.
|
|
1530
|
+
loginUnavailable(id) && !cliBrainUsable(id) ? c.red("no sign-in, needs API key") : ""
|
|
1502
1531
|
].filter(Boolean).join(" ");
|
|
1503
1532
|
console.log(` ${id.padEnd(10)} ${b.label.padEnd(16)} ${tags}`);
|
|
1504
1533
|
}
|
|
@@ -1517,8 +1546,9 @@ function printStatus(cfg) {
|
|
|
1517
1546
|
console.log("\n " + c.dim("[d] default brain [t] toggle brain [c] connect API brain [x] remove API brain"));
|
|
1518
1547
|
console.log(" " + c.dim("[a] launch command [m] connect Telegram [enter] done"));
|
|
1519
1548
|
}
|
|
1520
|
-
async function connectApiBrain(ask, cfg) {
|
|
1521
|
-
let provider = null;
|
|
1549
|
+
async function connectApiBrain(ask, cfg, preset) {
|
|
1550
|
+
let provider = preset ?? null;
|
|
1551
|
+
if (provider) console.log(c.dim(` provider: ${provider}`));
|
|
1522
1552
|
for (let tries = 0; tries < 3 && !provider; tries++) {
|
|
1523
1553
|
const provRaw = (await ask(` provider [${PROVIDERS.join("/")}] (enter for ${PROVIDERS[0]}, or "skip"): `) ?? "skip").trim().toLowerCase();
|
|
1524
1554
|
if (provRaw === "skip" || provRaw === "q" || provRaw === "n" || provRaw === "no") {
|
|
@@ -1536,7 +1566,9 @@ async function connectApiBrain(ask, cfg) {
|
|
|
1536
1566
|
const suggestion = PROVIDER_MODEL_SUGGESTION[provider];
|
|
1537
1567
|
const modelRaw = (await ask(` model (enter for ${suggestion}): `) ?? "").trim();
|
|
1538
1568
|
const model = modelRaw || suggestion;
|
|
1539
|
-
const
|
|
1569
|
+
const idHint = preset ? `${preset}-api` : "";
|
|
1570
|
+
const idPrompt = idHint ? ` short name for this brain (enter for ${idHint}): ` : " short name for this brain (e.g. sonnet): ";
|
|
1571
|
+
const idRaw = (await ask(idPrompt) ?? "").trim() || idHint;
|
|
1540
1572
|
if (!idRaw) {
|
|
1541
1573
|
console.log(c.dim(" cancelled (no name)."));
|
|
1542
1574
|
return null;
|
|
@@ -1592,8 +1624,12 @@ async function runSettings(ask) {
|
|
|
1592
1624
|
} else if (choice === "t") {
|
|
1593
1625
|
const pick = (await ask(` toggle which brain [${BRAIN_IDS.join("/")}]: `) ?? "").trim();
|
|
1594
1626
|
if (BRAIN_IDS.includes(pick)) {
|
|
1627
|
+
const goneLogin = loginUnavailable(pick);
|
|
1595
1628
|
if (!cfg.brains[pick].enabled && !isInstalled(cfg.brains[pick].command)) {
|
|
1596
1629
|
console.log(c.dim(` ${cfg.brains[pick].label} is not installed. Run "holt init" to install it.`));
|
|
1630
|
+
} else if (!cfg.brains[pick].enabled && goneLogin && !cliBrainUsable(pick)) {
|
|
1631
|
+
console.log(c.dim(` ${cfg.brains[pick].label} has no sign-in any more and no ${goneLogin.provider.toUpperCase()}_API_KEY set.`));
|
|
1632
|
+
console.log(c.dim(` Get a key at ${goneLogin.keyUrl}, then connect it here with "c".`));
|
|
1597
1633
|
} else {
|
|
1598
1634
|
cfg.brains[pick].enabled = !cfg.brains[pick].enabled;
|
|
1599
1635
|
if (!cfg.brains[pick].enabled && cfg.defaultBrain === pick) cfg.defaultBrain = selectableIds(cfg)[0] ?? null;
|
|
@@ -1912,8 +1948,7 @@ async function runQuestions(ask, depth) {
|
|
|
1912
1948
|
return out;
|
|
1913
1949
|
}
|
|
1914
1950
|
async function askKeepExcerpt(ask) {
|
|
1915
|
-
|
|
1916
|
-
return a === "y" || a === "yes";
|
|
1951
|
+
return askYesNo(ask, " Store a short excerpt so Holt can match your tone directly? [y/N] ", false);
|
|
1917
1952
|
}
|
|
1918
1953
|
async function addFileSample(ask, file, v) {
|
|
1919
1954
|
const path = resolve(process.cwd(), file);
|
|
@@ -2129,6 +2164,75 @@ async function voiceClear() {
|
|
|
2129
2164
|
console.log(had ? c.green("\n Voice profile removed.\n") : c.dim("\n No voice profile to remove.\n"));
|
|
2130
2165
|
}
|
|
2131
2166
|
|
|
2167
|
+
// src/commands/login.ts
|
|
2168
|
+
function printLoginUnavailable(id, info) {
|
|
2169
|
+
console.log("\n" + c.red(`${BRAIN_DEFS[id].label} sign-in is no longer available.`));
|
|
2170
|
+
console.log(c.dim(" " + info.reason));
|
|
2171
|
+
console.log("\n It still works on an API key. Two ways:");
|
|
2172
|
+
console.log(" 1. " + c.accent(`Get a key at ${info.keyUrl}`));
|
|
2173
|
+
console.log(" 2. Either export it as " + c.accent(`${info.provider.toUpperCase()}_API_KEY`) + c.dim(` (the ${BRAIN_DEFS[id].command} CLI reads it too), or connect it to Holt as a direct API brain.`));
|
|
2174
|
+
}
|
|
2175
|
+
async function offerKeyInstead(ask, id, info) {
|
|
2176
|
+
const cfg = loadConfig();
|
|
2177
|
+
if (!cfg) {
|
|
2178
|
+
console.log(c.dim('\n This folder is not set up yet. Run "holt init" and connect it as a direct API brain.\n'));
|
|
2179
|
+
return false;
|
|
2180
|
+
}
|
|
2181
|
+
if (!await askYesNo(ask, `
|
|
2182
|
+
Connect ${BRAIN_DEFS[id].label} to Holt with an API key now? [Y/n] `, true)) {
|
|
2183
|
+
console.log(c.dim(' Okay. Run "holt setting" then "c" whenever you have the key.\n'));
|
|
2184
|
+
return false;
|
|
2185
|
+
}
|
|
2186
|
+
const brain = await connectApiBrain(ask, cfg, info.provider);
|
|
2187
|
+
if (!brain) return false;
|
|
2188
|
+
if (!cfg.defaultBrain || cfg.defaultBrain === id) {
|
|
2189
|
+
cfg.defaultBrain = brain.id;
|
|
2190
|
+
console.log(c.dim(` default brain for this folder is now "${brain.id}".`));
|
|
2191
|
+
}
|
|
2192
|
+
if (cfg.brains[id]?.enabled) {
|
|
2193
|
+
cfg.brains[id].enabled = false;
|
|
2194
|
+
console.log(c.dim(` turned off the ${BRAIN_DEFS[id].label} brain (no sign-in available).`));
|
|
2195
|
+
}
|
|
2196
|
+
saveConfig(cfg);
|
|
2197
|
+
console.log(c.green("\n Done. Start Holt: holt\n"));
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
async function login(which) {
|
|
2201
|
+
const id = (which || "").toLowerCase();
|
|
2202
|
+
if (!BRAIN_IDS.includes(id)) {
|
|
2203
|
+
if (which) {
|
|
2204
|
+
console.error(`
|
|
2205
|
+
Unknown brain "${which}". Use one of: ${BRAIN_IDS.join(", ")}
|
|
2206
|
+
`);
|
|
2207
|
+
process.exitCode = 1;
|
|
2208
|
+
} else {
|
|
2209
|
+
console.log(c.dim(`
|
|
2210
|
+
Usage: holt login <${BRAIN_IDS.join("|")}>
|
|
2211
|
+
`));
|
|
2212
|
+
}
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
const gone = loginUnavailable(id);
|
|
2216
|
+
if (gone) {
|
|
2217
|
+
printLoginUnavailable(id, gone);
|
|
2218
|
+
if (cliBrainUsable(id)) {
|
|
2219
|
+
console.log(c.green(`
|
|
2220
|
+
${PROVIDER_ENV[gone.provider]} is already set, so ${BRAIN_DEFS[id].label} works as it is. Nothing to do.
|
|
2221
|
+
`));
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
const { ask, close } = createReader();
|
|
2225
|
+
await offerKeyInstead(ask, id, gone);
|
|
2226
|
+
close();
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const s = BRAIN_SETUP[id];
|
|
2230
|
+
console.log("\n" + c.accent(`Sign in to ${BRAIN_DEFS[id].label}`));
|
|
2231
|
+
console.log(c.dim(` Starting "${s.login.join(" ")}". Complete sign-in, then exit that tool.
|
|
2232
|
+
`));
|
|
2233
|
+
await runInteractive(s.login[0], s.login.slice(1));
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2132
2236
|
// src/commands/init.ts
|
|
2133
2237
|
function parseBrains(raw, found) {
|
|
2134
2238
|
const s = raw.trim().toLowerCase();
|
|
@@ -2149,7 +2253,8 @@ async function init() {
|
|
|
2149
2253
|
const found = [];
|
|
2150
2254
|
for (const id of BRAIN_IDS) {
|
|
2151
2255
|
const ok2 = isInstalled(BRAIN_DEFS[id].command);
|
|
2152
|
-
|
|
2256
|
+
const note2 = loginUnavailable(id) && !cliBrainUsable(id) ? c.dim(" (needs an API key, no sign-in)") : "";
|
|
2257
|
+
console.log(` ${ok2 ? c.green("found ") : c.dim("missing")} ${BRAIN_DEFS[id].label} (${BRAIN_DEFS[id].command})${note2}`);
|
|
2153
2258
|
if (ok2) found.push(id);
|
|
2154
2259
|
}
|
|
2155
2260
|
console.log("");
|
|
@@ -2161,13 +2266,24 @@ async function init() {
|
|
|
2161
2266
|
const toInstall = chosen.filter((id) => !isInstalled(BRAIN_DEFS[id].command));
|
|
2162
2267
|
const loginWanted = /* @__PURE__ */ new Set();
|
|
2163
2268
|
for (const id of toInstall) {
|
|
2164
|
-
|
|
2165
|
-
if (
|
|
2269
|
+
if (loginUnavailable(id)) continue;
|
|
2270
|
+
if (await askYesNo(ask, ` ${BRAIN_DEFS[id].label} is not installed. Sign in after install? [Y/n] `, true)) loginWanted.add(id);
|
|
2166
2271
|
}
|
|
2167
2272
|
const connectedApiBrains = [];
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2170
|
-
const
|
|
2273
|
+
const holder = defaultConfig();
|
|
2274
|
+
for (const id of chosen) {
|
|
2275
|
+
const gone = loginUnavailable(id);
|
|
2276
|
+
if (!gone || cliBrainUsable(id)) continue;
|
|
2277
|
+
printLoginUnavailable(id, gone);
|
|
2278
|
+
if (await askYesNo(ask, `
|
|
2279
|
+
Connect ${BRAIN_DEFS[id].label} with an API key now? [Y/n] `, true)) {
|
|
2280
|
+
const brain = await connectApiBrain(ask, holder, gone.provider);
|
|
2281
|
+
if (brain) connectedApiBrains.push(brain);
|
|
2282
|
+
} else {
|
|
2283
|
+
console.log(c.dim(' Okay. Run "holt setting" then "c" whenever you have the key.'));
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
if (await askYesNo(ask, "\nAlso connect a direct API brain (raw key, no CLI needed)? [y/N] ", false)) {
|
|
2171
2287
|
const brain = await connectApiBrain(ask, holder);
|
|
2172
2288
|
if (brain) connectedApiBrains.push(brain);
|
|
2173
2289
|
}
|
|
@@ -2195,13 +2311,11 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2195
2311
|
} else {
|
|
2196
2312
|
const ollamaHere = isInstalled("ollama");
|
|
2197
2313
|
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
|
-
|
|
2199
|
-
wantMemorySetup = a !== "n" && a !== "no";
|
|
2314
|
+
wantMemorySetup = await askYesNo(ask, "\n" + q, true);
|
|
2200
2315
|
if (!wantMemorySetup) console.log(c.dim(' Okay. Memory still works with keyword recall; run "holt init" again anytime.'));
|
|
2201
2316
|
}
|
|
2202
2317
|
if (!loadTelegramConfig()) {
|
|
2203
|
-
|
|
2204
|
-
if (tgAns === "y" || tgAns === "yes") await setupTelegram(ask);
|
|
2318
|
+
if (await askYesNo(ask, "\nChat with Holt from your phone over Telegram? [y/N] ", false)) await setupTelegram(ask);
|
|
2205
2319
|
}
|
|
2206
2320
|
close();
|
|
2207
2321
|
for (const id of toInstall) {
|
|
@@ -2240,7 +2354,7 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2240
2354
|
}
|
|
2241
2355
|
}
|
|
2242
2356
|
const cfg = loadConfig() ?? defaultConfig();
|
|
2243
|
-
for (const id of BRAIN_IDS) cfg.brains[id].enabled = chosen.includes(id) && isInstalled(BRAIN_DEFS[id].command);
|
|
2357
|
+
for (const id of BRAIN_IDS) cfg.brains[id].enabled = chosen.includes(id) && isInstalled(BRAIN_DEFS[id].command) && cliBrainUsable(id);
|
|
2244
2358
|
for (const b of connectedApiBrains) if (!cfg.apiBrains.some((a) => a.id === b.id)) cfg.apiBrains.push(b);
|
|
2245
2359
|
cfg.defaultBrain = cfg.brains[defaultBrain].enabled ? defaultBrain : BRAIN_IDS.find((id) => cfg.brains[id].enabled) ?? cfg.apiBrains[0]?.id ?? null;
|
|
2246
2360
|
saveConfig(cfg);
|
|
@@ -2253,17 +2367,17 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2253
2367
|
console.log("\nStart chatting:");
|
|
2254
2368
|
console.log(" " + c.accent(`source ${aliasNeedsSource}`) + c.dim(" (once; new terminals will not need it)"));
|
|
2255
2369
|
console.log(" " + c.accent(aliasAns) + "\n");
|
|
2256
|
-
console.log(c.dim(" Or right now, without sourcing: holt
|
|
2370
|
+
console.log(c.dim(" Or right now, without sourcing: holt\n"));
|
|
2257
2371
|
} else {
|
|
2258
|
-
console.log("Start chatting: " + c.accent("holt
|
|
2372
|
+
console.log("Start chatting: " + c.accent("holt") + "\n");
|
|
2259
2373
|
}
|
|
2260
2374
|
} else {
|
|
2261
2375
|
console.log(c.dim('No brain is ready yet. Install one, then run "holt init" again.\n'));
|
|
2262
2376
|
}
|
|
2263
2377
|
const voiceReader = createReader();
|
|
2264
|
-
const
|
|
2378
|
+
const voiceYes = await askYesNo(voiceReader.ask, "Want Holt to learn how you write, so it can draft in your voice? [y/N] ", false);
|
|
2265
2379
|
voiceReader.close();
|
|
2266
|
-
if (
|
|
2380
|
+
if (voiceYes) {
|
|
2267
2381
|
await voiceInterview();
|
|
2268
2382
|
} else {
|
|
2269
2383
|
console.log(c.dim(' Skipped. Run "holt voice" anytime to set up your writing voice.\n'));
|
|
@@ -4067,29 +4181,6 @@ function saveReply(reply, format, name) {
|
|
|
4067
4181
|
return path;
|
|
4068
4182
|
}
|
|
4069
4183
|
|
|
4070
|
-
// src/commands/login.ts
|
|
4071
|
-
async function login(which) {
|
|
4072
|
-
const id = (which || "").toLowerCase();
|
|
4073
|
-
if (!BRAIN_IDS.includes(id)) {
|
|
4074
|
-
if (which) {
|
|
4075
|
-
console.error(`
|
|
4076
|
-
Unknown brain "${which}". Use one of: ${BRAIN_IDS.join(", ")}
|
|
4077
|
-
`);
|
|
4078
|
-
process.exitCode = 1;
|
|
4079
|
-
} else {
|
|
4080
|
-
console.log(c.dim(`
|
|
4081
|
-
Usage: holt login <${BRAIN_IDS.join("|")}>
|
|
4082
|
-
`));
|
|
4083
|
-
}
|
|
4084
|
-
return;
|
|
4085
|
-
}
|
|
4086
|
-
const s = BRAIN_SETUP[id];
|
|
4087
|
-
console.log("\n" + c.accent(`Sign in to ${BRAIN_DEFS[id].label}`));
|
|
4088
|
-
console.log(c.dim(` Starting "${s.login.join(" ")}". Complete sign-in, then exit that tool.
|
|
4089
|
-
`));
|
|
4090
|
-
await runInteractive(s.login[0], s.login.slice(1));
|
|
4091
|
-
}
|
|
4092
|
-
|
|
4093
4184
|
// src/access.ts
|
|
4094
4185
|
import { existsSync as existsSync11, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4095
4186
|
import { homedir as homedir6 } from "os";
|
|
@@ -4222,14 +4313,14 @@ async function chat() {
|
|
|
4222
4313
|
}
|
|
4223
4314
|
let cfg = loadConfig();
|
|
4224
4315
|
if (!cfg || !cfg.defaultBrain) {
|
|
4225
|
-
const
|
|
4316
|
+
const setupYes = await askYesNo(ask, c.dim("No Holt setup in this folder. Set it up now? [Y/n] "), true);
|
|
4226
4317
|
close();
|
|
4227
|
-
if (
|
|
4318
|
+
if (!setupYes) {
|
|
4228
4319
|
console.log(c.dim(' Run "holt init" here when ready.\n'));
|
|
4229
4320
|
return;
|
|
4230
4321
|
}
|
|
4231
4322
|
await init();
|
|
4232
|
-
console.log(c.dim('\nSetup done. Run "holt
|
|
4323
|
+
console.log(c.dim('\nSetup done. Run "holt" to start talking.\n'));
|
|
4233
4324
|
return;
|
|
4234
4325
|
}
|
|
4235
4326
|
let active = resolveActive2(cfg, cfg.defaultBrain);
|
|
@@ -4357,7 +4448,7 @@ async function chat() {
|
|
|
4357
4448
|
sb.detach();
|
|
4358
4449
|
close();
|
|
4359
4450
|
await login(targetId);
|
|
4360
|
-
console.log(c.dim('\n Signed in. Run "holt
|
|
4451
|
+
console.log(c.dim(loginUnavailable(targetId) ? '\n Run "holt" again to continue.\n' : '\n Signed in. Run "holt" again to continue.\n'));
|
|
4361
4452
|
return;
|
|
4362
4453
|
}
|
|
4363
4454
|
if (cmd === "brain") {
|
|
@@ -4426,8 +4517,7 @@ async function chat() {
|
|
|
4426
4517
|
const outside = findOutsidePaths(line, workspace());
|
|
4427
4518
|
for (const dir of outside) {
|
|
4428
4519
|
if (grantedDirs.has(dir)) continue;
|
|
4429
|
-
|
|
4430
|
-
if (ans === "y" || ans === "yes") {
|
|
4520
|
+
if (await askYesNo(ask, c.dim(` Allow this session to access ${dir} (outside this folder)? [y/N] `), false)) {
|
|
4431
4521
|
grantedDirs.add(dir);
|
|
4432
4522
|
console.log(c.green(` granted this session: ${dir}`));
|
|
4433
4523
|
} else {
|
|
@@ -4456,7 +4546,8 @@ async function chat() {
|
|
|
4456
4546
|
};
|
|
4457
4547
|
const res = active.kind === "cli" ? await runBrain(cfg.brains[active.id], prompt, onChunk, extraArgs) : await runApiBrain(active.brain, prompt, onChunk);
|
|
4458
4548
|
if (looksLikeAuthError(res.text)) {
|
|
4459
|
-
const
|
|
4549
|
+
const goneLogin = active.kind === "cli" ? loginUnavailable(active.id) : null;
|
|
4550
|
+
const hint = goneLogin ? ` ${active.label} has no sign-in any more and no ${PROVIDER_ENV[goneLogin.provider]} to fall back on. Get a key at ${goneLogin.keyUrl}, then run "holt setting" to connect it.` : active.kind === "cli" ? ` ${active.label} looks signed out. Run "holt login ${active.id}" in a terminal, or type /login here.` : ` ${active.label} has no working key. Run "holt setting" to add or fix it.`;
|
|
4460
4551
|
if (!streamed || !res.text.endsWith("\n")) console.log("");
|
|
4461
4552
|
console.log(c.red(hint) + "\n");
|
|
4462
4553
|
showStatus();
|
|
@@ -4595,11 +4686,12 @@ function resolveTarget(cfg) {
|
|
|
4595
4686
|
if (!id) return null;
|
|
4596
4687
|
if (BRAIN_IDS.includes(id)) {
|
|
4597
4688
|
const bid = id;
|
|
4598
|
-
return { kind: "cli", id: bid, label: cfg.brains[bid].label };
|
|
4689
|
+
if (cliBrainUsable(bid)) return { kind: "cli", id: bid, label: cfg.brains[bid].label };
|
|
4690
|
+
} else {
|
|
4691
|
+
const api = findApiBrain(cfg, id);
|
|
4692
|
+
if (api) return { kind: "api", label: `${id} (api: ${api.provider}/${api.model})` };
|
|
4599
4693
|
}
|
|
4600
|
-
const
|
|
4601
|
-
if (api) return { kind: "api", label: `${id} (api: ${api.provider}/${api.model})` };
|
|
4602
|
-
const fallback = BRAIN_IDS.find((b) => cfg.brains[b].enabled);
|
|
4694
|
+
const fallback = BRAIN_IDS.find((b) => cfg.brains[b].enabled && cliBrainUsable(b));
|
|
4603
4695
|
if (fallback) return { kind: "cli", id: fallback, label: cfg.brains[fallback].label };
|
|
4604
4696
|
return null;
|
|
4605
4697
|
}
|
|
@@ -4639,13 +4731,23 @@ async function launch() {
|
|
|
4639
4731
|
await ensureSetup();
|
|
4640
4732
|
const target = resolveTarget(cfg);
|
|
4641
4733
|
if (!target) {
|
|
4734
|
+
const def = cfg.defaultBrain;
|
|
4735
|
+
const gone = def && BRAIN_IDS.includes(def) ? loginUnavailable(def) : null;
|
|
4736
|
+
if (gone) {
|
|
4737
|
+
console.log("\n " + c.red(`${cfg.brains[def].label} can no longer sign in.`));
|
|
4738
|
+
console.log(c.dim(" " + gone.reason));
|
|
4739
|
+
console.log(c.dim(` Get a key at ${gone.keyUrl}, then run "holt setting" and press "c" to connect it.`));
|
|
4740
|
+
if (cfg.apiBrains.length) {
|
|
4741
|
+
console.log(c.dim(` Or switch this folder to an API brain you already have (${cfg.apiBrains.map((b) => b.id).join(", ")}): "holt setting" then "d".`));
|
|
4742
|
+
}
|
|
4743
|
+
console.log("");
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4642
4746
|
console.log(c.dim('\n No brain is ready. Run "holt setting" or "holt init".\n'));
|
|
4643
4747
|
return;
|
|
4644
4748
|
}
|
|
4645
4749
|
if (target.kind === "api") {
|
|
4646
|
-
console.log(
|
|
4647
|
-
launch needs a CLI brain; starting chat instead (default is ${target.label}).
|
|
4648
|
-
`));
|
|
4750
|
+
console.log(renderIntroBox());
|
|
4649
4751
|
await chat();
|
|
4650
4752
|
return;
|
|
4651
4753
|
}
|
|
@@ -7061,7 +7163,7 @@ async function graph(args = []) {
|
|
|
7061
7163
|
if (skipNotes.length) console.log(c.dim(` ingest skipped: ${skipNotes.join(", ")}`));
|
|
7062
7164
|
}
|
|
7063
7165
|
if (turns.length === 0 && wikiPages.length === 0 && !codeGraph) {
|
|
7064
|
-
console.log(c.dim('\n No memory in this folder yet.
|
|
7166
|
+
console.log(c.dim('\n No memory in this folder yet. Talk to Holt here first with "holt", then come back.\n'));
|
|
7065
7167
|
console.log(c.dim(` Or ingest this folder's code/docs: "holt graph --code" / "holt graph --docs".
|
|
7066
7168
|
`));
|
|
7067
7169
|
return;
|
|
@@ -7384,7 +7486,7 @@ function holtTools() {
|
|
|
7384
7486
|
}
|
|
7385
7487
|
|
|
7386
7488
|
// src/version.ts
|
|
7387
|
-
var VERSION = "0.
|
|
7489
|
+
var VERSION = "0.16.0";
|
|
7388
7490
|
|
|
7389
7491
|
// src/commands/mcp.ts
|
|
7390
7492
|
function printSetup() {
|
|
@@ -7568,7 +7670,7 @@ async function run(args) {
|
|
|
7568
7670
|
close();
|
|
7569
7671
|
if (!ok2) return;
|
|
7570
7672
|
} else {
|
|
7571
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
7673
|
+
process.stderr.write("This folder is not trusted. Run holt init here once, then schedule/run.\n");
|
|
7572
7674
|
process.exitCode = 1;
|
|
7573
7675
|
return;
|
|
7574
7676
|
}
|
|
@@ -8075,7 +8177,7 @@ async function runCmd(rest) {
|
|
|
8075
8177
|
close();
|
|
8076
8178
|
if (!ok2) return;
|
|
8077
8179
|
} else {
|
|
8078
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
8180
|
+
process.stderr.write("This folder is not trusted. Run holt init here once, then run the routine.\n");
|
|
8079
8181
|
process.exitCode = 1;
|
|
8080
8182
|
return;
|
|
8081
8183
|
}
|
|
@@ -8478,8 +8580,14 @@ async function doctor(args = []) {
|
|
|
8478
8580
|
for (const id of BRAIN_IDS) {
|
|
8479
8581
|
const def = BRAIN_DEFS[id];
|
|
8480
8582
|
const here = isInstalled(def.command);
|
|
8481
|
-
|
|
8583
|
+
const gone = loginUnavailable(id);
|
|
8584
|
+
const usable = cliBrainUsable(id);
|
|
8585
|
+
if (here && usable) installed.push(def.label);
|
|
8482
8586
|
console.log(` ${here ? c.green("installed") : c.dim("missing ")} ${def.label} ${c.dim(`(${def.command})`)}`);
|
|
8587
|
+
if (here && gone && !usable) {
|
|
8588
|
+
warn(`${def.label} cannot sign in any more. ${gone.reason}`);
|
|
8589
|
+
steps.push(`Get a ${gone.provider} key at ${gone.keyUrl}, then connect it: holt setting -> "c".`);
|
|
8590
|
+
}
|
|
8483
8591
|
}
|
|
8484
8592
|
const cfg = loadConfig();
|
|
8485
8593
|
const apiBrains = cfg?.apiBrains ?? [];
|
|
@@ -8560,7 +8668,7 @@ async function doctor(args = []) {
|
|
|
8560
8668
|
steps.unshift('Set up this folder: run "holt init".');
|
|
8561
8669
|
}
|
|
8562
8670
|
if (steps.length === 0) {
|
|
8563
|
-
good('You are in good shape. Run "holt
|
|
8671
|
+
good('You are in good shape. Run "holt" to start.');
|
|
8564
8672
|
} else {
|
|
8565
8673
|
let n = 1;
|
|
8566
8674
|
for (const s of steps) console.log(` ${c.accent(String(n++) + ".")} ${s}`);
|
|
@@ -8695,8 +8803,7 @@ async function update() {
|
|
|
8695
8803
|
}
|
|
8696
8804
|
console.log(` Newer version available: ${c.bold(latest)}`);
|
|
8697
8805
|
if (isBrewInstall()) {
|
|
8698
|
-
|
|
8699
|
-
if (a2 === "n" || a2 === "no") {
|
|
8806
|
+
if (!await askYesNo(ask, " Update now via Homebrew? [Y/n] ", true)) {
|
|
8700
8807
|
console.log(c.dim(" Later, then. The command is: brew upgrade holt\n"));
|
|
8701
8808
|
return;
|
|
8702
8809
|
}
|
|
@@ -8706,8 +8813,7 @@ async function update() {
|
|
|
8706
8813
|
else console.log(c.dim("\n Homebrew reported a problem above. You can retry with: brew upgrade holt\n"));
|
|
8707
8814
|
return;
|
|
8708
8815
|
}
|
|
8709
|
-
|
|
8710
|
-
if (a === "n" || a === "no") {
|
|
8816
|
+
if (!await askYesNo(ask, " Update now? [Y/n] ", true)) {
|
|
8711
8817
|
console.log(c.dim(" Later, then. The command is: npm install -g @holt-os/holt\n"));
|
|
8712
8818
|
return;
|
|
8713
8819
|
}
|
|
@@ -8872,7 +8978,7 @@ async function write(args) {
|
|
|
8872
8978
|
close();
|
|
8873
8979
|
if (!ok2) return;
|
|
8874
8980
|
} else {
|
|
8875
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
8981
|
+
process.stderr.write("This folder is not trusted. Run holt init here once first.\n");
|
|
8876
8982
|
process.exitCode = 1;
|
|
8877
8983
|
return;
|
|
8878
8984
|
}
|
|
@@ -8986,12 +9092,12 @@ From your phone, while you sleep:
|
|
|
8986
9092
|
|
|
8987
9093
|
Housekeeping:
|
|
8988
9094
|
setting Change brains, API connections, and your launch word (per folder)
|
|
8989
|
-
login <brain> Sign in to a brain: claude
|
|
9095
|
+
login <brain> Sign in to a brain: claude or codex
|
|
9096
|
+
(Gemini has no sign-in left; it routes you to an API key)
|
|
8990
9097
|
update Get the latest Holt, in one word
|
|
8991
9098
|
reset Start this folder over (asks first; your own files are untouched)
|
|
8992
9099
|
hook Ambient memory inside plain Claude Code: holt hook [install | status]
|
|
8993
9100
|
mcp Share Holt's memory with other AI apps (Claude Code, Cursor, Codex)
|
|
8994
|
-
chat Lightweight built-in chat, used for API brains
|
|
8995
9101
|
init Full setup by hand (bare "holt" runs this for you when needed)
|
|
8996
9102
|
launch Same as bare "holt": start your assistant
|
|
8997
9103
|
version | help Print the version / show this help
|
|
@@ -9029,6 +9135,8 @@ async function main() {
|
|
|
9029
9135
|
case "init":
|
|
9030
9136
|
await init();
|
|
9031
9137
|
break;
|
|
9138
|
+
// Undocumented: bare `holt` reaches the REPL by itself for API brains.
|
|
9139
|
+
// Kept as a working alias so older scripts and aliases do not break.
|
|
9032
9140
|
case "chat":
|
|
9033
9141
|
await chat();
|
|
9034
9142
|
break;
|