threadnote 0.3.4 → 0.3.6
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 +32 -9
- package/dist/mcp_server.cjs +24 -14
- package/dist/threadnote.cjs +82 -35
- package/docs/agent-instructions.md +6 -0
- package/docs/migration.md +4 -2
- package/docs/troubleshooting.md +7 -7
- package/package.json +1 -1
- package/scripts/install.sh +0 -1
package/README.md
CHANGED
|
@@ -29,6 +29,28 @@ Run `threadnote seed-skills` to make local `SKILL.md` guidance discoverable thro
|
|
|
29
29
|
Agents are instructed to compact them into one concise replacement memory and forget only the clearly redundant originals,
|
|
30
30
|
keeping future recall sharper without losing useful detail.
|
|
31
31
|
|
|
32
|
+
**Still working on the same issue?**
|
|
33
|
+
Use `threadnote remember --replace <uri>` or `threadnote handoff --replace <uri>` to keep one current-state memory fresh
|
|
34
|
+
instead of accumulating near-duplicate progress notes.
|
|
35
|
+
|
|
36
|
+
## Why Not Just CLAUDE.md Or AGENTS.md?
|
|
37
|
+
|
|
38
|
+
Use them. Threadnote is not a replacement for checked-in instructions.
|
|
39
|
+
|
|
40
|
+
`CLAUDE.md`, `AGENTS.md`, Cursor rules, and repo docs are the right place for stable, canonical guidance: coding
|
|
41
|
+
standards, test commands, review rules, architecture notes, and anything the whole team should version and discuss.
|
|
42
|
+
|
|
43
|
+
They are less ideal for living context: what happened in the last agent session, which branch was halfway through a
|
|
44
|
+
refactor, what an on-call investigation concluded, which workaround was verified on one machine, or which duplicate
|
|
45
|
+
memories should be compacted. Putting that history into instruction files makes them noisy, stale, and expensive to load
|
|
46
|
+
into every context window.
|
|
47
|
+
|
|
48
|
+
Threadnote keeps that moving layer local, searchable, and shared across agents. Agents recall only the relevant memories,
|
|
49
|
+
handoffs, and skill/resource pointers when they need them, while the source files stay authoritative for project rules.
|
|
50
|
+
|
|
51
|
+
The split is simple: put durable repo policy in `CLAUDE.md`/`AGENTS.md`; put task history, handoffs, personal workflow
|
|
52
|
+
facts, and local cross-agent memory in Threadnote.
|
|
53
|
+
|
|
32
54
|
## Safety Model
|
|
33
55
|
|
|
34
56
|
- Machine writes stay **locally** under `THREADNOTE_HOME`, which defaults to `~/.openviking`.
|
|
@@ -50,13 +72,13 @@ Install with one command:
|
|
|
50
72
|
curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
This installs the published package from npmjs and runs `threadnote install`.
|
|
54
|
-
|
|
75
|
+
This installs the published package from npmjs and runs `threadnote install`. The install command sets up local config,
|
|
76
|
+
repairs the OpenViking Python environment, and starts the local server so health problems surface immediately. It does
|
|
77
|
+
not use npm `postinstall`, because setup writes local machine config and should be an explicit action.
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
Confirm it is healthy:
|
|
57
80
|
|
|
58
81
|
```bash
|
|
59
|
-
threadnote start
|
|
60
82
|
threadnote doctor --dry-run
|
|
61
83
|
```
|
|
62
84
|
|
|
@@ -74,7 +96,6 @@ Or install manually:
|
|
|
74
96
|
```bash
|
|
75
97
|
npm install --global threadnote
|
|
76
98
|
threadnote install
|
|
77
|
-
threadnote start
|
|
78
99
|
threadnote doctor --dry-run
|
|
79
100
|
```
|
|
80
101
|
|
|
@@ -194,7 +215,8 @@ This is it! Start working with your agents as usual. The agent will automaticall
|
|
|
194
215
|
|
|
195
216
|
- `doctor`: checks prerequisites, the generated command shim, manifest shape, templates, and local OpenViking health.
|
|
196
217
|
- `install`: installs `openviking[local-embed]==0.3.12` if missing, creates `~/.openviking` config files if absent,
|
|
197
|
-
writes the command shim,
|
|
218
|
+
writes the command shim, upserts user-level agent instructions, and starts/checks OpenViking health by default. Use
|
|
219
|
+
`--no-start` to skip the health check.
|
|
198
220
|
- `update`: updates the published Threadnote package, then runs `repair` so shims and MCP config point at the new
|
|
199
221
|
version.
|
|
200
222
|
- `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude/Cursor MCP configs from the
|
|
@@ -208,14 +230,16 @@ This is it! Start working with your agents as usual. The agent will automaticall
|
|
|
208
230
|
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog so agents can discover
|
|
209
231
|
reusable workflow guidance. Use `seed-skills --native` only after configuring a working VLM provider.
|
|
210
232
|
- `mcp-install codex|claude|cursor`: installs or prints OpenViking MCP configuration for Codex, Claude, or Cursor.
|
|
211
|
-
- `remember`: stores a durable memory.
|
|
233
|
+
- `remember`: stores a durable memory. Use `--replace <uri>` to store an updated memory and remove a superseded one
|
|
234
|
+
after the new memory succeeds.
|
|
212
235
|
- `migrate-memories`: migrates legacy session-only `MEMORY` and `HANDOFF` records into durable memory files. Run
|
|
213
236
|
`migrate-memories --dry-run` first; use `--all-accounts` when importing from older local OpenViking accounts.
|
|
214
237
|
- `recall`: searches shared OpenViking context. It infers repo or skill scope from queries like
|
|
215
238
|
`skills for api service`; use `--uri` or `--no-infer-scope` to override.
|
|
216
239
|
- `read`: reads a `viking://` URI returned by `recall` or `list`.
|
|
217
240
|
- `list` / `ls`: lists a `viking://` directory.
|
|
218
|
-
- `handoff`: stores current git state and next-step notes as a durable handoff.
|
|
241
|
+
- `handoff`: stores current git state and next-step notes as a durable handoff. Use `--replace <uri>` to update the
|
|
242
|
+
current handoff for the same active issue.
|
|
219
243
|
- `forget`: removes a `viking://` URI.
|
|
220
244
|
- `export-pack` / `import-pack`: moves local context through `.ovpack` files.
|
|
221
245
|
|
|
@@ -240,7 +264,6 @@ any repo or working directory:
|
|
|
240
264
|
```bash
|
|
241
265
|
threadnote doctor --dry-run
|
|
242
266
|
threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
|
|
243
|
-
threadnote start
|
|
244
267
|
threadnote seed --dry-run
|
|
245
268
|
threadnote seed-skills --dry-run
|
|
246
269
|
```
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -31095,6 +31095,7 @@ async function main() {
|
|
|
31095
31095
|
"Prefer `recall_context` to find candidate viking:// URIs, then `read_context` files or `list_context` directories.",
|
|
31096
31096
|
'Always pass JSON arguments. Example: recall_context({"query":"current repo latest handoff"}).',
|
|
31097
31097
|
"Older clients may use the compatibility aliases `search`, `read`, and `list`.",
|
|
31098
|
+
"When updating the same active issue, pass replaceUri to remember_context so the superseded memory is forgotten after the replacement is stored.",
|
|
31098
31099
|
"Do not store secrets, customer data, raw production logs, or credentials."
|
|
31099
31100
|
].join("\n")
|
|
31100
31101
|
}
|
|
@@ -31379,32 +31380,36 @@ function registerStoreTool(server, config2, name, description) {
|
|
|
31379
31380
|
server.registerTool(
|
|
31380
31381
|
name,
|
|
31381
31382
|
{
|
|
31382
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
31383
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
31383
31384
|
description: `${description} Never store secrets, credentials, customer data, or raw logs.`,
|
|
31384
31385
|
inputSchema: {
|
|
31386
|
+
replaceUri: external_exports.string().optional().describe("Optional viking:// memory URI to forget after the new memory is safely stored"),
|
|
31385
31387
|
text: external_exports.string().optional().describe("Required memory text to store"),
|
|
31386
31388
|
sourceAgentClient: external_exports.string().optional().describe("Originating client, for example cursor, codex, or claude")
|
|
31387
31389
|
}
|
|
31388
31390
|
},
|
|
31389
|
-
async ({ sourceAgentClient, text }) => {
|
|
31391
|
+
async ({ replaceUri, sourceAgentClient, text }) => {
|
|
31390
31392
|
const checkedText = requiredText(text, name, "text", { text: "Durable engineering note..." });
|
|
31391
31393
|
if (!checkedText.ok) {
|
|
31392
31394
|
return checkedText.error;
|
|
31393
31395
|
}
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
31402
|
-
|
|
31403
|
-
)
|
|
31396
|
+
const checkedReplaceUri = optionalVikingUri(replaceUri, name);
|
|
31397
|
+
if (!checkedReplaceUri.ok) {
|
|
31398
|
+
return checkedReplaceUri.error;
|
|
31399
|
+
}
|
|
31400
|
+
const header = [
|
|
31401
|
+
"MEMORY",
|
|
31402
|
+
`source_agent_client: ${sourceAgentClient ?? "mcp"}`,
|
|
31403
|
+
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
31404
|
+
];
|
|
31405
|
+
if (checkedReplaceUri.value) {
|
|
31406
|
+
header.push(`supersedes: ${checkedReplaceUri.value}`);
|
|
31407
|
+
}
|
|
31408
|
+
return writeDurableMemory(config2, [...header, "", checkedText.value].join("\n"), checkedReplaceUri.value);
|
|
31404
31409
|
}
|
|
31405
31410
|
);
|
|
31406
31411
|
}
|
|
31407
|
-
async function writeDurableMemory(config2, memory) {
|
|
31412
|
+
async function writeDurableMemory(config2, memory, replaceUri) {
|
|
31408
31413
|
try {
|
|
31409
31414
|
const ov = await requiredOpenVikingCli();
|
|
31410
31415
|
const directoryUri = durableMemoryDirectoryUri(config2);
|
|
@@ -31427,8 +31432,13 @@ async function writeDurableMemory(config2, memory) {
|
|
|
31427
31432
|
memoryUri,
|
|
31428
31433
|
withIdentity(config2, ["write", memoryUri, "--content", memory, "--mode", "create", "--wait", "--timeout", "120"])
|
|
31429
31434
|
);
|
|
31435
|
+
const messages = [`Stored durable memory: ${memoryUri}`];
|
|
31436
|
+
if (replaceUri) {
|
|
31437
|
+
await runCommand(ov, withIdentity(config2, ["rm", replaceUri]));
|
|
31438
|
+
messages.push(`Forgot replaced memory: ${replaceUri}`);
|
|
31439
|
+
}
|
|
31430
31440
|
const text = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
31431
|
-
return { content: [{ type: "text", text: [
|
|
31441
|
+
return { content: [{ type: "text", text: [...messages, text].filter(Boolean).join("\n") }] };
|
|
31432
31442
|
} catch (err) {
|
|
31433
31443
|
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
31434
31444
|
}
|
package/dist/threadnote.cjs
CHANGED
|
@@ -7038,14 +7038,16 @@ async function runRemember(config, options) {
|
|
|
7038
7038
|
if (!text.trim()) {
|
|
7039
7039
|
throw new Error("Provide memory text with --text or --stdin.");
|
|
7040
7040
|
}
|
|
7041
|
-
const
|
|
7041
|
+
const header = [
|
|
7042
7042
|
"MEMORY",
|
|
7043
7043
|
`source_agent_client: ${options.sourceAgentClient ?? "codex"}`,
|
|
7044
|
-
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7044
|
+
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
7045
|
+
];
|
|
7046
|
+
if (options.replace) {
|
|
7047
|
+
header.push(`supersedes: ${options.replace}`);
|
|
7048
|
+
}
|
|
7049
|
+
const memory = [...header, "", text.trim()].join("\n");
|
|
7050
|
+
await storeMemory(config, memory, { dryRun: options.dryRun === true, replaceUri: options.replace });
|
|
7049
7051
|
}
|
|
7050
7052
|
async function runMigrateMemories(config, options) {
|
|
7051
7053
|
const dryRun = options.dryRun === true;
|
|
@@ -7160,7 +7162,7 @@ async function runList(config, uri, options) {
|
|
|
7160
7162
|
}
|
|
7161
7163
|
async function runHandoff(config, options) {
|
|
7162
7164
|
const handoff = await buildHandoff(options);
|
|
7163
|
-
await storeMemory(config, handoff, options.dryRun === true);
|
|
7165
|
+
await storeMemory(config, handoff, { dryRun: options.dryRun === true, replaceUri: options.replace });
|
|
7164
7166
|
}
|
|
7165
7167
|
async function runForget(config, uri, options) {
|
|
7166
7168
|
assertVikingUri(uri);
|
|
@@ -7231,11 +7233,14 @@ async function printExactMemoryMatches(config, ov, query, dryRun) {
|
|
|
7231
7233
|
console.log("\nExact durable memory matches:");
|
|
7232
7234
|
console.log(outputs.join("\n\n"));
|
|
7233
7235
|
}
|
|
7234
|
-
async function storeMemory(config, memory,
|
|
7235
|
-
|
|
7236
|
+
async function storeMemory(config, memory, options) {
|
|
7237
|
+
if (options.replaceUri) {
|
|
7238
|
+
assertVikingUri(options.replaceUri);
|
|
7239
|
+
}
|
|
7240
|
+
const ov = await openVikingCliForMode(options.dryRun);
|
|
7236
7241
|
const memoryPath = (0, import_node_path4.join)(config.agentContextHome, "last-memory.txt");
|
|
7237
7242
|
const memoryUri = durableMemoryUri(config, memory);
|
|
7238
|
-
if (dryRun) {
|
|
7243
|
+
if (options.dryRun) {
|
|
7239
7244
|
console.log(memory);
|
|
7240
7245
|
console.log("\nWould run:");
|
|
7241
7246
|
console.log(
|
|
@@ -7254,6 +7259,9 @@ async function storeMemory(config, memory, dryRun) {
|
|
|
7254
7259
|
])
|
|
7255
7260
|
)
|
|
7256
7261
|
);
|
|
7262
|
+
if (options.replaceUri) {
|
|
7263
|
+
console.log(formatShellCommand(ov, withIdentity(config, ["rm", options.replaceUri])));
|
|
7264
|
+
}
|
|
7257
7265
|
return;
|
|
7258
7266
|
}
|
|
7259
7267
|
await (0, import_promises4.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
@@ -7261,6 +7269,10 @@ async function storeMemory(config, memory, dryRun) {
|
|
|
7261
7269
|
await ensureDurableMemoryDirectory(ov, config);
|
|
7262
7270
|
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath);
|
|
7263
7271
|
console.log(`Stored durable memory: ${memoryUri}`);
|
|
7272
|
+
if (options.replaceUri) {
|
|
7273
|
+
await maybeRun(false, ov, withIdentity(config, ["rm", options.replaceUri]));
|
|
7274
|
+
console.log(`Forgot replaced memory: ${options.replaceUri}`);
|
|
7275
|
+
}
|
|
7264
7276
|
}
|
|
7265
7277
|
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath) {
|
|
7266
7278
|
const args = withIdentity(config, [
|
|
@@ -7501,13 +7513,19 @@ async function buildHandoff(options) {
|
|
|
7501
7513
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
7502
7514
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
7503
7515
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
7504
|
-
|
|
7516
|
+
const header = [
|
|
7505
7517
|
"HANDOFF",
|
|
7506
7518
|
`repo: ${(0, import_node_path4.basename)(repoRoot)}`,
|
|
7507
7519
|
`repo_path: ${repoRoot}`,
|
|
7508
7520
|
`branch: ${branch || "unknown"}`,
|
|
7509
7521
|
`source_agent_client: ${options.sourceAgentClient ?? "codex"}`,
|
|
7510
|
-
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7522
|
+
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
7523
|
+
];
|
|
7524
|
+
if (options.replace) {
|
|
7525
|
+
header.push(`supersedes: ${options.replace}`);
|
|
7526
|
+
}
|
|
7527
|
+
return [
|
|
7528
|
+
...header,
|
|
7511
7529
|
`task: ${options.task ?? "unspecified"}`,
|
|
7512
7530
|
"",
|
|
7513
7531
|
"files_touched:",
|
|
@@ -7903,12 +7921,13 @@ Summary: ${failureCount} failure(s), ${warningCount} warning(s)`);
|
|
|
7903
7921
|
}
|
|
7904
7922
|
async function runInstall(config, options) {
|
|
7905
7923
|
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
|
|
7924
|
+
const dryRun = options.dryRun === true;
|
|
7925
|
+
await ensureDirectory(config.agentContextHome, dryRun);
|
|
7926
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "logs"), dryRun);
|
|
7927
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "redacted"), dryRun);
|
|
7928
|
+
await ensureDirectory((0, import_node_path6.join)(config.agentContextHome, "mcp"), dryRun);
|
|
7929
|
+
await installCommandShim(dryRun);
|
|
7930
|
+
await installUserAgentInstructions(dryRun);
|
|
7912
7931
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
7913
7932
|
if (serverPath) {
|
|
7914
7933
|
console.log(`OpenViking server already installed: ${serverPath}`);
|
|
@@ -7921,37 +7940,49 @@ async function runInstall(config, options) {
|
|
|
7921
7940
|
repairReasons.push("Python system certificate bridge is missing");
|
|
7922
7941
|
}
|
|
7923
7942
|
console.log(`OpenViking install needs repair: ${repairReasons.join("; ")}.`);
|
|
7924
|
-
await runInstallCommands(config, options.packageManager, true,
|
|
7943
|
+
await runInstallCommands(config, options.packageManager, true, dryRun);
|
|
7925
7944
|
} else if (pythonSystemCertificatesMissing) {
|
|
7926
7945
|
console.log("OpenViking install needs repair: Python system certificate bridge is missing.");
|
|
7927
7946
|
const installCommand = await getPythonSystemCertificatesInstallCommand(serverPath);
|
|
7928
|
-
await maybeRun(
|
|
7947
|
+
await maybeRun(dryRun, installCommand.executable, installCommand.args);
|
|
7929
7948
|
}
|
|
7930
7949
|
} else {
|
|
7931
|
-
await runInstallCommands(config, options.packageManager, false,
|
|
7950
|
+
await runInstallCommands(config, options.packageManager, false, dryRun);
|
|
7932
7951
|
}
|
|
7933
7952
|
await writeTemplateIfMissing({
|
|
7934
7953
|
config,
|
|
7935
7954
|
destinationPath: (0, import_node_path6.join)(config.agentContextHome, "ov.conf"),
|
|
7936
|
-
dryRun
|
|
7955
|
+
dryRun,
|
|
7937
7956
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7938
7957
|
templatePath: (0, import_node_path6.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
7939
7958
|
});
|
|
7940
7959
|
await writeTemplateIfMissing({
|
|
7941
7960
|
config,
|
|
7942
7961
|
destinationPath: (0, import_node_path6.join)(config.agentContextHome, "ovcli.conf"),
|
|
7943
|
-
dryRun
|
|
7962
|
+
dryRun,
|
|
7944
7963
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7945
7964
|
templatePath: (0, import_node_path6.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
7946
7965
|
});
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7966
|
+
if (options.start !== false) {
|
|
7967
|
+
const healthy = await repairServerHealth(config, dryRun);
|
|
7968
|
+
if (!healthy && !dryRun) {
|
|
7969
|
+
throw new Error(`OpenViking did not become healthy. Check logs: ${openVikingLogPath(config)}`);
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
if (options.printNextSteps !== false) {
|
|
7973
|
+
printInstallNextSteps({ dryRun, startsServer: options.start !== false });
|
|
7974
|
+
}
|
|
7950
7975
|
}
|
|
7951
7976
|
async function runRepair(config, options) {
|
|
7952
7977
|
const dryRun = options.dryRun === true;
|
|
7953
7978
|
console.log("Repairing local OpenViking agent context from this checkout.");
|
|
7954
|
-
await runInstall(config, {
|
|
7979
|
+
await runInstall(config, {
|
|
7980
|
+
dryRun,
|
|
7981
|
+
packageManager: options.packageManager,
|
|
7982
|
+
printNextSteps: false,
|
|
7983
|
+
repairInvalidConfigs: true,
|
|
7984
|
+
start: false
|
|
7985
|
+
});
|
|
7955
7986
|
await repairManifest(config, dryRun);
|
|
7956
7987
|
if (options.start !== false) {
|
|
7957
7988
|
await repairServerHealth(config, dryRun);
|
|
@@ -8047,13 +8078,15 @@ async function repairServerHealth(config, dryRun) {
|
|
|
8047
8078
|
const existingHealth = await readOpenVikingHealthIfAvailable(config, 800);
|
|
8048
8079
|
if (existingHealth) {
|
|
8049
8080
|
console.log(`OpenViking health OK at http://${config.host}:${config.port}/health`);
|
|
8050
|
-
return;
|
|
8081
|
+
return true;
|
|
8051
8082
|
}
|
|
8052
8083
|
console.log(`OpenViking health is not responding at http://${config.host}:${config.port}/health; starting server.`);
|
|
8053
8084
|
try {
|
|
8054
8085
|
await runStart(config, { dryRun });
|
|
8086
|
+
return true;
|
|
8055
8087
|
} catch (err) {
|
|
8056
8088
|
console.log(`WARN could not repair OpenViking health: ${errorMessage(err)}`);
|
|
8089
|
+
return false;
|
|
8057
8090
|
}
|
|
8058
8091
|
}
|
|
8059
8092
|
async function runStart(config, options) {
|
|
@@ -8385,14 +8418,28 @@ async function getInstallCommands(config, preferred, force) {
|
|
|
8385
8418
|
return [{ executable: "python3", args: pipArgs }];
|
|
8386
8419
|
}
|
|
8387
8420
|
async function detectPackageManager() {
|
|
8388
|
-
if (await findExecutable(["pipx"])) {
|
|
8389
|
-
return "pipx";
|
|
8390
|
-
}
|
|
8391
8421
|
if (await findExecutable(["uv"])) {
|
|
8392
8422
|
return "uv";
|
|
8393
8423
|
}
|
|
8424
|
+
if (await findExecutable(["pipx"])) {
|
|
8425
|
+
return "pipx";
|
|
8426
|
+
}
|
|
8394
8427
|
return "pip";
|
|
8395
8428
|
}
|
|
8429
|
+
function printInstallNextSteps(options) {
|
|
8430
|
+
if (options.dryRun) {
|
|
8431
|
+
console.log("Dry run complete. Run without --dry-run to install and start OpenViking.");
|
|
8432
|
+
return;
|
|
8433
|
+
}
|
|
8434
|
+
if (options.startsServer) {
|
|
8435
|
+
console.log("Install complete. OpenViking health is ready. Next:");
|
|
8436
|
+
console.log(" threadnote doctor --dry-run");
|
|
8437
|
+
return;
|
|
8438
|
+
}
|
|
8439
|
+
console.log("Install complete. Run start, then doctor:");
|
|
8440
|
+
console.log(" threadnote start");
|
|
8441
|
+
console.log(" threadnote doctor --dry-run");
|
|
8442
|
+
}
|
|
8396
8443
|
async function writeTemplateIfMissing(options) {
|
|
8397
8444
|
if (await exists(options.destinationPath)) {
|
|
8398
8445
|
const currentContent = await (0, import_promises6.readFile)(options.destinationPath, "utf8");
|
|
@@ -8946,7 +8993,7 @@ async function main() {
|
|
|
8946
8993
|
await runDoctor(config, options);
|
|
8947
8994
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
8948
8995
|
});
|
|
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>", "
|
|
8996
|
+
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
8997
|
const config = getRuntimeConfig(program2);
|
|
8951
8998
|
await runInstall(config, options);
|
|
8952
8999
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
@@ -8958,7 +9005,7 @@ async function main() {
|
|
|
8958
9005
|
"--mcp <clients>",
|
|
8959
9006
|
"MCP clients to repair: available, all, none, codex, claude, cursor, or comma-separated list",
|
|
8960
9007
|
"available"
|
|
8961
|
-
).option("--no-start", "Do not start OpenViking if health is failing").option("--package-manager <manager>", "
|
|
9008
|
+
).option("--no-start", "Do not start OpenViking if health is failing").option("--package-manager <manager>", "uv, pipx, or pip", parsePackageManager).action(async (options) => {
|
|
8962
9009
|
const config = getRuntimeConfig(program2);
|
|
8963
9010
|
await runRepair(config, options);
|
|
8964
9011
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
@@ -8990,7 +9037,7 @@ async function main() {
|
|
|
8990
9037
|
program2.command("mcp-install").description("Install OpenViking MCP config for a supported agent").argument("<agent>", "codex, claude, or cursor").option("--apply", "Actually modify the selected agent config").option("--name <name>", "MCP server name", OPENVIKING_MCP_NAME).option("--native-http", "Install OpenViking native HTTP MCP endpoint instead of the local stdio adapter").option("--scope <scope>", "Claude MCP config scope: user, local, or project", parseClaudeMcpScope, "user").option("--url <url>", "OpenViking native HTTP MCP URL").option("--bearer-token-env-var <name>", "Environment variable containing the local API key").action(async (agent, options) => {
|
|
8991
9038
|
await runMcpInstall(getRuntimeConfig(program2), parseAgentClient(agent), options);
|
|
8992
9039
|
});
|
|
8993
|
-
program2.command("remember").description("Store a durable engineering memory in OpenViking").option("--dry-run", "Print memory and ov command without storing").option("--source-agent-client <name>", "codex, claude, cursor, gemini, or another client name", "codex").option("--stdin", "Read memory text from stdin").option("--text <text>", "Memory text to store").action(async (options) => {
|
|
9040
|
+
program2.command("remember").description("Store a durable engineering memory in OpenViking").option("--dry-run", "Print memory and ov command without storing").option("--replace <uri>", "Supersede an existing viking:// memory after the new memory is stored").option("--source-agent-client <name>", "codex, claude, cursor, gemini, or another client name", "codex").option("--stdin", "Read memory text from stdin").option("--text <text>", "Memory text to store").action(async (options) => {
|
|
8994
9041
|
await runRemember(getRuntimeConfig(program2), options);
|
|
8995
9042
|
});
|
|
8996
9043
|
program2.command("migrate-memories").description("Migrate legacy session-only Threadnote memories into durable memory files").option("--all-accounts", "Scan all local OpenViking accounts under THREADNOTE_HOME").option("--dry-run", "Print migration actions without writing memories").option("--limit <count>", "Maximum number of memories to migrate").option(
|
|
@@ -9010,7 +9057,7 @@ async function main() {
|
|
|
9010
9057
|
program2.command("list").alias("ls").description("List a viking:// directory").argument("[uri]", "viking:// URI to list", "viking://").option("-a, --all", "Show hidden files such as .abstract.md and .overview.md").option("--dry-run", "Print ov command without listing").option("-n, --node-limit <count>", "Maximum number of nodes to list").option("-r, --recursive", "List subdirectories recursively").option("-s, --simple", "Print only paths").action(async (uri, options) => {
|
|
9011
9058
|
await runList(getRuntimeConfig(program2), uri, options);
|
|
9012
9059
|
});
|
|
9013
|
-
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--dry-run", "Print handoff without storing").option("--next-step <text>", "Suggested next step").option("--source-agent-client <name>", "codex, claude, cursor, gemini, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").action(async (options) => {
|
|
9060
|
+
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--dry-run", "Print handoff without storing").option("--next-step <text>", "Suggested next step").option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, gemini, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").action(async (options) => {
|
|
9014
9061
|
await runHandoff(getRuntimeConfig(program2), options);
|
|
9015
9062
|
});
|
|
9016
9063
|
program2.command("forget").description("Remove a viking:// URI from local OpenViking context").argument("<uri>", "viking:// URI to remove").option("--dry-run", "Print ov command without deleting").action(async (uri, options) => {
|
|
@@ -38,6 +38,11 @@ canonical docs. Prefer updating checked-in docs for canonical repo rules.
|
|
|
38
38
|
|
|
39
39
|
## Memory Compaction
|
|
40
40
|
|
|
41
|
+
When working on the same active issue, prefer keeping one current-state memory updated instead of creating many small
|
|
42
|
+
progress memories. If an existing memory is clearly the current state for the issue, store the updated version with
|
|
43
|
+
`remember_context({"text":"...","replaceUri":"<uri>"})`, `threadnote remember --replace <uri>`, or
|
|
44
|
+
`threadnote handoff --replace <uri>` so the old memory is removed only after the replacement is stored.
|
|
45
|
+
|
|
41
46
|
When recall/read surfaces several memories that describe the same durable fact, incident, branch, or handoff, compact
|
|
42
47
|
them when it is safe:
|
|
43
48
|
|
|
@@ -72,6 +77,7 @@ threadnote recall --query "last handoff for this branch"
|
|
|
72
77
|
threadnote read viking://agent/threadnote/memories/.abstract.md
|
|
73
78
|
threadnote list viking://agent/threadnote/memories --all --recursive
|
|
74
79
|
threadnote remember --text "Durable engineering note..."
|
|
80
|
+
threadnote remember --replace viking://user/example/memories/events/current.md --text "Updated durable engineering note..."
|
|
75
81
|
threadnote forget viking://user/example/memories/events/duplicate.md
|
|
76
82
|
threadnote handoff --task "short task summary" --tests "checks run" --next-step "what to do next"
|
|
77
83
|
```
|
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 continuing the same active issue, update the current-state memory with `remember --replace <uri>` or
|
|
174
|
+
`handoff --replace <uri>` instead of creating another near-duplicate progress memory.
|
|
174
175
|
- When recall surfaces clearly duplicate or stale memories, store one concise replacement memory and forget only the
|
|
175
176
|
redundant originals.
|
|
176
177
|
- Before pausing, switching agents, or finishing meaningful code changes, store a concise handoff with status, tests,
|
|
@@ -181,6 +182,7 @@ Manual CLI remains available for scripts and emergencies:
|
|
|
181
182
|
```bash
|
|
182
183
|
threadnote recall --query "last handoff for this branch"
|
|
183
184
|
threadnote remember --text "Durable engineering note..."
|
|
185
|
+
threadnote remember --replace viking://user/example/memories/events/current.md --text "Updated durable engineering note..."
|
|
184
186
|
threadnote handoff --task "short task summary" --tests "checks run" --next-step "what the next agent should do"
|
|
185
187
|
```
|
|
186
188
|
|
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"
|