threadnote 0.3.3 → 0.3.5
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 +40 -19
- package/dist/threadnote.cjs +50 -21
- package/docs/agent-instructions.md +14 -0
- package/docs/migration.md +3 -2
- package/docs/troubleshooting.md +7 -7
- package/package.json +1 -1
- package/scripts/install.sh +0 -1
package/README.md
CHANGED
|
@@ -6,18 +6,28 @@ and it does not index whole repositories by default.
|
|
|
6
6
|
|
|
7
7
|
## Real-World Uses
|
|
8
8
|
|
|
9
|
-
Want to continue work in a fresh agent session
|
|
10
|
-
new agents automatically recall recent handoffs and relevant memories before they start changing code.
|
|
9
|
+
**Want to continue work in a fresh agent session?**
|
|
10
|
+
`threadnote install` adds user-level Codex, Claude, and Cursor instructions so new agents automatically recall recent handoffs and relevant memories before they start changing code.
|
|
11
11
|
|
|
12
|
-
Implemented a feature a while ago and need to pick it up again
|
|
13
|
-
Threadnote returns auditable `viking://` pointers that the agent can read before deciding what still matters.
|
|
12
|
+
**Implemented a feature a while ago and need to pick it up again?**
|
|
13
|
+
Ask the agent to recall the feature, branch, or repo. Threadnote returns auditable `viking://` pointers that the agent can read before deciding what still matters.
|
|
14
14
|
|
|
15
|
-
Switching between Codex
|
|
16
|
-
handoff before they pause, so the next agent can search the same local memory layer instead of reconstructing context
|
|
15
|
+
**Switching between Codex, Claude, and Cursor?**\
|
|
16
|
+
Install the MCP adapter for each agent you use. The user-level instructions tell agents to store a handoff before they pause, so the next agent can search the same local memory layer instead of reconstructing context
|
|
17
17
|
from chat history.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
**Working through a long task until the agent context window fills up?**
|
|
20
|
+
After compaction, the next agent turn can recall the relevant Threadnote memories and handoffs instead of relying only on the compressed conversation summary.
|
|
21
|
+
|
|
22
|
+
**Found a durable workflow fact, like how a repo runs tests or where release notes live?**
|
|
23
|
+
Ask the agent to remember it. Threadnote keeps that memory local and searchable without editing unrelated repo files.
|
|
24
|
+
|
|
25
|
+
**Have reusable agent workflows already installed as skills?**\
|
|
26
|
+
Run `threadnote seed-skills` to make local `SKILL.md` guidance discoverable through recall. Agents can find relevant testing, release, on-call, debugging, or plugin-provided workflows without you reopening the same skill files by hand.
|
|
27
|
+
|
|
28
|
+
**Recall returned several overlapping memories?**
|
|
29
|
+
Agents are instructed to compact them into one concise replacement memory and forget only the clearly redundant originals,
|
|
30
|
+
keeping future recall sharper without losing useful detail.
|
|
21
31
|
|
|
22
32
|
## Safety Model
|
|
23
33
|
|
|
@@ -40,17 +50,17 @@ Install with one command:
|
|
|
40
50
|
curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
|
|
41
51
|
```
|
|
42
52
|
|
|
43
|
-
This installs the published package from npmjs and runs `threadnote install`.
|
|
44
|
-
|
|
53
|
+
This installs the published package from npmjs and runs `threadnote install`. The install command sets up local config,
|
|
54
|
+
repairs the OpenViking Python environment, and starts the local server so health problems surface immediately. It does
|
|
55
|
+
not use npm `postinstall`, because setup writes local machine config and should be an explicit action.
|
|
45
56
|
|
|
46
|
-
|
|
57
|
+
Confirm it is healthy:
|
|
47
58
|
|
|
48
59
|
```bash
|
|
49
|
-
threadnote start
|
|
50
60
|
threadnote doctor --dry-run
|
|
51
61
|
```
|
|
52
62
|
|
|
53
|
-
If install or health checks fail, see
|
|
63
|
+
If install or health checks fail, see [Troubleshooting](docs/troubleshooting.md).
|
|
54
64
|
|
|
55
65
|
To force a runtime:
|
|
56
66
|
|
|
@@ -64,7 +74,6 @@ Or install manually:
|
|
|
64
74
|
```bash
|
|
65
75
|
npm install --global threadnote
|
|
66
76
|
threadnote install
|
|
67
|
-
threadnote start
|
|
68
77
|
threadnote doctor --dry-run
|
|
69
78
|
```
|
|
70
79
|
|
|
@@ -157,7 +166,19 @@ threadnote seed --dry-run
|
|
|
157
166
|
threadnote seed
|
|
158
167
|
```
|
|
159
168
|
|
|
160
|
-
|
|
169
|
+
For Git worktrees, seed the stable checkout by default:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
threadnote init-manifest --repo ~/src/coda
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
You usually do not need to add every temporary worktree such as `~/src/worktrees/coda/my-branch`. Threadnote treats each
|
|
176
|
+
worktree path as its own manifest project today, so adding every worktree can duplicate seeded repo resources and make
|
|
177
|
+
recall noisier. Add a worktree only when it is long-lived or has branch-specific docs, instructions, or repo-local skills
|
|
178
|
+
that should be recalled separately.
|
|
179
|
+
|
|
180
|
+
Optionally seed shared and repo-local skills. This imports existing `SKILL.md` files as a searchable resource catalog so
|
|
181
|
+
agents can discover relevant workflow guidance; it does not install or activate skills in the agent runtime.
|
|
161
182
|
|
|
162
183
|
```bash
|
|
163
184
|
threadnote seed-skills --dry-run
|
|
@@ -172,7 +193,8 @@ This is it! Start working with your agents as usual. The agent will automaticall
|
|
|
172
193
|
|
|
173
194
|
- `doctor`: checks prerequisites, the generated command shim, manifest shape, templates, and local OpenViking health.
|
|
174
195
|
- `install`: installs `openviking[local-embed]==0.3.12` if missing, creates `~/.openviking` config files if absent,
|
|
175
|
-
writes the command shim,
|
|
196
|
+
writes the command shim, upserts user-level agent instructions, and starts/checks OpenViking health by default. Use
|
|
197
|
+
`--no-start` to skip the health check.
|
|
176
198
|
- `update`: updates the published Threadnote package, then runs `repair` so shims and MCP config point at the new
|
|
177
199
|
version.
|
|
178
200
|
- `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude/Cursor MCP configs from the
|
|
@@ -183,8 +205,8 @@ This is it! Start working with your agents as usual. The agent will automaticall
|
|
|
183
205
|
preserved by default; pass `--erase-memories` to delete `THREADNOTE_HOME`.
|
|
184
206
|
- `init-manifest`: creates or updates `~/.openviking/seed-manifest.yaml` from one or more developer repo roots.
|
|
185
207
|
- `seed`: imports curated repo guidance and docs from the manifest.
|
|
186
|
-
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog
|
|
187
|
-
`seed-skills --native` only after configuring a working VLM provider.
|
|
208
|
+
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog so agents can discover
|
|
209
|
+
reusable workflow guidance. Use `seed-skills --native` only after configuring a working VLM provider.
|
|
188
210
|
- `mcp-install codex|claude|cursor`: installs or prints OpenViking MCP configuration for Codex, Claude, or Cursor.
|
|
189
211
|
- `remember`: stores a durable memory.
|
|
190
212
|
- `migrate-memories`: migrates legacy session-only `MEMORY` and `HANDOFF` records into durable memory files. Run
|
|
@@ -218,7 +240,6 @@ any repo or working directory:
|
|
|
218
240
|
```bash
|
|
219
241
|
threadnote doctor --dry-run
|
|
220
242
|
threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
|
|
221
|
-
threadnote start
|
|
222
243
|
threadnote seed --dry-run
|
|
223
244
|
threadnote seed-skills --dry-run
|
|
224
245
|
```
|
package/dist/threadnote.cjs
CHANGED
|
@@ -7903,12 +7903,13 @@ Summary: ${failureCount} failure(s), ${warningCount} warning(s)`);
|
|
|
7903
7903
|
}
|
|
7904
7904
|
async function runInstall(config, options) {
|
|
7905
7905
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
7906
|
-
|
|
7907
|
-
await ensureDirectory(
|
|
7908
|
-
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "
|
|
7909
|
-
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "
|
|
7910
|
-
await
|
|
7911
|
-
await
|
|
7906
|
+
const dryRun = options.dryRun === true;
|
|
7907
|
+
await ensureDirectory(config.agentContextHome, dryRun);
|
|
7908
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "logs"), dryRun);
|
|
7909
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "redacted"), dryRun);
|
|
7910
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "mcp"), dryRun);
|
|
7911
|
+
await installCommandShim(dryRun);
|
|
7912
|
+
await installUserAgentInstructions(dryRun);
|
|
7912
7913
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
7913
7914
|
if (serverPath) {
|
|
7914
7915
|
console.log(`OpenViking server already installed: ${serverPath}`);
|
|
@@ -7921,37 +7922,49 @@ async function runInstall(config, options) {
|
|
|
7921
7922
|
repairReasons.push("Python system certificate bridge is missing");
|
|
7922
7923
|
}
|
|
7923
7924
|
console.log(`OpenViking install needs repair: ${repairReasons.join("; ")}.`);
|
|
7924
|
-
await runInstallCommands(config, options.packageManager, true,
|
|
7925
|
+
await runInstallCommands(config, options.packageManager, true, dryRun);
|
|
7925
7926
|
} else if (pythonSystemCertificatesMissing) {
|
|
7926
7927
|
console.log("OpenViking install needs repair: Python system certificate bridge is missing.");
|
|
7927
7928
|
const installCommand = await getPythonSystemCertificatesInstallCommand(serverPath);
|
|
7928
|
-
await maybeRun(
|
|
7929
|
+
await maybeRun(dryRun, installCommand.executable, installCommand.args);
|
|
7929
7930
|
}
|
|
7930
7931
|
} else {
|
|
7931
|
-
await runInstallCommands(config, options.packageManager, false,
|
|
7932
|
+
await runInstallCommands(config, options.packageManager, false, dryRun);
|
|
7932
7933
|
}
|
|
7933
7934
|
await writeTemplateIfMissing({
|
|
7934
7935
|
config,
|
|
7935
7936
|
destinationPath: (0, import_node_path6.join)(config.agentContextHome, "ov.conf"),
|
|
7936
|
-
dryRun
|
|
7937
|
+
dryRun,
|
|
7937
7938
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7938
7939
|
templatePath: (0, import_node_path6.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
7939
7940
|
});
|
|
7940
7941
|
await writeTemplateIfMissing({
|
|
7941
7942
|
config,
|
|
7942
7943
|
destinationPath: (0, import_node_path6.join)(config.agentContextHome, "ovcli.conf"),
|
|
7943
|
-
dryRun
|
|
7944
|
+
dryRun,
|
|
7944
7945
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7945
7946
|
templatePath: (0, import_node_path6.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
7946
7947
|
});
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7948
|
+
if (options.start !== false) {
|
|
7949
|
+
const healthy = await repairServerHealth(config, dryRun);
|
|
7950
|
+
if (!healthy && !dryRun) {
|
|
7951
|
+
throw new Error(`OpenViking did not become healthy. Check logs: ${openVikingLogPath(config)}`);
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
if (options.printNextSteps !== false) {
|
|
7955
|
+
printInstallNextSteps({ dryRun, startsServer: options.start !== false });
|
|
7956
|
+
}
|
|
7950
7957
|
}
|
|
7951
7958
|
async function runRepair(config, options) {
|
|
7952
7959
|
const dryRun = options.dryRun === true;
|
|
7953
7960
|
console.log("Repairing local OpenViking agent context from this checkout.");
|
|
7954
|
-
await runInstall(config, {
|
|
7961
|
+
await runInstall(config, {
|
|
7962
|
+
dryRun,
|
|
7963
|
+
packageManager: options.packageManager,
|
|
7964
|
+
printNextSteps: false,
|
|
7965
|
+
repairInvalidConfigs: true,
|
|
7966
|
+
start: false
|
|
7967
|
+
});
|
|
7955
7968
|
await repairManifest(config, dryRun);
|
|
7956
7969
|
if (options.start !== false) {
|
|
7957
7970
|
await repairServerHealth(config, dryRun);
|
|
@@ -8047,13 +8060,15 @@ async function repairServerHealth(config, dryRun) {
|
|
|
8047
8060
|
const existingHealth = await readOpenVikingHealthIfAvailable(config, 800);
|
|
8048
8061
|
if (existingHealth) {
|
|
8049
8062
|
console.log(`OpenViking health OK at http://${config.host}:${config.port}/health`);
|
|
8050
|
-
return;
|
|
8063
|
+
return true;
|
|
8051
8064
|
}
|
|
8052
8065
|
console.log(`OpenViking health is not responding at http://${config.host}:${config.port}/health; starting server.`);
|
|
8053
8066
|
try {
|
|
8054
8067
|
await runStart(config, { dryRun });
|
|
8068
|
+
return true;
|
|
8055
8069
|
} catch (err) {
|
|
8056
8070
|
console.log(`WARN could not repair OpenViking health: ${errorMessage(err)}`);
|
|
8071
|
+
return false;
|
|
8057
8072
|
}
|
|
8058
8073
|
}
|
|
8059
8074
|
async function runStart(config, options) {
|
|
@@ -8385,14 +8400,28 @@ async function getInstallCommands(config, preferred, force) {
|
|
|
8385
8400
|
return [{ executable: "python3", args: pipArgs }];
|
|
8386
8401
|
}
|
|
8387
8402
|
async function detectPackageManager() {
|
|
8388
|
-
if (await findExecutable(["pipx"])) {
|
|
8389
|
-
return "pipx";
|
|
8390
|
-
}
|
|
8391
8403
|
if (await findExecutable(["uv"])) {
|
|
8392
8404
|
return "uv";
|
|
8393
8405
|
}
|
|
8406
|
+
if (await findExecutable(["pipx"])) {
|
|
8407
|
+
return "pipx";
|
|
8408
|
+
}
|
|
8394
8409
|
return "pip";
|
|
8395
8410
|
}
|
|
8411
|
+
function printInstallNextSteps(options) {
|
|
8412
|
+
if (options.dryRun) {
|
|
8413
|
+
console.log("Dry run complete. Run without --dry-run to install and start OpenViking.");
|
|
8414
|
+
return;
|
|
8415
|
+
}
|
|
8416
|
+
if (options.startsServer) {
|
|
8417
|
+
console.log("Install complete. OpenViking health is ready. Next:");
|
|
8418
|
+
console.log(" threadnote doctor --dry-run");
|
|
8419
|
+
return;
|
|
8420
|
+
}
|
|
8421
|
+
console.log("Install complete. Run start, then doctor:");
|
|
8422
|
+
console.log(" threadnote start");
|
|
8423
|
+
console.log(" threadnote doctor --dry-run");
|
|
8424
|
+
}
|
|
8396
8425
|
async function writeTemplateIfMissing(options) {
|
|
8397
8426
|
if (await exists(options.destinationPath)) {
|
|
8398
8427
|
const currentContent = await (0, import_promises6.readFile)(options.destinationPath, "utf8");
|
|
@@ -8946,7 +8975,7 @@ async function main() {
|
|
|
8946
8975
|
await runDoctor(config, options);
|
|
8947
8976
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
8948
8977
|
});
|
|
8949
|
-
program2.command("install").description("Install OpenViking, local config files, command shim, and user-level agent instructions").option("--dry-run", "Print the actions without making changes").option("--package-manager <manager>", "
|
|
8978
|
+
program2.command("install").description("Install OpenViking, local config files, command shim, and user-level agent instructions").option("--dry-run", "Print the actions without making changes").option("--no-start", "Do not start OpenViking or check server health after installing").option("--package-manager <manager>", "uv, pipx, or pip", parsePackageManager).action(async (options) => {
|
|
8950
8979
|
const config = getRuntimeConfig(program2);
|
|
8951
8980
|
await runInstall(config, options);
|
|
8952
8981
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
@@ -8958,7 +8987,7 @@ async function main() {
|
|
|
8958
8987
|
"--mcp <clients>",
|
|
8959
8988
|
"MCP clients to repair: available, all, none, codex, claude, cursor, or comma-separated list",
|
|
8960
8989
|
"available"
|
|
8961
|
-
).option("--no-start", "Do not start OpenViking if health is failing").option("--package-manager <manager>", "
|
|
8990
|
+
).option("--no-start", "Do not start OpenViking if health is failing").option("--package-manager <manager>", "uv, pipx, or pip", parsePackageManager).action(async (options) => {
|
|
8962
8991
|
const config = getRuntimeConfig(program2);
|
|
8963
8992
|
await runRepair(config, options);
|
|
8964
8993
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
@@ -36,6 +36,19 @@ logs, or other sensitive data.
|
|
|
36
36
|
Also remember durable workflow facts you discover during work when they would help future agents and are not already in
|
|
37
37
|
canonical docs. Prefer updating checked-in docs for canonical repo rules.
|
|
38
38
|
|
|
39
|
+
## Memory Compaction
|
|
40
|
+
|
|
41
|
+
When recall/read surfaces several memories that describe the same durable fact, incident, branch, or handoff, compact
|
|
42
|
+
them when it is safe:
|
|
43
|
+
|
|
44
|
+
- Store one concise replacement memory that preserves the current status, the important facts, and the source `viking://`
|
|
45
|
+
URIs you merged.
|
|
46
|
+
- Remove superseded duplicates with `threadnote forget <uri>` only when they are clearly redundant or stale and contain
|
|
47
|
+
no unique useful detail.
|
|
48
|
+
- If the memories disagree or you are not sure what can be deleted, keep them and mention the possible cleanup instead.
|
|
49
|
+
|
|
50
|
+
Never compact secrets, credentials, customer data, raw production logs, or checked-in canonical docs into memory.
|
|
51
|
+
|
|
39
52
|
## Handoff
|
|
40
53
|
|
|
41
54
|
Before pausing, switching agents, or ending meaningful work with local changes, store a concise handoff. Include:
|
|
@@ -59,5 +72,6 @@ threadnote recall --query "last handoff for this branch"
|
|
|
59
72
|
threadnote read viking://agent/threadnote/memories/.abstract.md
|
|
60
73
|
threadnote list viking://agent/threadnote/memories --all --recursive
|
|
61
74
|
threadnote remember --text "Durable engineering note..."
|
|
75
|
+
threadnote forget viking://user/example/memories/events/duplicate.md
|
|
62
76
|
threadnote handoff --task "short task summary" --tests "checks run" --next-step "what to do next"
|
|
63
77
|
```
|
package/docs/migration.md
CHANGED
|
@@ -90,10 +90,9 @@ paths.
|
|
|
90
90
|
`--repo` can be repeated. Paths may be anywhere on the machine. If no `--repo` is provided, the current git repo is
|
|
91
91
|
used. The manifest is written to `~/.openviking/seed-manifest.yaml` by default and is intentionally not checked in.
|
|
92
92
|
|
|
93
|
-
4.
|
|
93
|
+
4. Verify the local service:
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
threadnote start
|
|
97
96
|
threadnote doctor --dry-run
|
|
98
97
|
```
|
|
99
98
|
|
|
@@ -171,6 +170,8 @@ Preferred agent behavior is automatic after `threadnote install` has updated the
|
|
|
171
170
|
|
|
172
171
|
- On non-trivial task start, search OpenViking for recent handoffs and relevant repo guidance.
|
|
173
172
|
- When the user says "remember", store the memory after checking that it contains no secret or customer data.
|
|
173
|
+
- When recall surfaces clearly duplicate or stale memories, store one concise replacement memory and forget only the
|
|
174
|
+
redundant originals.
|
|
174
175
|
- Before pausing, switching agents, or finishing meaningful code changes, store a concise handoff with status, tests,
|
|
175
176
|
blockers, and next steps.
|
|
176
177
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -8,7 +8,7 @@ Run:
|
|
|
8
8
|
threadnote install
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The installer prefers `
|
|
11
|
+
The installer prefers `uv`, then `pipx`, then `python3 -m pip install --user`.
|
|
12
12
|
|
|
13
13
|
## `uv` Fails With `UnknownIssuer`
|
|
14
14
|
|
|
@@ -30,13 +30,13 @@ threadnote install --package-manager pip
|
|
|
30
30
|
|
|
31
31
|
## Model Download Fails With `CERTIFICATE_VERIFY_FAILED`
|
|
32
32
|
|
|
33
|
-
On first start, OpenViking may download the local embedding model from Hugging Face. If
|
|
34
|
-
shows `SSLCertVerificationError`, `self-signed certificate in certificate chain`, or
|
|
35
|
-
model`, repair the OpenViking Python environment and start again:
|
|
33
|
+
On install or first start, OpenViking may download the local embedding model from Hugging Face. If
|
|
34
|
+
`~/.openviking/logs/server.log` shows `SSLCertVerificationError`, `self-signed certificate in certificate chain`, or
|
|
35
|
+
`Failed to download local embedding model`, repair the OpenViking Python environment and start again:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
threadnote repair --package-manager uv
|
|
39
|
-
threadnote
|
|
39
|
+
threadnote doctor --dry-run
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Threadnote installs `pip-system-certs` into the OpenViking environment so Python `requests` can use certificates trusted
|
|
@@ -63,8 +63,8 @@ The installer repairs this by installing `openviking[local-embed]`.
|
|
|
63
63
|
|
|
64
64
|
## Server Health Fails
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
Start it and recheck:
|
|
66
|
+
Current Threadnote installs start the local server by default. If `doctor` reports
|
|
67
|
+
`WARN openviking health: connect ECONNREFUSED 127.0.0.1:1933`, the local server is not running. Start it and recheck:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
threadnote start
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -126,7 +126,6 @@ fi
|
|
|
126
126
|
|
|
127
127
|
say ""
|
|
128
128
|
say "Threadnote is installed. Next:"
|
|
129
|
-
say " threadnote start"
|
|
130
129
|
say " threadnote doctor --dry-run"
|
|
131
130
|
say " threadnote mcp-install codex --apply # if you use Codex"
|
|
132
131
|
say " threadnote mcp-install claude --apply # if you use Claude"
|