threadnote 0.4.1 → 0.5.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 +35 -0
- package/dist/threadnote.cjs +395 -158
- package/docs/.nojekyll +0 -0
- package/docs/index.html +1532 -0
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3498,6 +3498,12 @@ var USER_AGENT_INSTRUCTION_TARGETS = [
|
|
|
3498
3498
|
path: "~/.copilot/instructions/threadnote.instructions.md"
|
|
3499
3499
|
}
|
|
3500
3500
|
];
|
|
3501
|
+
var CLAUDE_SETTINGS_PATH = "~/.claude/settings.json";
|
|
3502
|
+
var THREADNOTE_HOOK_MARKER = "_threadnote";
|
|
3503
|
+
var THREADNOTE_HOOK_MARKER_VALUE = "managed";
|
|
3504
|
+
var HOOK_PRE_COMPACT_COMMAND = "threadnote pre-compact-hook";
|
|
3505
|
+
var HOOK_SESSION_START_COMMAND = "threadnote session-start-hook";
|
|
3506
|
+
var HOOK_AUTO_PRECOMPACT_TOPIC = "auto-precompact";
|
|
3501
3507
|
var DEFAULT_SEED_PATTERNS = [
|
|
3502
3508
|
"AGENTS.md",
|
|
3503
3509
|
"CLAUDE.md",
|
|
@@ -3515,6 +3521,16 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3515
3521
|
"docs/**/*.md"
|
|
3516
3522
|
];
|
|
3517
3523
|
|
|
3524
|
+
// src/hooks.ts
|
|
3525
|
+
var import_promises5 = require("node:fs/promises");
|
|
3526
|
+
var import_node_path5 = require("node:path");
|
|
3527
|
+
|
|
3528
|
+
// src/mcp.ts
|
|
3529
|
+
var import_node_fs2 = require("node:fs");
|
|
3530
|
+
var import_promises2 = require("node:fs/promises");
|
|
3531
|
+
var import_node_path2 = require("node:path");
|
|
3532
|
+
var import_node_os2 = require("node:os");
|
|
3533
|
+
|
|
3518
3534
|
// src/utils.ts
|
|
3519
3535
|
var import_node_child_process = require("node:child_process");
|
|
3520
3536
|
var import_node_crypto = require("node:crypto");
|
|
@@ -3986,10 +4002,6 @@ function errorMessage(err) {
|
|
|
3986
4002
|
}
|
|
3987
4003
|
|
|
3988
4004
|
// src/mcp.ts
|
|
3989
|
-
var import_node_fs2 = require("node:fs");
|
|
3990
|
-
var import_promises2 = require("node:fs/promises");
|
|
3991
|
-
var import_node_path2 = require("node:path");
|
|
3992
|
-
var import_node_os2 = require("node:os");
|
|
3993
4005
|
async function runMcpInstall(config, agent, options) {
|
|
3994
4006
|
const name = options.name ?? OPENVIKING_MCP_NAME;
|
|
3995
4007
|
const url = options.url ?? `http://${config.host}:${config.port}/mcp`;
|
|
@@ -4470,50 +4482,6 @@ function existsSyncDirectory(path) {
|
|
|
4470
4482
|
}
|
|
4471
4483
|
}
|
|
4472
4484
|
|
|
4473
|
-
// src/runtime.ts
|
|
4474
|
-
var import_node_fs3 = require("node:fs");
|
|
4475
|
-
var import_node_os3 = require("node:os");
|
|
4476
|
-
var import_node_path3 = require("node:path");
|
|
4477
|
-
function getRuntimeConfig(program2, manifestOverride) {
|
|
4478
|
-
const options = program2.opts();
|
|
4479
|
-
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
4480
|
-
const manifestPath = expandPath(
|
|
4481
|
-
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
4482
|
-
);
|
|
4483
|
-
return {
|
|
4484
|
-
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
4485
|
-
agentContextHome: threadnoteHome,
|
|
4486
|
-
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
4487
|
-
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
4488
|
-
manifestPath,
|
|
4489
|
-
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
4490
|
-
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
4491
|
-
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
4492
|
-
};
|
|
4493
|
-
}
|
|
4494
|
-
function defaultManifestPath(agentContextHome) {
|
|
4495
|
-
const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
4496
|
-
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
4497
|
-
}
|
|
4498
|
-
function builtInExampleManifestPath() {
|
|
4499
|
-
return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
4500
|
-
}
|
|
4501
|
-
function openVikingHealthUrl(config) {
|
|
4502
|
-
return `http://${config.host}:${config.port}/health`;
|
|
4503
|
-
}
|
|
4504
|
-
function openVikingLogPath(config) {
|
|
4505
|
-
return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
|
|
4506
|
-
}
|
|
4507
|
-
function openVikingServerArgs(config) {
|
|
4508
|
-
return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
4509
|
-
}
|
|
4510
|
-
function withIdentity(config, args) {
|
|
4511
|
-
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
4512
|
-
}
|
|
4513
|
-
function renderTemplate(template, config) {
|
|
4514
|
-
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);
|
|
4515
|
-
}
|
|
4516
|
-
|
|
4517
4485
|
// src/memory.ts
|
|
4518
4486
|
var import_promises4 = require("node:fs/promises");
|
|
4519
4487
|
var import_node_path4 = require("node:path");
|
|
@@ -7205,6 +7173,50 @@ function readStringArray(object, key) {
|
|
|
7205
7173
|
return value;
|
|
7206
7174
|
}
|
|
7207
7175
|
|
|
7176
|
+
// src/runtime.ts
|
|
7177
|
+
var import_node_fs3 = require("node:fs");
|
|
7178
|
+
var import_node_os3 = require("node:os");
|
|
7179
|
+
var import_node_path3 = require("node:path");
|
|
7180
|
+
function getRuntimeConfig(program2, manifestOverride) {
|
|
7181
|
+
const options = program2.opts();
|
|
7182
|
+
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
7183
|
+
const manifestPath = expandPath(
|
|
7184
|
+
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
7185
|
+
);
|
|
7186
|
+
return {
|
|
7187
|
+
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
7188
|
+
agentContextHome: threadnoteHome,
|
|
7189
|
+
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
7190
|
+
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
7191
|
+
manifestPath,
|
|
7192
|
+
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
7193
|
+
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
7194
|
+
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
7195
|
+
};
|
|
7196
|
+
}
|
|
7197
|
+
function defaultManifestPath(agentContextHome) {
|
|
7198
|
+
const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7199
|
+
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7200
|
+
}
|
|
7201
|
+
function builtInExampleManifestPath() {
|
|
7202
|
+
return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7203
|
+
}
|
|
7204
|
+
function openVikingHealthUrl(config) {
|
|
7205
|
+
return `http://${config.host}:${config.port}/health`;
|
|
7206
|
+
}
|
|
7207
|
+
function openVikingLogPath(config) {
|
|
7208
|
+
return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
|
|
7209
|
+
}
|
|
7210
|
+
function openVikingServerArgs(config) {
|
|
7211
|
+
return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7212
|
+
}
|
|
7213
|
+
function withIdentity(config, args) {
|
|
7214
|
+
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
7215
|
+
}
|
|
7216
|
+
function renderTemplate(template, config) {
|
|
7217
|
+
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);
|
|
7218
|
+
}
|
|
7219
|
+
|
|
7208
7220
|
// src/memory.ts
|
|
7209
7221
|
function parseMemoryKind(value) {
|
|
7210
7222
|
if (["durable", "handoff", "incident", "preference", "smoke"].includes(value)) {
|
|
@@ -8083,9 +8095,201 @@ function formatBlock(value, emptyValue) {
|
|
|
8083
8095
|
return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
|
|
8084
8096
|
}
|
|
8085
8097
|
|
|
8098
|
+
// src/hooks.ts
|
|
8099
|
+
var MANAGED_HOOKS = [
|
|
8100
|
+
{
|
|
8101
|
+
event: "PreCompact",
|
|
8102
|
+
command: HOOK_PRE_COMPACT_COMMAND,
|
|
8103
|
+
description: "Auto-store a handoff snapshot before context compaction so the next turn can recall it."
|
|
8104
|
+
},
|
|
8105
|
+
{
|
|
8106
|
+
event: "SessionStart",
|
|
8107
|
+
command: HOOK_SESSION_START_COMMAND,
|
|
8108
|
+
description: "Pre-load the latest handoff for the current repo into the new session context."
|
|
8109
|
+
}
|
|
8110
|
+
];
|
|
8111
|
+
async function runHooksInstall(config, agent, options) {
|
|
8112
|
+
const apply = options.apply === true && options.dryRun !== true;
|
|
8113
|
+
const remove = options.remove === true;
|
|
8114
|
+
switch (agent) {
|
|
8115
|
+
case "claude":
|
|
8116
|
+
await runClaudeHooksInstall({ apply, remove });
|
|
8117
|
+
return;
|
|
8118
|
+
case "codex":
|
|
8119
|
+
printCodexHooksNotice(remove);
|
|
8120
|
+
return;
|
|
8121
|
+
case "cursor":
|
|
8122
|
+
printNoHooksSupported("cursor", remove);
|
|
8123
|
+
return;
|
|
8124
|
+
case "copilot":
|
|
8125
|
+
printNoHooksSupported("copilot", remove);
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
async function runClaudeHooksInstall(options) {
|
|
8130
|
+
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
8131
|
+
const existingRaw = await exists(path) ? await (0, import_promises5.readFile)(path, "utf8") : "{}";
|
|
8132
|
+
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
8133
|
+
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
8134
|
+
const before = JSON.stringify(parsed);
|
|
8135
|
+
const after = JSON.stringify(next);
|
|
8136
|
+
if (before === after) {
|
|
8137
|
+
console.log(`Claude hooks already ${options.remove ? "absent" : "managed"} in ${path}.`);
|
|
8138
|
+
return;
|
|
8139
|
+
}
|
|
8140
|
+
console.log(`${options.apply ? "Updating" : "Would update"} ${path}:`);
|
|
8141
|
+
for (const entry of MANAGED_HOOKS) {
|
|
8142
|
+
console.log(` ${options.remove ? "-" : "+"} ${entry.event}: ${entry.command}`);
|
|
8143
|
+
}
|
|
8144
|
+
if (!options.apply) {
|
|
8145
|
+
console.log("\nRe-run with --apply to actually modify the file.");
|
|
8146
|
+
return;
|
|
8147
|
+
}
|
|
8148
|
+
await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
8149
|
+
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
8150
|
+
`;
|
|
8151
|
+
await (0, import_promises5.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
8152
|
+
await (0, import_promises5.chmod)(path, 384);
|
|
8153
|
+
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
8154
|
+
}
|
|
8155
|
+
function withThreadnoteHooks(input2) {
|
|
8156
|
+
const hooks = ensureMutableObject(input2.hooks);
|
|
8157
|
+
for (const entry of MANAGED_HOOKS) {
|
|
8158
|
+
const list = ensureMutableArray(hooks[entry.event]).filter((item) => !isManagedThreadnoteEntry(item));
|
|
8159
|
+
list.push({
|
|
8160
|
+
[THREADNOTE_HOOK_MARKER]: THREADNOTE_HOOK_MARKER_VALUE,
|
|
8161
|
+
matcher: "",
|
|
8162
|
+
hooks: [{ type: "command", command: entry.command }]
|
|
8163
|
+
});
|
|
8164
|
+
hooks[entry.event] = list;
|
|
8165
|
+
}
|
|
8166
|
+
return { ...input2, hooks };
|
|
8167
|
+
}
|
|
8168
|
+
function withoutThreadnoteHooks(input2) {
|
|
8169
|
+
if (!isJsonObject(input2.hooks)) {
|
|
8170
|
+
return input2;
|
|
8171
|
+
}
|
|
8172
|
+
const hooks = {};
|
|
8173
|
+
for (const [event, value] of Object.entries(input2.hooks)) {
|
|
8174
|
+
if (!Array.isArray(value)) {
|
|
8175
|
+
hooks[event] = value;
|
|
8176
|
+
continue;
|
|
8177
|
+
}
|
|
8178
|
+
const filtered = value.filter((item) => !isManagedThreadnoteEntry(item));
|
|
8179
|
+
if (filtered.length > 0) {
|
|
8180
|
+
hooks[event] = filtered;
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
if (Object.keys(hooks).length === 0) {
|
|
8184
|
+
const next = { ...input2 };
|
|
8185
|
+
delete next.hooks;
|
|
8186
|
+
return next;
|
|
8187
|
+
}
|
|
8188
|
+
return { ...input2, hooks };
|
|
8189
|
+
}
|
|
8190
|
+
function isManagedThreadnoteEntry(value) {
|
|
8191
|
+
return isJsonObject(value) && value[THREADNOTE_HOOK_MARKER] === THREADNOTE_HOOK_MARKER_VALUE;
|
|
8192
|
+
}
|
|
8193
|
+
function ensureMutableObject(value) {
|
|
8194
|
+
return isJsonObject(value) ? { ...value } : {};
|
|
8195
|
+
}
|
|
8196
|
+
function ensureMutableArray(value) {
|
|
8197
|
+
return Array.isArray(value) ? [...value] : [];
|
|
8198
|
+
}
|
|
8199
|
+
function printCodexHooksNotice(remove) {
|
|
8200
|
+
if (remove) {
|
|
8201
|
+
console.log("Codex CLI does not expose a managed hook surface today, so there is nothing to remove.");
|
|
8202
|
+
return;
|
|
8203
|
+
}
|
|
8204
|
+
console.log(
|
|
8205
|
+
[
|
|
8206
|
+
"Codex CLI does not currently expose lifecycle hooks (no PreCompact or SessionStart analog).",
|
|
8207
|
+
"Threadnote already installs Codex user instructions at ~/.codex/AGENTS.md; that remains the active guidance surface.",
|
|
8208
|
+
"If a future Codex release adds hook events, threadnote will pick them up via `install-hooks codex`."
|
|
8209
|
+
].join("\n")
|
|
8210
|
+
);
|
|
8211
|
+
}
|
|
8212
|
+
function printNoHooksSupported(agent, remove) {
|
|
8213
|
+
if (remove) {
|
|
8214
|
+
console.log(`${agent} does not expose a hook surface; nothing to remove.`);
|
|
8215
|
+
return;
|
|
8216
|
+
}
|
|
8217
|
+
console.log(
|
|
8218
|
+
[
|
|
8219
|
+
`${agent} does not expose agent-mode hooks today.`,
|
|
8220
|
+
"Threadnote already installs user-level instructions for this agent; that remains the active guidance surface.",
|
|
8221
|
+
"Hooks support will be added if the agent gains a hook surface upstream."
|
|
8222
|
+
].join("\n")
|
|
8223
|
+
);
|
|
8224
|
+
}
|
|
8225
|
+
async function hasManagedClaudeHooks() {
|
|
8226
|
+
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
8227
|
+
if (!await exists(path)) {
|
|
8228
|
+
return false;
|
|
8229
|
+
}
|
|
8230
|
+
const raw = await (0, import_promises5.readFile)(path, "utf8");
|
|
8231
|
+
const parsed = parseJsonConfigObject(raw);
|
|
8232
|
+
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
8233
|
+
return false;
|
|
8234
|
+
}
|
|
8235
|
+
for (const value of Object.values(parsed.hooks)) {
|
|
8236
|
+
if (!Array.isArray(value)) {
|
|
8237
|
+
continue;
|
|
8238
|
+
}
|
|
8239
|
+
if (value.some((item) => isManagedThreadnoteEntry(item))) {
|
|
8240
|
+
return true;
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
return false;
|
|
8244
|
+
}
|
|
8245
|
+
async function runPreCompactHook(config, options = {}) {
|
|
8246
|
+
try {
|
|
8247
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
8248
|
+
const project = repoRoot ? (0, import_node_path5.basename)(repoRoot) : "general";
|
|
8249
|
+
await runHandoff(config, {
|
|
8250
|
+
blockers: "- none recorded",
|
|
8251
|
+
dryRun: options.dryRun === true,
|
|
8252
|
+
nextStep: "Continue from this auto-snapshot. A manual `threadnote handoff` will produce a richer write-up if you have more context.",
|
|
8253
|
+
project,
|
|
8254
|
+
sourceAgentClient: "claude",
|
|
8255
|
+
task: "Auto-snapshot captured at Claude PreCompact (deterministic safety net before context compaction).",
|
|
8256
|
+
tests: "- not recorded (auto-snapshot)",
|
|
8257
|
+
topic: HOOK_AUTO_PRECOMPACT_TOPIC
|
|
8258
|
+
});
|
|
8259
|
+
} catch (err) {
|
|
8260
|
+
process.stderr.write(
|
|
8261
|
+
`threadnote pre-compact-hook: snapshot skipped (${err instanceof Error ? err.message : String(err)})
|
|
8262
|
+
`
|
|
8263
|
+
);
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
async function runSessionStartHook(config, options = {}) {
|
|
8267
|
+
try {
|
|
8268
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
8269
|
+
if (!repoRoot) {
|
|
8270
|
+
return;
|
|
8271
|
+
}
|
|
8272
|
+
const project = (0, import_node_path5.basename)(repoRoot);
|
|
8273
|
+
process.stdout.write(`## Threadnote \u2014 latest context for ${project}
|
|
8274
|
+
|
|
8275
|
+
`);
|
|
8276
|
+
await runRecall(config, {
|
|
8277
|
+
dryRun: options.dryRun === true,
|
|
8278
|
+
inferScope: true,
|
|
8279
|
+
nodeLimit: "5",
|
|
8280
|
+
query: `${project} latest handoff durable feature memory`
|
|
8281
|
+
});
|
|
8282
|
+
} catch (err) {
|
|
8283
|
+
process.stderr.write(
|
|
8284
|
+
`threadnote session-start-hook: recall skipped (${err instanceof Error ? err.message : String(err)})
|
|
8285
|
+
`
|
|
8286
|
+
);
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
|
|
8086
8290
|
// src/seeding.ts
|
|
8087
|
-
var
|
|
8088
|
-
var
|
|
8291
|
+
var import_promises6 = require("node:fs/promises");
|
|
8292
|
+
var import_node_path6 = require("node:path");
|
|
8089
8293
|
async function runSeed(config, options) {
|
|
8090
8294
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
8091
8295
|
const ignorePatterns = await loadIgnorePatterns();
|
|
@@ -8114,7 +8318,7 @@ async function runSeed(config, options) {
|
|
|
8114
8318
|
}
|
|
8115
8319
|
async function runInitManifest(config, options) {
|
|
8116
8320
|
const manifestPath = expandPath(
|
|
8117
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
8321
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path6.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
8118
8322
|
);
|
|
8119
8323
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
8120
8324
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -8157,9 +8361,9 @@ async function runInitManifest(config, options) {
|
|
|
8157
8361
|
console.log(output2.trimEnd());
|
|
8158
8362
|
return;
|
|
8159
8363
|
}
|
|
8160
|
-
await ensureDirectory((0,
|
|
8161
|
-
await (0,
|
|
8162
|
-
await (0,
|
|
8364
|
+
await ensureDirectory((0, import_node_path6.dirname)(manifestPath), false);
|
|
8365
|
+
await (0, import_promises6.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
8366
|
+
await (0, import_promises6.chmod)(manifestPath, 384);
|
|
8163
8367
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
8164
8368
|
console.log("Seed with:");
|
|
8165
8369
|
console.log(" threadnote seed --dry-run");
|
|
@@ -8189,13 +8393,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
8189
8393
|
async function projectIdentity(path) {
|
|
8190
8394
|
const expanded = expandPath(path);
|
|
8191
8395
|
try {
|
|
8192
|
-
return await (0,
|
|
8396
|
+
return await (0, import_promises6.realpath)(expanded);
|
|
8193
8397
|
} catch (_err) {
|
|
8194
8398
|
return expanded;
|
|
8195
8399
|
}
|
|
8196
8400
|
}
|
|
8197
8401
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
8198
|
-
const baseName = uriSegment((0,
|
|
8402
|
+
const baseName = uriSegment((0, import_node_path6.basename)(repoRoot));
|
|
8199
8403
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
8200
8404
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
8201
8405
|
let name = baseName;
|
|
@@ -8217,7 +8421,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8217
8421
|
for (const pattern of project.seed) {
|
|
8218
8422
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
8219
8423
|
for (const filePath of files) {
|
|
8220
|
-
const relativePath = toPosixPath((0,
|
|
8424
|
+
const relativePath = toPosixPath((0, import_node_path6.relative)(projectRoot, filePath));
|
|
8221
8425
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
8222
8426
|
continue;
|
|
8223
8427
|
}
|
|
@@ -8235,20 +8439,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8235
8439
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
8236
8440
|
const normalizedPattern = toPosixPath(pattern);
|
|
8237
8441
|
if (!hasGlob(normalizedPattern)) {
|
|
8238
|
-
const filePath = (0,
|
|
8442
|
+
const filePath = (0, import_node_path6.join)(projectRoot, normalizedPattern);
|
|
8239
8443
|
return await isFile(filePath) ? [filePath] : [];
|
|
8240
8444
|
}
|
|
8241
8445
|
const globBase = getGlobBase(normalizedPattern);
|
|
8242
|
-
const basePath = (0,
|
|
8446
|
+
const basePath = (0, import_node_path6.join)(projectRoot, globBase);
|
|
8243
8447
|
if (!await exists(basePath)) {
|
|
8244
8448
|
return [];
|
|
8245
8449
|
}
|
|
8246
8450
|
const regex = globToRegExp(normalizedPattern);
|
|
8247
8451
|
const files = await walkFiles(basePath);
|
|
8248
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
8452
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path6.relative)(projectRoot, filePath))));
|
|
8249
8453
|
}
|
|
8250
8454
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
8251
|
-
const content = await (0,
|
|
8455
|
+
const content = await (0, import_promises6.readFile)(candidate.filePath, "utf8");
|
|
8252
8456
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
8253
8457
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
8254
8458
|
if (secretMatches.length > 0) {
|
|
@@ -8260,14 +8464,14 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
8260
8464
|
if (redactedContent === content) {
|
|
8261
8465
|
return candidate.filePath;
|
|
8262
8466
|
}
|
|
8263
|
-
const redactedPath = (0,
|
|
8467
|
+
const redactedPath = (0, import_node_path6.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
8264
8468
|
if (dryRun) {
|
|
8265
8469
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
8266
8470
|
return redactedPath;
|
|
8267
8471
|
}
|
|
8268
|
-
await ensureDirectory((0,
|
|
8269
|
-
await (0,
|
|
8270
|
-
await (0,
|
|
8472
|
+
await ensureDirectory((0, import_node_path6.dirname)(redactedPath), false);
|
|
8473
|
+
await (0, import_promises6.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
8474
|
+
await (0, import_promises6.chmod)(redactedPath, 384);
|
|
8271
8475
|
return redactedPath;
|
|
8272
8476
|
}
|
|
8273
8477
|
async function collectSkillCandidates(config) {
|
|
@@ -8292,7 +8496,7 @@ async function collectSkillCandidates(config) {
|
|
|
8292
8496
|
for (const source of sources) {
|
|
8293
8497
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
8294
8498
|
for (const filePath of files) {
|
|
8295
|
-
const content = await (0,
|
|
8499
|
+
const content = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
8296
8500
|
const matches = detectSecretMatches(content);
|
|
8297
8501
|
if (matches.length > 0) {
|
|
8298
8502
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -8323,10 +8527,10 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
8323
8527
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
8324
8528
|
}
|
|
8325
8529
|
function skillResourceUri(skill) {
|
|
8326
|
-
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0,
|
|
8530
|
+
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path6.basename)((0, import_node_path6.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
|
|
8327
8531
|
}
|
|
8328
8532
|
async function loadIgnorePatterns() {
|
|
8329
|
-
const raw = await (0,
|
|
8533
|
+
const raw = await (0, import_promises6.readFile)((0, import_node_path6.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
8330
8534
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8331
8535
|
}
|
|
8332
8536
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -8397,9 +8601,9 @@ function detectSecretMatches(content) {
|
|
|
8397
8601
|
}
|
|
8398
8602
|
|
|
8399
8603
|
// src/share.ts
|
|
8400
|
-
var
|
|
8604
|
+
var import_promises7 = require("node:fs/promises");
|
|
8401
8605
|
var import_node_os4 = require("node:os");
|
|
8402
|
-
var
|
|
8606
|
+
var import_node_path7 = require("node:path");
|
|
8403
8607
|
var TEAMS_FILE_VERSION = 1;
|
|
8404
8608
|
var SHARED_SEGMENT = "shared";
|
|
8405
8609
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -8437,8 +8641,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8437
8641
|
if (await exists(gitdir)) {
|
|
8438
8642
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
8439
8643
|
}
|
|
8440
|
-
await ensureDirectory((0,
|
|
8441
|
-
await ensureDirectory((0,
|
|
8644
|
+
await ensureDirectory((0, import_node_path7.dirname)(worktree), dryRun);
|
|
8645
|
+
await ensureDirectory((0, import_node_path7.dirname)(gitdir), dryRun);
|
|
8442
8646
|
const git = await requiredExecutable("git");
|
|
8443
8647
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
8444
8648
|
const newConfig = {
|
|
@@ -8469,7 +8673,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8469
8673
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
8470
8674
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
8471
8675
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
8472
|
-
const gitignorePath = (0,
|
|
8676
|
+
const gitignorePath = (0, import_node_path7.join)(worktree, ".gitignore");
|
|
8473
8677
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
8474
8678
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
8475
8679
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -8488,7 +8692,7 @@ async function ensureSharedGitignore(worktree, git, push) {
|
|
|
8488
8692
|
segments.push(SHARED_GITIGNORE_HEADER, "\n");
|
|
8489
8693
|
}
|
|
8490
8694
|
segments.push(missingPatterns.join("\n"), "\n");
|
|
8491
|
-
await (0,
|
|
8695
|
+
await (0, import_promises7.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
|
|
8492
8696
|
console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
|
|
8493
8697
|
await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
|
|
8494
8698
|
const commitResult = await runCommand(
|
|
@@ -8553,7 +8757,7 @@ async function runShareSync(config, options) {
|
|
|
8553
8757
|
if (dryRun) {
|
|
8554
8758
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
8555
8759
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
8556
|
-
if (await exists((0,
|
|
8760
|
+
if (await exists((0, import_node_path7.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path7.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8557
8761
|
throw new Error(
|
|
8558
8762
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
8559
8763
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -8704,10 +8908,10 @@ function normalizeTeamName(input2) {
|
|
|
8704
8908
|
return candidate;
|
|
8705
8909
|
}
|
|
8706
8910
|
function teamsFilePath(config) {
|
|
8707
|
-
return (0,
|
|
8911
|
+
return (0, import_node_path7.join)(config.agentContextHome, "share", "teams.json");
|
|
8708
8912
|
}
|
|
8709
8913
|
function teamWorktreePath(config, team) {
|
|
8710
|
-
return (0,
|
|
8914
|
+
return (0, import_node_path7.join)(
|
|
8711
8915
|
config.agentContextHome,
|
|
8712
8916
|
"data",
|
|
8713
8917
|
"viking",
|
|
@@ -8720,7 +8924,7 @@ function teamWorktreePath(config, team) {
|
|
|
8720
8924
|
);
|
|
8721
8925
|
}
|
|
8722
8926
|
function teamGitdirPath(config, team) {
|
|
8723
|
-
return (0,
|
|
8927
|
+
return (0, import_node_path7.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
8724
8928
|
}
|
|
8725
8929
|
async function readTeamsFile(config) {
|
|
8726
8930
|
const path = teamsFilePath(config);
|
|
@@ -8763,13 +8967,13 @@ async function readTeamsFile(config) {
|
|
|
8763
8967
|
}
|
|
8764
8968
|
async function writeTeamsFile(config, contents) {
|
|
8765
8969
|
const path = teamsFilePath(config);
|
|
8766
|
-
await (0,
|
|
8970
|
+
await (0, import_promises7.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
|
|
8767
8971
|
const serializable = {
|
|
8768
8972
|
defaultTeam: contents.defaultTeam,
|
|
8769
8973
|
teams: contents.teams,
|
|
8770
8974
|
version: contents.version
|
|
8771
8975
|
};
|
|
8772
|
-
await (0,
|
|
8976
|
+
await (0, import_promises7.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
|
|
8773
8977
|
`, { encoding: "utf8", mode: 384 });
|
|
8774
8978
|
}
|
|
8775
8979
|
async function resolveTeam(config, requested) {
|
|
@@ -8799,7 +9003,7 @@ async function assertWorktreeUsable(worktree) {
|
|
|
8799
9003
|
if (!await isDirectory(worktree)) {
|
|
8800
9004
|
throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
|
|
8801
9005
|
}
|
|
8802
|
-
const entries = await (0,
|
|
9006
|
+
const entries = await (0, import_promises7.readdir)(worktree);
|
|
8803
9007
|
if (entries.length > 0) {
|
|
8804
9008
|
const preview = entries.slice(0, 5).join(", ");
|
|
8805
9009
|
const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
|
|
@@ -8823,7 +9027,7 @@ async function walkMemoryFiles(root) {
|
|
|
8823
9027
|
async function visit(path, depth) {
|
|
8824
9028
|
let entries;
|
|
8825
9029
|
try {
|
|
8826
|
-
entries = await (0,
|
|
9030
|
+
entries = await (0, import_promises7.readdir)(path, { withFileTypes: true });
|
|
8827
9031
|
} catch (err) {
|
|
8828
9032
|
console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
|
|
8829
9033
|
return;
|
|
@@ -8832,7 +9036,7 @@ async function walkMemoryFiles(root) {
|
|
|
8832
9036
|
if (entry.name === ".git") {
|
|
8833
9037
|
continue;
|
|
8834
9038
|
}
|
|
8835
|
-
const full = (0,
|
|
9039
|
+
const full = (0, import_node_path7.join)(path, entry.name);
|
|
8836
9040
|
if (entry.isDirectory()) {
|
|
8837
9041
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
8838
9042
|
continue;
|
|
@@ -8856,7 +9060,7 @@ async function walkMemoryFiles(root) {
|
|
|
8856
9060
|
return out;
|
|
8857
9061
|
}
|
|
8858
9062
|
function workfileToVikingUri(config, team, filePath) {
|
|
8859
|
-
const rel = (0,
|
|
9063
|
+
const rel = (0, import_node_path7.relative)(team.worktree, filePath).split(import_node_path7.sep).join("/");
|
|
8860
9064
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
8861
9065
|
}
|
|
8862
9066
|
function isInSharedNamespace(config, uri) {
|
|
@@ -8950,13 +9154,13 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun) {
|
|
|
8950
9154
|
console.log(`Would run: ${formatShellCommand(ov, args)}`);
|
|
8951
9155
|
return;
|
|
8952
9156
|
}
|
|
8953
|
-
const stagingDir = await (0,
|
|
8954
|
-
const tempPath = (0,
|
|
9157
|
+
const stagingDir = await (0, import_promises7.mkdtemp)((0, import_node_path7.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
|
|
9158
|
+
const tempPath = (0, import_node_path7.join)(stagingDir, "body.txt");
|
|
8955
9159
|
try {
|
|
8956
|
-
await (0,
|
|
9160
|
+
await (0, import_promises7.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
8957
9161
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode);
|
|
8958
9162
|
} finally {
|
|
8959
|
-
await (0,
|
|
9163
|
+
await (0, import_promises7.rm)(stagingDir, { force: true, recursive: true });
|
|
8960
9164
|
}
|
|
8961
9165
|
}
|
|
8962
9166
|
async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode) {
|
|
@@ -9014,7 +9218,7 @@ ${stdout}`.toLowerCase();
|
|
|
9014
9218
|
return output2.includes("resource is busy") || output2.includes("resource is being processed") || output2.includes("network error") || output2.includes("error sending request") || output2.includes("http request failed") || output2.includes("connection refused") || output2.includes("connection reset") || output2.includes("timed out");
|
|
9015
9219
|
}
|
|
9016
9220
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode) {
|
|
9017
|
-
const content = await (0,
|
|
9221
|
+
const content = await (0, import_promises7.readFile)(filePath, "utf8");
|
|
9018
9222
|
await writeMemoryFile(config, ov, uri, content, initialMode, false);
|
|
9019
9223
|
}
|
|
9020
9224
|
async function removeWithRollback(config, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
|
|
@@ -9056,7 +9260,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
|
|
|
9056
9260
|
if (!relative3) {
|
|
9057
9261
|
return;
|
|
9058
9262
|
}
|
|
9059
|
-
await (0,
|
|
9263
|
+
await (0, import_promises7.rm)((0, import_node_path7.join)(worktree, relative3), { force: true });
|
|
9060
9264
|
}
|
|
9061
9265
|
async function removeMemoryUri(config, ov, uri, dryRun) {
|
|
9062
9266
|
const args = withIdentity(config, ["rm", uri]);
|
|
@@ -9113,8 +9317,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9113
9317
|
const oldRel = entries[index + 1];
|
|
9114
9318
|
const newRel = entries[index + 2];
|
|
9115
9319
|
if (oldRel && newRel) {
|
|
9116
|
-
changes.push({ path: (0,
|
|
9117
|
-
changes.push({ path: (0,
|
|
9320
|
+
changes.push({ path: (0, import_node_path7.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
9321
|
+
changes.push({ path: (0, import_node_path7.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
9118
9322
|
}
|
|
9119
9323
|
index += 3;
|
|
9120
9324
|
continue;
|
|
@@ -9122,7 +9326,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9122
9326
|
const rel = entries[index + 1];
|
|
9123
9327
|
if (rel) {
|
|
9124
9328
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
9125
|
-
changes.push({ path: (0,
|
|
9329
|
+
changes.push({ path: (0, import_node_path7.join)(worktree, rel), relativePath: rel, status });
|
|
9126
9330
|
}
|
|
9127
9331
|
index += 2;
|
|
9128
9332
|
}
|
|
@@ -9160,18 +9364,18 @@ async function applyChangesToOpenViking(config, team, changes) {
|
|
|
9160
9364
|
// src/lifecycle.ts
|
|
9161
9365
|
var import_node_child_process2 = require("node:child_process");
|
|
9162
9366
|
var import_node_fs5 = require("node:fs");
|
|
9163
|
-
var
|
|
9367
|
+
var import_promises10 = require("node:fs/promises");
|
|
9164
9368
|
var import_node_os6 = require("node:os");
|
|
9165
|
-
var
|
|
9369
|
+
var import_node_path9 = require("node:path");
|
|
9166
9370
|
var import_node_process2 = require("node:process");
|
|
9167
|
-
var
|
|
9371
|
+
var import_promises11 = require("node:readline/promises");
|
|
9168
9372
|
|
|
9169
9373
|
// src/update.ts
|
|
9170
9374
|
var import_node_fs4 = require("node:fs");
|
|
9171
|
-
var
|
|
9375
|
+
var import_promises8 = require("node:fs/promises");
|
|
9172
9376
|
var import_node_os5 = require("node:os");
|
|
9173
|
-
var
|
|
9174
|
-
var
|
|
9377
|
+
var import_node_path8 = require("node:path");
|
|
9378
|
+
var import_promises9 = require("node:readline/promises");
|
|
9175
9379
|
var import_node_process = require("node:process");
|
|
9176
9380
|
var NPM_PACKAGE_NAME = "threadnote";
|
|
9177
9381
|
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -9319,7 +9523,7 @@ async function getUpdateInfo(config, options) {
|
|
|
9319
9523
|
};
|
|
9320
9524
|
}
|
|
9321
9525
|
async function currentPackageVersion() {
|
|
9322
|
-
const rawPackage = await (0,
|
|
9526
|
+
const rawPackage = await (0, import_promises8.readFile)((0, import_node_path8.join)(toolRoot(), "package.json"), "utf8");
|
|
9323
9527
|
const parsed = JSON.parse(rawPackage);
|
|
9324
9528
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
9325
9529
|
throw new Error("Could not read current threadnote package version.");
|
|
@@ -9369,11 +9573,11 @@ async function readFreshCache(config, registry) {
|
|
|
9369
9573
|
}
|
|
9370
9574
|
async function writeUpdateCache(config, cache) {
|
|
9371
9575
|
await ensureDirectory(config.agentContextHome, false);
|
|
9372
|
-
await (0,
|
|
9576
|
+
await (0, import_promises8.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
9373
9577
|
`, { encoding: "utf8", mode: 384 });
|
|
9374
9578
|
}
|
|
9375
9579
|
function updateCachePath(config) {
|
|
9376
|
-
return (0,
|
|
9580
|
+
return (0, import_node_path8.join)(config.agentContextHome, "update-check.json");
|
|
9377
9581
|
}
|
|
9378
9582
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
9379
9583
|
const state = await readPostUpdateState(config);
|
|
@@ -9437,7 +9641,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
9437
9641
|
return applicable;
|
|
9438
9642
|
}
|
|
9439
9643
|
async function readPostUpdateMigrations() {
|
|
9440
|
-
const raw = await readFileIfExists((0,
|
|
9644
|
+
const raw = await readFileIfExists((0, import_node_path8.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
9441
9645
|
if (!raw) {
|
|
9442
9646
|
return [];
|
|
9443
9647
|
}
|
|
@@ -9476,7 +9680,7 @@ function printPostUpdateMigration(migration) {
|
|
|
9476
9680
|
}
|
|
9477
9681
|
}
|
|
9478
9682
|
async function confirmPostUpdateMigration(prompt) {
|
|
9479
|
-
const readline = (0,
|
|
9683
|
+
const readline = (0, import_promises9.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
9480
9684
|
try {
|
|
9481
9685
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
9482
9686
|
return answer === "y" || answer === "yes";
|
|
@@ -9508,11 +9712,11 @@ async function readPostUpdateState(config) {
|
|
|
9508
9712
|
}
|
|
9509
9713
|
async function writePostUpdateState(config, state) {
|
|
9510
9714
|
await ensureDirectory(config.agentContextHome, false);
|
|
9511
|
-
await (0,
|
|
9715
|
+
await (0, import_promises8.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
9512
9716
|
`, { encoding: "utf8", mode: 384 });
|
|
9513
9717
|
}
|
|
9514
9718
|
function postUpdateStatePath(config) {
|
|
9515
|
-
return (0,
|
|
9719
|
+
return (0, import_node_path8.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
9516
9720
|
}
|
|
9517
9721
|
async function resolveUpdateRuntime(runtime) {
|
|
9518
9722
|
if (runtime !== "auto") {
|
|
@@ -9542,18 +9746,18 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
9542
9746
|
if (runtime === "npm") {
|
|
9543
9747
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
9544
9748
|
const prefix = result.stdout.trim();
|
|
9545
|
-
return prefix ? (0,
|
|
9749
|
+
return prefix ? (0, import_node_path8.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
9546
9750
|
}
|
|
9547
9751
|
if (runtime === "bun") {
|
|
9548
9752
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
9549
9753
|
const binDir = result.stdout.trim();
|
|
9550
|
-
return binDir ? (0,
|
|
9754
|
+
return binDir ? (0, import_node_path8.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
9551
9755
|
}
|
|
9552
|
-
return (0,
|
|
9756
|
+
return (0, import_node_path8.join)(process.env.DENO_INSTALL ?? (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
9553
9757
|
}
|
|
9554
9758
|
async function isExecutable(path) {
|
|
9555
9759
|
try {
|
|
9556
|
-
await (0,
|
|
9760
|
+
await (0, import_promises8.access)(path, import_node_fs4.constants.X_OK);
|
|
9557
9761
|
return true;
|
|
9558
9762
|
} catch (_err) {
|
|
9559
9763
|
return false;
|
|
@@ -9644,9 +9848,9 @@ async function runDoctor(config, options) {
|
|
|
9644
9848
|
checks.push(await commandShimCheck());
|
|
9645
9849
|
checks.push(...await userAgentInstructionsChecks());
|
|
9646
9850
|
checks.push(await manifestCheck(config.manifestPath));
|
|
9647
|
-
checks.push(await fileCheck((0,
|
|
9648
|
-
checks.push(await fileCheck((0,
|
|
9649
|
-
checks.push(await fileCheck((0,
|
|
9851
|
+
checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
9852
|
+
checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
9853
|
+
checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
9650
9854
|
checks.push(await healthCheck(config));
|
|
9651
9855
|
for (const check of checks) {
|
|
9652
9856
|
console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
|
|
@@ -9663,9 +9867,9 @@ async function runInstall(config, options) {
|
|
|
9663
9867
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
9664
9868
|
const dryRun = options.dryRun === true;
|
|
9665
9869
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
9666
|
-
await ensureDirectory((0,
|
|
9667
|
-
await ensureDirectory((0,
|
|
9668
|
-
await ensureDirectory((0,
|
|
9870
|
+
await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "logs"), dryRun);
|
|
9871
|
+
await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "redacted"), dryRun);
|
|
9872
|
+
await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "mcp"), dryRun);
|
|
9669
9873
|
await installCommandShim(dryRun);
|
|
9670
9874
|
await installUserAgentInstructions(dryRun);
|
|
9671
9875
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
@@ -9691,17 +9895,17 @@ async function runInstall(config, options) {
|
|
|
9691
9895
|
}
|
|
9692
9896
|
await writeTemplateIfMissing({
|
|
9693
9897
|
config,
|
|
9694
|
-
destinationPath: (0,
|
|
9898
|
+
destinationPath: (0, import_node_path9.join)(config.agentContextHome, "ov.conf"),
|
|
9695
9899
|
dryRun,
|
|
9696
9900
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9697
|
-
templatePath: (0,
|
|
9901
|
+
templatePath: (0, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
9698
9902
|
});
|
|
9699
9903
|
await writeTemplateIfMissing({
|
|
9700
9904
|
config,
|
|
9701
|
-
destinationPath: (0,
|
|
9905
|
+
destinationPath: (0, import_node_path9.join)(config.agentContextHome, "ovcli.conf"),
|
|
9702
9906
|
dryRun,
|
|
9703
9907
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9704
|
-
templatePath: (0,
|
|
9908
|
+
templatePath: (0, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
9705
9909
|
});
|
|
9706
9910
|
if (options.start !== false) {
|
|
9707
9911
|
const healthy = await repairServerHealth(config, dryRun);
|
|
@@ -9738,6 +9942,10 @@ async function runRepair(config, options) {
|
|
|
9738
9942
|
await runMcpInstall(config, client, { apply: !dryRun, name: OPENVIKING_MCP_NAME });
|
|
9739
9943
|
}
|
|
9740
9944
|
}
|
|
9945
|
+
if (await hasManagedClaudeHooks()) {
|
|
9946
|
+
console.log("\nRepairing claude hooks (re-asserting threadnote-managed entries).");
|
|
9947
|
+
await runHooksInstall(config, "claude", { apply: !dryRun, dryRun });
|
|
9948
|
+
}
|
|
9741
9949
|
console.log("\nPost-repair doctor:");
|
|
9742
9950
|
await runDoctor(config, { dryRun, strict: false });
|
|
9743
9951
|
if (options.postUpdate !== false) {
|
|
@@ -9751,10 +9959,13 @@ async function runUninstall(config, options) {
|
|
|
9751
9959
|
}
|
|
9752
9960
|
console.log("Uninstalling local Threadnote setup.");
|
|
9753
9961
|
await runStop(config, { dryRun });
|
|
9754
|
-
await removePathIfExists((0,
|
|
9962
|
+
await removePathIfExists((0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
9755
9963
|
await removeLaunchAgent(dryRun);
|
|
9756
9964
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
9757
9965
|
await removeMcpSnippets(config, dryRun);
|
|
9966
|
+
if (await hasManagedClaudeHooks()) {
|
|
9967
|
+
await runHooksInstall(config, "claude", { apply: !dryRun, dryRun, remove: true });
|
|
9968
|
+
}
|
|
9758
9969
|
await removeCommandShim(dryRun);
|
|
9759
9970
|
await removeUserAgentInstructions(dryRun);
|
|
9760
9971
|
if (options.eraseMemories === true) {
|
|
@@ -9805,16 +10016,16 @@ async function repairManifest(config, dryRun) {
|
|
|
9805
10016
|
console.log(output2.trimEnd());
|
|
9806
10017
|
return;
|
|
9807
10018
|
}
|
|
9808
|
-
await ensureDirectory((0,
|
|
10019
|
+
await ensureDirectory((0, import_node_path9.dirname)(config.manifestPath), false);
|
|
9809
10020
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
9810
10021
|
if (currentContent !== void 0) {
|
|
9811
10022
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
9812
|
-
await (0,
|
|
9813
|
-
await (0,
|
|
10023
|
+
await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
10024
|
+
await (0, import_promises10.chmod)(backupPath, 384);
|
|
9814
10025
|
console.log(`Backup: ${backupPath}`);
|
|
9815
10026
|
}
|
|
9816
|
-
await (0,
|
|
9817
|
-
await (0,
|
|
10027
|
+
await (0, import_promises10.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
10028
|
+
await (0, import_promises10.chmod)(config.manifestPath, 384);
|
|
9818
10029
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
9819
10030
|
}
|
|
9820
10031
|
async function repairServerHealth(config, dryRun) {
|
|
@@ -9855,7 +10066,7 @@ async function runStart(config, options) {
|
|
|
9855
10066
|
);
|
|
9856
10067
|
}
|
|
9857
10068
|
const logPath = openVikingLogPath(config);
|
|
9858
|
-
await ensureDirectory((0,
|
|
10069
|
+
await ensureDirectory((0, import_node_path9.dirname)(logPath), false);
|
|
9859
10070
|
if (options.foreground === true) {
|
|
9860
10071
|
const result = await runInteractive(server, args);
|
|
9861
10072
|
process.exitCode = result;
|
|
@@ -9864,7 +10075,7 @@ async function runStart(config, options) {
|
|
|
9864
10075
|
const logFd = (0, import_node_fs5.openSync)(logPath, "a");
|
|
9865
10076
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
9866
10077
|
child.unref();
|
|
9867
|
-
await (0,
|
|
10078
|
+
await (0, import_promises10.writeFile)((0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
9868
10079
|
`, "utf8");
|
|
9869
10080
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
9870
10081
|
if (health) {
|
|
@@ -9897,7 +10108,7 @@ async function runStop(config, options) {
|
|
|
9897
10108
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
9898
10109
|
}
|
|
9899
10110
|
}
|
|
9900
|
-
const pidPath = (0,
|
|
10111
|
+
const pidPath = (0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid");
|
|
9901
10112
|
const pidText = await readFileIfExists(pidPath);
|
|
9902
10113
|
if (!pidText) {
|
|
9903
10114
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -9993,7 +10204,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
9993
10204
|
};
|
|
9994
10205
|
}
|
|
9995
10206
|
async function commandShimCheck() {
|
|
9996
|
-
const shimPath = (0,
|
|
10207
|
+
const shimPath = (0, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
9997
10208
|
const content = await readFileIfExists(shimPath);
|
|
9998
10209
|
if (content === void 0) {
|
|
9999
10210
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -10059,11 +10270,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
10059
10270
|
async function siblingPythonForExecutable(executablePath) {
|
|
10060
10271
|
let resolvedPath;
|
|
10061
10272
|
try {
|
|
10062
|
-
resolvedPath = await (0,
|
|
10273
|
+
resolvedPath = await (0, import_promises10.realpath)(executablePath);
|
|
10063
10274
|
} catch (_err) {
|
|
10064
10275
|
return void 0;
|
|
10065
10276
|
}
|
|
10066
|
-
const pythonPath = (0,
|
|
10277
|
+
const pythonPath = (0, import_node_path9.join)((0, import_node_path9.dirname)(resolvedPath), "python");
|
|
10067
10278
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
10068
10279
|
}
|
|
10069
10280
|
async function manifestCheck(path) {
|
|
@@ -10134,7 +10345,7 @@ async function offerToInstallUv() {
|
|
|
10134
10345
|
);
|
|
10135
10346
|
return false;
|
|
10136
10347
|
}
|
|
10137
|
-
const readline = (0,
|
|
10348
|
+
const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
10138
10349
|
let answer;
|
|
10139
10350
|
try {
|
|
10140
10351
|
answer = (await readline.question(
|
|
@@ -10257,38 +10468,38 @@ function printInstallNextSteps(options) {
|
|
|
10257
10468
|
}
|
|
10258
10469
|
async function writeTemplateIfMissing(options) {
|
|
10259
10470
|
if (await exists(options.destinationPath)) {
|
|
10260
|
-
const currentContent = await (0,
|
|
10471
|
+
const currentContent = await (0, import_promises10.readFile)(options.destinationPath, "utf8");
|
|
10261
10472
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
10262
10473
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
10263
10474
|
return;
|
|
10264
10475
|
}
|
|
10265
|
-
const rendered2 = renderTemplate(await (0,
|
|
10476
|
+
const rendered2 = renderTemplate(await (0, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
|
|
10266
10477
|
if (options.dryRun) {
|
|
10267
10478
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
10268
10479
|
return;
|
|
10269
10480
|
}
|
|
10270
10481
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
10271
|
-
await (0,
|
|
10272
|
-
await (0,
|
|
10273
|
-
await (0,
|
|
10274
|
-
await (0,
|
|
10482
|
+
await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
10483
|
+
await (0, import_promises10.chmod)(backupPath, 384);
|
|
10484
|
+
await (0, import_promises10.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
10485
|
+
await (0, import_promises10.chmod)(options.destinationPath, 384);
|
|
10275
10486
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
10276
10487
|
console.log(`Backup: ${backupPath}`);
|
|
10277
10488
|
return;
|
|
10278
10489
|
}
|
|
10279
|
-
const rendered = renderTemplate(await (0,
|
|
10490
|
+
const rendered = renderTemplate(await (0, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
|
|
10280
10491
|
if (options.dryRun) {
|
|
10281
10492
|
console.log(`Would write ${options.destinationPath}`);
|
|
10282
10493
|
return;
|
|
10283
10494
|
}
|
|
10284
|
-
await ensureDirectory((0,
|
|
10285
|
-
await (0,
|
|
10286
|
-
await (0,
|
|
10495
|
+
await ensureDirectory((0, import_node_path9.dirname)(options.destinationPath), false);
|
|
10496
|
+
await (0, import_promises10.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
10497
|
+
await (0, import_promises10.chmod)(options.destinationPath, 384);
|
|
10287
10498
|
console.log(`Wrote ${options.destinationPath}`);
|
|
10288
10499
|
}
|
|
10289
10500
|
async function installCommandShim(dryRun) {
|
|
10290
10501
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
10291
|
-
const shimPath = (0,
|
|
10502
|
+
const shimPath = (0, import_node_path9.join)(binDir, "threadnote");
|
|
10292
10503
|
const existingContent = await readFileIfExists(shimPath);
|
|
10293
10504
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
10294
10505
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -10304,12 +10515,12 @@ async function installCommandShim(dryRun) {
|
|
|
10304
10515
|
return;
|
|
10305
10516
|
}
|
|
10306
10517
|
await ensureDirectory(binDir, false);
|
|
10307
|
-
await (0,
|
|
10308
|
-
await (0,
|
|
10518
|
+
await (0, import_promises10.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
10519
|
+
await (0, import_promises10.chmod)(shimPath, 493);
|
|
10309
10520
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
10310
10521
|
}
|
|
10311
10522
|
async function removeCommandShim(dryRun) {
|
|
10312
|
-
const shimPath = (0,
|
|
10523
|
+
const shimPath = (0, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
10313
10524
|
const content = await readFileIfExists(shimPath);
|
|
10314
10525
|
if (content === void 0) {
|
|
10315
10526
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -10343,8 +10554,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
10343
10554
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
10344
10555
|
continue;
|
|
10345
10556
|
}
|
|
10346
|
-
await ensureDirectory((0,
|
|
10347
|
-
await (0,
|
|
10557
|
+
await ensureDirectory((0, import_node_path9.dirname)(targetPath), false);
|
|
10558
|
+
await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10348
10559
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
10349
10560
|
}
|
|
10350
10561
|
}
|
|
@@ -10381,7 +10592,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
10381
10592
|
console.log(`Would update ${targetPath}`);
|
|
10382
10593
|
continue;
|
|
10383
10594
|
}
|
|
10384
|
-
await (0,
|
|
10595
|
+
await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10385
10596
|
console.log(`Updated ${targetPath}`);
|
|
10386
10597
|
}
|
|
10387
10598
|
}
|
|
@@ -10401,7 +10612,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
10401
10612
|
].join("\n");
|
|
10402
10613
|
}
|
|
10403
10614
|
async function renderUserAgentInstructionsBlock() {
|
|
10404
|
-
const instructions = (await (0,
|
|
10615
|
+
const instructions = (await (0, import_promises10.readFile)((0, import_node_path9.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
10405
10616
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
10406
10617
|
${instructions}
|
|
10407
10618
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -10479,9 +10690,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10479
10690
|
if ((0, import_node_os6.platform)() !== "darwin") {
|
|
10480
10691
|
throw new Error("launchd autostart is only supported on macOS.");
|
|
10481
10692
|
}
|
|
10482
|
-
const source = (0,
|
|
10693
|
+
const source = (0, import_node_path9.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
10483
10694
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
10484
|
-
const rendered = renderTemplate(await (0,
|
|
10695
|
+
const rendered = renderTemplate(await (0, import_promises10.readFile)(source, "utf8"), config);
|
|
10485
10696
|
if (dryRun) {
|
|
10486
10697
|
console.log(`Would write ${destination}`);
|
|
10487
10698
|
console.log(`Would run: launchctl unload ${destination}`);
|
|
@@ -10489,9 +10700,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10489
10700
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
10490
10701
|
return;
|
|
10491
10702
|
}
|
|
10492
|
-
await ensureDirectory((0,
|
|
10493
|
-
await ensureDirectory((0,
|
|
10494
|
-
await (0,
|
|
10703
|
+
await ensureDirectory((0, import_node_path9.dirname)(destination), false);
|
|
10704
|
+
await ensureDirectory((0, import_node_path9.dirname)(openVikingLogPath(config)), false);
|
|
10705
|
+
await (0, import_promises10.writeFile)(destination, rendered, "utf8");
|
|
10495
10706
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
10496
10707
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
10497
10708
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -10554,7 +10765,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
10554
10765
|
if (typeof parsed.default_user !== "string") {
|
|
10555
10766
|
return false;
|
|
10556
10767
|
}
|
|
10557
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
10768
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path9.join)(config.agentContextHome, "data")) {
|
|
10558
10769
|
return false;
|
|
10559
10770
|
}
|
|
10560
10771
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -10582,9 +10793,20 @@ async function main() {
|
|
|
10582
10793
|
await runDoctor(config, options);
|
|
10583
10794
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
10584
10795
|
});
|
|
10585
|
-
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).
|
|
10796
|
+
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).option(
|
|
10797
|
+
"--with-hooks",
|
|
10798
|
+
"Also install agent-side hooks (Claude PreCompact + SessionStart) for deterministic handoff snapshots and context preload"
|
|
10799
|
+
).action(async (options) => {
|
|
10586
10800
|
const config = getRuntimeConfig(program2);
|
|
10587
10801
|
await runInstall(config, options);
|
|
10802
|
+
if (options.withHooks === true) {
|
|
10803
|
+
const dryRun = options.dryRun === true;
|
|
10804
|
+
for (const agent of ["claude", "codex", "cursor", "copilot"]) {
|
|
10805
|
+
console.log(`
|
|
10806
|
+
--- ${agent} hooks ---`);
|
|
10807
|
+
await runHooksInstall(config, agent, { apply: !dryRun, dryRun });
|
|
10808
|
+
}
|
|
10809
|
+
}
|
|
10588
10810
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
10589
10811
|
});
|
|
10590
10812
|
program2.command("update").description("Update the published Threadnote package, then repair local shims and MCP config").option("--check", "Only check whether a newer version is available").option("--dry-run", "Print update and repair commands without running them").option("--force", "Run package-manager update even if this version is already current").option("--registry <url>", "npm registry URL", process.env.THREADNOTE_NPM_REGISTRY).option("--runtime <runtime>", "auto, npm, bun, or deno", parseUpdateRuntime, "auto").option("--no-repair", "Skip threadnote repair after updating the package").option("--no-post-update", "Skip post-update migration prompts").option("--yes", "Accept applicable post-update migrations without prompting").action(async (options) => {
|
|
@@ -10629,6 +10851,21 @@ async function main() {
|
|
|
10629
10851
|
program2.command("mcp-install").description("Install OpenViking MCP config for a supported agent").argument("<agent>", "codex, claude, cursor, or copilot").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) => {
|
|
10630
10852
|
await runMcpInstall(getRuntimeConfig(program2), parseAgentClient(agent), options);
|
|
10631
10853
|
});
|
|
10854
|
+
program2.command("install-hooks").description(
|
|
10855
|
+
"Install deterministic agent hooks (Claude PreCompact + SessionStart). Soft instruction files remain the cross-agent guidance surface; hooks add a deterministic safety net where the agent supports it."
|
|
10856
|
+
).argument("<agent>", "codex, claude, cursor, or copilot").option("--apply", "Actually modify the selected agent config").option("--dry-run", "Print the planned change without applying it").option("--remove", "Remove threadnote-managed hook entries instead of adding them").action(async (agent, options) => {
|
|
10857
|
+
await runHooksInstall(getRuntimeConfig(program2), parseAgentClient(agent), options);
|
|
10858
|
+
});
|
|
10859
|
+
program2.command("pre-compact-hook", { hidden: true }).description(
|
|
10860
|
+
"Hook entry point: store a handoff snapshot before context compaction. Used by `install-hooks claude`."
|
|
10861
|
+
).option("--dry-run", "Print the handoff payload without writing it").action(async (options) => {
|
|
10862
|
+
await runPreCompactHook(getRuntimeConfig(program2), options);
|
|
10863
|
+
});
|
|
10864
|
+
program2.command("session-start-hook", { hidden: true }).description(
|
|
10865
|
+
"Hook entry point: print the latest threadnote handoff/feature memory for the current repo so Claude can preload it."
|
|
10866
|
+
).option("--dry-run", "Print the planned ov command without running it").action(async (options) => {
|
|
10867
|
+
await runSessionStartHook(getRuntimeConfig(program2), options);
|
|
10868
|
+
});
|
|
10632
10869
|
program2.command("remember").description("Store a durable engineering memory in OpenViking").option("--dry-run", "Print memory and ov command without storing").option("--kind <kind>", "durable, handoff, incident, preference, or smoke", parseMemoryKind, "durable").option("--project <name>", "Project/repo/topic namespace for lifecycle-aware storage").option("--replace <uri>", "Supersede an existing viking:// memory after the new memory is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--status <status>", "active, archived, or superseded", parseMemoryStatus, "active").option("--stdin", "Read memory text from stdin").option("--text <text>", "Memory text to store").option("--topic <name>", "Stable topic name; active memories with the same project/topic update one file").action(async (options) => {
|
|
10633
10870
|
await runRemember(getRuntimeConfig(program2), options);
|
|
10634
10871
|
});
|