threadnote 0.1.0 → 0.2.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 +25 -0
- package/dist/threadnote.cjs +163 -20
- package/docs/rollout.md +1 -1
- package/docs/security.md +1 -0
- package/docs/troubleshooting.md +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ The bundled `config/seed-manifest.example.yaml` is only an example. Each develop
|
|
|
89
89
|
current checkout.
|
|
90
90
|
- `start`: starts `openviking-server` on `127.0.0.1:1933`.
|
|
91
91
|
- `stop`: stops the detached server pid or macOS LaunchAgent.
|
|
92
|
+
- `uninstall`: removes Threadnote shims, MCP config, launchd config, and managed user instructions. Memories are
|
|
93
|
+
preserved by default; pass `--erase-memories` to delete `THREADNOTE_HOME`.
|
|
92
94
|
- `init-manifest`: creates or updates `~/.openviking/seed-manifest.yaml` from one or more developer repo roots.
|
|
93
95
|
- `seed`: imports curated repo guidance and docs from the manifest.
|
|
94
96
|
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog. Use
|
|
@@ -164,6 +166,29 @@ If a future OpenViking build exposes a healthy native endpoint, install it expli
|
|
|
164
166
|
threadnote mcp-install claude --native-http --apply
|
|
165
167
|
```
|
|
166
168
|
|
|
169
|
+
## Uninstall
|
|
170
|
+
|
|
171
|
+
Preview removal:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
threadnote uninstall --dry-run
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Remove Threadnote setup while keeping local OpenViking memories:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
threadnote uninstall
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Erase local memories too:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
threadnote uninstall --erase-memories
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`uninstall` removes only Threadnote-managed files and agent config. The npm, Bun, or Deno package remains installed;
|
|
190
|
+
remove that with the package manager you used to install it.
|
|
191
|
+
|
|
167
192
|
## Notes
|
|
168
193
|
|
|
169
194
|
OpenViking itself is not vendored here. This prototype installs and controls a local service and should get legal and
|
package/dist/threadnote.cjs
CHANGED
|
@@ -3491,11 +3491,12 @@ var import_promises6 = require("node:fs/promises");
|
|
|
3491
3491
|
var import_promises7 = require("node:fs/promises");
|
|
3492
3492
|
var import_node_path5 = require("node:path");
|
|
3493
3493
|
var import_node_path6 = require("node:path");
|
|
3494
|
+
var import_promises8 = require("node:fs/promises");
|
|
3494
3495
|
var import_node_path7 = require("node:path");
|
|
3495
3496
|
var import_node_child_process = require("node:child_process");
|
|
3496
|
-
var import_promises8 = require("node:fs/promises");
|
|
3497
|
-
var import_node_os3 = require("node:os");
|
|
3498
3497
|
var import_promises9 = require("node:fs/promises");
|
|
3498
|
+
var import_node_os3 = require("node:os");
|
|
3499
|
+
var import_promises10 = require("node:fs/promises");
|
|
3499
3500
|
|
|
3500
3501
|
// node_modules/.deno/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
3501
3502
|
function isNothing(subject) {
|
|
@@ -6178,6 +6179,13 @@ async function main() {
|
|
|
6178
6179
|
program2.command("stop").description("Stop the local OpenViking server or LaunchAgent").option("--dry-run", "Print the stop actions without running them").action(async (options) => {
|
|
6179
6180
|
await runStop(getRuntimeConfig(program2), options);
|
|
6180
6181
|
});
|
|
6182
|
+
program2.command("uninstall").description("Remove Threadnote setup and optionally erase local memories").option("--dry-run", "Print uninstall actions without making changes").option(
|
|
6183
|
+
"--mcp <clients>",
|
|
6184
|
+
"MCP clients to remove: available, all, none, codex, claude, or comma-separated list",
|
|
6185
|
+
"available"
|
|
6186
|
+
).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
|
|
6187
|
+
await runUninstall(getRuntimeConfig(program2), options);
|
|
6188
|
+
});
|
|
6181
6189
|
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--manifest <path>", "Manifest path for this seed run").action(async (options) => {
|
|
6182
6190
|
await runSeed(getRuntimeConfig(program2, options.manifest), options);
|
|
6183
6191
|
});
|
|
@@ -6318,7 +6326,7 @@ async function runRepair(config, options) {
|
|
|
6318
6326
|
} else {
|
|
6319
6327
|
console.log("Skipping server health repair because --no-start was provided.");
|
|
6320
6328
|
}
|
|
6321
|
-
const mcpClients = await
|
|
6329
|
+
const mcpClients = await resolveMcpClients(options.mcp ?? "available", "repair");
|
|
6322
6330
|
if (mcpClients.length === 0) {
|
|
6323
6331
|
console.log("Skipping MCP config repair.");
|
|
6324
6332
|
} else {
|
|
@@ -6330,6 +6338,28 @@ async function runRepair(config, options) {
|
|
|
6330
6338
|
console.log("\nPost-repair doctor:");
|
|
6331
6339
|
await runDoctor(config, { dryRun, strict: false });
|
|
6332
6340
|
}
|
|
6341
|
+
async function runUninstall(config, options) {
|
|
6342
|
+
const dryRun = options.dryRun === true;
|
|
6343
|
+
if (options.eraseMemories === true && options.preserveMemories === true) {
|
|
6344
|
+
throw new Error("Use either --erase-memories or --preserve-memories, not both.");
|
|
6345
|
+
}
|
|
6346
|
+
console.log("Uninstalling local Threadnote setup.");
|
|
6347
|
+
await runStop(config, { dryRun });
|
|
6348
|
+
await removePathIfExists((0, import_node_path4.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
6349
|
+
await removeLaunchAgent(dryRun);
|
|
6350
|
+
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
6351
|
+
await removeMcpSnippets(config, dryRun);
|
|
6352
|
+
await removeCommandShim(dryRun);
|
|
6353
|
+
await removeUserAgentInstructions(dryRun);
|
|
6354
|
+
if (options.eraseMemories === true) {
|
|
6355
|
+
await eraseThreadnoteHome(config.agentContextHome, dryRun);
|
|
6356
|
+
} else {
|
|
6357
|
+
console.log(`Preserving local memories and OpenViking home: ${config.agentContextHome}`);
|
|
6358
|
+
console.log("Use --erase-memories to delete this directory during uninstall.");
|
|
6359
|
+
}
|
|
6360
|
+
console.log("Uninstall complete.");
|
|
6361
|
+
console.log("The package remains installed. Remove it with your package manager if desired.");
|
|
6362
|
+
}
|
|
6333
6363
|
async function repairManifest(config, dryRun) {
|
|
6334
6364
|
try {
|
|
6335
6365
|
await readSeedManifest(config.manifestPath);
|
|
@@ -6373,11 +6403,11 @@ async function repairManifest(config, dryRun) {
|
|
|
6373
6403
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
6374
6404
|
if (currentContent !== void 0) {
|
|
6375
6405
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
6376
|
-
await (0,
|
|
6406
|
+
await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
6377
6407
|
await (0, import_promises2.chmod)(backupPath, 384);
|
|
6378
6408
|
console.log(`Backup: ${backupPath}`);
|
|
6379
6409
|
}
|
|
6380
|
-
await (0,
|
|
6410
|
+
await (0, import_promises10.writeFile)(config.manifestPath, output, { encoding: "utf8", mode: 384 });
|
|
6381
6411
|
await (0, import_promises2.chmod)(config.manifestPath, 384);
|
|
6382
6412
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
6383
6413
|
}
|
|
@@ -6425,7 +6455,7 @@ async function runStart(config, options) {
|
|
|
6425
6455
|
const logFd = (0, import_node_fs3.openSync)(logPath, "a");
|
|
6426
6456
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
6427
6457
|
child.unref();
|
|
6428
|
-
await (0,
|
|
6458
|
+
await (0, import_promises10.writeFile)((0, import_node_path4.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
6429
6459
|
`, "utf8");
|
|
6430
6460
|
console.log(`Started OpenViking with pid ${child.pid}. Logs: ${logPath}`);
|
|
6431
6461
|
}
|
|
@@ -6445,7 +6475,11 @@ function spawnDetachedServer(server, args, logFd) {
|
|
|
6445
6475
|
async function runStop(config, options) {
|
|
6446
6476
|
const launchAgentPath = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
6447
6477
|
if ((0, import_node_os2.platform)() === "darwin") {
|
|
6448
|
-
|
|
6478
|
+
if (options.dryRun === true || await exists(launchAgentPath)) {
|
|
6479
|
+
await maybeRun(options.dryRun === true, "launchctl", ["unload", launchAgentPath], { allowFailure: true });
|
|
6480
|
+
} else {
|
|
6481
|
+
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
6482
|
+
}
|
|
6449
6483
|
}
|
|
6450
6484
|
const pidPath = (0, import_node_path4.join)(config.agentContextHome, "openviking-server.pid");
|
|
6451
6485
|
const pidText = await readFileIfExists(pidPath);
|
|
@@ -6541,7 +6575,7 @@ async function runInitManifest(config, options) {
|
|
|
6541
6575
|
return;
|
|
6542
6576
|
}
|
|
6543
6577
|
await ensureDirectory((0, import_node_path2.dirname)(manifestPath), false);
|
|
6544
|
-
await (0,
|
|
6578
|
+
await (0, import_promises10.writeFile)(manifestPath, output, { encoding: "utf8", mode: 384 });
|
|
6545
6579
|
await (0, import_promises2.chmod)(manifestPath, 384);
|
|
6546
6580
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
6547
6581
|
console.log("Seed with:");
|
|
@@ -6920,9 +6954,9 @@ async function writeTemplateIfMissing(options) {
|
|
|
6920
6954
|
return;
|
|
6921
6955
|
}
|
|
6922
6956
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
6923
|
-
await (0,
|
|
6957
|
+
await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
6924
6958
|
await (0, import_promises2.chmod)(backupPath, 384);
|
|
6925
|
-
await (0,
|
|
6959
|
+
await (0, import_promises10.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
6926
6960
|
await (0, import_promises2.chmod)(options.destinationPath, 384);
|
|
6927
6961
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
6928
6962
|
console.log(`Backup: ${backupPath}`);
|
|
@@ -6934,7 +6968,7 @@ async function writeTemplateIfMissing(options) {
|
|
|
6934
6968
|
return;
|
|
6935
6969
|
}
|
|
6936
6970
|
await ensureDirectory((0, import_node_path2.dirname)(options.destinationPath), false);
|
|
6937
|
-
await (0,
|
|
6971
|
+
await (0, import_promises10.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
6938
6972
|
await (0, import_promises2.chmod)(options.destinationPath, 384);
|
|
6939
6973
|
console.log(`Wrote ${options.destinationPath}`);
|
|
6940
6974
|
}
|
|
@@ -6956,10 +6990,23 @@ async function installCommandShim(dryRun) {
|
|
|
6956
6990
|
return;
|
|
6957
6991
|
}
|
|
6958
6992
|
await ensureDirectory(binDir, false);
|
|
6959
|
-
await (0,
|
|
6993
|
+
await (0, import_promises10.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
6960
6994
|
await (0, import_promises2.chmod)(shimPath, 493);
|
|
6961
6995
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
6962
6996
|
}
|
|
6997
|
+
async function removeCommandShim(dryRun) {
|
|
6998
|
+
const shimPath = (0, import_node_path4.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
6999
|
+
const content = await readFileIfExists(shimPath);
|
|
7000
|
+
if (content === void 0) {
|
|
7001
|
+
console.log(`Already absent: ${shimPath}`);
|
|
7002
|
+
return;
|
|
7003
|
+
}
|
|
7004
|
+
if (!isManagedCommandShim(content)) {
|
|
7005
|
+
console.log(`WARN not removing unmanaged command shim: ${shimPath}`);
|
|
7006
|
+
return;
|
|
7007
|
+
}
|
|
7008
|
+
await removePath(shimPath, "command shim", dryRun);
|
|
7009
|
+
}
|
|
6963
7010
|
async function installUserAgentInstructions(dryRun) {
|
|
6964
7011
|
const block = await renderUserAgentInstructionsBlock();
|
|
6965
7012
|
for (const target of USER_AGENT_INSTRUCTION_TARGETS) {
|
|
@@ -6979,10 +7026,39 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
6979
7026
|
continue;
|
|
6980
7027
|
}
|
|
6981
7028
|
await ensureDirectory((0, import_node_path2.dirname)(targetPath), false);
|
|
6982
|
-
await (0,
|
|
7029
|
+
await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
6983
7030
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
6984
7031
|
}
|
|
6985
7032
|
}
|
|
7033
|
+
async function removeUserAgentInstructions(dryRun) {
|
|
7034
|
+
for (const target of USER_AGENT_INSTRUCTION_TARGETS) {
|
|
7035
|
+
const targetPath = expandPath(target.path);
|
|
7036
|
+
const currentContent = await readFileIfExists(targetPath);
|
|
7037
|
+
if (currentContent === void 0) {
|
|
7038
|
+
console.log(`Already absent: ${targetPath}`);
|
|
7039
|
+
continue;
|
|
7040
|
+
}
|
|
7041
|
+
const nextContent = removeManagedBlock(currentContent);
|
|
7042
|
+
if (nextContent === void 0) {
|
|
7043
|
+
console.log(`WARN ${targetPath} has partial threadnote markers; not modifying it`);
|
|
7044
|
+
continue;
|
|
7045
|
+
}
|
|
7046
|
+
if (nextContent === currentContent) {
|
|
7047
|
+
console.log(`No threadnote block found: ${targetPath}`);
|
|
7048
|
+
continue;
|
|
7049
|
+
}
|
|
7050
|
+
if (nextContent.trim().length === 0) {
|
|
7051
|
+
await removePath(targetPath, target.label, dryRun);
|
|
7052
|
+
continue;
|
|
7053
|
+
}
|
|
7054
|
+
if (dryRun) {
|
|
7055
|
+
console.log(`Would update ${targetPath}`);
|
|
7056
|
+
continue;
|
|
7057
|
+
}
|
|
7058
|
+
await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
7059
|
+
console.log(`Updated ${targetPath}`);
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
6986
7062
|
async function renderUserAgentInstructionsBlock() {
|
|
6987
7063
|
const instructions = (await (0, import_promises5.readFile)((0, import_node_path4.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
6988
7064
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
@@ -7010,6 +7086,20 @@ function upsertManagedBlock(content, block) {
|
|
|
7010
7086
|
}
|
|
7011
7087
|
return joinMarkdownSections([content.trimEnd(), block]);
|
|
7012
7088
|
}
|
|
7089
|
+
function removeManagedBlock(content) {
|
|
7090
|
+
const startIndex = content.indexOf(USER_INSTRUCTIONS_START_MARKER);
|
|
7091
|
+
const endIndex = content.indexOf(USER_INSTRUCTIONS_END_MARKER);
|
|
7092
|
+
if (startIndex === -1 !== (endIndex === -1) || endIndex < startIndex) {
|
|
7093
|
+
return void 0;
|
|
7094
|
+
}
|
|
7095
|
+
if (startIndex === -1) {
|
|
7096
|
+
return content;
|
|
7097
|
+
}
|
|
7098
|
+
const before = content.slice(0, startIndex).trimEnd();
|
|
7099
|
+
const after = content.slice(endIndex + USER_INSTRUCTIONS_END_MARKER.length).trimStart();
|
|
7100
|
+
const nextContent = joinMarkdownSections([before, after]);
|
|
7101
|
+
return nextContent.trim().length > 0 ? nextContent : "";
|
|
7102
|
+
}
|
|
7013
7103
|
function joinMarkdownSections(sections) {
|
|
7014
7104
|
return `${sections.filter((section) => section.length > 0).join("\n\n")}
|
|
7015
7105
|
`;
|
|
@@ -7059,12 +7149,44 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
7059
7149
|
return;
|
|
7060
7150
|
}
|
|
7061
7151
|
await ensureDirectory((0, import_node_path2.dirname)(destination), false);
|
|
7062
|
-
await (0,
|
|
7152
|
+
await (0, import_promises10.writeFile)(destination, rendered, "utf8");
|
|
7063
7153
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
7064
7154
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
7065
7155
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
7066
7156
|
console.log(`Installed and started ${LAUNCHD_LABEL}`);
|
|
7067
7157
|
}
|
|
7158
|
+
async function removeLaunchAgent(dryRun) {
|
|
7159
|
+
const launchAgentPath = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
7160
|
+
const content = await readFileIfExists(launchAgentPath);
|
|
7161
|
+
if (content === void 0) {
|
|
7162
|
+
console.log(`Already absent: ${launchAgentPath}`);
|
|
7163
|
+
return;
|
|
7164
|
+
}
|
|
7165
|
+
if (!content.includes(LAUNCHD_LABEL) || !content.includes(OPENVIKING_SERVER_COMMAND)) {
|
|
7166
|
+
console.log(`WARN not removing unmanaged LaunchAgent: ${launchAgentPath}`);
|
|
7167
|
+
return;
|
|
7168
|
+
}
|
|
7169
|
+
await removePath(launchAgentPath, "LaunchAgent", dryRun);
|
|
7170
|
+
}
|
|
7171
|
+
async function removeMcpConfigs(value, dryRun) {
|
|
7172
|
+
const clients = await resolveMcpClients(value, "remove");
|
|
7173
|
+
if (clients.length === 0) {
|
|
7174
|
+
console.log("Skipping MCP config removal.");
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7177
|
+
for (const client of clients) {
|
|
7178
|
+
const command = buildMcpRemoveCommand(client, OPENVIKING_MCP_NAME);
|
|
7179
|
+
await maybeRun(dryRun, command.executable, command.args, { allowFailure: true, cwd: command.cwd });
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
async function removeMcpSnippets(config, dryRun) {
|
|
7183
|
+
await removePathIfExists((0, import_node_path4.join)(config.agentContextHome, "mcp", `${OPENVIKING_MCP_NAME}.codex.toml`), "MCP snippet", dryRun);
|
|
7184
|
+
await removePathIfExists((0, import_node_path4.join)(config.agentContextHome, "mcp", `${OPENVIKING_MCP_NAME}.claude.txt`), "MCP snippet", dryRun);
|
|
7185
|
+
}
|
|
7186
|
+
async function eraseThreadnoteHome(path, dryRun) {
|
|
7187
|
+
assertSafeThreadnoteHomeForErase(path);
|
|
7188
|
+
await removePathIfExists(path, "THREADNOTE_HOME and all memories", dryRun);
|
|
7189
|
+
}
|
|
7068
7190
|
function openVikingServerArgs(config) {
|
|
7069
7191
|
return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7070
7192
|
}
|
|
@@ -7123,7 +7245,7 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
7123
7245
|
return redactedPath;
|
|
7124
7246
|
}
|
|
7125
7247
|
await ensureDirectory((0, import_node_path2.dirname)(redactedPath), false);
|
|
7126
|
-
await (0,
|
|
7248
|
+
await (0, import_promises10.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
7127
7249
|
await (0, import_promises2.chmod)(redactedPath, 384);
|
|
7128
7250
|
return redactedPath;
|
|
7129
7251
|
}
|
|
@@ -7263,7 +7385,7 @@ async function storeMemory(config, memory, dryRun) {
|
|
|
7263
7385
|
console.log(formatShellCommand(ov, withIdentity(config, ["add-memory", memory])));
|
|
7264
7386
|
return;
|
|
7265
7387
|
}
|
|
7266
|
-
await (0,
|
|
7388
|
+
await (0, import_promises10.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
7267
7389
|
await (0, import_promises2.chmod)(memoryPath, 384);
|
|
7268
7390
|
await maybeRun(false, ov, withIdentity(config, ["add-memory", memory]));
|
|
7269
7391
|
}
|
|
@@ -7764,14 +7886,14 @@ async function exists(path) {
|
|
|
7764
7886
|
}
|
|
7765
7887
|
async function isFile(path) {
|
|
7766
7888
|
try {
|
|
7767
|
-
return (await (0,
|
|
7889
|
+
return (await (0, import_promises9.stat)(path)).isFile();
|
|
7768
7890
|
} catch (_err) {
|
|
7769
7891
|
return false;
|
|
7770
7892
|
}
|
|
7771
7893
|
}
|
|
7772
7894
|
async function isDirectory(path) {
|
|
7773
7895
|
try {
|
|
7774
|
-
return (await (0,
|
|
7896
|
+
return (await (0, import_promises9.stat)(path)).isDirectory();
|
|
7775
7897
|
} catch (_err) {
|
|
7776
7898
|
return false;
|
|
7777
7899
|
}
|
|
@@ -7783,6 +7905,21 @@ async function readFileIfExists(path) {
|
|
|
7783
7905
|
return void 0;
|
|
7784
7906
|
}
|
|
7785
7907
|
}
|
|
7908
|
+
async function removePathIfExists(path, label, dryRun) {
|
|
7909
|
+
if (!await exists(path)) {
|
|
7910
|
+
console.log(`Already absent: ${path}`);
|
|
7911
|
+
return;
|
|
7912
|
+
}
|
|
7913
|
+
await removePath(path, label, dryRun);
|
|
7914
|
+
}
|
|
7915
|
+
async function removePath(path, label, dryRun) {
|
|
7916
|
+
if (dryRun) {
|
|
7917
|
+
console.log(`Would remove ${label}: ${path}`);
|
|
7918
|
+
return;
|
|
7919
|
+
}
|
|
7920
|
+
await (0, import_promises8.rm)(path, { force: true, recursive: true });
|
|
7921
|
+
console.log(`Removed ${label}: ${path}`);
|
|
7922
|
+
}
|
|
7786
7923
|
function renderTemplate(template, config) {
|
|
7787
7924
|
return template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
|
|
7788
7925
|
}
|
|
@@ -7818,7 +7955,7 @@ function parseClaudeMcpScope(value) {
|
|
|
7818
7955
|
}
|
|
7819
7956
|
throw new Error(`Invalid Claude MCP scope: ${value}. Expected local, project, or user.`);
|
|
7820
7957
|
}
|
|
7821
|
-
async function
|
|
7958
|
+
async function resolveMcpClients(value, action) {
|
|
7822
7959
|
const normalized = value.trim().toLowerCase();
|
|
7823
7960
|
if (normalized === "none" || normalized === "false" || normalized === "off") {
|
|
7824
7961
|
return [];
|
|
@@ -7832,7 +7969,7 @@ async function resolveRepairMcpClients(value) {
|
|
|
7832
7969
|
const clients = [];
|
|
7833
7970
|
for (const client of requested) {
|
|
7834
7971
|
if (!await findExecutable([client])) {
|
|
7835
|
-
console.log(`WARN ${client} command not found; cannot
|
|
7972
|
+
console.log(`WARN ${client} command not found; cannot ${action} ${client} MCP config.`);
|
|
7836
7973
|
continue;
|
|
7837
7974
|
}
|
|
7838
7975
|
if (!clients.includes(client)) {
|
|
@@ -7858,6 +7995,12 @@ function expandPath(path) {
|
|
|
7858
7995
|
}
|
|
7859
7996
|
return (0, import_node_path3.isAbsolute)(path) ? path : (0, import_node_path6.resolve)(getInvocationCwd(), path);
|
|
7860
7997
|
}
|
|
7998
|
+
function assertSafeThreadnoteHomeForErase(path) {
|
|
7999
|
+
const resolvedPath = (0, import_node_path6.resolve)(path);
|
|
8000
|
+
if (resolvedPath === "/" || resolvedPath === (0, import_node_os.homedir)() || resolvedPath === (0, import_node_path2.dirname)((0, import_node_os.homedir)())) {
|
|
8001
|
+
throw new Error(`Refusing to erase unsafe THREADNOTE_HOME: ${resolvedPath}`);
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
7861
8004
|
function portablePath(path) {
|
|
7862
8005
|
const home = (0, import_node_os.homedir)();
|
|
7863
8006
|
const resolvedPath = (0, import_node_path6.resolve)(path);
|
package/docs/rollout.md
CHANGED
|
@@ -21,4 +21,4 @@ Start with a local-only pilot.
|
|
|
21
21
|
- Codex and Claude can both store and recall a shared handoff.
|
|
22
22
|
- Seeding curated guidance does not import known secret patterns.
|
|
23
23
|
- Fresh agents can recall repo testing guidance and discover relevant skills.
|
|
24
|
-
-
|
|
24
|
+
- `uninstall --dry-run` previews removal, and `uninstall` leaves memories intact unless `--erase-memories` is explicit.
|
package/docs/security.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- `mcp-install` requires `--apply` before it changes Codex or Claude config.
|
|
18
18
|
- `install` updates user-level Codex and Claude instruction files through a managed Markdown block. Existing personal
|
|
19
19
|
instructions outside that block are preserved.
|
|
20
|
+
- `uninstall` preserves local memories by default. `--erase-memories` is required before deleting `THREADNOTE_HOME`.
|
|
20
21
|
- Config files created under `THREADNOTE_HOME` are written with user-only permissions.
|
|
21
22
|
|
|
22
23
|
## Rollout Requirements
|
package/docs/troubleshooting.md
CHANGED
|
@@ -105,3 +105,15 @@ This is expected. Run with `--apply` after reviewing the command:
|
|
|
105
105
|
```bash
|
|
106
106
|
threadnote mcp-install codex --apply
|
|
107
107
|
```
|
|
108
|
+
|
|
109
|
+
## Uninstall Without Losing Memories
|
|
110
|
+
|
|
111
|
+
Run:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
threadnote uninstall --dry-run
|
|
115
|
+
threadnote uninstall
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
By default, uninstall removes Threadnote-managed shims, MCP config, launchd config, and user instruction blocks while
|
|
119
|
+
preserving `THREADNOTE_HOME`. To delete local OpenViking data too, pass `--erase-memories`.
|