@holt-os/holt 0.15.1 → 0.17.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 +30 -12
- package/dist/cli.js +229 -63
- package/package.json +1 -1
- package/skills/audit/SKILL.md +111 -0
- package/skills/level-up/SKILL.md +118 -0
- package/skills/onboard/SKILL.md +125 -0
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
- **Private memory you can see.** Per-folder semantic recall and distilled facts, an interactive knowledge graph (`holt graph`), and a self-maintaining wiki (`holt wiki`) over everything you tell it. Nothing leaves your machine.
|
|
23
23
|
- **Ambient by default.** `holt hook` wires memory into Claude Code so it recalls and remembers with no manual step, and an MCP server exposes the same memory to Cursor and Codex.
|
|
24
24
|
- **Runs your work.** Run a task once, on a schedule, or as a named routine, and reach it from your phone over Telegram.
|
|
25
|
-
- **
|
|
25
|
+
- **Useful on day one.** `/skill onboard` interviews you and fills memory in about fifteen minutes, `/skill audit` shows what is still missing, and `/skill level-up` turns one manual chore a week into something Holt does.
|
|
26
|
+
- **Extensible, zero-infra.** Thirteen built-in skills plus a git-based community registry (`holt skill search`) in the portable `SKILL.md` format.
|
|
26
27
|
- **Writes in your voice.** `holt write` drafts in a style Holt learns from you, with built-in anti-AI checks.
|
|
27
28
|
|
|
28
29
|
> **Status:** shipping and usable today. Every capability above works now.
|
|
@@ -43,6 +44,14 @@ Or with npm, if you already have Node:
|
|
|
43
44
|
npm install -g @holt-os/holt
|
|
44
45
|
```
|
|
45
46
|
|
|
47
|
+
**Not comfortable in a terminal?** Install [Claude Code](https://claude.com/claude-code), open it, and paste this:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Set up Holt for me: read https://raw.githubusercontent.com/holt-os/holt/main/docs/INSTALL.md and follow it exactly.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It installs Holt, checks its own work, and fixes what breaks. Full detail, system requirements, and troubleshooting: [docs/INSTALL.md](./docs/INSTALL.md).
|
|
54
|
+
|
|
46
55
|
## Quickstart
|
|
47
56
|
|
|
48
57
|
```bash
|
|
@@ -50,7 +59,7 @@ cd ~/where-you-want-to-work
|
|
|
50
59
|
holt # start your assistant (or use your custom command, e.g. `ai`)
|
|
51
60
|
```
|
|
52
61
|
|
|
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
|
|
62
|
+
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
63
|
|
|
55
64
|
Prefer to step through setup on its own first? `holt init` still does that, then `holt` starts the session.
|
|
56
65
|
|
|
@@ -59,10 +68,12 @@ Prefer to step through setup on its own first? `holt init` still does that, then
|
|
|
59
68
|
`holt` (with no command) is the front door: **your assistant, with memory of you.**
|
|
60
69
|
|
|
61
70
|
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
|
-
|
|
71
|
+
> **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.
|
|
72
|
+
|
|
73
|
+
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
74
|
3. **Brands the session as Holt.** See [Branding](#branding-holt-forward) below.
|
|
64
75
|
|
|
65
|
-
If your default brain is a **direct API brain** (no interactive TUI), bare `holt`
|
|
76
|
+
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
77
|
|
|
67
78
|
### Branding (Holt-forward)
|
|
68
79
|
|
|
@@ -96,7 +107,7 @@ During `holt init` you:
|
|
|
96
107
|
|
|
97
108
|
## Using it
|
|
98
109
|
|
|
99
|
-
Inside
|
|
110
|
+
Inside a Holt session:
|
|
100
111
|
|
|
101
112
|
```
|
|
102
113
|
/brain list your brains and see which is active
|
|
@@ -127,7 +138,7 @@ Holt runs the brain in the folder you launched it from, so by default the brain
|
|
|
127
138
|
- `/allow <path>` grants a folder up front with no prompt.
|
|
128
139
|
- `/allowed` lists what you have granted.
|
|
129
140
|
|
|
130
|
-
Grants are **in-memory and session-scoped**: nothing is written to disk, and they reset the next time you run `holt
|
|
141
|
+
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
142
|
|
|
132
143
|
***
|
|
133
144
|
|
|
@@ -137,6 +148,8 @@ Every exchange is saved to `<folder>/.holt/memory/turns.jsonl`, private and loca
|
|
|
137
148
|
|
|
138
149
|
When you end a chat, Holt asks the brain to distill 1 to 5 durable facts from the session (decisions, preferences, key names, numbers) and writes them to a human-readable `<folder>/.holt/memory/facts.md` you can read and edit. Those facts are also embedded and ranked slightly higher than raw turns during recall, so the signal rises to the top over time. See them with `holt memory facts`. Turn it off by setting `memory.extractFacts` to `false` in `config.json`.
|
|
139
150
|
|
|
151
|
+
You do not have to wait for the end of a session. `holt memory add "<fact>"` saves one immediately, and duplicates are ignored, so it is safe to run twice. Piping in several facts, one per line, works too. Prefer this over editing `facts.md` by hand: a fact typed straight into that file is never embedded, so semantic recall will not find it.
|
|
152
|
+
|
|
140
153
|
Two recall modes, picked automatically:
|
|
141
154
|
|
|
142
155
|
- **Semantic** (best): a local [Ollama](https://ollama.com) with an embedding model, which `holt init` offers to set up for you. Recall matches by meaning: asking "who owns my apartment" finds "my landlord is called Pieter". No API keys, nothing leaves your machine.
|
|
@@ -146,6 +159,7 @@ Inspect it any time:
|
|
|
146
159
|
|
|
147
160
|
```bash
|
|
148
161
|
holt memory # stats for this folder
|
|
162
|
+
holt memory add "<fact>" # teach it something durable right now
|
|
149
163
|
holt memory facts # show the distilled facts (facts.md)
|
|
150
164
|
holt memory search <query> # find remembered moments
|
|
151
165
|
holt memory embed # embed older moments for semantic recall
|
|
@@ -248,7 +262,7 @@ holt wiki setup # recommend a local model for this machine's RAM
|
|
|
248
262
|
|
|
249
263
|
**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
264
|
|
|
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
|
|
265
|
+
**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
266
|
|
|
253
267
|
**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
268
|
|
|
@@ -313,6 +327,11 @@ Holt ships with a small, curated set of general-purpose skills, available in eve
|
|
|
313
327
|
- **plan**: break a goal into a concrete, ordered set of steps.
|
|
314
328
|
- **rewrite**: tighten and clarify a piece of text without changing its meaning.
|
|
315
329
|
- **decide**: weigh a decision and give a clear recommendation.
|
|
330
|
+
- **onboard**: a guided first-day interview that fills memory with who you are and what you are working on.
|
|
331
|
+
- **audit**: a read-only health check across the four rungs, naming the gaps and the single next fix.
|
|
332
|
+
- **level-up**: a weekly session that turns one recurring manual chore into something Holt does, and ships it.
|
|
333
|
+
|
|
334
|
+
The last three are the setup loop. `onboard` on day one, then `audit` and `level-up` weekly. Holt gets useful in four rungs, in order: **Know** (it holds facts about you), **Reach** (it can see your real material), **Do** (it runs your repeated work), **Run** (it acts while you are away). Automating on top of an empty memory just produces confident nonsense on a timer, so the order matters.
|
|
316
335
|
|
|
317
336
|
### Skill registry (`holt skill search` / `publish`)
|
|
318
337
|
|
|
@@ -378,7 +397,7 @@ Point the client at the folder whose memory you want it to use; Holt serves what
|
|
|
378
397
|
|
|
379
398
|
## Ambient memory for Claude Code (`holt hook`)
|
|
380
399
|
|
|
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
|
|
400
|
+
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
401
|
|
|
383
402
|
- **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
403
|
- **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 +413,7 @@ holt hook remove # remove ONLY Holt's hooks, leaving everything
|
|
|
394
413
|
|
|
395
414
|
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
415
|
|
|
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`
|
|
416
|
+
**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
417
|
|
|
399
418
|
**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
419
|
|
|
@@ -418,7 +437,7 @@ Run a single task non-interactively. It recalls relevant memory, injects your sk
|
|
|
418
437
|
holt run "summarize the open items in this folder"
|
|
419
438
|
```
|
|
420
439
|
|
|
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`
|
|
440
|
+
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
441
|
|
|
423
442
|
### holt schedule
|
|
424
443
|
|
|
@@ -557,7 +576,6 @@ holt start your assistant: set up if needed, then launch the rea
|
|
|
557
576
|
interactive brain (Claude Code/Codex/Gemini), branded as Holt
|
|
558
577
|
holt launch same as bare "holt"
|
|
559
578
|
holt init set up (trust, brains, sign-in, defaults) for this folder
|
|
560
|
-
holt chat lightweight REPL that remembers past ones (used for API brains)
|
|
561
579
|
holt run <task> run one task non-interactively (recall, brain, remember)
|
|
562
580
|
holt schedule run a task on a timer: add | list | remove
|
|
563
581
|
holt routine named, reusable, scheduled jobs: add | run | list | show | remove
|
|
@@ -577,7 +595,7 @@ holt graph see your memory as an interactive knowledge graph
|
|
|
577
595
|
holt mcp run an MCP server so other tools use this folder's memory (holt mcp setup)
|
|
578
596
|
holt hook ambient memory for Claude Code: install | remove | status
|
|
579
597
|
holt skill manage skills: list | show | create | add | remove
|
|
580
|
-
holt memory inspect memory: holt memory [search <query> | facts | embed | clear]
|
|
598
|
+
holt memory inspect memory: holt memory [add <fact> | search <query> | facts | embed | clear]
|
|
581
599
|
holt wiki derived knowledge wiki: holt wiki [sync | auto | rebuild | lint | list | show | status]
|
|
582
600
|
holt setting configure brains, API brains, and launch command
|
|
583
601
|
holt login <brain> sign in to claude, codex, or gemini
|
package/dist/cli.js
CHANGED
|
@@ -205,12 +205,22 @@ var BRAIN_SETUP = {
|
|
|
205
205
|
codex: { install: ["npm", "install", "-g", "@openai/codex"], login: ["codex", "login"] },
|
|
206
206
|
gemini: { install: ["npm", "install", "-g", "@google/gemini-cli"], login: ["gemini"] }
|
|
207
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
|
+
}
|
|
208
218
|
var BRAIN_IDS = ["claude", "codex", "gemini"];
|
|
209
219
|
var PROVIDERS = ["anthropic", "openai", "gemini", "kimi", "grok"];
|
|
210
220
|
var PROVIDER_MODEL_SUGGESTION = {
|
|
211
|
-
anthropic: "claude-sonnet-
|
|
221
|
+
anthropic: "claude-sonnet-5",
|
|
212
222
|
openai: "gpt-5",
|
|
213
|
-
gemini: "gemini-
|
|
223
|
+
gemini: "gemini-flash-latest",
|
|
214
224
|
kimi: "kimi-k2-turbo-preview",
|
|
215
225
|
grok: "grok-4"
|
|
216
226
|
};
|
|
@@ -221,6 +231,11 @@ var PROVIDER_ENV = {
|
|
|
221
231
|
kimi: "MOONSHOT_API_KEY",
|
|
222
232
|
grok: "XAI_API_KEY"
|
|
223
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
|
+
}
|
|
224
239
|
function defaultConfig() {
|
|
225
240
|
const brains = {};
|
|
226
241
|
for (const id of BRAIN_IDS) {
|
|
@@ -662,7 +677,7 @@ function clearMemory() {
|
|
|
662
677
|
function normalizeFact(s) {
|
|
663
678
|
return (s || "").toLowerCase().replace(/\s+/g, " ").trim().replace(/[.,;:!?]+$/, "");
|
|
664
679
|
}
|
|
665
|
-
var FACTS_HEADER =
|
|
680
|
+
var FACTS_HEADER = '# Holt facts\n\nDistilled memories for this folder. Edit or delete lines freely.\nTo ADD one, use `holt memory add "<fact>"`: a line typed in here by hand is\nnever embedded, so semantic recall will not find it.\n';
|
|
666
681
|
async function saveFact(content, session) {
|
|
667
682
|
const clean = content.trim();
|
|
668
683
|
if (!clean) return false;
|
|
@@ -1509,7 +1524,10 @@ function printStatus(cfg) {
|
|
|
1509
1524
|
const b = cfg.brains[id];
|
|
1510
1525
|
const tags = [
|
|
1511
1526
|
b.enabled ? c.green("enabled") : isInstalled(b.command) ? c.dim("installed, off") : c.dim("not installed"),
|
|
1512
|
-
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") : ""
|
|
1513
1531
|
].filter(Boolean).join(" ");
|
|
1514
1532
|
console.log(` ${id.padEnd(10)} ${b.label.padEnd(16)} ${tags}`);
|
|
1515
1533
|
}
|
|
@@ -1528,8 +1546,9 @@ function printStatus(cfg) {
|
|
|
1528
1546
|
console.log("\n " + c.dim("[d] default brain [t] toggle brain [c] connect API brain [x] remove API brain"));
|
|
1529
1547
|
console.log(" " + c.dim("[a] launch command [m] connect Telegram [enter] done"));
|
|
1530
1548
|
}
|
|
1531
|
-
async function connectApiBrain(ask, cfg) {
|
|
1532
|
-
let provider = null;
|
|
1549
|
+
async function connectApiBrain(ask, cfg, preset) {
|
|
1550
|
+
let provider = preset ?? null;
|
|
1551
|
+
if (provider) console.log(c.dim(` provider: ${provider}`));
|
|
1533
1552
|
for (let tries = 0; tries < 3 && !provider; tries++) {
|
|
1534
1553
|
const provRaw = (await ask(` provider [${PROVIDERS.join("/")}] (enter for ${PROVIDERS[0]}, or "skip"): `) ?? "skip").trim().toLowerCase();
|
|
1535
1554
|
if (provRaw === "skip" || provRaw === "q" || provRaw === "n" || provRaw === "no") {
|
|
@@ -1547,7 +1566,9 @@ async function connectApiBrain(ask, cfg) {
|
|
|
1547
1566
|
const suggestion = PROVIDER_MODEL_SUGGESTION[provider];
|
|
1548
1567
|
const modelRaw = (await ask(` model (enter for ${suggestion}): `) ?? "").trim();
|
|
1549
1568
|
const model = modelRaw || suggestion;
|
|
1550
|
-
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;
|
|
1551
1572
|
if (!idRaw) {
|
|
1552
1573
|
console.log(c.dim(" cancelled (no name)."));
|
|
1553
1574
|
return null;
|
|
@@ -1603,8 +1624,12 @@ async function runSettings(ask) {
|
|
|
1603
1624
|
} else if (choice === "t") {
|
|
1604
1625
|
const pick = (await ask(` toggle which brain [${BRAIN_IDS.join("/")}]: `) ?? "").trim();
|
|
1605
1626
|
if (BRAIN_IDS.includes(pick)) {
|
|
1627
|
+
const goneLogin = loginUnavailable(pick);
|
|
1606
1628
|
if (!cfg.brains[pick].enabled && !isInstalled(cfg.brains[pick].command)) {
|
|
1607
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".`));
|
|
1608
1633
|
} else {
|
|
1609
1634
|
cfg.brains[pick].enabled = !cfg.brains[pick].enabled;
|
|
1610
1635
|
if (!cfg.brains[pick].enabled && cfg.defaultBrain === pick) cfg.defaultBrain = selectableIds(cfg)[0] ?? null;
|
|
@@ -2139,6 +2164,75 @@ async function voiceClear() {
|
|
|
2139
2164
|
console.log(had ? c.green("\n Voice profile removed.\n") : c.dim("\n No voice profile to remove.\n"));
|
|
2140
2165
|
}
|
|
2141
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
|
+
|
|
2142
2236
|
// src/commands/init.ts
|
|
2143
2237
|
function parseBrains(raw, found) {
|
|
2144
2238
|
const s = raw.trim().toLowerCase();
|
|
@@ -2159,7 +2253,8 @@ async function init() {
|
|
|
2159
2253
|
const found = [];
|
|
2160
2254
|
for (const id of BRAIN_IDS) {
|
|
2161
2255
|
const ok2 = isInstalled(BRAIN_DEFS[id].command);
|
|
2162
|
-
|
|
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}`);
|
|
2163
2258
|
if (ok2) found.push(id);
|
|
2164
2259
|
}
|
|
2165
2260
|
console.log("");
|
|
@@ -2171,11 +2266,24 @@ async function init() {
|
|
|
2171
2266
|
const toInstall = chosen.filter((id) => !isInstalled(BRAIN_DEFS[id].command));
|
|
2172
2267
|
const loginWanted = /* @__PURE__ */ new Set();
|
|
2173
2268
|
for (const id of toInstall) {
|
|
2269
|
+
if (loginUnavailable(id)) continue;
|
|
2174
2270
|
if (await askYesNo(ask, ` ${BRAIN_DEFS[id].label} is not installed. Sign in after install? [Y/n] `, true)) loginWanted.add(id);
|
|
2175
2271
|
}
|
|
2176
2272
|
const connectedApiBrains = [];
|
|
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
|
+
}
|
|
2177
2286
|
if (await askYesNo(ask, "\nAlso connect a direct API brain (raw key, no CLI needed)? [y/N] ", false)) {
|
|
2178
|
-
const holder = defaultConfig();
|
|
2179
2287
|
const brain = await connectApiBrain(ask, holder);
|
|
2180
2288
|
if (brain) connectedApiBrains.push(brain);
|
|
2181
2289
|
}
|
|
@@ -2246,7 +2354,7 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2246
2354
|
}
|
|
2247
2355
|
}
|
|
2248
2356
|
const cfg = loadConfig() ?? defaultConfig();
|
|
2249
|
-
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);
|
|
2250
2358
|
for (const b of connectedApiBrains) if (!cfg.apiBrains.some((a) => a.id === b.id)) cfg.apiBrains.push(b);
|
|
2251
2359
|
cfg.defaultBrain = cfg.brains[defaultBrain].enabled ? defaultBrain : BRAIN_IDS.find((id) => cfg.brains[id].enabled) ?? cfg.apiBrains[0]?.id ?? null;
|
|
2252
2360
|
saveConfig(cfg);
|
|
@@ -2259,9 +2367,9 @@ Default brain? [${chosen.join("/")}] (${defPick}): `) ?? "").trim();
|
|
|
2259
2367
|
console.log("\nStart chatting:");
|
|
2260
2368
|
console.log(" " + c.accent(`source ${aliasNeedsSource}`) + c.dim(" (once; new terminals will not need it)"));
|
|
2261
2369
|
console.log(" " + c.accent(aliasAns) + "\n");
|
|
2262
|
-
console.log(c.dim(" Or right now, without sourcing: holt
|
|
2370
|
+
console.log(c.dim(" Or right now, without sourcing: holt\n"));
|
|
2263
2371
|
} else {
|
|
2264
|
-
console.log("Start chatting: " + c.accent("holt
|
|
2372
|
+
console.log("Start chatting: " + c.accent("holt") + "\n");
|
|
2265
2373
|
}
|
|
2266
2374
|
} else {
|
|
2267
2375
|
console.log(c.dim('No brain is ready yet. Install one, then run "holt init" again.\n'));
|
|
@@ -2407,6 +2515,24 @@ function buildCronLine(job, holtPath) {
|
|
|
2407
2515
|
const command = buildCommand(job, holtPath);
|
|
2408
2516
|
return `${minute} ${hour} * * * ${command} # ${CRON_MARKER}${job.id}`;
|
|
2409
2517
|
}
|
|
2518
|
+
function buildWindowsTaskFields(job, holtPath) {
|
|
2519
|
+
let args;
|
|
2520
|
+
if (job.runArgs && job.runArgs.length > 0) {
|
|
2521
|
+
args = [...job.runArgs, "--quiet"];
|
|
2522
|
+
} else {
|
|
2523
|
+
args = ["run", job.task];
|
|
2524
|
+
if (job.brain) args.push("--brain", job.brain);
|
|
2525
|
+
args.push("--quiet", "--out", logPath(job.id));
|
|
2526
|
+
}
|
|
2527
|
+
const quoted = args.map((a) => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
|
|
2528
|
+
return {
|
|
2529
|
+
program: holtPath,
|
|
2530
|
+
args: quoted.join(" "),
|
|
2531
|
+
startIn: job.workspace,
|
|
2532
|
+
when: job.when,
|
|
2533
|
+
name: `Holt ${job.id}`
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2410
2536
|
function stripCronLines(crontab, id) {
|
|
2411
2537
|
const marker = `# ${CRON_MARKER}${id}`;
|
|
2412
2538
|
const kept = crontab.split("\n").filter((line) => !line.includes(marker));
|
|
@@ -4073,29 +4199,6 @@ function saveReply(reply, format, name) {
|
|
|
4073
4199
|
return path;
|
|
4074
4200
|
}
|
|
4075
4201
|
|
|
4076
|
-
// src/commands/login.ts
|
|
4077
|
-
async function login(which) {
|
|
4078
|
-
const id = (which || "").toLowerCase();
|
|
4079
|
-
if (!BRAIN_IDS.includes(id)) {
|
|
4080
|
-
if (which) {
|
|
4081
|
-
console.error(`
|
|
4082
|
-
Unknown brain "${which}". Use one of: ${BRAIN_IDS.join(", ")}
|
|
4083
|
-
`);
|
|
4084
|
-
process.exitCode = 1;
|
|
4085
|
-
} else {
|
|
4086
|
-
console.log(c.dim(`
|
|
4087
|
-
Usage: holt login <${BRAIN_IDS.join("|")}>
|
|
4088
|
-
`));
|
|
4089
|
-
}
|
|
4090
|
-
return;
|
|
4091
|
-
}
|
|
4092
|
-
const s = BRAIN_SETUP[id];
|
|
4093
|
-
console.log("\n" + c.accent(`Sign in to ${BRAIN_DEFS[id].label}`));
|
|
4094
|
-
console.log(c.dim(` Starting "${s.login.join(" ")}". Complete sign-in, then exit that tool.
|
|
4095
|
-
`));
|
|
4096
|
-
await runInteractive(s.login[0], s.login.slice(1));
|
|
4097
|
-
}
|
|
4098
|
-
|
|
4099
4202
|
// src/access.ts
|
|
4100
4203
|
import { existsSync as existsSync11, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4101
4204
|
import { homedir as homedir6 } from "os";
|
|
@@ -4235,7 +4338,7 @@ async function chat() {
|
|
|
4235
4338
|
return;
|
|
4236
4339
|
}
|
|
4237
4340
|
await init();
|
|
4238
|
-
console.log(c.dim('\nSetup done. Run "holt
|
|
4341
|
+
console.log(c.dim('\nSetup done. Run "holt" to start talking.\n'));
|
|
4239
4342
|
return;
|
|
4240
4343
|
}
|
|
4241
4344
|
let active = resolveActive2(cfg, cfg.defaultBrain);
|
|
@@ -4363,7 +4466,7 @@ async function chat() {
|
|
|
4363
4466
|
sb.detach();
|
|
4364
4467
|
close();
|
|
4365
4468
|
await login(targetId);
|
|
4366
|
-
console.log(c.dim('\n Signed in. Run "holt
|
|
4469
|
+
console.log(c.dim(loginUnavailable(targetId) ? '\n Run "holt" again to continue.\n' : '\n Signed in. Run "holt" again to continue.\n'));
|
|
4367
4470
|
return;
|
|
4368
4471
|
}
|
|
4369
4472
|
if (cmd === "brain") {
|
|
@@ -4461,7 +4564,8 @@ async function chat() {
|
|
|
4461
4564
|
};
|
|
4462
4565
|
const res = active.kind === "cli" ? await runBrain(cfg.brains[active.id], prompt, onChunk, extraArgs) : await runApiBrain(active.brain, prompt, onChunk);
|
|
4463
4566
|
if (looksLikeAuthError(res.text)) {
|
|
4464
|
-
const
|
|
4567
|
+
const goneLogin = active.kind === "cli" ? loginUnavailable(active.id) : null;
|
|
4568
|
+
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.`;
|
|
4465
4569
|
if (!streamed || !res.text.endsWith("\n")) console.log("");
|
|
4466
4570
|
console.log(c.red(hint) + "\n");
|
|
4467
4571
|
showStatus();
|
|
@@ -4600,11 +4704,12 @@ function resolveTarget(cfg) {
|
|
|
4600
4704
|
if (!id) return null;
|
|
4601
4705
|
if (BRAIN_IDS.includes(id)) {
|
|
4602
4706
|
const bid = id;
|
|
4603
|
-
return { kind: "cli", id: bid, label: cfg.brains[bid].label };
|
|
4707
|
+
if (cliBrainUsable(bid)) return { kind: "cli", id: bid, label: cfg.brains[bid].label };
|
|
4708
|
+
} else {
|
|
4709
|
+
const api = findApiBrain(cfg, id);
|
|
4710
|
+
if (api) return { kind: "api", label: `${id} (api: ${api.provider}/${api.model})` };
|
|
4604
4711
|
}
|
|
4605
|
-
const
|
|
4606
|
-
if (api) return { kind: "api", label: `${id} (api: ${api.provider}/${api.model})` };
|
|
4607
|
-
const fallback = BRAIN_IDS.find((b) => cfg.brains[b].enabled);
|
|
4712
|
+
const fallback = BRAIN_IDS.find((b) => cfg.brains[b].enabled && cliBrainUsable(b));
|
|
4608
4713
|
if (fallback) return { kind: "cli", id: fallback, label: cfg.brains[fallback].label };
|
|
4609
4714
|
return null;
|
|
4610
4715
|
}
|
|
@@ -4644,13 +4749,23 @@ async function launch() {
|
|
|
4644
4749
|
await ensureSetup();
|
|
4645
4750
|
const target = resolveTarget(cfg);
|
|
4646
4751
|
if (!target) {
|
|
4752
|
+
const def = cfg.defaultBrain;
|
|
4753
|
+
const gone = def && BRAIN_IDS.includes(def) ? loginUnavailable(def) : null;
|
|
4754
|
+
if (gone) {
|
|
4755
|
+
console.log("\n " + c.red(`${cfg.brains[def].label} can no longer sign in.`));
|
|
4756
|
+
console.log(c.dim(" " + gone.reason));
|
|
4757
|
+
console.log(c.dim(` Get a key at ${gone.keyUrl}, then run "holt setting" and press "c" to connect it.`));
|
|
4758
|
+
if (cfg.apiBrains.length) {
|
|
4759
|
+
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".`));
|
|
4760
|
+
}
|
|
4761
|
+
console.log("");
|
|
4762
|
+
return;
|
|
4763
|
+
}
|
|
4647
4764
|
console.log(c.dim('\n No brain is ready. Run "holt setting" or "holt init".\n'));
|
|
4648
4765
|
return;
|
|
4649
4766
|
}
|
|
4650
4767
|
if (target.kind === "api") {
|
|
4651
|
-
console.log(
|
|
4652
|
-
launch needs a CLI brain; starting chat instead (default is ${target.label}).
|
|
4653
|
-
`));
|
|
4768
|
+
console.log(renderIntroBox());
|
|
4654
4769
|
await chat();
|
|
4655
4770
|
return;
|
|
4656
4771
|
}
|
|
@@ -4727,6 +4842,34 @@ async function memoryCmd(sub, rest = []) {
|
|
|
4727
4842
|
close();
|
|
4728
4843
|
return;
|
|
4729
4844
|
}
|
|
4845
|
+
if (action === "add" || action === "remember") {
|
|
4846
|
+
let items = [];
|
|
4847
|
+
const inline = rest.join(" ").trim();
|
|
4848
|
+
if (inline) items = [inline];
|
|
4849
|
+
else if (!process.stdin.isTTY) {
|
|
4850
|
+
items = readFileSync14(0, "utf8").split("\n").map((l) => l.replace(/^\s*[-*]\s+/, "").trim()).filter(Boolean);
|
|
4851
|
+
}
|
|
4852
|
+
if (items.length === 0) {
|
|
4853
|
+
console.log(c.dim('\n Usage: holt memory add "<fact>" (or pipe one fact per line on stdin)\n'));
|
|
4854
|
+
close();
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4857
|
+
const session = newSessionId();
|
|
4858
|
+
let saved = 0;
|
|
4859
|
+
let dupes = 0;
|
|
4860
|
+
for (const item of items) {
|
|
4861
|
+
if (await saveFact(item, session)) saved++;
|
|
4862
|
+
else dupes++;
|
|
4863
|
+
}
|
|
4864
|
+
const dupNote = dupes ? c.dim(` (${dupes} already known)`) : "";
|
|
4865
|
+
console.log("\n" + c.green(` Remembered ${saved} fact${saved === 1 ? "" : "s"}.`) + dupNote);
|
|
4866
|
+
if (saved && !await embeddingsAvailable()) {
|
|
4867
|
+
console.log(c.dim(' Saved in keyword mode. Run "holt doctor --fix" for semantic recall.'));
|
|
4868
|
+
}
|
|
4869
|
+
console.log("");
|
|
4870
|
+
close();
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4730
4873
|
if (action === "facts") {
|
|
4731
4874
|
if (!existsSync13(factsMdPath())) {
|
|
4732
4875
|
console.log(c.dim("\n No facts distilled yet. They form when you end a chat session.\n"));
|
|
@@ -4767,9 +4910,9 @@ async function memoryCmd(sub, rest = []) {
|
|
|
4767
4910
|
close();
|
|
4768
4911
|
return;
|
|
4769
4912
|
}
|
|
4770
|
-
if (action && !["clear", "embed", "facts", "search", "global"].includes(action)) {
|
|
4913
|
+
if (action && !["add", "remember", "clear", "embed", "facts", "search", "global"].includes(action)) {
|
|
4771
4914
|
console.error(`
|
|
4772
|
-
Unknown memory subcommand: "${sub}". Use: search <query> | facts | embed | global | clear
|
|
4915
|
+
Unknown memory subcommand: "${sub}". Use: add <fact> | search <query> | facts | embed | global | clear
|
|
4773
4916
|
`);
|
|
4774
4917
|
process.exitCode = 1;
|
|
4775
4918
|
close();
|
|
@@ -4791,7 +4934,8 @@ async function memoryCmd(sub, rest = []) {
|
|
|
4791
4934
|
}
|
|
4792
4935
|
const gEnabled = isGlobalEnabled(workspace());
|
|
4793
4936
|
console.log(` global ${gEnabled ? c.green("on") + c.dim(" (contributes + reads shared facts)") : c.dim("off (this folder is isolated, the default)")}`);
|
|
4794
|
-
console.log(c.dim(
|
|
4937
|
+
console.log(c.dim('\n holt memory add "<fact>" teach it something durable right now'));
|
|
4938
|
+
console.log(c.dim(" holt memory search <query> find remembered moments"));
|
|
4795
4939
|
console.log(c.dim(" holt memory facts show distilled facts (facts.md)"));
|
|
4796
4940
|
console.log(c.dim(" holt memory embed embed older memories for semantic recall"));
|
|
4797
4941
|
console.log(c.dim(" holt memory global share high-value facts across your folders"));
|
|
@@ -7066,7 +7210,7 @@ async function graph(args = []) {
|
|
|
7066
7210
|
if (skipNotes.length) console.log(c.dim(` ingest skipped: ${skipNotes.join(", ")}`));
|
|
7067
7211
|
}
|
|
7068
7212
|
if (turns.length === 0 && wikiPages.length === 0 && !codeGraph) {
|
|
7069
|
-
console.log(c.dim('\n No memory in this folder yet.
|
|
7213
|
+
console.log(c.dim('\n No memory in this folder yet. Talk to Holt here first with "holt", then come back.\n'));
|
|
7070
7214
|
console.log(c.dim(` Or ingest this folder's code/docs: "holt graph --code" / "holt graph --docs".
|
|
7071
7215
|
`));
|
|
7072
7216
|
return;
|
|
@@ -7389,7 +7533,7 @@ function holtTools() {
|
|
|
7389
7533
|
}
|
|
7390
7534
|
|
|
7391
7535
|
// src/version.ts
|
|
7392
|
-
var VERSION = "0.
|
|
7536
|
+
var VERSION = "0.17.0";
|
|
7393
7537
|
|
|
7394
7538
|
// src/commands/mcp.ts
|
|
7395
7539
|
function printSetup() {
|
|
@@ -7573,7 +7717,7 @@ async function run(args) {
|
|
|
7573
7717
|
close();
|
|
7574
7718
|
if (!ok2) return;
|
|
7575
7719
|
} else {
|
|
7576
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
7720
|
+
process.stderr.write("This folder is not trusted. Run holt init here once, then schedule/run.\n");
|
|
7577
7721
|
process.exitCode = 1;
|
|
7578
7722
|
return;
|
|
7579
7723
|
}
|
|
@@ -7704,11 +7848,28 @@ async function addCmd(rest) {
|
|
|
7704
7848
|
console.log(` log ${logPath(job.id)}`);
|
|
7705
7849
|
if (process.platform === "darwin") installDarwin(job, holtPath);
|
|
7706
7850
|
else if (process.platform === "linux") installLinux(job, holtPath);
|
|
7707
|
-
else
|
|
7851
|
+
else printManualInstall(job, holtPath);
|
|
7852
|
+
console.log("");
|
|
7853
|
+
}
|
|
7854
|
+
function printManualInstall(job, holtPath) {
|
|
7855
|
+
if (process.platform !== "win32") {
|
|
7708
7856
|
console.log(c.dim("\n Your OS has no built-in installer. Install this entry yourself:"));
|
|
7709
7857
|
console.log(" " + buildCronLine(job, holtPath));
|
|
7858
|
+
return;
|
|
7859
|
+
}
|
|
7860
|
+
const f = buildWindowsTaskFields(job, holtPath);
|
|
7861
|
+
console.log(c.dim("\n Saved, but not started: Holt cannot install timers on Windows yet."));
|
|
7862
|
+
console.log(c.dim(' Open Task Scheduler, choose "Create Basic Task", and fill in:'));
|
|
7863
|
+
console.log(` Name ${f.name}`);
|
|
7864
|
+
console.log(` Trigger Daily at ${f.when}`);
|
|
7865
|
+
console.log(` Program ${f.program}`);
|
|
7866
|
+
console.log(` Arguments ${f.args}`);
|
|
7867
|
+
console.log(` Start in ${f.startIn}`);
|
|
7868
|
+
if (job.notify) {
|
|
7869
|
+
console.log(
|
|
7870
|
+
c.dim("\n Note: --notify does not carry over to Task Scheduler. Read the log file instead.")
|
|
7871
|
+
);
|
|
7710
7872
|
}
|
|
7711
|
-
console.log("");
|
|
7712
7873
|
}
|
|
7713
7874
|
function listCmd2() {
|
|
7714
7875
|
const jobs = loadJobs();
|
|
@@ -7910,10 +8071,7 @@ function installSchedule(r) {
|
|
|
7910
8071
|
addJob(job);
|
|
7911
8072
|
if (process.platform === "darwin") installDarwin2(job, holtPath);
|
|
7912
8073
|
else if (process.platform === "linux") installLinux2(job, holtPath);
|
|
7913
|
-
else
|
|
7914
|
-
console.log(c.dim("\n Your OS has no built-in installer. Install this entry yourself:"));
|
|
7915
|
-
console.log(" " + buildCronLine(job, holtPath));
|
|
7916
|
-
}
|
|
8074
|
+
else printManualInstall(job, holtPath);
|
|
7917
8075
|
}
|
|
7918
8076
|
function removeScheduleDarwin(id) {
|
|
7919
8077
|
const path = plistPath(id);
|
|
@@ -8080,7 +8238,7 @@ async function runCmd(rest) {
|
|
|
8080
8238
|
close();
|
|
8081
8239
|
if (!ok2) return;
|
|
8082
8240
|
} else {
|
|
8083
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
8241
|
+
process.stderr.write("This folder is not trusted. Run holt init here once, then run the routine.\n");
|
|
8084
8242
|
process.exitCode = 1;
|
|
8085
8243
|
return;
|
|
8086
8244
|
}
|
|
@@ -8483,8 +8641,14 @@ async function doctor(args = []) {
|
|
|
8483
8641
|
for (const id of BRAIN_IDS) {
|
|
8484
8642
|
const def = BRAIN_DEFS[id];
|
|
8485
8643
|
const here = isInstalled(def.command);
|
|
8486
|
-
|
|
8644
|
+
const gone = loginUnavailable(id);
|
|
8645
|
+
const usable = cliBrainUsable(id);
|
|
8646
|
+
if (here && usable) installed.push(def.label);
|
|
8487
8647
|
console.log(` ${here ? c.green("installed") : c.dim("missing ")} ${def.label} ${c.dim(`(${def.command})`)}`);
|
|
8648
|
+
if (here && gone && !usable) {
|
|
8649
|
+
warn(`${def.label} cannot sign in any more. ${gone.reason}`);
|
|
8650
|
+
steps.push(`Get a ${gone.provider} key at ${gone.keyUrl}, then connect it: holt setting -> "c".`);
|
|
8651
|
+
}
|
|
8488
8652
|
}
|
|
8489
8653
|
const cfg = loadConfig();
|
|
8490
8654
|
const apiBrains = cfg?.apiBrains ?? [];
|
|
@@ -8565,7 +8729,7 @@ async function doctor(args = []) {
|
|
|
8565
8729
|
steps.unshift('Set up this folder: run "holt init".');
|
|
8566
8730
|
}
|
|
8567
8731
|
if (steps.length === 0) {
|
|
8568
|
-
good('You are in good shape. Run "holt
|
|
8732
|
+
good('You are in good shape. Run "holt" to start.');
|
|
8569
8733
|
} else {
|
|
8570
8734
|
let n = 1;
|
|
8571
8735
|
for (const s of steps) console.log(` ${c.accent(String(n++) + ".")} ${s}`);
|
|
@@ -8875,7 +9039,7 @@ async function write(args) {
|
|
|
8875
9039
|
close();
|
|
8876
9040
|
if (!ok2) return;
|
|
8877
9041
|
} else {
|
|
8878
|
-
process.stderr.write("This folder is not trusted. Run holt init
|
|
9042
|
+
process.stderr.write("This folder is not trusted. Run holt init here once first.\n");
|
|
8879
9043
|
process.exitCode = 1;
|
|
8880
9044
|
return;
|
|
8881
9045
|
}
|
|
@@ -8989,12 +9153,12 @@ From your phone, while you sleep:
|
|
|
8989
9153
|
|
|
8990
9154
|
Housekeeping:
|
|
8991
9155
|
setting Change brains, API connections, and your launch word (per folder)
|
|
8992
|
-
login <brain> Sign in to a brain: claude
|
|
9156
|
+
login <brain> Sign in to a brain: claude or codex
|
|
9157
|
+
(Gemini has no sign-in left; it routes you to an API key)
|
|
8993
9158
|
update Get the latest Holt, in one word
|
|
8994
9159
|
reset Start this folder over (asks first; your own files are untouched)
|
|
8995
9160
|
hook Ambient memory inside plain Claude Code: holt hook [install | status]
|
|
8996
9161
|
mcp Share Holt's memory with other AI apps (Claude Code, Cursor, Codex)
|
|
8997
|
-
chat Lightweight built-in chat, used for API brains
|
|
8998
9162
|
init Full setup by hand (bare "holt" runs this for you when needed)
|
|
8999
9163
|
launch Same as bare "holt": start your assistant
|
|
9000
9164
|
version | help Print the version / show this help
|
|
@@ -9032,6 +9196,8 @@ async function main() {
|
|
|
9032
9196
|
case "init":
|
|
9033
9197
|
await init();
|
|
9034
9198
|
break;
|
|
9199
|
+
// Undocumented: bare `holt` reaches the REPL by itself for API brains.
|
|
9200
|
+
// Kept as a working alias so older scripts and aliases do not break.
|
|
9035
9201
|
case "chat":
|
|
9036
9202
|
await chat();
|
|
9037
9203
|
break;
|
package/package.json
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audit
|
|
3
|
+
description: A read-only health check of your Holt setup across the four rungs (Know, Reach, Do, Run), naming the gaps and the single next thing to fix. Use weekly, or when Holt feels less useful than expected.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# audit
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
The user wants to know whether Holt is actually set up well, or why it feels
|
|
11
|
+
thin. This skill inspects the real state of the folder and reports back. It
|
|
12
|
+
changes nothing: no files written, no settings altered, no facts saved. If a fix
|
|
13
|
+
is obvious, name the command and let the user run it.
|
|
14
|
+
|
|
15
|
+
## The four rungs
|
|
16
|
+
|
|
17
|
+
Each rung depends on the one below it. A gap low down makes everything above it
|
|
18
|
+
worse, so always report the lowest gap first.
|
|
19
|
+
|
|
20
|
+
1. **Know**: it holds facts about the user and their work.
|
|
21
|
+
2. **Reach**: it can see the real material, not just what is typed at it.
|
|
22
|
+
3. **Do**: it runs their repeated work as skills.
|
|
23
|
+
4. **Run**: it acts without the user being present.
|
|
24
|
+
|
|
25
|
+
## Instructions
|
|
26
|
+
|
|
27
|
+
### Gather the state
|
|
28
|
+
|
|
29
|
+
Run these and read the output. If you cannot run commands in this session, ask
|
|
30
|
+
the user to paste the output of the first three rather than guessing.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
holt memory facts count, embedded count, recall mode
|
|
34
|
+
holt memory facts what it actually knows
|
|
35
|
+
holt voice show is there a writing profile
|
|
36
|
+
holt hook status ambient memory in Claude Code
|
|
37
|
+
holt skill list which skills exist, and which are built in
|
|
38
|
+
holt routine list named jobs
|
|
39
|
+
holt schedule list timers
|
|
40
|
+
holt doctor machine level problems
|
|
41
|
+
holt wiki status derived knowledge, if used
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Score each rung
|
|
45
|
+
|
|
46
|
+
Be strict. A generous audit is a useless one. Mark each rung **solid**, **thin**,
|
|
47
|
+
or **missing**, and say what the evidence was.
|
|
48
|
+
|
|
49
|
+
- **Know** is solid when memory holds a few dozen facts that are about the
|
|
50
|
+
user's actual work, and recall is running on embeddings rather than keyword
|
|
51
|
+
match. It is thin when the facts are mostly setup trivia, or when
|
|
52
|
+
`holt memory` shows moments that were never embedded. Read the facts, do not
|
|
53
|
+
just count them: fifty facts about one afternoon is still thin.
|
|
54
|
+
- **Reach** is solid when Holt sees material without the user pasting it: the
|
|
55
|
+
hook is installed, or MCP is wired into the tools they use, or the folder
|
|
56
|
+
holds the real files. It is missing when every session starts by pasting
|
|
57
|
+
context in by hand.
|
|
58
|
+
- **Do** is solid when at least one skill exists that the user or their
|
|
59
|
+
registry provided, tuned to their work. Built-in skills alone do not count;
|
|
60
|
+
everyone has those. Note which ones are actually being used.
|
|
61
|
+
- **Run** is solid when something has produced output while they were away, and
|
|
62
|
+
they read it. A routine that exists but has never run is thin, not solid.
|
|
63
|
+
|
|
64
|
+
### Report
|
|
65
|
+
|
|
66
|
+
1. Name the rung they are on: the highest one that is solid, with everything
|
|
67
|
+
below it also solid. Say it in one line.
|
|
68
|
+
2. List the gaps, lowest rung first. For each, state what is missing and the
|
|
69
|
+
command that would close it.
|
|
70
|
+
3. Give **one** next action. Not a menu, not a ranked list of five. The single
|
|
71
|
+
thing that unblocks the most.
|
|
72
|
+
4. Flag anything actively broken separately from anything merely absent. A
|
|
73
|
+
scheduled routine that fails every night is worse than no routine, and a
|
|
74
|
+
memory in keyword mode is quietly degrading every answer.
|
|
75
|
+
|
|
76
|
+
Keep the whole report short enough to read in a minute. It is a dashboard, not
|
|
77
|
+
an essay.
|
|
78
|
+
|
|
79
|
+
### Known traps to check for
|
|
80
|
+
|
|
81
|
+
- **Keyword mode.** If `holt memory` says recall is keyword rather than
|
|
82
|
+
embeddings, semantic recall is off and every answer is worse. `holt doctor
|
|
83
|
+
--fix` usually repairs it.
|
|
84
|
+
- **Unembedded moments.** If embedded count is below total, older memories are
|
|
85
|
+
invisible to semantic recall. `holt memory embed` fixes it in one pass.
|
|
86
|
+
- **Hand-edited facts.** Facts added by editing `facts.md` directly are never
|
|
87
|
+
embedded, so they never come back through recall. They must be re-added with
|
|
88
|
+
`holt memory add`.
|
|
89
|
+
- **Windows and timers.** On Windows, `holt schedule` and a routine's `--at`
|
|
90
|
+
time do not install anything. Anything on rung four there has to run another
|
|
91
|
+
way, so check the platform before calling rung four solid.
|
|
92
|
+
- **Automation above an empty memory.** If Run looks solid while Know is thin,
|
|
93
|
+
say so bluntly. That setup produces confident wrong output on a timer, and it
|
|
94
|
+
is the most expensive failure on this list.
|
|
95
|
+
|
|
96
|
+
## Example
|
|
97
|
+
|
|
98
|
+
You are on rung two. Know and Reach are solid, Do is thin.
|
|
99
|
+
|
|
100
|
+
Know: solid. 41 facts covering the agro project, the job search, and family
|
|
101
|
+
health. Recall is on embeddings.
|
|
102
|
+
Reach: solid. Hook installed, so Claude Code sessions recall automatically.
|
|
103
|
+
Do: thin. Ten skills listed, all built in. Nothing tuned to your work.
|
|
104
|
+
Run: missing. No routines, no schedules.
|
|
105
|
+
|
|
106
|
+
Broken: 12 of 340 moments have no embeddings, so a slice of last month is
|
|
107
|
+
invisible to recall.
|
|
108
|
+
|
|
109
|
+
Next action: run `/skill level-up` and turn the weekly client update into a
|
|
110
|
+
skill. Rung four is not worth touching until something on rung three has earned
|
|
111
|
+
its place.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: level-up
|
|
3
|
+
description: A weekly working session that turns one piece of recurring manual work into something Holt does for you, and ships it before the session ends. Use as a standing ritual once memory has real facts in it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# level-up
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
The user has been using Holt and wants it to carry more. This is the ritual that
|
|
11
|
+
compounds: once a week, one drain becomes one shipped capability. Run it after
|
|
12
|
+
memory holds real facts about their work; on an empty folder, run `onboard`
|
|
13
|
+
first.
|
|
14
|
+
|
|
15
|
+
The session fails if it ends in a plan. It succeeds if something exists that did
|
|
16
|
+
not exist an hour ago.
|
|
17
|
+
|
|
18
|
+
## Instructions
|
|
19
|
+
|
|
20
|
+
### 1. Find the constraint
|
|
21
|
+
|
|
22
|
+
Read what memory already knows. Relevant past notes are injected above; also run
|
|
23
|
+
`holt memory facts` and search for the work they complain about:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
holt memory search <what they keep doing by hand>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then ask what ate their time this week, and what they did more than once. Pick
|
|
30
|
+
**one**. The best candidate is frequent, rule-shaped, and low-stakes if it comes
|
|
31
|
+
out slightly wrong. Say plainly why you picked it over the others.
|
|
32
|
+
|
|
33
|
+
If several are tempting, choose the boring one. A dull thing that runs every
|
|
34
|
+
week beats an ambitious thing that runs once.
|
|
35
|
+
|
|
36
|
+
### 2. Decide what to do with it
|
|
37
|
+
|
|
38
|
+
Three options, in this order:
|
|
39
|
+
|
|
40
|
+
- **Drop it.** Does this task need to exist? Work that only continues out of
|
|
41
|
+
habit should be stopped, not automated. Ask this first and mean it; the
|
|
42
|
+
cheapest automation is the one you do not build.
|
|
43
|
+
- **Automate it.** Rule-shaped and repeatable, with a checkable output.
|
|
44
|
+
- **Hand it off.** It needs judgement, relationships, or accountability a
|
|
45
|
+
machine should not hold. Say so and move to the next candidate.
|
|
46
|
+
|
|
47
|
+
Automating something that should have been dropped makes the waste permanent
|
|
48
|
+
and harder to see. Push back before building.
|
|
49
|
+
|
|
50
|
+
### 3. Map it before building
|
|
51
|
+
|
|
52
|
+
Walk the task through in the user's own words: what starts it, what information
|
|
53
|
+
it needs, what it produces, and who sees the output. Write the steps down. If
|
|
54
|
+
you cannot state the steps in plain sentences, it is not ready to build.
|
|
55
|
+
|
|
56
|
+
Then set how much rope it gets:
|
|
57
|
+
|
|
58
|
+
- **Drafts, you send.** Anything that leaves the house: email, posts, messages.
|
|
59
|
+
- **Runs, you review.** Produces a file or a note you read afterwards.
|
|
60
|
+
- **Runs unattended.** Only for work that is reversible and already proven by
|
|
61
|
+
hand for a few cycles.
|
|
62
|
+
|
|
63
|
+
Start one level more cautious than feels necessary. Moving up later is easy;
|
|
64
|
+
un-sending is not.
|
|
65
|
+
|
|
66
|
+
### 4. Build it now
|
|
67
|
+
|
|
68
|
+
Pick the lightest thing that works:
|
|
69
|
+
|
|
70
|
+
- A **skill**, when it is a repeatable way of thinking or writing:
|
|
71
|
+
`holt skill create <name>`, then write the instructions into its `SKILL.md`.
|
|
72
|
+
Check the registry first with `holt skill search <topic>`, because the work
|
|
73
|
+
may be done already.
|
|
74
|
+
- A **routine**, when it is a task worth naming and rerunning:
|
|
75
|
+
`holt routine add <name> "<task>"`, run it with `holt routine run <name>`.
|
|
76
|
+
- A **schedule**, when it should happen without being asked:
|
|
77
|
+
`holt schedule add`, or a scheduled routine. Only after it has produced good
|
|
78
|
+
output by hand at least twice.
|
|
79
|
+
- A **connection**, when the blocker is that Holt cannot see the material:
|
|
80
|
+
`holt hook` for ambient memory in Claude Code, `holt mcp setup` for other
|
|
81
|
+
tools. Sometimes the whole win is that it can finally read the thing.
|
|
82
|
+
|
|
83
|
+
Prefer a fixed sequence of steps over an open-ended agent. Predictable and
|
|
84
|
+
slightly dumb beats clever and unrepeatable.
|
|
85
|
+
|
|
86
|
+
### 5. Test it on something real
|
|
87
|
+
|
|
88
|
+
Run it once, now, on genuine input, and read the output with the user. If it is
|
|
89
|
+
wrong, fix it in this session. A capability that has never run on real data is
|
|
90
|
+
not shipped.
|
|
91
|
+
|
|
92
|
+
Then agree how it gets switched off: which command removes it, or which file to
|
|
93
|
+
delete. Anything that runs on a timer needs a way to stop.
|
|
94
|
+
|
|
95
|
+
### 6. Close the loop
|
|
96
|
+
|
|
97
|
+
1. Save what was built and why, so next week starts informed:
|
|
98
|
+
```
|
|
99
|
+
holt memory add "Built the <name> routine on <date>: <what it does, what triggers it>"
|
|
100
|
+
```
|
|
101
|
+
2. Note what to watch for: the failure mode most likely to show up first.
|
|
102
|
+
3. Name the single next candidate from the list you did not pick, so the next
|
|
103
|
+
session starts with a running head start.
|
|
104
|
+
|
|
105
|
+
## Example
|
|
106
|
+
|
|
107
|
+
This week: forty minutes lost to writing the same client update every Friday.
|
|
108
|
+
|
|
109
|
+
Not dropped, it is genuinely wanted. Not handed off, the facts live here.
|
|
110
|
+
Automate it, at drafts-you-send, because it goes to a client.
|
|
111
|
+
|
|
112
|
+
Built as a routine over the folder's memory, run once against this week's real
|
|
113
|
+
work. First draft named the wrong milestone; instructions tightened, second
|
|
114
|
+
draft was sendable. Off switch: `holt routine remove friday-update`.
|
|
115
|
+
|
|
116
|
+
Watch for: it will keep sounding confident about numbers it has not been given.
|
|
117
|
+
|
|
118
|
+
Next week's candidate: the invoice chase.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onboard
|
|
3
|
+
description: A guided first-day interview that fills Holt's memory with who you are and what you are working on, so recall is useful from day one instead of after weeks of use. Use on a fresh folder, or to top up a thin memory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# onboard
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Holt is installed but it does not know the user yet. An empty memory makes every
|
|
11
|
+
answer generic, which is the most common reason people install Holt and drift
|
|
12
|
+
away. This skill front-loads the knowledge that recall needs, in about fifteen
|
|
13
|
+
minutes, and ends with one real piece of work.
|
|
14
|
+
|
|
15
|
+
Also use it when `holt memory` shows very few facts and answers still feel
|
|
16
|
+
generic, or when the user starts a new folder for a different part of their life.
|
|
17
|
+
|
|
18
|
+
## The ladder
|
|
19
|
+
|
|
20
|
+
Holt gets useful in four rungs, in order. Onboarding is rung one.
|
|
21
|
+
|
|
22
|
+
1. **Know**: it holds facts about you and your work. (`holt memory`)
|
|
23
|
+
2. **Reach**: it can see your real material: files, notes, other tools. (`holt hook`, `holt mcp`)
|
|
24
|
+
3. **Do**: it runs your repeated work as skills. (`holt skill`)
|
|
25
|
+
4. **Run**: it acts without you being there. (`holt routine`, `holt schedule`)
|
|
26
|
+
|
|
27
|
+
Do not skip ahead. Automation on top of an empty memory produces confident
|
|
28
|
+
nonsense on a timer.
|
|
29
|
+
|
|
30
|
+
## Instructions
|
|
31
|
+
|
|
32
|
+
### Before you start
|
|
33
|
+
|
|
34
|
+
1. Check what is already known: run `holt memory` and, if any facts exist,
|
|
35
|
+
`holt memory facts`. If the folder already holds real facts, say so and offer
|
|
36
|
+
a top-up on the thin areas instead of the full interview. Never re-ask
|
|
37
|
+
something already in memory.
|
|
38
|
+
2. Set expectations in two lines: seven questions, about fifteen minutes, and
|
|
39
|
+
they can stop at any point because you save as you go.
|
|
40
|
+
|
|
41
|
+
### Running the interview
|
|
42
|
+
|
|
43
|
+
Ask **one question at a time.** Wait for the answer before moving on. Never
|
|
44
|
+
present the whole list at once; a wall of questions is the main reason people
|
|
45
|
+
abandon setup. Keep your own words short between questions.
|
|
46
|
+
|
|
47
|
+
Ask for the real thing, never a description of it. "Paste the last update you
|
|
48
|
+
sent your team" beats "how would you describe your writing style". Concrete
|
|
49
|
+
input is what makes recall work later.
|
|
50
|
+
|
|
51
|
+
If an answer is thin or abstract, ask one follow-up for a specific name, number,
|
|
52
|
+
or example, then move on. Two follow-ups on one question is too many.
|
|
53
|
+
|
|
54
|
+
The seven questions:
|
|
55
|
+
|
|
56
|
+
1. **Who you are.** What is your work, and what does a normal day actually
|
|
57
|
+
involve? Get the role, and what they are responsible for.
|
|
58
|
+
2. **What is live.** The three to five things genuinely in flight right now, and
|
|
59
|
+
the state of each. Push for status, not just titles.
|
|
60
|
+
3. **Who matters.** The people, clients, teams, or companies that come up
|
|
61
|
+
regularly, and how each relates to them. Names matter here.
|
|
62
|
+
4. **Standing rules.** Decisions and preferences you should never re-ask:
|
|
63
|
+
how they want to be spoken to, what is off limits, choices already made.
|
|
64
|
+
5. **Where the work lives.** Which folders, tools, and accounts hold the real
|
|
65
|
+
material. This becomes the map for rung two, so note what is not reachable
|
|
66
|
+
yet rather than pretending it is.
|
|
67
|
+
6. **What drains the time.** The recurring work they resent doing by hand. Ask
|
|
68
|
+
for the last three times it happened. These are the candidates the `level-up`
|
|
69
|
+
skill will draw from later.
|
|
70
|
+
7. **Their voice.** Ask them to paste two or three samples of their real
|
|
71
|
+
writing: a sent email, a message, a published post. Then run
|
|
72
|
+
`holt voice add <file>` if the samples are in files, or tell them the command
|
|
73
|
+
so their drafts sound like them rather than like a model.
|
|
74
|
+
|
|
75
|
+
### Saving as you go
|
|
76
|
+
|
|
77
|
+
After every two questions, write what you learned to memory with:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
holt memory add "<one durable fact>"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
One fact per call. Several facts can be piped in at once, one per line.
|
|
84
|
+
|
|
85
|
+
Write facts that will still be true in a month and that a future session could
|
|
86
|
+
not guess. "Runs product for a fintech in Bhubaneswar, nine years in" is a fact.
|
|
87
|
+
"Wants to be more productive" is noise. Include names, numbers, and dates. Write
|
|
88
|
+
the fact so it stands alone, because recall will surface it without this
|
|
89
|
+
conversation around it.
|
|
90
|
+
|
|
91
|
+
If you cannot run commands in this session, write the facts out as a block of
|
|
92
|
+
`holt memory add` lines and ask the user to paste them into their terminal.
|
|
93
|
+
Do not hand-edit `facts.md`: facts added that way are never embedded, so
|
|
94
|
+
semantic recall will not find them.
|
|
95
|
+
|
|
96
|
+
### Finishing
|
|
97
|
+
|
|
98
|
+
1. Show what was captured: run `holt memory facts` so they see their own words
|
|
99
|
+
stored in a file they own and can edit.
|
|
100
|
+
2. Prove it works. Ask one question whose answer depends on what they just told
|
|
101
|
+
you, and answer it from memory. This is the moment the tool becomes real, so
|
|
102
|
+
do not skip it.
|
|
103
|
+
3. Do one real thing now. Pick the smallest useful task from what they said
|
|
104
|
+
drains their time and complete it in this session. An artifact beats a plan.
|
|
105
|
+
4. Name rung two. Tell them the single next step, usually `holt hook` so Claude
|
|
106
|
+
Code remembers automatically, or `holt mcp setup` to reach their other tools.
|
|
107
|
+
One step, not a menu.
|
|
108
|
+
5. Point at the ritual: `/skill level-up` weekly turns one drain into one
|
|
109
|
+
automation, and `/skill audit` shows which rung they are on.
|
|
110
|
+
|
|
111
|
+
## Example
|
|
112
|
+
|
|
113
|
+
Not: "Tell me about your business, your projects, your clients, your
|
|
114
|
+
preferences, and your writing style."
|
|
115
|
+
|
|
116
|
+
Instead: "First one. What is your work, and what does a normal Tuesday actually
|
|
117
|
+
look like?" then, after the answer: "Got it. Now the live list: what are the three to five
|
|
118
|
+
things genuinely in flight this week?"
|
|
119
|
+
|
|
120
|
+
Then, quietly, between questions:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
holt memory add "Senior product manager, nine years, currently between roles and job-hunting in the Netherlands"
|
|
124
|
+
holt memory add "Runs Sasyam Agro with an uncle in Bargarh; demo plots are on the uncle's land"
|
|
125
|
+
```
|