threadnote 0.3.6 → 0.3.7
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 +44 -3
- package/config/post-update-migrations.json +23 -0
- package/dist/mcp_server.cjs +252 -53
- package/dist/threadnote.cjs +985 -425
- package/docs/agent-instructions.md +23 -3
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -552,13 +552,13 @@ var require_help = __commonJS({
|
|
|
552
552
|
function callFormatItem(term, description) {
|
|
553
553
|
return helper.formatItem(term, termWidth, description, helper);
|
|
554
554
|
}
|
|
555
|
-
let
|
|
555
|
+
let output2 = [
|
|
556
556
|
`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
557
557
|
""
|
|
558
558
|
];
|
|
559
559
|
const commandDescription = helper.commandDescription(cmd);
|
|
560
560
|
if (commandDescription.length > 0) {
|
|
561
|
-
|
|
561
|
+
output2 = output2.concat([
|
|
562
562
|
helper.boxWrap(
|
|
563
563
|
helper.styleCommandDescription(commandDescription),
|
|
564
564
|
helpWidth
|
|
@@ -572,7 +572,7 @@ var require_help = __commonJS({
|
|
|
572
572
|
helper.styleArgumentDescription(helper.argumentDescription(argument))
|
|
573
573
|
);
|
|
574
574
|
});
|
|
575
|
-
|
|
575
|
+
output2 = output2.concat(
|
|
576
576
|
this.formatItemList("Arguments:", argumentList, helper)
|
|
577
577
|
);
|
|
578
578
|
const optionGroups = this.groupItems(
|
|
@@ -587,7 +587,7 @@ var require_help = __commonJS({
|
|
|
587
587
|
helper.styleOptionDescription(helper.optionDescription(option))
|
|
588
588
|
);
|
|
589
589
|
});
|
|
590
|
-
|
|
590
|
+
output2 = output2.concat(this.formatItemList(group, optionList, helper));
|
|
591
591
|
});
|
|
592
592
|
if (helper.showGlobalOptions) {
|
|
593
593
|
const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
|
|
@@ -596,7 +596,7 @@ var require_help = __commonJS({
|
|
|
596
596
|
helper.styleOptionDescription(helper.optionDescription(option))
|
|
597
597
|
);
|
|
598
598
|
});
|
|
599
|
-
|
|
599
|
+
output2 = output2.concat(
|
|
600
600
|
this.formatItemList("Global Options:", globalOptionList, helper)
|
|
601
601
|
);
|
|
602
602
|
}
|
|
@@ -612,9 +612,9 @@ var require_help = __commonJS({
|
|
|
612
612
|
helper.styleSubcommandDescription(helper.subcommandDescription(sub))
|
|
613
613
|
);
|
|
614
614
|
});
|
|
615
|
-
|
|
615
|
+
output2 = output2.concat(this.formatItemList(group, commandList, helper));
|
|
616
616
|
});
|
|
617
|
-
return
|
|
617
|
+
return output2.join("\n");
|
|
618
618
|
}
|
|
619
619
|
/**
|
|
620
620
|
* Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.
|
|
@@ -3564,7 +3564,7 @@ async function walkFiles(root) {
|
|
|
3564
3564
|
return files;
|
|
3565
3565
|
}
|
|
3566
3566
|
function globToRegExp(glob) {
|
|
3567
|
-
let
|
|
3567
|
+
let output2 = "^";
|
|
3568
3568
|
for (let index = 0; index < glob.length; index += 1) {
|
|
3569
3569
|
const char = glob[index];
|
|
3570
3570
|
if (char === "*") {
|
|
@@ -3572,23 +3572,23 @@ function globToRegExp(glob) {
|
|
|
3572
3572
|
if (next === "*") {
|
|
3573
3573
|
const afterNext = glob[index + 2];
|
|
3574
3574
|
if (afterNext === "/") {
|
|
3575
|
-
|
|
3575
|
+
output2 += "(?:.*/)?";
|
|
3576
3576
|
index += 2;
|
|
3577
3577
|
} else {
|
|
3578
|
-
|
|
3578
|
+
output2 += ".*";
|
|
3579
3579
|
index += 1;
|
|
3580
3580
|
}
|
|
3581
3581
|
} else {
|
|
3582
|
-
|
|
3582
|
+
output2 += "[^/]*";
|
|
3583
3583
|
}
|
|
3584
3584
|
} else if (char === "?") {
|
|
3585
|
-
|
|
3585
|
+
output2 += "[^/]";
|
|
3586
3586
|
} else {
|
|
3587
|
-
|
|
3587
|
+
output2 += escapeRegExp(char);
|
|
3588
3588
|
}
|
|
3589
3589
|
}
|
|
3590
|
-
|
|
3591
|
-
return new RegExp(
|
|
3590
|
+
output2 += "$";
|
|
3591
|
+
return new RegExp(output2);
|
|
3592
3592
|
}
|
|
3593
3593
|
function getGlobBase(pattern) {
|
|
3594
3594
|
const parts = pattern.split("/");
|
|
@@ -3933,8 +3933,8 @@ function exactRecallTerms(query) {
|
|
|
3933
3933
|
}
|
|
3934
3934
|
return terms.sort((left, right) => exactRecallTermScore(right) - exactRecallTermScore(left)).slice(0, 4);
|
|
3935
3935
|
}
|
|
3936
|
-
function grepOutputHasMatches(
|
|
3937
|
-
return !
|
|
3936
|
+
function grepOutputHasMatches(output2) {
|
|
3937
|
+
return !output2.includes("matches []") && !output2.includes('"matches":[]') && !output2.includes("match_count 0");
|
|
3938
3938
|
}
|
|
3939
3939
|
function exactRecallTermScore(term) {
|
|
3940
3940
|
let score = term.length;
|
|
@@ -4991,14 +4991,14 @@ function resolveYamlBinary(data) {
|
|
|
4991
4991
|
return bitlen % 8 === 0;
|
|
4992
4992
|
}
|
|
4993
4993
|
function constructYamlBinary(data) {
|
|
4994
|
-
var idx, tailbits,
|
|
4994
|
+
var idx, tailbits, input2 = data.replace(/[\r\n=]/g, ""), max = input2.length, map2 = BASE64_MAP, bits = 0, result = [];
|
|
4995
4995
|
for (idx = 0; idx < max; idx++) {
|
|
4996
4996
|
if (idx % 4 === 0 && idx) {
|
|
4997
4997
|
result.push(bits >> 16 & 255);
|
|
4998
4998
|
result.push(bits >> 8 & 255);
|
|
4999
4999
|
result.push(bits & 255);
|
|
5000
5000
|
}
|
|
5001
|
-
bits = bits << 6 | map2.indexOf(
|
|
5001
|
+
bits = bits << 6 | map2.indexOf(input2.charAt(idx));
|
|
5002
5002
|
}
|
|
5003
5003
|
tailbits = max % 4 * 6;
|
|
5004
5004
|
if (tailbits === 0) {
|
|
@@ -5231,8 +5231,8 @@ for (i = 0; i < 256; i++) {
|
|
|
5231
5231
|
simpleEscapeMap[i] = simpleEscapeSequence(i);
|
|
5232
5232
|
}
|
|
5233
5233
|
var i;
|
|
5234
|
-
function State$1(
|
|
5235
|
-
this.input =
|
|
5234
|
+
function State$1(input2, options) {
|
|
5235
|
+
this.input = input2;
|
|
5236
5236
|
this.filename = options["filename"] || null;
|
|
5237
5237
|
this.schema = options["schema"] || _default;
|
|
5238
5238
|
this.onWarning = options["onWarning"] || null;
|
|
@@ -5241,7 +5241,7 @@ function State$1(input, options) {
|
|
|
5241
5241
|
this.listener = options["listener"] || null;
|
|
5242
5242
|
this.implicitTypes = this.schema.compiledImplicit;
|
|
5243
5243
|
this.typeMap = this.schema.compiledTypeMap;
|
|
5244
|
-
this.length =
|
|
5244
|
+
this.length = input2.length;
|
|
5245
5245
|
this.position = 0;
|
|
5246
5246
|
this.line = 0;
|
|
5247
5247
|
this.lineStart = 0;
|
|
@@ -6245,19 +6245,19 @@ function readDocument(state) {
|
|
|
6245
6245
|
return;
|
|
6246
6246
|
}
|
|
6247
6247
|
}
|
|
6248
|
-
function loadDocuments(
|
|
6249
|
-
|
|
6248
|
+
function loadDocuments(input2, options) {
|
|
6249
|
+
input2 = String(input2);
|
|
6250
6250
|
options = options || {};
|
|
6251
|
-
if (
|
|
6252
|
-
if (
|
|
6253
|
-
|
|
6251
|
+
if (input2.length !== 0) {
|
|
6252
|
+
if (input2.charCodeAt(input2.length - 1) !== 10 && input2.charCodeAt(input2.length - 1) !== 13) {
|
|
6253
|
+
input2 += "\n";
|
|
6254
6254
|
}
|
|
6255
|
-
if (
|
|
6256
|
-
|
|
6255
|
+
if (input2.charCodeAt(0) === 65279) {
|
|
6256
|
+
input2 = input2.slice(1);
|
|
6257
6257
|
}
|
|
6258
6258
|
}
|
|
6259
|
-
var state = new State$1(
|
|
6260
|
-
var nullpos =
|
|
6259
|
+
var state = new State$1(input2, options);
|
|
6260
|
+
var nullpos = input2.indexOf("\0");
|
|
6261
6261
|
if (nullpos !== -1) {
|
|
6262
6262
|
state.position = nullpos;
|
|
6263
6263
|
throwError(state, "null byte is not allowed in input");
|
|
@@ -6272,12 +6272,12 @@ function loadDocuments(input, options) {
|
|
|
6272
6272
|
}
|
|
6273
6273
|
return state.documents;
|
|
6274
6274
|
}
|
|
6275
|
-
function loadAll$1(
|
|
6275
|
+
function loadAll$1(input2, iterator, options) {
|
|
6276
6276
|
if (iterator !== null && typeof iterator === "object" && typeof options === "undefined") {
|
|
6277
6277
|
options = iterator;
|
|
6278
6278
|
iterator = null;
|
|
6279
6279
|
}
|
|
6280
|
-
var documents = loadDocuments(
|
|
6280
|
+
var documents = loadDocuments(input2, options);
|
|
6281
6281
|
if (typeof iterator !== "function") {
|
|
6282
6282
|
return documents;
|
|
6283
6283
|
}
|
|
@@ -6285,8 +6285,8 @@ function loadAll$1(input, iterator, options) {
|
|
|
6285
6285
|
iterator(documents[index]);
|
|
6286
6286
|
}
|
|
6287
6287
|
}
|
|
6288
|
-
function load$1(
|
|
6289
|
-
var documents = loadDocuments(
|
|
6288
|
+
function load$1(input2, options) {
|
|
6289
|
+
var documents = loadDocuments(input2, options);
|
|
6290
6290
|
if (documents.length === 0) {
|
|
6291
6291
|
return void 0;
|
|
6292
6292
|
} else if (documents.length === 1) {
|
|
@@ -6907,11 +6907,11 @@ function inspectNode(object, objects, duplicatesIndexes) {
|
|
|
6907
6907
|
}
|
|
6908
6908
|
}
|
|
6909
6909
|
}
|
|
6910
|
-
function dump$1(
|
|
6910
|
+
function dump$1(input2, options) {
|
|
6911
6911
|
options = options || {};
|
|
6912
6912
|
var state = new State(options);
|
|
6913
|
-
if (!state.noRefs) getDuplicateReferences(
|
|
6914
|
-
var value =
|
|
6913
|
+
if (!state.noRefs) getDuplicateReferences(input2, state);
|
|
6914
|
+
var value = input2;
|
|
6915
6915
|
if (state.replacer) {
|
|
6916
6916
|
value = state.replacer.call({ "": value }, "", value);
|
|
6917
6917
|
}
|
|
@@ -7033,21 +7033,34 @@ function readStringArray(object, key) {
|
|
|
7033
7033
|
}
|
|
7034
7034
|
|
|
7035
7035
|
// src/memory.ts
|
|
7036
|
+
function parseMemoryKind(value) {
|
|
7037
|
+
if (["durable", "handoff", "incident", "preference", "smoke"].includes(value)) {
|
|
7038
|
+
return value;
|
|
7039
|
+
}
|
|
7040
|
+
throw new Error(`Unsupported memory kind "${value}". Expected durable, handoff, incident, preference, or smoke.`);
|
|
7041
|
+
}
|
|
7042
|
+
function parseMemoryStatus(value) {
|
|
7043
|
+
if (["active", "archived", "superseded"].includes(value)) {
|
|
7044
|
+
return value;
|
|
7045
|
+
}
|
|
7046
|
+
throw new Error(`Unsupported memory status "${value}". Expected active, archived, or superseded.`);
|
|
7047
|
+
}
|
|
7036
7048
|
async function runRemember(config, options) {
|
|
7037
7049
|
const text = await getInputText(options.text, options.stdin === true);
|
|
7038
7050
|
if (!text.trim()) {
|
|
7039
7051
|
throw new Error("Provide memory text with --text or --stdin.");
|
|
7040
7052
|
}
|
|
7041
|
-
const
|
|
7042
|
-
"
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7053
|
+
const metadata = {
|
|
7054
|
+
kind: options.kind ?? "durable",
|
|
7055
|
+
project: normalizeOptionalMetadata(options.project),
|
|
7056
|
+
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
7057
|
+
status: options.status ?? "active",
|
|
7058
|
+
supersedes: options.replace,
|
|
7059
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7060
|
+
topic: normalizeOptionalMetadata(options.topic)
|
|
7061
|
+
};
|
|
7062
|
+
const memory = formatMemoryDocument("MEMORY", metadata, text.trim());
|
|
7063
|
+
await storeMemory(config, memory, { dryRun: options.dryRun === true, metadata, replaceUri: options.replace });
|
|
7051
7064
|
}
|
|
7052
7065
|
async function runMigrateMemories(config, options) {
|
|
7053
7066
|
const dryRun = options.dryRun === true;
|
|
@@ -7098,7 +7111,7 @@ async function runMigrateMemories(config, options) {
|
|
|
7098
7111
|
if (!dryRun) {
|
|
7099
7112
|
await (0, import_promises4.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
|
|
7100
7113
|
await (0, import_promises4.chmod)(migrationPath, 384);
|
|
7101
|
-
await writeDurableMemoryFile(ov, config, memoryUri, migrationPath);
|
|
7114
|
+
await writeDurableMemoryFile(ov, config, memoryUri, migrationPath, "create");
|
|
7102
7115
|
existingHashes.add(candidate.hash);
|
|
7103
7116
|
}
|
|
7104
7117
|
migratedCount += 1;
|
|
@@ -7118,6 +7131,62 @@ async function runMigrateMemories(config, options) {
|
|
|
7118
7131
|
].join("; ")
|
|
7119
7132
|
);
|
|
7120
7133
|
}
|
|
7134
|
+
async function runMigrateLifecycle(config, options) {
|
|
7135
|
+
const dryRun = options.dryRun === true || options.apply !== true;
|
|
7136
|
+
const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
|
|
7137
|
+
const ov = await openVikingCliForMode(dryRun);
|
|
7138
|
+
const candidates = await legacyLifecycleHandoffCandidates(config);
|
|
7139
|
+
const migrationPath = (0, import_node_path4.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
|
|
7140
|
+
let existingCount = 0;
|
|
7141
|
+
let migratedCount = 0;
|
|
7142
|
+
let skippedCount = 0;
|
|
7143
|
+
try {
|
|
7144
|
+
for (const candidate of candidates) {
|
|
7145
|
+
if (limit !== void 0 && migratedCount >= limit) {
|
|
7146
|
+
break;
|
|
7147
|
+
}
|
|
7148
|
+
const destinationUri = lifecycleMigrationUri(config, candidate.metadata, sha256(candidate.original.trim()));
|
|
7149
|
+
const migratedMemory = formatMemoryDocument(
|
|
7150
|
+
"HANDOFF",
|
|
7151
|
+
candidate.metadata,
|
|
7152
|
+
["Migrated legacy handoff from the historical events trail.", "", candidate.original.trim()].join("\n")
|
|
7153
|
+
);
|
|
7154
|
+
console.log(`${dryRun ? "Would migrate" : "Migrating"} ${candidate.sourceUri} -> ${destinationUri}`);
|
|
7155
|
+
if (!dryRun) {
|
|
7156
|
+
if (await vikingResourceExists(ov, config, destinationUri)) {
|
|
7157
|
+
existingCount += 1;
|
|
7158
|
+
console.log(`Archived copy already exists; cleaning up legacy source: ${candidate.sourceUri}`);
|
|
7159
|
+
} else {
|
|
7160
|
+
await (0, import_promises4.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
|
|
7161
|
+
await (0, import_promises4.chmod)(migrationPath, 384);
|
|
7162
|
+
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, candidate.metadata));
|
|
7163
|
+
await writeDurableMemoryFile(ov, config, destinationUri, migrationPath, "create");
|
|
7164
|
+
}
|
|
7165
|
+
const removedOriginal = await removeVikingResourceWithRetry(ov, config, candidate.sourceUri);
|
|
7166
|
+
if (!removedOriginal) {
|
|
7167
|
+
console.error(
|
|
7168
|
+
`Migrated copy stored, but original is still processing. Retry later: threadnote forget ${candidate.sourceUri}`
|
|
7169
|
+
);
|
|
7170
|
+
skippedCount += 1;
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
migratedCount += 1;
|
|
7174
|
+
}
|
|
7175
|
+
} finally {
|
|
7176
|
+
if (!dryRun) {
|
|
7177
|
+
await (0, import_promises4.rm)(migrationPath, { force: true });
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
console.log(
|
|
7181
|
+
[
|
|
7182
|
+
`Lifecycle migration summary: ${migratedCount} clear legacy handoff(s) ${dryRun ? "would be migrated" : "migrated"}`,
|
|
7183
|
+
`${existingCount} existing archived copy/copies reused`,
|
|
7184
|
+
`${skippedCount} legacy source(s) still processing`,
|
|
7185
|
+
`${candidates.length} clear legacy handoff candidate(s) found`,
|
|
7186
|
+
dryRun ? "Run with --apply to perform this migration." : void 0
|
|
7187
|
+
].filter((part) => part !== void 0).join("; ")
|
|
7188
|
+
);
|
|
7189
|
+
}
|
|
7121
7190
|
async function runRecall(config, options) {
|
|
7122
7191
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
7123
7192
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, options.query));
|
|
@@ -7130,7 +7199,10 @@ async function runRecall(config, options) {
|
|
|
7130
7199
|
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
7131
7200
|
}
|
|
7132
7201
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
7133
|
-
await printExactMemoryMatches(config, ov, options.query,
|
|
7202
|
+
await printExactMemoryMatches(config, ov, options.query, {
|
|
7203
|
+
dryRun: options.dryRun === true,
|
|
7204
|
+
includeArchived: options.includeArchived === true
|
|
7205
|
+
});
|
|
7134
7206
|
}
|
|
7135
7207
|
async function runRead(config, uri, options) {
|
|
7136
7208
|
assertVikingUri(uri);
|
|
@@ -7161,13 +7233,70 @@ async function runList(config, uri, options) {
|
|
|
7161
7233
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
7162
7234
|
}
|
|
7163
7235
|
async function runHandoff(config, options) {
|
|
7164
|
-
const handoff = await buildHandoff(options);
|
|
7165
|
-
await storeMemory(config, handoff, { dryRun: options.dryRun === true, replaceUri: options.replace });
|
|
7236
|
+
const { handoff, metadata } = await buildHandoff(options);
|
|
7237
|
+
await storeMemory(config, handoff, { dryRun: options.dryRun === true, metadata, replaceUri: options.replace });
|
|
7238
|
+
}
|
|
7239
|
+
async function runArchive(config, uri, options) {
|
|
7240
|
+
assertVikingUri(uri);
|
|
7241
|
+
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
7242
|
+
const readResult = await maybeRun(options.dryRun === true, ov, withIdentity(config, ["read", uri]));
|
|
7243
|
+
const original = readResult?.stdout.trim();
|
|
7244
|
+
if (options.dryRun === true) {
|
|
7245
|
+
const fallbackMetadata = {
|
|
7246
|
+
archivedFrom: uri,
|
|
7247
|
+
kind: options.kind ?? "handoff",
|
|
7248
|
+
project: normalizeOptionalMetadata(options.project),
|
|
7249
|
+
sourceAgentClient: "threadnote",
|
|
7250
|
+
status: "archived",
|
|
7251
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7252
|
+
topic: normalizeOptionalMetadata(options.topic)
|
|
7253
|
+
};
|
|
7254
|
+
const archiveMemory2 = formatMemoryDocument(
|
|
7255
|
+
"MEMORY",
|
|
7256
|
+
fallbackMetadata,
|
|
7257
|
+
["Archived original Threadnote memory.", "", "<original memory content would be read here>"].join("\n")
|
|
7258
|
+
);
|
|
7259
|
+
await storeMemory(config, archiveMemory2, { dryRun: true, metadata: fallbackMetadata });
|
|
7260
|
+
console.log(formatShellCommand(ov, withIdentity(config, ["rm", uri])));
|
|
7261
|
+
return;
|
|
7262
|
+
}
|
|
7263
|
+
if (!original) {
|
|
7264
|
+
throw new Error(`Could not read ${uri} before archiving.`);
|
|
7265
|
+
}
|
|
7266
|
+
const inferredMetadata = inferMemoryMetadata(original);
|
|
7267
|
+
const metadata = {
|
|
7268
|
+
archivedFrom: uri,
|
|
7269
|
+
kind: options.kind ?? inferredMetadata.kind ?? "handoff",
|
|
7270
|
+
project: normalizeOptionalMetadata(options.project) ?? inferredMetadata.project,
|
|
7271
|
+
sourceAgentClient: "threadnote",
|
|
7272
|
+
status: "archived",
|
|
7273
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7274
|
+
topic: normalizeOptionalMetadata(options.topic) ?? inferredMetadata.topic
|
|
7275
|
+
};
|
|
7276
|
+
const archiveMemory = formatMemoryDocument(
|
|
7277
|
+
"MEMORY",
|
|
7278
|
+
metadata,
|
|
7279
|
+
["Archived original Threadnote memory.", "", original].join("\n")
|
|
7280
|
+
);
|
|
7281
|
+
await storeMemory(config, archiveMemory, { dryRun: false, metadata });
|
|
7282
|
+
const removedOriginal = await removeVikingResourceWithRetry(ov, config, uri);
|
|
7283
|
+
if (removedOriginal) {
|
|
7284
|
+
console.log(`Archived original memory: ${uri}`);
|
|
7285
|
+
} else {
|
|
7286
|
+
console.error(`Archive stored, but original memory is still processing. Retry later: threadnote forget ${uri}`);
|
|
7287
|
+
}
|
|
7166
7288
|
}
|
|
7167
7289
|
async function runForget(config, uri, options) {
|
|
7168
7290
|
assertVikingUri(uri);
|
|
7169
7291
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
7170
|
-
|
|
7292
|
+
if (options.dryRun === true) {
|
|
7293
|
+
await maybeRun(true, ov, withIdentity(config, ["rm", uri]));
|
|
7294
|
+
return;
|
|
7295
|
+
}
|
|
7296
|
+
const removed = await removeVikingResourceWithRetry(ov, config, uri);
|
|
7297
|
+
if (!removed) {
|
|
7298
|
+
throw new Error(`Resource is still being processed; retry later: threadnote forget ${uri}`);
|
|
7299
|
+
}
|
|
7171
7300
|
}
|
|
7172
7301
|
async function runExportPack(config, options) {
|
|
7173
7302
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
@@ -7203,27 +7332,24 @@ async function inferProjectFromQuery(config, normalizedQuery) {
|
|
|
7203
7332
|
return void 0;
|
|
7204
7333
|
}
|
|
7205
7334
|
}
|
|
7206
|
-
async function printExactMemoryMatches(config, ov, query,
|
|
7335
|
+
async function printExactMemoryMatches(config, ov, query, options) {
|
|
7207
7336
|
const terms = exactRecallTerms(query);
|
|
7208
7337
|
if (terms.length === 0) {
|
|
7209
7338
|
return;
|
|
7210
7339
|
}
|
|
7211
|
-
const scopes =
|
|
7212
|
-
`viking://user/${uriSegment(config.user)}/memories`,
|
|
7213
|
-
`viking://agent/${uriSegment(config.agentId)}/memories`
|
|
7214
|
-
];
|
|
7340
|
+
const scopes = exactMemoryScopes(config, options.includeArchived);
|
|
7215
7341
|
const outputs = [];
|
|
7216
7342
|
for (const term of terms) {
|
|
7217
7343
|
for (const scope of scopes) {
|
|
7218
7344
|
const args = withIdentity(config, ["grep", term, "--uri", scope, "--node-limit", "5"]);
|
|
7219
|
-
if (dryRun) {
|
|
7345
|
+
if (options.dryRun) {
|
|
7220
7346
|
outputs.push(formatShellCommand(ov, args));
|
|
7221
7347
|
continue;
|
|
7222
7348
|
}
|
|
7223
7349
|
const result = await runCommand(ov, args, { allowFailure: true });
|
|
7224
|
-
const
|
|
7225
|
-
if (result.exitCode === 0 && grepOutputHasMatches(
|
|
7226
|
-
outputs.push(
|
|
7350
|
+
const output2 = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
7351
|
+
if (result.exitCode === 0 && grepOutputHasMatches(output2)) {
|
|
7352
|
+
outputs.push(output2);
|
|
7227
7353
|
}
|
|
7228
7354
|
}
|
|
7229
7355
|
}
|
|
@@ -7239,7 +7365,8 @@ async function storeMemory(config, memory, options) {
|
|
|
7239
7365
|
}
|
|
7240
7366
|
const ov = await openVikingCliForMode(options.dryRun);
|
|
7241
7367
|
const memoryPath = (0, import_node_path4.join)(config.agentContextHome, "last-memory.txt");
|
|
7242
|
-
const memoryUri =
|
|
7368
|
+
const memoryUri = memoryUriFor(config, memory, options.metadata);
|
|
7369
|
+
const writeMode = await memoryWriteMode(ov, config, memoryUri, options.metadata);
|
|
7243
7370
|
if (options.dryRun) {
|
|
7244
7371
|
console.log(memory);
|
|
7245
7372
|
console.log("\nWould run:");
|
|
@@ -7252,36 +7379,44 @@ async function storeMemory(config, memory, options) {
|
|
|
7252
7379
|
"--from-file",
|
|
7253
7380
|
memoryPath,
|
|
7254
7381
|
"--mode",
|
|
7255
|
-
|
|
7382
|
+
writeMode,
|
|
7256
7383
|
"--wait",
|
|
7257
7384
|
"--timeout",
|
|
7258
7385
|
"120"
|
|
7259
7386
|
])
|
|
7260
7387
|
)
|
|
7261
7388
|
);
|
|
7262
|
-
if (options.replaceUri) {
|
|
7389
|
+
if (options.replaceUri && options.replaceUri !== memoryUri) {
|
|
7263
7390
|
console.log(formatShellCommand(ov, withIdentity(config, ["rm", options.replaceUri])));
|
|
7264
7391
|
}
|
|
7265
7392
|
return;
|
|
7266
7393
|
}
|
|
7267
7394
|
await (0, import_promises4.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
7268
7395
|
await (0, import_promises4.chmod)(memoryPath, 384);
|
|
7269
|
-
await
|
|
7270
|
-
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath);
|
|
7271
|
-
console.log(`Stored
|
|
7272
|
-
if (options.replaceUri) {
|
|
7273
|
-
await
|
|
7274
|
-
|
|
7396
|
+
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, options.metadata));
|
|
7397
|
+
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
|
|
7398
|
+
console.log(`Stored memory: ${memoryUri}`);
|
|
7399
|
+
if (options.replaceUri && options.replaceUri !== memoryUri) {
|
|
7400
|
+
const removedReplacedMemory = await removeVikingResourceWithRetry(ov, config, options.replaceUri);
|
|
7401
|
+
if (removedReplacedMemory) {
|
|
7402
|
+
console.log(`Forgot replaced memory: ${options.replaceUri}`);
|
|
7403
|
+
} else {
|
|
7404
|
+
console.error(
|
|
7405
|
+
`Replacement stored, but the superseded memory is still processing. Retry later: threadnote forget ${options.replaceUri}`
|
|
7406
|
+
);
|
|
7407
|
+
}
|
|
7408
|
+
} else if (options.replaceUri === memoryUri) {
|
|
7409
|
+
console.log(`Updated existing memory in place: ${memoryUri}`);
|
|
7275
7410
|
}
|
|
7276
7411
|
}
|
|
7277
|
-
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath) {
|
|
7412
|
+
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
|
|
7278
7413
|
const args = withIdentity(config, [
|
|
7279
7414
|
"write",
|
|
7280
7415
|
memoryUri,
|
|
7281
7416
|
"--from-file",
|
|
7282
7417
|
memoryPath,
|
|
7283
7418
|
"--mode",
|
|
7284
|
-
|
|
7419
|
+
writeMode,
|
|
7285
7420
|
"--wait",
|
|
7286
7421
|
"--timeout",
|
|
7287
7422
|
"120"
|
|
@@ -7318,29 +7453,49 @@ async function waitForOpenVikingQueue(ov, config) {
|
|
|
7318
7453
|
console.error(result.stderr.trim());
|
|
7319
7454
|
}
|
|
7320
7455
|
}
|
|
7456
|
+
async function removeVikingResourceWithRetry(ov, config, uri) {
|
|
7457
|
+
const args = withIdentity(config, ["rm", uri]);
|
|
7458
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
7459
|
+
console.log(`${attempt === 0 ? "Running" : "Retrying"}: ${formatShellCommand(ov, args)}`);
|
|
7460
|
+
const result = await runCommand(ov, args, { allowFailure: true });
|
|
7461
|
+
if (result.exitCode === 0) {
|
|
7462
|
+
if (result.stdout.trim()) {
|
|
7463
|
+
console.log(result.stdout.trim());
|
|
7464
|
+
}
|
|
7465
|
+
if (result.stderr.trim()) {
|
|
7466
|
+
console.error(result.stderr.trim());
|
|
7467
|
+
}
|
|
7468
|
+
return true;
|
|
7469
|
+
}
|
|
7470
|
+
if (isResourceBusy(result.stderr, result.stdout) && attempt === 3) {
|
|
7471
|
+
return false;
|
|
7472
|
+
}
|
|
7473
|
+
if (!isResourceBusy(result.stderr, result.stdout)) {
|
|
7474
|
+
throw new Error(`${formatShellCommand(ov, args)} failed: ${result.stderr || result.stdout}`);
|
|
7475
|
+
}
|
|
7476
|
+
await sleep(1e3 * (attempt + 1));
|
|
7477
|
+
}
|
|
7478
|
+
return false;
|
|
7479
|
+
}
|
|
7321
7480
|
async function vikingResourceExists(ov, config, uri) {
|
|
7322
7481
|
const stat3 = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
|
|
7323
7482
|
return stat3.exitCode === 0;
|
|
7324
7483
|
}
|
|
7325
7484
|
async function ensureDurableMemoryDirectory(ov, config) {
|
|
7326
|
-
|
|
7327
|
-
const stat3 = await runCommand(ov, withIdentity(config, ["stat", directoryUri]), { allowFailure: true });
|
|
7328
|
-
if (stat3.exitCode === 0) {
|
|
7329
|
-
return;
|
|
7330
|
-
}
|
|
7331
|
-
await maybeRun(
|
|
7332
|
-
false,
|
|
7333
|
-
ov,
|
|
7334
|
-
withIdentity(config, [
|
|
7335
|
-
"mkdir",
|
|
7336
|
-
directoryUri,
|
|
7337
|
-
"--description",
|
|
7338
|
-
"Threadnote durable handoffs, memories, and cross-agent notes."
|
|
7339
|
-
])
|
|
7340
|
-
);
|
|
7485
|
+
await ensureMemoryDirectory(ov, config, durableMemoryDirectoryUri(config));
|
|
7341
7486
|
}
|
|
7342
|
-
function
|
|
7343
|
-
|
|
7487
|
+
async function ensureMemoryDirectory(ov, config, directoryUri) {
|
|
7488
|
+
for (const uri of vikingDirectoryChain(directoryUri)) {
|
|
7489
|
+
const statResult = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
|
|
7490
|
+
if (statResult.exitCode === 0) {
|
|
7491
|
+
continue;
|
|
7492
|
+
}
|
|
7493
|
+
await maybeRun(
|
|
7494
|
+
false,
|
|
7495
|
+
ov,
|
|
7496
|
+
withIdentity(config, ["mkdir", uri, "--description", "Threadnote lifecycle-aware local memories."])
|
|
7497
|
+
);
|
|
7498
|
+
}
|
|
7344
7499
|
}
|
|
7345
7500
|
function durableMemoryDirectoryUri(config) {
|
|
7346
7501
|
return `viking://user/${uriSegment(config.user)}/memories/events`;
|
|
@@ -7348,6 +7503,185 @@ function durableMemoryDirectoryUri(config) {
|
|
|
7348
7503
|
function migratedDurableMemoryUri(config, hash) {
|
|
7349
7504
|
return `${durableMemoryDirectoryUri(config)}/threadnote-migrated-${hash.slice(0, 16)}.md`;
|
|
7350
7505
|
}
|
|
7506
|
+
async function hasLegacyLifecycleHandoffCandidates(config) {
|
|
7507
|
+
return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
|
|
7508
|
+
}
|
|
7509
|
+
async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
7510
|
+
const eventsRoot = (0, import_node_path4.join)(localUserMemoriesRoot(config), "events");
|
|
7511
|
+
let entries;
|
|
7512
|
+
try {
|
|
7513
|
+
entries = await (0, import_promises4.readdir)(eventsRoot, { withFileTypes: true });
|
|
7514
|
+
} catch (_err) {
|
|
7515
|
+
return [];
|
|
7516
|
+
}
|
|
7517
|
+
const candidates = [];
|
|
7518
|
+
for (const entry of entries) {
|
|
7519
|
+
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
7520
|
+
continue;
|
|
7521
|
+
}
|
|
7522
|
+
const sourcePath = (0, import_node_path4.join)(eventsRoot, entry.name);
|
|
7523
|
+
const original = await readTextIfExists(sourcePath);
|
|
7524
|
+
if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
|
|
7525
|
+
continue;
|
|
7526
|
+
}
|
|
7527
|
+
const sourceUri = `${durableMemoryDirectoryUri(config)}/${entry.name}`;
|
|
7528
|
+
candidates.push({
|
|
7529
|
+
metadata: {
|
|
7530
|
+
archivedFrom: sourceUri,
|
|
7531
|
+
kind: "handoff",
|
|
7532
|
+
project: inferLegacyProject(original),
|
|
7533
|
+
sourceAgentClient: "threadnote",
|
|
7534
|
+
status: "archived",
|
|
7535
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7536
|
+
},
|
|
7537
|
+
original,
|
|
7538
|
+
sourceUri
|
|
7539
|
+
});
|
|
7540
|
+
if (limit !== void 0 && candidates.length >= limit) {
|
|
7541
|
+
break;
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
return candidates;
|
|
7545
|
+
}
|
|
7546
|
+
function lifecycleMigrationUri(config, metadata, hash) {
|
|
7547
|
+
return `${memoryDirectoryUri(config, metadata)}/legacy-${hash.slice(0, 16)}.md`;
|
|
7548
|
+
}
|
|
7549
|
+
function exactMemoryScopes(config, includeArchived) {
|
|
7550
|
+
const userBase = `viking://user/${uriSegment(config.user)}/memories`;
|
|
7551
|
+
const scopes = [
|
|
7552
|
+
`${userBase}/preferences`,
|
|
7553
|
+
`${userBase}/durable/projects`,
|
|
7554
|
+
`${userBase}/handoffs/active`,
|
|
7555
|
+
`${userBase}/incidents/active`,
|
|
7556
|
+
`${userBase}/events`,
|
|
7557
|
+
`viking://agent/${uriSegment(config.agentId)}/memories`
|
|
7558
|
+
];
|
|
7559
|
+
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
7560
|
+
}
|
|
7561
|
+
function memoryUriFor(config, memory, metadata) {
|
|
7562
|
+
const filename = shouldUseStableMemoryUri(metadata) ? `${uriSegment(metadata.topic ?? "current")}.md` : `threadnote-${safeTimestamp()}-${sha256(memory).slice(0, 12)}.md`;
|
|
7563
|
+
return `${memoryDirectoryUri(config, metadata)}/${filename}`;
|
|
7564
|
+
}
|
|
7565
|
+
function memoryDirectoryUri(config, metadata) {
|
|
7566
|
+
const baseUri = `viking://user/${uriSegment(config.user)}/memories`;
|
|
7567
|
+
const projectSegment = uriSegment(metadata.project ?? "general");
|
|
7568
|
+
switch (metadata.kind) {
|
|
7569
|
+
case "preference":
|
|
7570
|
+
return metadata.status === "active" ? `${baseUri}/preferences` : `${baseUri}/preferences/${uriSegment(metadata.status)}`;
|
|
7571
|
+
case "handoff":
|
|
7572
|
+
return `${baseUri}/handoffs/${uriSegment(metadata.status)}/${projectSegment}`;
|
|
7573
|
+
case "incident":
|
|
7574
|
+
return `${baseUri}/incidents/${uriSegment(metadata.status)}/${projectSegment}`;
|
|
7575
|
+
case "smoke":
|
|
7576
|
+
return `${baseUri}/smoke/${uriSegment(metadata.status)}`;
|
|
7577
|
+
case "durable":
|
|
7578
|
+
return metadata.status === "active" ? `${baseUri}/durable/projects/${projectSegment}` : `${baseUri}/durable/${uriSegment(metadata.status)}/${projectSegment}`;
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
function shouldUseStableMemoryUri(metadata) {
|
|
7582
|
+
return metadata.status === "active" && metadata.topic !== void 0 && metadata.kind !== "smoke";
|
|
7583
|
+
}
|
|
7584
|
+
async function memoryWriteMode(ov, config, memoryUri, metadata) {
|
|
7585
|
+
if (!shouldUseStableMemoryUri(metadata)) {
|
|
7586
|
+
return "create";
|
|
7587
|
+
}
|
|
7588
|
+
return await vikingResourceExists(ov, config, memoryUri) ? "replace" : "create";
|
|
7589
|
+
}
|
|
7590
|
+
function vikingDirectoryChain(directoryUri) {
|
|
7591
|
+
const prefix = "viking://";
|
|
7592
|
+
if (!directoryUri.startsWith(prefix)) {
|
|
7593
|
+
return [directoryUri];
|
|
7594
|
+
}
|
|
7595
|
+
const parts = directoryUri.slice(prefix.length).split("/").filter(Boolean);
|
|
7596
|
+
const startIndex = parts[0] === "user" && parts.length > 2 ? 3 : 1;
|
|
7597
|
+
const chain = [];
|
|
7598
|
+
for (let index = startIndex; index <= parts.length; index += 1) {
|
|
7599
|
+
chain.push(`${prefix}${parts.slice(0, index).join("/")}`);
|
|
7600
|
+
}
|
|
7601
|
+
return chain;
|
|
7602
|
+
}
|
|
7603
|
+
function formatMemoryDocument(title, metadata, body) {
|
|
7604
|
+
const header = [
|
|
7605
|
+
title,
|
|
7606
|
+
`kind: ${metadata.kind}`,
|
|
7607
|
+
`status: ${metadata.status}`,
|
|
7608
|
+
metadata.project ? `project: ${metadata.project}` : void 0,
|
|
7609
|
+
metadata.topic ? `topic: ${metadata.topic}` : void 0,
|
|
7610
|
+
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
7611
|
+
`timestamp: ${metadata.timestamp}`,
|
|
7612
|
+
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
7613
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
7614
|
+
].filter((line) => line !== void 0);
|
|
7615
|
+
return [...header, "", body.trim()].join("\n");
|
|
7616
|
+
}
|
|
7617
|
+
function inferMemoryMetadata(memory) {
|
|
7618
|
+
const header = memory.slice(0, Math.max(0, memory.indexOf("\n\n")) || memory.length);
|
|
7619
|
+
const firstLine2 = header.split("\n")[0]?.trim();
|
|
7620
|
+
const kind = parseOptionalMemoryKind(parseHeaderValue(header, "kind")) ?? (firstLine2 === "HANDOFF" ? "handoff" : void 0);
|
|
7621
|
+
const status = parseOptionalMemoryStatus(parseHeaderValue(header, "status"));
|
|
7622
|
+
const project = normalizeOptionalMetadata(parseHeaderValue(header, "project")) ?? normalizeOptionalMetadata(parseHeaderValue(header, "repo"));
|
|
7623
|
+
const topic = normalizeOptionalMetadata(parseHeaderValue(header, "topic")) ?? normalizeOptionalMetadata(parseHeaderValue(header, "task"));
|
|
7624
|
+
return {
|
|
7625
|
+
kind,
|
|
7626
|
+
project,
|
|
7627
|
+
sourceAgentClient: parseHeaderValue(header, "source_agent_client") ?? void 0,
|
|
7628
|
+
status,
|
|
7629
|
+
topic
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
function parseHeaderValue(header, key) {
|
|
7633
|
+
const prefix = `${key}:`;
|
|
7634
|
+
return header.split("\n").find((line) => line.startsWith(prefix))?.slice(prefix.length).trim();
|
|
7635
|
+
}
|
|
7636
|
+
function isClearLegacyHandoffMemory(memory) {
|
|
7637
|
+
if (/^kind:\s*/m.test(memory) || /^status:\s*/m.test(memory)) {
|
|
7638
|
+
return false;
|
|
7639
|
+
}
|
|
7640
|
+
const trimmed = memory.trim();
|
|
7641
|
+
if (trimmed.startsWith("HANDOFF\n")) {
|
|
7642
|
+
return true;
|
|
7643
|
+
}
|
|
7644
|
+
if (!trimmed.startsWith("MEMORY\n")) {
|
|
7645
|
+
return false;
|
|
7646
|
+
}
|
|
7647
|
+
return /^(?:#+\s*)?(?:final\s+)?handoff(?:\s+update)?\b/i.test(memoryBody(trimmed));
|
|
7648
|
+
}
|
|
7649
|
+
function memoryBody(memory) {
|
|
7650
|
+
const separatorIndex = memory.indexOf("\n\n");
|
|
7651
|
+
return separatorIndex === -1 ? "" : memory.slice(separatorIndex + 2).trim();
|
|
7652
|
+
}
|
|
7653
|
+
function inferLegacyProject(memory) {
|
|
7654
|
+
const explicit = parseHeaderValue(memory, "project") ?? parseHeaderValue(memory, "repo") ?? parseHeaderValue(memory, "repo_path") ?? /\brepo(?:_path)?\s+([~/A-Za-z0-9_.:/-]+)/.exec(memory)?.[1];
|
|
7655
|
+
if (!explicit) {
|
|
7656
|
+
return "general";
|
|
7657
|
+
}
|
|
7658
|
+
const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
|
|
7659
|
+
return trimmed.includes("/") ? (0, import_node_path4.basename)(trimmed) : trimmed;
|
|
7660
|
+
}
|
|
7661
|
+
function parseOptionalMemoryKind(value) {
|
|
7662
|
+
if (!value) {
|
|
7663
|
+
return void 0;
|
|
7664
|
+
}
|
|
7665
|
+
try {
|
|
7666
|
+
return parseMemoryKind(value);
|
|
7667
|
+
} catch (_err) {
|
|
7668
|
+
return void 0;
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
7671
|
+
function parseOptionalMemoryStatus(value) {
|
|
7672
|
+
if (!value) {
|
|
7673
|
+
return void 0;
|
|
7674
|
+
}
|
|
7675
|
+
try {
|
|
7676
|
+
return parseMemoryStatus(value);
|
|
7677
|
+
} catch (_err) {
|
|
7678
|
+
return void 0;
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
function normalizeOptionalMetadata(value) {
|
|
7682
|
+
const trimmed = value?.trim();
|
|
7683
|
+
return trimmed ? trimmed : void 0;
|
|
7684
|
+
}
|
|
7351
7685
|
async function legacySourceAccounts(config, options) {
|
|
7352
7686
|
const explicitAccounts = options.sourceAccount?.filter((account) => account.trim().length > 0) ?? [];
|
|
7353
7687
|
if (explicitAccounts.length > 0) {
|
|
@@ -7500,12 +7834,16 @@ function legacySourceLabel(candidate) {
|
|
|
7500
7834
|
function localVikingDataRoot(config) {
|
|
7501
7835
|
return (0, import_node_path4.join)(config.agentContextHome, "data", "viking");
|
|
7502
7836
|
}
|
|
7837
|
+
function localUserMemoriesRoot(config) {
|
|
7838
|
+
return (0, import_node_path4.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
|
|
7839
|
+
}
|
|
7503
7840
|
function uniqueStrings(values) {
|
|
7504
7841
|
return [...new Set(values)].sort();
|
|
7505
7842
|
}
|
|
7506
7843
|
function isResourceBusy(stderr, stdout) {
|
|
7507
|
-
|
|
7508
|
-
${stdout}`.
|
|
7844
|
+
const output2 = `${stderr}
|
|
7845
|
+
${stdout}`.toLowerCase();
|
|
7846
|
+
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
7509
7847
|
}
|
|
7510
7848
|
async function buildHandoff(options) {
|
|
7511
7849
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]) ?? getInvocationCwd();
|
|
@@ -7513,19 +7851,20 @@ async function buildHandoff(options) {
|
|
|
7513
7851
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
7514
7852
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
7515
7853
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
7516
|
-
const
|
|
7517
|
-
|
|
7518
|
-
|
|
7854
|
+
const repoName = (0, import_node_path4.basename)(repoRoot);
|
|
7855
|
+
const metadata = {
|
|
7856
|
+
kind: "handoff",
|
|
7857
|
+
project: normalizeOptionalMetadata(options.project) ?? repoName,
|
|
7858
|
+
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
7859
|
+
status: "active",
|
|
7860
|
+
supersedes: options.replace,
|
|
7861
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7862
|
+
topic: normalizeOptionalMetadata(options.topic)
|
|
7863
|
+
};
|
|
7864
|
+
const body = [
|
|
7865
|
+
`repo: ${repoName}`,
|
|
7519
7866
|
`repo_path: ${repoRoot}`,
|
|
7520
7867
|
`branch: ${branch || "unknown"}`,
|
|
7521
|
-
`source_agent_client: ${options.sourceAgentClient ?? "codex"}`,
|
|
7522
|
-
`timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
7523
|
-
];
|
|
7524
|
-
if (options.replace) {
|
|
7525
|
-
header.push(`supersedes: ${options.replace}`);
|
|
7526
|
-
}
|
|
7527
|
-
return [
|
|
7528
|
-
...header,
|
|
7529
7868
|
`task: ${options.task ?? "unspecified"}`,
|
|
7530
7869
|
"",
|
|
7531
7870
|
"files_touched:",
|
|
@@ -7546,6 +7885,7 @@ async function buildHandoff(options) {
|
|
|
7546
7885
|
"next_step:",
|
|
7547
7886
|
options.nextStep ?? "- inspect the current repo state and continue from this handoff"
|
|
7548
7887
|
].join("\n");
|
|
7888
|
+
return { handoff: formatMemoryDocument("HANDOFF", metadata, body), metadata };
|
|
7549
7889
|
}
|
|
7550
7890
|
async function gitTouchedFiles(cwd) {
|
|
7551
7891
|
const changedFiles = await gitValue(["diff", "--name-only", "HEAD"], cwd);
|
|
@@ -7637,14 +7977,14 @@ async function runInitManifest(config, options) {
|
|
|
7637
7977
|
uri: existingManifest.futureMonorepo.uri
|
|
7638
7978
|
};
|
|
7639
7979
|
}
|
|
7640
|
-
const
|
|
7980
|
+
const output2 = jsYaml.dump(outputManifest, { lineWidth: 120, noRefs: true });
|
|
7641
7981
|
if (options.dryRun === true) {
|
|
7642
7982
|
console.log(`# Would write ${manifestPath}`);
|
|
7643
|
-
console.log(
|
|
7983
|
+
console.log(output2.trimEnd());
|
|
7644
7984
|
return;
|
|
7645
7985
|
}
|
|
7646
7986
|
await ensureDirectory((0, import_node_path5.dirname)(manifestPath), false);
|
|
7647
|
-
await (0, import_promises5.writeFile)(manifestPath,
|
|
7987
|
+
await (0, import_promises5.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
7648
7988
|
await (0, import_promises5.chmod)(manifestPath, 384);
|
|
7649
7989
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
7650
7990
|
console.log("Seed with:");
|
|
@@ -7855,11 +8195,11 @@ function redactJsonValue(value) {
|
|
|
7855
8195
|
if (!isJsonObject(value)) {
|
|
7856
8196
|
return value;
|
|
7857
8197
|
}
|
|
7858
|
-
const
|
|
8198
|
+
const output2 = {};
|
|
7859
8199
|
for (const [key, nestedValue] of Object.entries(value)) {
|
|
7860
|
-
|
|
8200
|
+
output2[key] = isSensitiveKey(key) ? "[REDACTED]" : redactJsonValue(nestedValue);
|
|
7861
8201
|
}
|
|
7862
|
-
return
|
|
8202
|
+
return output2;
|
|
7863
8203
|
}
|
|
7864
8204
|
function isSensitiveKey(key) {
|
|
7865
8205
|
return /token|secret|password|credential|authorization|api[_-]?key|client[_-]?secret|bearer/i.test(key);
|
|
@@ -7883,15 +8223,476 @@ function detectSecretMatches(content) {
|
|
|
7883
8223
|
}
|
|
7884
8224
|
|
|
7885
8225
|
// src/lifecycle.ts
|
|
7886
|
-
var import_node_child_process2 = require("node:child_process");
|
|
7887
|
-
var
|
|
7888
|
-
var
|
|
7889
|
-
var
|
|
7890
|
-
var
|
|
8226
|
+
var import_node_child_process2 = require("node:child_process");
|
|
8227
|
+
var import_node_fs4 = require("node:fs");
|
|
8228
|
+
var import_promises8 = require("node:fs/promises");
|
|
8229
|
+
var import_node_os5 = require("node:os");
|
|
8230
|
+
var import_node_path7 = require("node:path");
|
|
8231
|
+
|
|
8232
|
+
// src/update.ts
|
|
8233
|
+
var import_node_fs3 = require("node:fs");
|
|
8234
|
+
var import_promises6 = require("node:fs/promises");
|
|
8235
|
+
var import_node_os4 = require("node:os");
|
|
8236
|
+
var import_node_path6 = require("node:path");
|
|
8237
|
+
var import_promises7 = require("node:readline/promises");
|
|
8238
|
+
var import_node_process = require("node:process");
|
|
8239
|
+
var NPM_PACKAGE_NAME = "threadnote";
|
|
8240
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
8241
|
+
var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8242
|
+
var POST_UPDATE_MIGRATIONS_FILE = "post-update-migrations.json";
|
|
8243
|
+
var POST_UPDATE_STATE_FILE = "post-update-state.json";
|
|
8244
|
+
function parseUpdateRuntime(value) {
|
|
8245
|
+
if (value === "auto" || value === "npm" || value === "bun" || value === "deno") {
|
|
8246
|
+
return value;
|
|
8247
|
+
}
|
|
8248
|
+
throw new Error(`Invalid update runtime: ${value}. Expected auto, npm, bun, or deno.`);
|
|
8249
|
+
}
|
|
8250
|
+
async function maybeNotifyUpdate(config, options = {}) {
|
|
8251
|
+
if (isUpdateNotificationDisabled()) {
|
|
8252
|
+
return;
|
|
8253
|
+
}
|
|
8254
|
+
try {
|
|
8255
|
+
const info = await getUpdateInfo(config, {
|
|
8256
|
+
allowCacheWrite: options.dryRun !== true,
|
|
8257
|
+
preferFresh: false,
|
|
8258
|
+
registry: updateRegistry()
|
|
8259
|
+
});
|
|
8260
|
+
if (!info.isUpdateAvailable) {
|
|
8261
|
+
return;
|
|
8262
|
+
}
|
|
8263
|
+
console.log("");
|
|
8264
|
+
console.log(`Update available: threadnote ${info.currentVersion} -> ${info.latestVersion}`);
|
|
8265
|
+
console.log("Run: threadnote update");
|
|
8266
|
+
} catch (_err) {
|
|
8267
|
+
return;
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
async function runUpdate(config, options) {
|
|
8271
|
+
const registry = normalizeRegistry(options.registry ?? updateRegistry());
|
|
8272
|
+
const info = await getUpdateInfo(config, {
|
|
8273
|
+
allowCacheWrite: options.dryRun !== true,
|
|
8274
|
+
preferFresh: true,
|
|
8275
|
+
registry
|
|
8276
|
+
});
|
|
8277
|
+
console.log(`Current version: ${info.currentVersion}`);
|
|
8278
|
+
console.log(`Latest version: ${info.latestVersion}`);
|
|
8279
|
+
console.log(`Registry: ${info.registry}`);
|
|
8280
|
+
if (options.check === true) {
|
|
8281
|
+
if (info.isUpdateAvailable) {
|
|
8282
|
+
console.log(`Update available. Run: threadnote update`);
|
|
8283
|
+
} else {
|
|
8284
|
+
console.log(
|
|
8285
|
+
compareVersions(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
|
|
8286
|
+
);
|
|
8287
|
+
}
|
|
8288
|
+
return;
|
|
8289
|
+
}
|
|
8290
|
+
if (!info.isUpdateAvailable && options.force !== true) {
|
|
8291
|
+
console.log("Threadnote is up to date.");
|
|
8292
|
+
return;
|
|
8293
|
+
}
|
|
8294
|
+
const runtime = await resolveUpdateRuntime(options.runtime ?? "auto");
|
|
8295
|
+
const updateCommand = updatePackageCommand(runtime, registry);
|
|
8296
|
+
await maybeRun(options.dryRun === true, updateCommand.executable, updateCommand.args);
|
|
8297
|
+
if (options.repair === false) {
|
|
8298
|
+
console.log("Skipping repair because --no-repair was provided.");
|
|
8299
|
+
return;
|
|
8300
|
+
}
|
|
8301
|
+
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
8302
|
+
await maybeRun(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
|
|
8303
|
+
if (options.postUpdate !== false) {
|
|
8304
|
+
const postUpdateArgs = [
|
|
8305
|
+
"post-update",
|
|
8306
|
+
"--from-version",
|
|
8307
|
+
info.currentVersion,
|
|
8308
|
+
"--to-version",
|
|
8309
|
+
info.latestVersion,
|
|
8310
|
+
...options.yes === true ? ["--yes"] : []
|
|
8311
|
+
];
|
|
8312
|
+
if (options.dryRun === true) {
|
|
8313
|
+
await maybeRun(true, threadnoteCommand, postUpdateArgs);
|
|
8314
|
+
} else {
|
|
8315
|
+
console.log(`Running: ${formatShellCommand(threadnoteCommand, postUpdateArgs)}`);
|
|
8316
|
+
const postUpdateExitCode = await runInteractive(threadnoteCommand, postUpdateArgs);
|
|
8317
|
+
if (postUpdateExitCode !== 0) {
|
|
8318
|
+
throw new Error(`${formatShellCommand(threadnoteCommand, postUpdateArgs)} exited with ${postUpdateExitCode}.`);
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
} else {
|
|
8322
|
+
console.log("Skipping post-update migration prompts because --no-post-update was provided.");
|
|
8323
|
+
}
|
|
8324
|
+
console.log("Update complete. Restart Cursor, Codex, Claude, or open a fresh agent session so MCP tools reload.");
|
|
8325
|
+
}
|
|
8326
|
+
async function runPostUpdate(config, options) {
|
|
8327
|
+
if (!options.fromVersion || !options.toVersion) {
|
|
8328
|
+
throw new Error("Provide --from-version and --to-version for post-update.");
|
|
8329
|
+
}
|
|
8330
|
+
await runApplicablePostUpdateMigrations(config, {
|
|
8331
|
+
dryRun: options.dryRun === true,
|
|
8332
|
+
fromVersion: options.fromVersion,
|
|
8333
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
8334
|
+
markHandled: true,
|
|
8335
|
+
toVersion: options.toVersion,
|
|
8336
|
+
yes: options.yes === true
|
|
8337
|
+
});
|
|
8338
|
+
}
|
|
8339
|
+
async function maybeRunPostUpdateAfterRepair(config, options) {
|
|
8340
|
+
const toVersion = await currentPackageVersion();
|
|
8341
|
+
const state = await readPostUpdateState(config);
|
|
8342
|
+
const migrations = await applicablePostUpdateMigrations(config, {
|
|
8343
|
+
fromVersion: "0.0.0",
|
|
8344
|
+
handledMigrationIds: state.handledMigrationIds,
|
|
8345
|
+
toVersion
|
|
8346
|
+
});
|
|
8347
|
+
if (migrations.length === 0) {
|
|
8348
|
+
return;
|
|
8349
|
+
}
|
|
8350
|
+
console.log("");
|
|
8351
|
+
console.log("Repair found package post-update migrations.");
|
|
8352
|
+
console.log("This also covers updates launched by older Threadnote versions that only knew how to run repair.");
|
|
8353
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
8354
|
+
console.log(
|
|
8355
|
+
"This process is non-interactive, so Threadnote will print the manual migration command instead of prompting."
|
|
8356
|
+
);
|
|
8357
|
+
console.log(`Run the prompt manually with: threadnote post-update --from-version 0.0.0 --to-version ${toVersion}`);
|
|
8358
|
+
}
|
|
8359
|
+
await runApplicablePostUpdateMigrations(config, {
|
|
8360
|
+
dryRun: options.dryRun,
|
|
8361
|
+
fromVersion: "0.0.0",
|
|
8362
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
8363
|
+
markHandled: true,
|
|
8364
|
+
toVersion,
|
|
8365
|
+
yes: false
|
|
8366
|
+
});
|
|
8367
|
+
}
|
|
8368
|
+
async function getUpdateInfo(config, options) {
|
|
8369
|
+
const currentVersion = await currentPackageVersion();
|
|
8370
|
+
const cached = options.preferFresh ? void 0 : await readFreshCache(config, options.registry);
|
|
8371
|
+
const latestVersion = cached?.latestVersion ?? await fetchLatestVersion(options.registry);
|
|
8372
|
+
if (!cached && options.allowCacheWrite) {
|
|
8373
|
+
await writeUpdateCache(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
|
|
8374
|
+
}
|
|
8375
|
+
return {
|
|
8376
|
+
currentVersion,
|
|
8377
|
+
isUpdateAvailable: compareVersions(currentVersion, latestVersion) < 0,
|
|
8378
|
+
latestVersion,
|
|
8379
|
+
registry: options.registry
|
|
8380
|
+
};
|
|
8381
|
+
}
|
|
8382
|
+
async function currentPackageVersion() {
|
|
8383
|
+
const rawPackage = await (0, import_promises6.readFile)((0, import_node_path6.join)(toolRoot(), "package.json"), "utf8");
|
|
8384
|
+
const parsed = JSON.parse(rawPackage);
|
|
8385
|
+
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
8386
|
+
throw new Error("Could not read current threadnote package version.");
|
|
8387
|
+
}
|
|
8388
|
+
return parsed.version;
|
|
8389
|
+
}
|
|
8390
|
+
async function fetchLatestVersion(registry) {
|
|
8391
|
+
const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
|
|
8392
|
+
const controller = new AbortController();
|
|
8393
|
+
const timeout = setTimeout(() => {
|
|
8394
|
+
controller.abort();
|
|
8395
|
+
}, 2500);
|
|
8396
|
+
try {
|
|
8397
|
+
const response = await fetch(url, { headers: { accept: "application/json" }, signal: controller.signal });
|
|
8398
|
+
if (!response.ok) {
|
|
8399
|
+
throw new Error(`npm registry returned HTTP ${response.status}`);
|
|
8400
|
+
}
|
|
8401
|
+
const parsed = await response.json();
|
|
8402
|
+
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
8403
|
+
throw new Error("npm registry response did not include a version.");
|
|
8404
|
+
}
|
|
8405
|
+
return parsed.version;
|
|
8406
|
+
} catch (err) {
|
|
8407
|
+
throw new Error(`Could not check npm for updates: ${errorMessage(err)}`, { cause: err });
|
|
8408
|
+
} finally {
|
|
8409
|
+
clearTimeout(timeout);
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
async function readFreshCache(config, registry) {
|
|
8413
|
+
const rawCache = await readFileIfExists(updateCachePath(config));
|
|
8414
|
+
if (!rawCache) {
|
|
8415
|
+
return void 0;
|
|
8416
|
+
}
|
|
8417
|
+
try {
|
|
8418
|
+
const parsed = JSON.parse(rawCache);
|
|
8419
|
+
if (!isJsonObject(parsed) || typeof parsed.checkedAt !== "string" || typeof parsed.latestVersion !== "string" || parsed.registry !== registry) {
|
|
8420
|
+
return void 0;
|
|
8421
|
+
}
|
|
8422
|
+
const checkedAt = Date.parse(parsed.checkedAt);
|
|
8423
|
+
if (!Number.isFinite(checkedAt) || Date.now() - checkedAt > UPDATE_CHECK_TTL_MS) {
|
|
8424
|
+
return void 0;
|
|
8425
|
+
}
|
|
8426
|
+
return { checkedAt: parsed.checkedAt, latestVersion: parsed.latestVersion, registry };
|
|
8427
|
+
} catch (_err) {
|
|
8428
|
+
return void 0;
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
async function writeUpdateCache(config, cache) {
|
|
8432
|
+
await ensureDirectory(config.agentContextHome, false);
|
|
8433
|
+
await (0, import_promises6.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
8434
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8435
|
+
}
|
|
8436
|
+
function updateCachePath(config) {
|
|
8437
|
+
return (0, import_node_path6.join)(config.agentContextHome, "update-check.json");
|
|
8438
|
+
}
|
|
8439
|
+
async function runApplicablePostUpdateMigrations(config, options) {
|
|
8440
|
+
const state = await readPostUpdateState(config);
|
|
8441
|
+
const migrations = await applicablePostUpdateMigrations(config, {
|
|
8442
|
+
fromVersion: options.fromVersion,
|
|
8443
|
+
handledMigrationIds: state.handledMigrationIds,
|
|
8444
|
+
toVersion: options.toVersion
|
|
8445
|
+
});
|
|
8446
|
+
if (migrations.length === 0) {
|
|
8447
|
+
console.log("No post-update memory migrations apply.");
|
|
8448
|
+
return;
|
|
8449
|
+
}
|
|
8450
|
+
console.log("");
|
|
8451
|
+
console.log("Post-update memory migrations are available.");
|
|
8452
|
+
const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
8453
|
+
const handledMigrationIds = new Set(state.handledMigrationIds);
|
|
8454
|
+
for (const migration of migrations) {
|
|
8455
|
+
printPostUpdateMigration(migration);
|
|
8456
|
+
const accepted = options.dryRun || options.yes || options.interactive && await confirmPostUpdateMigration("Apply this migration now? [y/N] ");
|
|
8457
|
+
if (!accepted) {
|
|
8458
|
+
console.log("Skipped. Run manually later:");
|
|
8459
|
+
console.log(` ${formatMigrationCommand(threadnoteCommand, migration.commandArgs)}`);
|
|
8460
|
+
continue;
|
|
8461
|
+
}
|
|
8462
|
+
await maybeRun(options.dryRun, threadnoteCommand, migration.commandArgs);
|
|
8463
|
+
if (!options.dryRun) {
|
|
8464
|
+
handledMigrationIds.add(migration.id);
|
|
8465
|
+
for (const instruction of migration.instructions) {
|
|
8466
|
+
console.log(instruction);
|
|
8467
|
+
}
|
|
8468
|
+
} else {
|
|
8469
|
+
console.log("After this migration succeeds, Threadnote will print:");
|
|
8470
|
+
for (const instruction of migration.instructions) {
|
|
8471
|
+
console.log(` ${instruction}`);
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
if (!options.dryRun && options.markHandled) {
|
|
8476
|
+
await writePostUpdateState(config, { handledMigrationIds: [...handledMigrationIds].sort() });
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
async function applicablePostUpdateMigrations(config, options) {
|
|
8480
|
+
const migrations = await readPostUpdateMigrations();
|
|
8481
|
+
const handled = new Set(options.handledMigrationIds);
|
|
8482
|
+
const applicable = [];
|
|
8483
|
+
for (const migration of migrations) {
|
|
8484
|
+
if (handled.has(migration.id)) {
|
|
8485
|
+
continue;
|
|
8486
|
+
}
|
|
8487
|
+
if (compareVersions(options.fromVersion, migration.introducedIn) >= 0) {
|
|
8488
|
+
continue;
|
|
8489
|
+
}
|
|
8490
|
+
if (compareVersions(migration.introducedIn, options.toVersion) > 0) {
|
|
8491
|
+
continue;
|
|
8492
|
+
}
|
|
8493
|
+
if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
|
|
8494
|
+
continue;
|
|
8495
|
+
}
|
|
8496
|
+
applicable.push(migration);
|
|
8497
|
+
}
|
|
8498
|
+
return applicable;
|
|
8499
|
+
}
|
|
8500
|
+
async function readPostUpdateMigrations() {
|
|
8501
|
+
const raw = await readFileIfExists((0, import_node_path6.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
8502
|
+
if (!raw) {
|
|
8503
|
+
return [];
|
|
8504
|
+
}
|
|
8505
|
+
const parsed = JSON.parse(raw);
|
|
8506
|
+
if (!isJsonObject(parsed) || !Array.isArray(parsed.migrations)) {
|
|
8507
|
+
throw new Error(`${POST_UPDATE_MIGRATIONS_FILE} must contain a migrations array.`);
|
|
8508
|
+
}
|
|
8509
|
+
return parsed.migrations.map(parsePostUpdateMigration);
|
|
8510
|
+
}
|
|
8511
|
+
function parsePostUpdateMigration(value) {
|
|
8512
|
+
if (!isJsonObject(value) || typeof value.id !== "string" || typeof value.introducedIn !== "string" || typeof value.title !== "string" || !Array.isArray(value.description) || !Array.isArray(value.commandArgs) || !Array.isArray(value.instructions)) {
|
|
8513
|
+
throw new Error(`Invalid entry in ${POST_UPDATE_MIGRATIONS_FILE}.`);
|
|
8514
|
+
}
|
|
8515
|
+
return {
|
|
8516
|
+
commandArgs: stringArray(value, "commandArgs"),
|
|
8517
|
+
description: stringArray(value, "description"),
|
|
8518
|
+
id: value.id,
|
|
8519
|
+
instructions: stringArray(value, "instructions"),
|
|
8520
|
+
introducedIn: value.introducedIn,
|
|
8521
|
+
requiresLegacyHandoffs: value.requiresLegacyHandoffs === true,
|
|
8522
|
+
title: value.title
|
|
8523
|
+
};
|
|
8524
|
+
}
|
|
8525
|
+
function stringArray(value, key) {
|
|
8526
|
+
const raw = value[key];
|
|
8527
|
+
if (!Array.isArray(raw) || !raw.every((item) => typeof item === "string")) {
|
|
8528
|
+
throw new Error(`Invalid ${key} in ${POST_UPDATE_MIGRATIONS_FILE}.`);
|
|
8529
|
+
}
|
|
8530
|
+
return raw;
|
|
8531
|
+
}
|
|
8532
|
+
function printPostUpdateMigration(migration) {
|
|
8533
|
+
console.log("");
|
|
8534
|
+
console.log(`${migration.title} (${migration.introducedIn})`);
|
|
8535
|
+
for (const line of migration.description) {
|
|
8536
|
+
console.log(`- ${line}`);
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
async function confirmPostUpdateMigration(prompt) {
|
|
8540
|
+
const readline = (0, import_promises7.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
8541
|
+
try {
|
|
8542
|
+
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
8543
|
+
return answer === "y" || answer === "yes";
|
|
8544
|
+
} finally {
|
|
8545
|
+
readline.close();
|
|
8546
|
+
}
|
|
8547
|
+
}
|
|
8548
|
+
function formatMigrationCommand(executable, args) {
|
|
8549
|
+
return [executable, ...args].map((part) => /\s/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
8550
|
+
}
|
|
8551
|
+
function currentThreadnoteCommand() {
|
|
8552
|
+
const entrypoint = process.argv[1]?.trim();
|
|
8553
|
+
return entrypoint ? entrypoint : void 0;
|
|
8554
|
+
}
|
|
8555
|
+
async function readPostUpdateState(config) {
|
|
8556
|
+
const raw = await readFileIfExists(postUpdateStatePath(config));
|
|
8557
|
+
if (!raw) {
|
|
8558
|
+
return { handledMigrationIds: [] };
|
|
8559
|
+
}
|
|
8560
|
+
try {
|
|
8561
|
+
const parsed = JSON.parse(raw);
|
|
8562
|
+
if (!isJsonObject(parsed) || !Array.isArray(parsed.handledMigrationIds)) {
|
|
8563
|
+
return { handledMigrationIds: [] };
|
|
8564
|
+
}
|
|
8565
|
+
return { handledMigrationIds: parsed.handledMigrationIds.filter((id) => typeof id === "string") };
|
|
8566
|
+
} catch (_err) {
|
|
8567
|
+
return { handledMigrationIds: [] };
|
|
8568
|
+
}
|
|
8569
|
+
}
|
|
8570
|
+
async function writePostUpdateState(config, state) {
|
|
8571
|
+
await ensureDirectory(config.agentContextHome, false);
|
|
8572
|
+
await (0, import_promises6.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
8573
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8574
|
+
}
|
|
8575
|
+
function postUpdateStatePath(config) {
|
|
8576
|
+
return (0, import_node_path6.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
8577
|
+
}
|
|
8578
|
+
async function resolveUpdateRuntime(runtime) {
|
|
8579
|
+
if (runtime !== "auto") {
|
|
8580
|
+
await requireRuntime(runtime);
|
|
8581
|
+
return runtime;
|
|
8582
|
+
}
|
|
8583
|
+
for (const candidate of ["npm", "bun", "deno"]) {
|
|
8584
|
+
if (await findExecutable([candidate])) {
|
|
8585
|
+
return candidate;
|
|
8586
|
+
}
|
|
8587
|
+
}
|
|
8588
|
+
throw new Error("Install Node/npm, Bun, or Deno to update threadnote.");
|
|
8589
|
+
}
|
|
8590
|
+
async function requireRuntime(runtime) {
|
|
8591
|
+
if (!await findExecutable([runtime])) {
|
|
8592
|
+
throw new Error(`${runtime} was requested but was not found on PATH.`);
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
async function installedThreadnoteCommand(runtime) {
|
|
8596
|
+
const runtimeBin = await runtimeThreadnoteBin(runtime);
|
|
8597
|
+
if (runtimeBin && await isExecutable(runtimeBin)) {
|
|
8598
|
+
return runtimeBin;
|
|
8599
|
+
}
|
|
8600
|
+
return await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
8601
|
+
}
|
|
8602
|
+
async function runtimeThreadnoteBin(runtime) {
|
|
8603
|
+
if (runtime === "npm") {
|
|
8604
|
+
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
8605
|
+
const prefix = result.stdout.trim();
|
|
8606
|
+
return prefix ? (0, import_node_path6.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
8607
|
+
}
|
|
8608
|
+
if (runtime === "bun") {
|
|
8609
|
+
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
8610
|
+
const binDir = result.stdout.trim();
|
|
8611
|
+
return binDir ? (0, import_node_path6.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
8612
|
+
}
|
|
8613
|
+
return (0, import_node_path6.join)(process.env.DENO_INSTALL ?? (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
8614
|
+
}
|
|
8615
|
+
async function isExecutable(path) {
|
|
8616
|
+
try {
|
|
8617
|
+
await (0, import_promises6.access)(path, import_node_fs3.constants.X_OK);
|
|
8618
|
+
return true;
|
|
8619
|
+
} catch (_err) {
|
|
8620
|
+
return false;
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
function updatePackageCommand(runtime, registry) {
|
|
8624
|
+
if (runtime === "npm") {
|
|
8625
|
+
return { executable: "npm", args: ["install", "--global", `${NPM_PACKAGE_NAME}@latest`, `--registry=${registry}`] };
|
|
8626
|
+
}
|
|
8627
|
+
if (runtime === "bun") {
|
|
8628
|
+
return { executable: "bun", args: ["install", "--global", `${NPM_PACKAGE_NAME}@latest`, `--registry=${registry}`] };
|
|
8629
|
+
}
|
|
8630
|
+
return {
|
|
8631
|
+
executable: "env",
|
|
8632
|
+
args: [
|
|
8633
|
+
`NPM_CONFIG_REGISTRY=${registry}`,
|
|
8634
|
+
"deno",
|
|
8635
|
+
"install",
|
|
8636
|
+
"--global",
|
|
8637
|
+
"--force",
|
|
8638
|
+
"--name",
|
|
8639
|
+
NPM_PACKAGE_NAME,
|
|
8640
|
+
"--allow-read",
|
|
8641
|
+
"--allow-write",
|
|
8642
|
+
"--allow-run",
|
|
8643
|
+
"--allow-env",
|
|
8644
|
+
"--allow-net",
|
|
8645
|
+
`npm:${NPM_PACKAGE_NAME}@latest`
|
|
8646
|
+
]
|
|
8647
|
+
};
|
|
8648
|
+
}
|
|
8649
|
+
function normalizeRegistry(registry) {
|
|
8650
|
+
return registry.endsWith("/") ? registry : `${registry}/`;
|
|
8651
|
+
}
|
|
8652
|
+
function updateRegistry() {
|
|
8653
|
+
return normalizeRegistry(process.env.THREADNOTE_NPM_REGISTRY ?? DEFAULT_NPM_REGISTRY);
|
|
8654
|
+
}
|
|
8655
|
+
function isUpdateNotificationDisabled() {
|
|
8656
|
+
return process.env.CI !== void 0 || process.env.NO_UPDATE_NOTIFIER !== void 0 || process.env.THREADNOTE_NO_UPDATE_CHECK !== void 0;
|
|
8657
|
+
}
|
|
8658
|
+
function compareVersions(currentVersion, latestVersion) {
|
|
8659
|
+
const current = parseVersion(currentVersion);
|
|
8660
|
+
const latest = parseVersion(latestVersion);
|
|
8661
|
+
for (let index = 0; index < 3; index += 1) {
|
|
8662
|
+
const difference = current.numbers[index] - latest.numbers[index];
|
|
8663
|
+
if (difference !== 0) {
|
|
8664
|
+
return difference;
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
if (current.prerelease === latest.prerelease) {
|
|
8668
|
+
return 0;
|
|
8669
|
+
}
|
|
8670
|
+
if (current.prerelease === void 0) {
|
|
8671
|
+
return 1;
|
|
8672
|
+
}
|
|
8673
|
+
if (latest.prerelease === void 0) {
|
|
8674
|
+
return -1;
|
|
8675
|
+
}
|
|
8676
|
+
return current.prerelease.localeCompare(latest.prerelease);
|
|
8677
|
+
}
|
|
8678
|
+
function parseVersion(version) {
|
|
8679
|
+
const normalized = version.trim().replace(/^v/, "");
|
|
8680
|
+
const [core2, prerelease] = normalized.split("-", 2);
|
|
8681
|
+
const parts = core2.split(".").map((part) => Number(part));
|
|
8682
|
+
return {
|
|
8683
|
+
numbers: [safeVersionNumber(parts[0]), safeVersionNumber(parts[1]), safeVersionNumber(parts[2])],
|
|
8684
|
+
prerelease
|
|
8685
|
+
};
|
|
8686
|
+
}
|
|
8687
|
+
function safeVersionNumber(value) {
|
|
8688
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
8689
|
+
}
|
|
8690
|
+
|
|
8691
|
+
// src/lifecycle.ts
|
|
7891
8692
|
async function runDoctor(config, options) {
|
|
7892
8693
|
const checks = [];
|
|
7893
8694
|
checks.push({ name: "mode", status: "ok", detail: options.dryRun ? "dry run; no writes" : "read-only checks" });
|
|
7894
|
-
checks.push({ name: "platform", status: (0,
|
|
8695
|
+
checks.push({ name: "platform", status: (0, import_node_os5.platform)() === "darwin" ? "ok" : "warn", detail: (0, import_node_os5.platform)() });
|
|
7895
8696
|
checks.push(await commandCheck("node", ["--version"]));
|
|
7896
8697
|
checks.push(await commandCheck("python3", ["--version"]));
|
|
7897
8698
|
checks.push(await commandCheck("openviking-server", ["--help"]));
|
|
@@ -7904,9 +8705,9 @@ async function runDoctor(config, options) {
|
|
|
7904
8705
|
checks.push(await commandShimCheck());
|
|
7905
8706
|
checks.push(...await userAgentInstructionsChecks());
|
|
7906
8707
|
checks.push(await manifestCheck(config.manifestPath));
|
|
7907
|
-
checks.push(await fileCheck((0,
|
|
7908
|
-
checks.push(await fileCheck((0,
|
|
7909
|
-
checks.push(await fileCheck((0,
|
|
8708
|
+
checks.push(await fileCheck((0, import_node_path7.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
8709
|
+
checks.push(await fileCheck((0, import_node_path7.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
8710
|
+
checks.push(await fileCheck((0, import_node_path7.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
7910
8711
|
checks.push(await healthCheck(config));
|
|
7911
8712
|
for (const check of checks) {
|
|
7912
8713
|
console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
|
|
@@ -7923,9 +8724,9 @@ async function runInstall(config, options) {
|
|
|
7923
8724
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
7924
8725
|
const dryRun = options.dryRun === true;
|
|
7925
8726
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
7926
|
-
await ensureDirectory((0,
|
|
7927
|
-
await ensureDirectory((0,
|
|
7928
|
-
await ensureDirectory((0,
|
|
8727
|
+
await ensureDirectory((0, import_node_path7.join)(config.agentContextHome, "logs"), dryRun);
|
|
8728
|
+
await ensureDirectory((0, import_node_path7.join)(config.agentContextHome, "redacted"), dryRun);
|
|
8729
|
+
await ensureDirectory((0, import_node_path7.join)(config.agentContextHome, "mcp"), dryRun);
|
|
7929
8730
|
await installCommandShim(dryRun);
|
|
7930
8731
|
await installUserAgentInstructions(dryRun);
|
|
7931
8732
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
@@ -7951,17 +8752,17 @@ async function runInstall(config, options) {
|
|
|
7951
8752
|
}
|
|
7952
8753
|
await writeTemplateIfMissing({
|
|
7953
8754
|
config,
|
|
7954
|
-
destinationPath: (0,
|
|
8755
|
+
destinationPath: (0, import_node_path7.join)(config.agentContextHome, "ov.conf"),
|
|
7955
8756
|
dryRun,
|
|
7956
8757
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7957
|
-
templatePath: (0,
|
|
8758
|
+
templatePath: (0, import_node_path7.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
7958
8759
|
});
|
|
7959
8760
|
await writeTemplateIfMissing({
|
|
7960
8761
|
config,
|
|
7961
|
-
destinationPath: (0,
|
|
8762
|
+
destinationPath: (0, import_node_path7.join)(config.agentContextHome, "ovcli.conf"),
|
|
7962
8763
|
dryRun,
|
|
7963
8764
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
7964
|
-
templatePath: (0,
|
|
8765
|
+
templatePath: (0, import_node_path7.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
7965
8766
|
});
|
|
7966
8767
|
if (options.start !== false) {
|
|
7967
8768
|
const healthy = await repairServerHealth(config, dryRun);
|
|
@@ -8000,6 +8801,9 @@ async function runRepair(config, options) {
|
|
|
8000
8801
|
}
|
|
8001
8802
|
console.log("\nPost-repair doctor:");
|
|
8002
8803
|
await runDoctor(config, { dryRun, strict: false });
|
|
8804
|
+
if (options.postUpdate !== false) {
|
|
8805
|
+
await maybeRunPostUpdateAfterRepair(config, { dryRun });
|
|
8806
|
+
}
|
|
8003
8807
|
}
|
|
8004
8808
|
async function runUninstall(config, options) {
|
|
8005
8809
|
const dryRun = options.dryRun === true;
|
|
@@ -8008,7 +8812,7 @@ async function runUninstall(config, options) {
|
|
|
8008
8812
|
}
|
|
8009
8813
|
console.log("Uninstalling local Threadnote setup.");
|
|
8010
8814
|
await runStop(config, { dryRun });
|
|
8011
|
-
await removePathIfExists((0,
|
|
8815
|
+
await removePathIfExists((0, import_node_path7.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
8012
8816
|
await removeLaunchAgent(dryRun);
|
|
8013
8817
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
8014
8818
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -8043,7 +8847,7 @@ async function repairManifest(config, dryRun) {
|
|
|
8043
8847
|
return;
|
|
8044
8848
|
}
|
|
8045
8849
|
const project = projectManifestForRepo(repoRoot, []);
|
|
8046
|
-
const
|
|
8850
|
+
const output2 = jsYaml.dump(
|
|
8047
8851
|
{
|
|
8048
8852
|
version: 1,
|
|
8049
8853
|
projects: [
|
|
@@ -8059,19 +8863,19 @@ async function repairManifest(config, dryRun) {
|
|
|
8059
8863
|
);
|
|
8060
8864
|
if (dryRun) {
|
|
8061
8865
|
console.log(`# Would write replacement manifest: ${config.manifestPath}`);
|
|
8062
|
-
console.log(
|
|
8866
|
+
console.log(output2.trimEnd());
|
|
8063
8867
|
return;
|
|
8064
8868
|
}
|
|
8065
|
-
await ensureDirectory((0,
|
|
8869
|
+
await ensureDirectory((0, import_node_path7.dirname)(config.manifestPath), false);
|
|
8066
8870
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
8067
8871
|
if (currentContent !== void 0) {
|
|
8068
8872
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
8069
|
-
await (0,
|
|
8070
|
-
await (0,
|
|
8873
|
+
await (0, import_promises8.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
8874
|
+
await (0, import_promises8.chmod)(backupPath, 384);
|
|
8071
8875
|
console.log(`Backup: ${backupPath}`);
|
|
8072
8876
|
}
|
|
8073
|
-
await (0,
|
|
8074
|
-
await (0,
|
|
8877
|
+
await (0, import_promises8.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
8878
|
+
await (0, import_promises8.chmod)(config.manifestPath, 384);
|
|
8075
8879
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
8076
8880
|
}
|
|
8077
8881
|
async function repairServerHealth(config, dryRun) {
|
|
@@ -8112,16 +8916,16 @@ async function runStart(config, options) {
|
|
|
8112
8916
|
);
|
|
8113
8917
|
}
|
|
8114
8918
|
const logPath = openVikingLogPath(config);
|
|
8115
|
-
await ensureDirectory((0,
|
|
8919
|
+
await ensureDirectory((0, import_node_path7.dirname)(logPath), false);
|
|
8116
8920
|
if (options.foreground === true) {
|
|
8117
8921
|
const result = await runInteractive(server, args);
|
|
8118
8922
|
process.exitCode = result;
|
|
8119
8923
|
return;
|
|
8120
8924
|
}
|
|
8121
|
-
const logFd = (0,
|
|
8925
|
+
const logFd = (0, import_node_fs4.openSync)(logPath, "a");
|
|
8122
8926
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
8123
8927
|
child.unref();
|
|
8124
|
-
await (0,
|
|
8928
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
8125
8929
|
`, "utf8");
|
|
8126
8930
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
8127
8931
|
if (health) {
|
|
@@ -8136,7 +8940,7 @@ function spawnDetachedServerWithLog(server, args, logFd) {
|
|
|
8136
8940
|
try {
|
|
8137
8941
|
return spawnDetachedServer(server, args, logFd);
|
|
8138
8942
|
} finally {
|
|
8139
|
-
(0,
|
|
8943
|
+
(0, import_node_fs4.closeSync)(logFd);
|
|
8140
8944
|
}
|
|
8141
8945
|
}
|
|
8142
8946
|
function spawnDetachedServer(server, args, logFd) {
|
|
@@ -8147,14 +8951,14 @@ function spawnDetachedServer(server, args, logFd) {
|
|
|
8147
8951
|
}
|
|
8148
8952
|
async function runStop(config, options) {
|
|
8149
8953
|
const launchAgentPath = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
8150
|
-
if ((0,
|
|
8954
|
+
if ((0, import_node_os5.platform)() === "darwin") {
|
|
8151
8955
|
if (options.dryRun === true || await exists(launchAgentPath)) {
|
|
8152
8956
|
await maybeRun(options.dryRun === true, "launchctl", ["unload", launchAgentPath], { allowFailure: true });
|
|
8153
8957
|
} else {
|
|
8154
8958
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
8155
8959
|
}
|
|
8156
8960
|
}
|
|
8157
|
-
const pidPath = (0,
|
|
8961
|
+
const pidPath = (0, import_node_path7.join)(config.agentContextHome, "openviking-server.pid");
|
|
8158
8962
|
const pidText = await readFileIfExists(pidPath);
|
|
8159
8963
|
if (!pidText) {
|
|
8160
8964
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -8250,7 +9054,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
8250
9054
|
};
|
|
8251
9055
|
}
|
|
8252
9056
|
async function commandShimCheck() {
|
|
8253
|
-
const shimPath = (0,
|
|
9057
|
+
const shimPath = (0, import_node_path7.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
8254
9058
|
const content = await readFileIfExists(shimPath);
|
|
8255
9059
|
if (content === void 0) {
|
|
8256
9060
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -8316,11 +9120,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
8316
9120
|
async function siblingPythonForExecutable(executablePath) {
|
|
8317
9121
|
let resolvedPath;
|
|
8318
9122
|
try {
|
|
8319
|
-
resolvedPath = await (0,
|
|
9123
|
+
resolvedPath = await (0, import_promises8.realpath)(executablePath);
|
|
8320
9124
|
} catch (_err) {
|
|
8321
9125
|
return void 0;
|
|
8322
9126
|
}
|
|
8323
|
-
const pythonPath = (0,
|
|
9127
|
+
const pythonPath = (0, import_node_path7.join)((0, import_node_path7.dirname)(resolvedPath), "python");
|
|
8324
9128
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
8325
9129
|
}
|
|
8326
9130
|
async function manifestCheck(path) {
|
|
@@ -8442,38 +9246,38 @@ function printInstallNextSteps(options) {
|
|
|
8442
9246
|
}
|
|
8443
9247
|
async function writeTemplateIfMissing(options) {
|
|
8444
9248
|
if (await exists(options.destinationPath)) {
|
|
8445
|
-
const currentContent = await (0,
|
|
9249
|
+
const currentContent = await (0, import_promises8.readFile)(options.destinationPath, "utf8");
|
|
8446
9250
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
8447
9251
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
8448
9252
|
return;
|
|
8449
9253
|
}
|
|
8450
|
-
const rendered2 = renderTemplate(await (0,
|
|
9254
|
+
const rendered2 = renderTemplate(await (0, import_promises8.readFile)(options.templatePath, "utf8"), options.config);
|
|
8451
9255
|
if (options.dryRun) {
|
|
8452
9256
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
8453
9257
|
return;
|
|
8454
9258
|
}
|
|
8455
9259
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
8456
|
-
await (0,
|
|
8457
|
-
await (0,
|
|
8458
|
-
await (0,
|
|
8459
|
-
await (0,
|
|
9260
|
+
await (0, import_promises8.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
9261
|
+
await (0, import_promises8.chmod)(backupPath, 384);
|
|
9262
|
+
await (0, import_promises8.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
9263
|
+
await (0, import_promises8.chmod)(options.destinationPath, 384);
|
|
8460
9264
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
8461
9265
|
console.log(`Backup: ${backupPath}`);
|
|
8462
9266
|
return;
|
|
8463
9267
|
}
|
|
8464
|
-
const rendered = renderTemplate(await (0,
|
|
9268
|
+
const rendered = renderTemplate(await (0, import_promises8.readFile)(options.templatePath, "utf8"), options.config);
|
|
8465
9269
|
if (options.dryRun) {
|
|
8466
9270
|
console.log(`Would write ${options.destinationPath}`);
|
|
8467
9271
|
return;
|
|
8468
9272
|
}
|
|
8469
|
-
await ensureDirectory((0,
|
|
8470
|
-
await (0,
|
|
8471
|
-
await (0,
|
|
9273
|
+
await ensureDirectory((0, import_node_path7.dirname)(options.destinationPath), false);
|
|
9274
|
+
await (0, import_promises8.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
9275
|
+
await (0, import_promises8.chmod)(options.destinationPath, 384);
|
|
8472
9276
|
console.log(`Wrote ${options.destinationPath}`);
|
|
8473
9277
|
}
|
|
8474
9278
|
async function installCommandShim(dryRun) {
|
|
8475
9279
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
8476
|
-
const shimPath = (0,
|
|
9280
|
+
const shimPath = (0, import_node_path7.join)(binDir, "threadnote");
|
|
8477
9281
|
const existingContent = await readFileIfExists(shimPath);
|
|
8478
9282
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
8479
9283
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -8489,12 +9293,12 @@ async function installCommandShim(dryRun) {
|
|
|
8489
9293
|
return;
|
|
8490
9294
|
}
|
|
8491
9295
|
await ensureDirectory(binDir, false);
|
|
8492
|
-
await (0,
|
|
8493
|
-
await (0,
|
|
9296
|
+
await (0, import_promises8.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
9297
|
+
await (0, import_promises8.chmod)(shimPath, 493);
|
|
8494
9298
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
8495
9299
|
}
|
|
8496
9300
|
async function removeCommandShim(dryRun) {
|
|
8497
|
-
const shimPath = (0,
|
|
9301
|
+
const shimPath = (0, import_node_path7.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
8498
9302
|
const content = await readFileIfExists(shimPath);
|
|
8499
9303
|
if (content === void 0) {
|
|
8500
9304
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -8524,8 +9328,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
8524
9328
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
8525
9329
|
continue;
|
|
8526
9330
|
}
|
|
8527
|
-
await ensureDirectory((0,
|
|
8528
|
-
await (0,
|
|
9331
|
+
await ensureDirectory((0, import_node_path7.dirname)(targetPath), false);
|
|
9332
|
+
await (0, import_promises8.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
8529
9333
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
8530
9334
|
}
|
|
8531
9335
|
}
|
|
@@ -8554,12 +9358,12 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
8554
9358
|
console.log(`Would update ${targetPath}`);
|
|
8555
9359
|
continue;
|
|
8556
9360
|
}
|
|
8557
|
-
await (0,
|
|
9361
|
+
await (0, import_promises8.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
8558
9362
|
console.log(`Updated ${targetPath}`);
|
|
8559
9363
|
}
|
|
8560
9364
|
}
|
|
8561
9365
|
async function renderUserAgentInstructionsBlock() {
|
|
8562
|
-
const instructions = (await (0,
|
|
9366
|
+
const instructions = (await (0, import_promises8.readFile)((0, import_node_path7.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
8563
9367
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
8564
9368
|
${instructions}
|
|
8565
9369
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -8634,12 +9438,12 @@ function isManagedCommandShim(content) {
|
|
|
8634
9438
|
return content.includes(SHIM_MARKER);
|
|
8635
9439
|
}
|
|
8636
9440
|
async function installLaunchAgent(config, dryRun) {
|
|
8637
|
-
if ((0,
|
|
9441
|
+
if ((0, import_node_os5.platform)() !== "darwin") {
|
|
8638
9442
|
throw new Error("launchd autostart is only supported on macOS.");
|
|
8639
9443
|
}
|
|
8640
|
-
const source = (0,
|
|
9444
|
+
const source = (0, import_node_path7.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
8641
9445
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
8642
|
-
const rendered = renderTemplate(await (0,
|
|
9446
|
+
const rendered = renderTemplate(await (0, import_promises8.readFile)(source, "utf8"), config);
|
|
8643
9447
|
if (dryRun) {
|
|
8644
9448
|
console.log(`Would write ${destination}`);
|
|
8645
9449
|
console.log(`Would run: launchctl unload ${destination}`);
|
|
@@ -8647,9 +9451,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
8647
9451
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
8648
9452
|
return;
|
|
8649
9453
|
}
|
|
8650
|
-
await ensureDirectory((0,
|
|
8651
|
-
await ensureDirectory((0,
|
|
8652
|
-
await (0,
|
|
9454
|
+
await ensureDirectory((0, import_node_path7.dirname)(destination), false);
|
|
9455
|
+
await ensureDirectory((0, import_node_path7.dirname)(openVikingLogPath(config)), false);
|
|
9456
|
+
await (0, import_promises8.writeFile)(destination, rendered, "utf8");
|
|
8653
9457
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
8654
9458
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
8655
9459
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -8712,7 +9516,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
8712
9516
|
if (typeof parsed.default_user !== "string") {
|
|
8713
9517
|
return false;
|
|
8714
9518
|
}
|
|
8715
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
9519
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path7.join)(config.agentContextHome, "data")) {
|
|
8716
9520
|
return false;
|
|
8717
9521
|
}
|
|
8718
9522
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -8731,259 +9535,6 @@ function parsePackageManager(value) {
|
|
|
8731
9535
|
throw new Error(`Invalid package manager: ${value}`);
|
|
8732
9536
|
}
|
|
8733
9537
|
|
|
8734
|
-
// src/update.ts
|
|
8735
|
-
var import_node_fs4 = require("node:fs");
|
|
8736
|
-
var import_promises7 = require("node:fs/promises");
|
|
8737
|
-
var import_node_os5 = require("node:os");
|
|
8738
|
-
var import_node_path7 = require("node:path");
|
|
8739
|
-
var NPM_PACKAGE_NAME = "threadnote";
|
|
8740
|
-
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
8741
|
-
var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8742
|
-
function parseUpdateRuntime(value) {
|
|
8743
|
-
if (value === "auto" || value === "npm" || value === "bun" || value === "deno") {
|
|
8744
|
-
return value;
|
|
8745
|
-
}
|
|
8746
|
-
throw new Error(`Invalid update runtime: ${value}. Expected auto, npm, bun, or deno.`);
|
|
8747
|
-
}
|
|
8748
|
-
async function maybeNotifyUpdate(config, options = {}) {
|
|
8749
|
-
if (isUpdateNotificationDisabled()) {
|
|
8750
|
-
return;
|
|
8751
|
-
}
|
|
8752
|
-
try {
|
|
8753
|
-
const info = await getUpdateInfo(config, {
|
|
8754
|
-
allowCacheWrite: options.dryRun !== true,
|
|
8755
|
-
preferFresh: false,
|
|
8756
|
-
registry: updateRegistry()
|
|
8757
|
-
});
|
|
8758
|
-
if (!info.isUpdateAvailable) {
|
|
8759
|
-
return;
|
|
8760
|
-
}
|
|
8761
|
-
console.log("");
|
|
8762
|
-
console.log(`Update available: threadnote ${info.currentVersion} -> ${info.latestVersion}`);
|
|
8763
|
-
console.log("Run: threadnote update");
|
|
8764
|
-
} catch (_err) {
|
|
8765
|
-
return;
|
|
8766
|
-
}
|
|
8767
|
-
}
|
|
8768
|
-
async function runUpdate(config, options) {
|
|
8769
|
-
const registry = normalizeRegistry(options.registry ?? updateRegistry());
|
|
8770
|
-
const info = await getUpdateInfo(config, {
|
|
8771
|
-
allowCacheWrite: options.dryRun !== true,
|
|
8772
|
-
preferFresh: true,
|
|
8773
|
-
registry
|
|
8774
|
-
});
|
|
8775
|
-
console.log(`Current version: ${info.currentVersion}`);
|
|
8776
|
-
console.log(`Latest version: ${info.latestVersion}`);
|
|
8777
|
-
console.log(`Registry: ${info.registry}`);
|
|
8778
|
-
if (options.check === true) {
|
|
8779
|
-
if (info.isUpdateAvailable) {
|
|
8780
|
-
console.log(`Update available. Run: threadnote update`);
|
|
8781
|
-
} else {
|
|
8782
|
-
console.log(
|
|
8783
|
-
compareVersions(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
|
|
8784
|
-
);
|
|
8785
|
-
}
|
|
8786
|
-
return;
|
|
8787
|
-
}
|
|
8788
|
-
if (!info.isUpdateAvailable && options.force !== true) {
|
|
8789
|
-
console.log("Threadnote is up to date.");
|
|
8790
|
-
return;
|
|
8791
|
-
}
|
|
8792
|
-
const runtime = await resolveUpdateRuntime(options.runtime ?? "auto");
|
|
8793
|
-
const updateCommand = updatePackageCommand(runtime, registry);
|
|
8794
|
-
await maybeRun(options.dryRun === true, updateCommand.executable, updateCommand.args);
|
|
8795
|
-
if (options.repair === false) {
|
|
8796
|
-
console.log("Skipping repair because --no-repair was provided.");
|
|
8797
|
-
return;
|
|
8798
|
-
}
|
|
8799
|
-
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
8800
|
-
await maybeRun(options.dryRun === true, threadnoteCommand, ["repair"]);
|
|
8801
|
-
console.log("Update complete. Restart Cursor, Codex, Claude, or open a fresh agent session so MCP tools reload.");
|
|
8802
|
-
}
|
|
8803
|
-
async function getUpdateInfo(config, options) {
|
|
8804
|
-
const currentVersion = await currentPackageVersion();
|
|
8805
|
-
const cached = options.preferFresh ? void 0 : await readFreshCache(config, options.registry);
|
|
8806
|
-
const latestVersion = cached?.latestVersion ?? await fetchLatestVersion(options.registry);
|
|
8807
|
-
if (!cached && options.allowCacheWrite) {
|
|
8808
|
-
await writeUpdateCache(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
|
|
8809
|
-
}
|
|
8810
|
-
return {
|
|
8811
|
-
currentVersion,
|
|
8812
|
-
isUpdateAvailable: compareVersions(currentVersion, latestVersion) < 0,
|
|
8813
|
-
latestVersion,
|
|
8814
|
-
registry: options.registry
|
|
8815
|
-
};
|
|
8816
|
-
}
|
|
8817
|
-
async function currentPackageVersion() {
|
|
8818
|
-
const rawPackage = await (0, import_promises7.readFile)((0, import_node_path7.join)(toolRoot(), "package.json"), "utf8");
|
|
8819
|
-
const parsed = JSON.parse(rawPackage);
|
|
8820
|
-
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
8821
|
-
throw new Error("Could not read current threadnote package version.");
|
|
8822
|
-
}
|
|
8823
|
-
return parsed.version;
|
|
8824
|
-
}
|
|
8825
|
-
async function fetchLatestVersion(registry) {
|
|
8826
|
-
const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
|
|
8827
|
-
const controller = new AbortController();
|
|
8828
|
-
const timeout = setTimeout(() => {
|
|
8829
|
-
controller.abort();
|
|
8830
|
-
}, 2500);
|
|
8831
|
-
try {
|
|
8832
|
-
const response = await fetch(url, { headers: { accept: "application/json" }, signal: controller.signal });
|
|
8833
|
-
if (!response.ok) {
|
|
8834
|
-
throw new Error(`npm registry returned HTTP ${response.status}`);
|
|
8835
|
-
}
|
|
8836
|
-
const parsed = await response.json();
|
|
8837
|
-
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
8838
|
-
throw new Error("npm registry response did not include a version.");
|
|
8839
|
-
}
|
|
8840
|
-
return parsed.version;
|
|
8841
|
-
} catch (err) {
|
|
8842
|
-
throw new Error(`Could not check npm for updates: ${errorMessage(err)}`, { cause: err });
|
|
8843
|
-
} finally {
|
|
8844
|
-
clearTimeout(timeout);
|
|
8845
|
-
}
|
|
8846
|
-
}
|
|
8847
|
-
async function readFreshCache(config, registry) {
|
|
8848
|
-
const rawCache = await readFileIfExists(updateCachePath(config));
|
|
8849
|
-
if (!rawCache) {
|
|
8850
|
-
return void 0;
|
|
8851
|
-
}
|
|
8852
|
-
try {
|
|
8853
|
-
const parsed = JSON.parse(rawCache);
|
|
8854
|
-
if (!isJsonObject(parsed) || typeof parsed.checkedAt !== "string" || typeof parsed.latestVersion !== "string" || parsed.registry !== registry) {
|
|
8855
|
-
return void 0;
|
|
8856
|
-
}
|
|
8857
|
-
const checkedAt = Date.parse(parsed.checkedAt);
|
|
8858
|
-
if (!Number.isFinite(checkedAt) || Date.now() - checkedAt > UPDATE_CHECK_TTL_MS) {
|
|
8859
|
-
return void 0;
|
|
8860
|
-
}
|
|
8861
|
-
return { checkedAt: parsed.checkedAt, latestVersion: parsed.latestVersion, registry };
|
|
8862
|
-
} catch (_err) {
|
|
8863
|
-
return void 0;
|
|
8864
|
-
}
|
|
8865
|
-
}
|
|
8866
|
-
async function writeUpdateCache(config, cache) {
|
|
8867
|
-
await ensureDirectory(config.agentContextHome, false);
|
|
8868
|
-
await (0, import_promises7.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
8869
|
-
`, { encoding: "utf8", mode: 384 });
|
|
8870
|
-
}
|
|
8871
|
-
function updateCachePath(config) {
|
|
8872
|
-
return (0, import_node_path7.join)(config.agentContextHome, "update-check.json");
|
|
8873
|
-
}
|
|
8874
|
-
async function resolveUpdateRuntime(runtime) {
|
|
8875
|
-
if (runtime !== "auto") {
|
|
8876
|
-
await requireRuntime(runtime);
|
|
8877
|
-
return runtime;
|
|
8878
|
-
}
|
|
8879
|
-
for (const candidate of ["npm", "bun", "deno"]) {
|
|
8880
|
-
if (await findExecutable([candidate])) {
|
|
8881
|
-
return candidate;
|
|
8882
|
-
}
|
|
8883
|
-
}
|
|
8884
|
-
throw new Error("Install Node/npm, Bun, or Deno to update threadnote.");
|
|
8885
|
-
}
|
|
8886
|
-
async function requireRuntime(runtime) {
|
|
8887
|
-
if (!await findExecutable([runtime])) {
|
|
8888
|
-
throw new Error(`${runtime} was requested but was not found on PATH.`);
|
|
8889
|
-
}
|
|
8890
|
-
}
|
|
8891
|
-
async function installedThreadnoteCommand(runtime) {
|
|
8892
|
-
const runtimeBin = await runtimeThreadnoteBin(runtime);
|
|
8893
|
-
if (runtimeBin && await isExecutable(runtimeBin)) {
|
|
8894
|
-
return runtimeBin;
|
|
8895
|
-
}
|
|
8896
|
-
return await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
8897
|
-
}
|
|
8898
|
-
async function runtimeThreadnoteBin(runtime) {
|
|
8899
|
-
if (runtime === "npm") {
|
|
8900
|
-
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
8901
|
-
const prefix = result.stdout.trim();
|
|
8902
|
-
return prefix ? (0, import_node_path7.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
8903
|
-
}
|
|
8904
|
-
if (runtime === "bun") {
|
|
8905
|
-
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
8906
|
-
const binDir = result.stdout.trim();
|
|
8907
|
-
return binDir ? (0, import_node_path7.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
8908
|
-
}
|
|
8909
|
-
return (0, import_node_path7.join)(process.env.DENO_INSTALL ?? (0, import_node_path7.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
8910
|
-
}
|
|
8911
|
-
async function isExecutable(path) {
|
|
8912
|
-
try {
|
|
8913
|
-
await (0, import_promises7.access)(path, import_node_fs4.constants.X_OK);
|
|
8914
|
-
return true;
|
|
8915
|
-
} catch (_err) {
|
|
8916
|
-
return false;
|
|
8917
|
-
}
|
|
8918
|
-
}
|
|
8919
|
-
function updatePackageCommand(runtime, registry) {
|
|
8920
|
-
if (runtime === "npm") {
|
|
8921
|
-
return { executable: "npm", args: ["install", "--global", `${NPM_PACKAGE_NAME}@latest`, `--registry=${registry}`] };
|
|
8922
|
-
}
|
|
8923
|
-
if (runtime === "bun") {
|
|
8924
|
-
return { executable: "bun", args: ["install", "--global", `${NPM_PACKAGE_NAME}@latest`, `--registry=${registry}`] };
|
|
8925
|
-
}
|
|
8926
|
-
return {
|
|
8927
|
-
executable: "env",
|
|
8928
|
-
args: [
|
|
8929
|
-
`NPM_CONFIG_REGISTRY=${registry}`,
|
|
8930
|
-
"deno",
|
|
8931
|
-
"install",
|
|
8932
|
-
"--global",
|
|
8933
|
-
"--force",
|
|
8934
|
-
"--name",
|
|
8935
|
-
NPM_PACKAGE_NAME,
|
|
8936
|
-
"--allow-read",
|
|
8937
|
-
"--allow-write",
|
|
8938
|
-
"--allow-run",
|
|
8939
|
-
"--allow-env",
|
|
8940
|
-
"--allow-net",
|
|
8941
|
-
`npm:${NPM_PACKAGE_NAME}@latest`
|
|
8942
|
-
]
|
|
8943
|
-
};
|
|
8944
|
-
}
|
|
8945
|
-
function normalizeRegistry(registry) {
|
|
8946
|
-
return registry.endsWith("/") ? registry : `${registry}/`;
|
|
8947
|
-
}
|
|
8948
|
-
function updateRegistry() {
|
|
8949
|
-
return normalizeRegistry(process.env.THREADNOTE_NPM_REGISTRY ?? DEFAULT_NPM_REGISTRY);
|
|
8950
|
-
}
|
|
8951
|
-
function isUpdateNotificationDisabled() {
|
|
8952
|
-
return process.env.CI !== void 0 || process.env.NO_UPDATE_NOTIFIER !== void 0 || process.env.THREADNOTE_NO_UPDATE_CHECK !== void 0;
|
|
8953
|
-
}
|
|
8954
|
-
function compareVersions(currentVersion, latestVersion) {
|
|
8955
|
-
const current = parseVersion(currentVersion);
|
|
8956
|
-
const latest = parseVersion(latestVersion);
|
|
8957
|
-
for (let index = 0; index < 3; index += 1) {
|
|
8958
|
-
const difference = current.numbers[index] - latest.numbers[index];
|
|
8959
|
-
if (difference !== 0) {
|
|
8960
|
-
return difference;
|
|
8961
|
-
}
|
|
8962
|
-
}
|
|
8963
|
-
if (current.prerelease === latest.prerelease) {
|
|
8964
|
-
return 0;
|
|
8965
|
-
}
|
|
8966
|
-
if (current.prerelease === void 0) {
|
|
8967
|
-
return 1;
|
|
8968
|
-
}
|
|
8969
|
-
if (latest.prerelease === void 0) {
|
|
8970
|
-
return -1;
|
|
8971
|
-
}
|
|
8972
|
-
return current.prerelease.localeCompare(latest.prerelease);
|
|
8973
|
-
}
|
|
8974
|
-
function parseVersion(version) {
|
|
8975
|
-
const normalized = version.trim().replace(/^v/, "");
|
|
8976
|
-
const [core2, prerelease] = normalized.split("-", 2);
|
|
8977
|
-
const parts = core2.split(".").map((part) => Number(part));
|
|
8978
|
-
return {
|
|
8979
|
-
numbers: [safeVersionNumber(parts[0]), safeVersionNumber(parts[1]), safeVersionNumber(parts[2])],
|
|
8980
|
-
prerelease
|
|
8981
|
-
};
|
|
8982
|
-
}
|
|
8983
|
-
function safeVersionNumber(value) {
|
|
8984
|
-
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
8985
|
-
}
|
|
8986
|
-
|
|
8987
9538
|
// src/threadnote.ts
|
|
8988
9539
|
async function main() {
|
|
8989
9540
|
const program2 = new Command();
|
|
@@ -8998,14 +9549,17 @@ async function main() {
|
|
|
8998
9549
|
await runInstall(config, options);
|
|
8999
9550
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
9000
9551
|
});
|
|
9001
|
-
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").action(async (options) => {
|
|
9552
|
+
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) => {
|
|
9002
9553
|
await runUpdate(getRuntimeConfig(program2), options);
|
|
9003
9554
|
});
|
|
9555
|
+
program2.command("post-update", { hidden: true }).description("Run packaged post-update migration prompts").requiredOption("--from-version <version>", "Version before update").requiredOption("--to-version <version>", "Version after update").option("--dry-run", "Print post-update actions without running them").option("--yes", "Accept applicable post-update migrations without prompting").action(async (options) => {
|
|
9556
|
+
await runPostUpdate(getRuntimeConfig(program2), options);
|
|
9557
|
+
});
|
|
9004
9558
|
program2.command("repair").description("Repair local OpenViking install, config files, server health, shim, manifest, and MCP config").option("--dry-run", "Print the repair actions without making changes").option(
|
|
9005
9559
|
"--mcp <clients>",
|
|
9006
9560
|
"MCP clients to repair: available, all, none, codex, claude, cursor, or comma-separated list",
|
|
9007
9561
|
"available"
|
|
9008
|
-
).option("--no-start", "Do not start OpenViking if health is failing").option("--package-manager <manager>", "uv, pipx, or pip", parsePackageManager).action(async (options) => {
|
|
9562
|
+
).option("--no-start", "Do not start OpenViking if health is failing").option("--no-post-update", "Skip post-update migration prompts after repair").option("--package-manager <manager>", "uv, pipx, or pip", parsePackageManager).action(async (options) => {
|
|
9009
9563
|
const config = getRuntimeConfig(program2);
|
|
9010
9564
|
await runRepair(config, options);
|
|
9011
9565
|
await maybeNotifyUpdate(config, { dryRun: options.dryRun === true });
|
|
@@ -9037,7 +9591,7 @@ async function main() {
|
|
|
9037
9591
|
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) => {
|
|
9038
9592
|
await runMcpInstall(getRuntimeConfig(program2), parseAgentClient(agent), options);
|
|
9039
9593
|
});
|
|
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) => {
|
|
9594
|
+
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, gemini, 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) => {
|
|
9041
9595
|
await runRemember(getRuntimeConfig(program2), options);
|
|
9042
9596
|
});
|
|
9043
9597
|
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(
|
|
@@ -9048,7 +9602,10 @@ async function main() {
|
|
|
9048
9602
|
).action(async (options) => {
|
|
9049
9603
|
await runMigrateMemories(getRuntimeConfig(program2), options);
|
|
9050
9604
|
});
|
|
9051
|
-
program2.command("
|
|
9605
|
+
program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
|
|
9606
|
+
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
9607
|
+
});
|
|
9608
|
+
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in exact durable-memory matches").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
|
|
9052
9609
|
await runRecall(getRuntimeConfig(program2), options);
|
|
9053
9610
|
});
|
|
9054
9611
|
program2.command("read").description("Read a viking:// URI returned by recall or list").argument("<uri>", "viking:// URI to read").option("--dry-run", "Print ov command without reading").action(async (uri, options) => {
|
|
@@ -9057,9 +9614,12 @@ async function main() {
|
|
|
9057
9614
|
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) => {
|
|
9058
9615
|
await runList(getRuntimeConfig(program2), uri, options);
|
|
9059
9616
|
});
|
|
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) => {
|
|
9617
|
+
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("--project <name>", "Project/repo namespace; defaults to current repo basename").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").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
|
|
9061
9618
|
await runHandoff(getRuntimeConfig(program2), options);
|
|
9062
9619
|
});
|
|
9620
|
+
program2.command("archive").description("Move a memory into the archived lifecycle tree, then remove the original after the archive is stored").argument("<uri>", "viking:// memory URI to archive").option("--dry-run", "Print archive content and ov commands without changing anything").option("--kind <kind>", "durable, handoff, incident, preference, or smoke", parseMemoryKind).option("--project <name>", "Override inferred project/repo namespace").option("--topic <name>", "Override inferred topic").action(async (uri, options) => {
|
|
9621
|
+
await runArchive(getRuntimeConfig(program2), uri, options);
|
|
9622
|
+
});
|
|
9063
9623
|
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) => {
|
|
9064
9624
|
await runForget(getRuntimeConfig(program2), uri, options);
|
|
9065
9625
|
});
|