threadnote 0.5.0 → 0.6.1
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 +9 -1
- package/dist/mcp_server.cjs +103 -31
- package/dist/threadnote.cjs +417 -176
- package/docs/index.html +35 -24
- package/docs/share.md +26 -5
- package/docs/threadnote-logo-inverted.svg +1 -0
- package/docs/threadnote-logo.svg +1 -0
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3522,8 +3522,8 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3522
3522
|
];
|
|
3523
3523
|
|
|
3524
3524
|
// src/hooks.ts
|
|
3525
|
-
var
|
|
3526
|
-
var
|
|
3525
|
+
var import_promises6 = require("node:fs/promises");
|
|
3526
|
+
var import_node_path7 = require("node:path");
|
|
3527
3527
|
|
|
3528
3528
|
// src/mcp.ts
|
|
3529
3529
|
var import_node_fs2 = require("node:fs");
|
|
@@ -7240,12 +7240,16 @@ async function runRemember(config, options) {
|
|
|
7240
7240
|
project: normalizeOptionalMetadata(options.project),
|
|
7241
7241
|
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
7242
7242
|
status: options.status ?? "active",
|
|
7243
|
-
supersedes: options.replace,
|
|
7244
7243
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7245
7244
|
topic: normalizeOptionalMetadata(options.topic)
|
|
7246
7245
|
};
|
|
7247
|
-
|
|
7248
|
-
|
|
7246
|
+
await storeMemory(config, {
|
|
7247
|
+
bodyText: text.trim(),
|
|
7248
|
+
dryRun: options.dryRun === true,
|
|
7249
|
+
metadata,
|
|
7250
|
+
replaceUri: options.replace,
|
|
7251
|
+
title: "MEMORY"
|
|
7252
|
+
});
|
|
7249
7253
|
}
|
|
7250
7254
|
async function runMigrateMemories(config, options) {
|
|
7251
7255
|
const dryRun = options.dryRun === true;
|
|
@@ -7418,8 +7422,14 @@ async function runList(config, uri, options) {
|
|
|
7418
7422
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
7419
7423
|
}
|
|
7420
7424
|
async function runHandoff(config, options) {
|
|
7421
|
-
const {
|
|
7422
|
-
await storeMemory(config,
|
|
7425
|
+
const { bodyText, metadata } = await buildHandoff(options);
|
|
7426
|
+
await storeMemory(config, {
|
|
7427
|
+
bodyText,
|
|
7428
|
+
dryRun: options.dryRun === true,
|
|
7429
|
+
metadata,
|
|
7430
|
+
replaceUri: options.replace,
|
|
7431
|
+
title: "HANDOFF"
|
|
7432
|
+
});
|
|
7423
7433
|
}
|
|
7424
7434
|
async function runArchive(config, uri, options) {
|
|
7425
7435
|
assertVikingUri(uri);
|
|
@@ -7436,12 +7446,12 @@ async function runArchive(config, uri, options) {
|
|
|
7436
7446
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7437
7447
|
topic: normalizeOptionalMetadata(options.topic)
|
|
7438
7448
|
};
|
|
7439
|
-
|
|
7440
|
-
"
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7449
|
+
await storeMemory(config, {
|
|
7450
|
+
bodyText: ["Archived original Threadnote memory.", "", "<original memory content would be read here>"].join("\n"),
|
|
7451
|
+
dryRun: true,
|
|
7452
|
+
metadata: fallbackMetadata,
|
|
7453
|
+
title: "MEMORY"
|
|
7454
|
+
});
|
|
7445
7455
|
console.log(formatShellCommand(ov, withIdentity(config, ["rm", uri])));
|
|
7446
7456
|
return;
|
|
7447
7457
|
}
|
|
@@ -7458,12 +7468,12 @@ async function runArchive(config, uri, options) {
|
|
|
7458
7468
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7459
7469
|
topic: normalizeOptionalMetadata(options.topic) ?? inferredMetadata.topic
|
|
7460
7470
|
};
|
|
7461
|
-
|
|
7462
|
-
"
|
|
7471
|
+
await storeMemory(config, {
|
|
7472
|
+
bodyText: ["Archived original Threadnote memory.", "", original].join("\n"),
|
|
7473
|
+
dryRun: false,
|
|
7463
7474
|
metadata,
|
|
7464
|
-
|
|
7465
|
-
);
|
|
7466
|
-
await storeMemory(config, archiveMemory, { dryRun: false, metadata });
|
|
7475
|
+
title: "MEMORY"
|
|
7476
|
+
});
|
|
7467
7477
|
const removedOriginal = await removeVikingResourceWithRetry(ov, config, uri);
|
|
7468
7478
|
if (removedOriginal) {
|
|
7469
7479
|
console.log(`Archived original memory: ${uri}`);
|
|
@@ -7544,14 +7554,19 @@ async function printExactMemoryMatches(config, ov, query, options) {
|
|
|
7544
7554
|
console.log("\nExact durable memory matches:");
|
|
7545
7555
|
console.log(outputs.join("\n\n"));
|
|
7546
7556
|
}
|
|
7547
|
-
async function storeMemory(config,
|
|
7557
|
+
async function storeMemory(config, options) {
|
|
7548
7558
|
if (options.replaceUri) {
|
|
7549
7559
|
assertVikingUri(options.replaceUri);
|
|
7550
7560
|
}
|
|
7551
7561
|
const ov = await openVikingCliForMode(options.dryRun);
|
|
7552
7562
|
const memoryPath = (0, import_node_path4.join)(config.agentContextHome, "last-memory.txt");
|
|
7553
|
-
const
|
|
7554
|
-
const
|
|
7563
|
+
const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
|
|
7564
|
+
const candidateMemory = formatMemoryDocument(options.title, candidateMetadata, options.bodyText);
|
|
7565
|
+
const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
|
|
7566
|
+
const isInPlaceUpdate = options.replaceUri !== void 0 && options.replaceUri === memoryUri;
|
|
7567
|
+
const finalMetadata = isInPlaceUpdate ? { ...options.metadata, supersedes: void 0 } : candidateMetadata;
|
|
7568
|
+
const memory = isInPlaceUpdate ? formatMemoryDocument(options.title, finalMetadata, options.bodyText) : candidateMemory;
|
|
7569
|
+
const writeMode = await memoryWriteMode(ov, config, memoryUri, finalMetadata);
|
|
7555
7570
|
if (options.dryRun) {
|
|
7556
7571
|
console.log(memory);
|
|
7557
7572
|
console.log("\nWould run:");
|
|
@@ -7571,17 +7586,17 @@ async function storeMemory(config, memory, options) {
|
|
|
7571
7586
|
])
|
|
7572
7587
|
)
|
|
7573
7588
|
);
|
|
7574
|
-
if (options.replaceUri &&
|
|
7589
|
+
if (options.replaceUri && !isInPlaceUpdate) {
|
|
7575
7590
|
console.log(formatShellCommand(ov, withIdentity(config, ["rm", options.replaceUri])));
|
|
7576
7591
|
}
|
|
7577
7592
|
return;
|
|
7578
7593
|
}
|
|
7579
7594
|
await (0, import_promises4.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
7580
7595
|
await (0, import_promises4.chmod)(memoryPath, 384);
|
|
7581
|
-
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config,
|
|
7596
|
+
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, finalMetadata));
|
|
7582
7597
|
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
|
|
7583
7598
|
console.log(`Stored memory: ${memoryUri}`);
|
|
7584
|
-
if (options.replaceUri &&
|
|
7599
|
+
if (options.replaceUri && !isInPlaceUpdate) {
|
|
7585
7600
|
const removedReplacedMemory = await removeVikingResourceWithRetry(ov, config, options.replaceUri);
|
|
7586
7601
|
if (removedReplacedMemory) {
|
|
7587
7602
|
console.log(`Forgot replaced memory: ${options.replaceUri}`);
|
|
@@ -7590,7 +7605,7 @@ async function storeMemory(config, memory, options) {
|
|
|
7590
7605
|
`Replacement stored, but the superseded memory is still processing. Retry later: threadnote forget ${options.replaceUri}`
|
|
7591
7606
|
);
|
|
7592
7607
|
}
|
|
7593
|
-
} else if (
|
|
7608
|
+
} else if (isInPlaceUpdate) {
|
|
7594
7609
|
console.log(`Updated existing memory in place: ${memoryUri}`);
|
|
7595
7610
|
}
|
|
7596
7611
|
}
|
|
@@ -8043,11 +8058,10 @@ async function buildHandoff(options) {
|
|
|
8043
8058
|
project: normalizeOptionalMetadata(options.project) ?? repoName,
|
|
8044
8059
|
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
8045
8060
|
status: "active",
|
|
8046
|
-
supersedes: options.replace,
|
|
8047
8061
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8048
8062
|
topic: normalizeOptionalMetadata(options.topic)
|
|
8049
8063
|
};
|
|
8050
|
-
const
|
|
8064
|
+
const bodyText = [
|
|
8051
8065
|
`repo: ${repoName}`,
|
|
8052
8066
|
`repo_path: ${repoRoot}`,
|
|
8053
8067
|
`branch: ${branch || "unknown"}`,
|
|
@@ -8071,7 +8085,7 @@ async function buildHandoff(options) {
|
|
|
8071
8085
|
"next_step:",
|
|
8072
8086
|
options.nextStep ?? "- inspect the current repo state and continue from this handoff"
|
|
8073
8087
|
].join("\n");
|
|
8074
|
-
return {
|
|
8088
|
+
return { bodyText, metadata };
|
|
8075
8089
|
}
|
|
8076
8090
|
async function gitTouchedFiles(cwd) {
|
|
8077
8091
|
const changedFiles = await gitValue(["diff", "--name-only", "HEAD"], cwd);
|
|
@@ -8095,6 +8109,128 @@ function formatBlock(value, emptyValue) {
|
|
|
8095
8109
|
return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
|
|
8096
8110
|
}
|
|
8097
8111
|
|
|
8112
|
+
// src/update-check.ts
|
|
8113
|
+
var import_node_child_process2 = require("node:child_process");
|
|
8114
|
+
var import_promises5 = require("node:fs/promises");
|
|
8115
|
+
var import_node_path5 = require("node:path");
|
|
8116
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8117
|
+
var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
|
|
8118
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
8119
|
+
async function checkForThreadnoteUpdate(args) {
|
|
8120
|
+
if (args.currentVersion === "unknown") {
|
|
8121
|
+
return void 0;
|
|
8122
|
+
}
|
|
8123
|
+
const cached = await readUpdateCache(args.cachePath);
|
|
8124
|
+
if (cached && isCacheFresh(cached)) {
|
|
8125
|
+
return compareVersions(args.currentVersion, cached.latestVersion);
|
|
8126
|
+
}
|
|
8127
|
+
const fresh = await fetchLatestVersion();
|
|
8128
|
+
if (fresh) {
|
|
8129
|
+
await writeUpdateCache(args.cachePath, {
|
|
8130
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8131
|
+
latestVersion: fresh,
|
|
8132
|
+
version: 1
|
|
8133
|
+
});
|
|
8134
|
+
return compareVersions(args.currentVersion, fresh);
|
|
8135
|
+
}
|
|
8136
|
+
if (cached) {
|
|
8137
|
+
return compareVersions(args.currentVersion, cached.latestVersion);
|
|
8138
|
+
}
|
|
8139
|
+
return void 0;
|
|
8140
|
+
}
|
|
8141
|
+
function spawnDetachedAutoUpdate() {
|
|
8142
|
+
try {
|
|
8143
|
+
const entry = process.argv[1];
|
|
8144
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
8145
|
+
return;
|
|
8146
|
+
}
|
|
8147
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [entry, "update", "--yes"], {
|
|
8148
|
+
detached: true,
|
|
8149
|
+
stdio: "ignore"
|
|
8150
|
+
});
|
|
8151
|
+
child.unref();
|
|
8152
|
+
} catch {
|
|
8153
|
+
}
|
|
8154
|
+
}
|
|
8155
|
+
function compareVersions(currentVersion, latestVersion) {
|
|
8156
|
+
return {
|
|
8157
|
+
currentVersion,
|
|
8158
|
+
latestVersion,
|
|
8159
|
+
outdated: isNewerVersion(latestVersion, currentVersion)
|
|
8160
|
+
};
|
|
8161
|
+
}
|
|
8162
|
+
function isNewerVersion(candidate, baseline) {
|
|
8163
|
+
const candidateParts = parseVersion(candidate);
|
|
8164
|
+
const baselineParts = parseVersion(baseline);
|
|
8165
|
+
for (let index = 0; index < 3; index += 1) {
|
|
8166
|
+
if (candidateParts[index] !== baselineParts[index]) {
|
|
8167
|
+
return candidateParts[index] > baselineParts[index];
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
return false;
|
|
8171
|
+
}
|
|
8172
|
+
function parseVersion(value) {
|
|
8173
|
+
const parts = value.split(".").map((part) => parseInt(part, 10));
|
|
8174
|
+
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
|
|
8175
|
+
}
|
|
8176
|
+
function isCacheFresh(cache) {
|
|
8177
|
+
const checkedAt = new Date(cache.checkedAt).getTime();
|
|
8178
|
+
return Number.isFinite(checkedAt) && Date.now() - checkedAt < CACHE_TTL_MS;
|
|
8179
|
+
}
|
|
8180
|
+
async function readUpdateCache(cachePath) {
|
|
8181
|
+
try {
|
|
8182
|
+
const raw = await (0, import_promises5.readFile)(cachePath, "utf8");
|
|
8183
|
+
const parsed = JSON.parse(raw);
|
|
8184
|
+
if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
|
|
8185
|
+
return void 0;
|
|
8186
|
+
}
|
|
8187
|
+
return { checkedAt: parsed.checkedAt, latestVersion: parsed.latestVersion, version: 1 };
|
|
8188
|
+
} catch {
|
|
8189
|
+
return void 0;
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
async function writeUpdateCache(cachePath, contents) {
|
|
8193
|
+
try {
|
|
8194
|
+
await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(cachePath), { recursive: true });
|
|
8195
|
+
await (0, import_promises5.writeFile)(cachePath, `${JSON.stringify(contents)}
|
|
8196
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8197
|
+
} catch {
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
async function fetchLatestVersion() {
|
|
8201
|
+
try {
|
|
8202
|
+
const response = await fetch(NPM_LATEST_URL, {
|
|
8203
|
+
headers: { accept: "application/json" },
|
|
8204
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
8205
|
+
});
|
|
8206
|
+
if (!response.ok) {
|
|
8207
|
+
return void 0;
|
|
8208
|
+
}
|
|
8209
|
+
const data = await response.json();
|
|
8210
|
+
return typeof data.version === "string" && data.version.length > 0 ? data.version : void 0;
|
|
8211
|
+
} catch {
|
|
8212
|
+
return void 0;
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
// src/version.ts
|
|
8217
|
+
var import_node_fs4 = require("node:fs");
|
|
8218
|
+
var import_node_path6 = require("node:path");
|
|
8219
|
+
var cachedVersion;
|
|
8220
|
+
function getThreadnoteVersion() {
|
|
8221
|
+
if (cachedVersion !== void 0) {
|
|
8222
|
+
return cachedVersion;
|
|
8223
|
+
}
|
|
8224
|
+
try {
|
|
8225
|
+
const packageJsonPath = (0, import_node_path6.join)(__dirname, "..", "package.json");
|
|
8226
|
+
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
|
|
8227
|
+
cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
|
|
8228
|
+
} catch {
|
|
8229
|
+
cachedVersion = "unknown";
|
|
8230
|
+
}
|
|
8231
|
+
return cachedVersion;
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8098
8234
|
// src/hooks.ts
|
|
8099
8235
|
var MANAGED_HOOKS = [
|
|
8100
8236
|
{
|
|
@@ -8128,7 +8264,7 @@ async function runHooksInstall(config, agent, options) {
|
|
|
8128
8264
|
}
|
|
8129
8265
|
async function runClaudeHooksInstall(options) {
|
|
8130
8266
|
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
8131
|
-
const existingRaw = await exists(path) ? await (0,
|
|
8267
|
+
const existingRaw = await exists(path) ? await (0, import_promises6.readFile)(path, "utf8") : "{}";
|
|
8132
8268
|
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
8133
8269
|
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
8134
8270
|
const before = JSON.stringify(parsed);
|
|
@@ -8145,11 +8281,11 @@ async function runClaudeHooksInstall(options) {
|
|
|
8145
8281
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
8146
8282
|
return;
|
|
8147
8283
|
}
|
|
8148
|
-
await (0,
|
|
8284
|
+
await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
|
|
8149
8285
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
8150
8286
|
`;
|
|
8151
|
-
await (0,
|
|
8152
|
-
await (0,
|
|
8287
|
+
await (0, import_promises6.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
8288
|
+
await (0, import_promises6.chmod)(path, 384);
|
|
8153
8289
|
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
8154
8290
|
}
|
|
8155
8291
|
function withThreadnoteHooks(input2) {
|
|
@@ -8227,7 +8363,7 @@ async function hasManagedClaudeHooks() {
|
|
|
8227
8363
|
if (!await exists(path)) {
|
|
8228
8364
|
return false;
|
|
8229
8365
|
}
|
|
8230
|
-
const raw = await (0,
|
|
8366
|
+
const raw = await (0, import_promises6.readFile)(path, "utf8");
|
|
8231
8367
|
const parsed = parseJsonConfigObject(raw);
|
|
8232
8368
|
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
8233
8369
|
return false;
|
|
@@ -8245,7 +8381,7 @@ async function hasManagedClaudeHooks() {
|
|
|
8245
8381
|
async function runPreCompactHook(config, options = {}) {
|
|
8246
8382
|
try {
|
|
8247
8383
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
8248
|
-
const project = repoRoot ? (0,
|
|
8384
|
+
const project = repoRoot ? (0, import_node_path7.basename)(repoRoot) : "general";
|
|
8249
8385
|
await runHandoff(config, {
|
|
8250
8386
|
blockers: "- none recorded",
|
|
8251
8387
|
dryRun: options.dryRun === true,
|
|
@@ -8269,7 +8405,8 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
8269
8405
|
if (!repoRoot) {
|
|
8270
8406
|
return;
|
|
8271
8407
|
}
|
|
8272
|
-
const project = (0,
|
|
8408
|
+
const project = (0, import_node_path7.basename)(repoRoot);
|
|
8409
|
+
await emitUpdateBannerIfOutdated(config);
|
|
8273
8410
|
process.stdout.write(`## Threadnote \u2014 latest context for ${project}
|
|
8274
8411
|
|
|
8275
8412
|
`);
|
|
@@ -8286,10 +8423,36 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
8286
8423
|
);
|
|
8287
8424
|
}
|
|
8288
8425
|
}
|
|
8426
|
+
async function emitUpdateBannerIfOutdated(config) {
|
|
8427
|
+
try {
|
|
8428
|
+
const result = await checkForThreadnoteUpdate({
|
|
8429
|
+
cachePath: (0, import_node_path7.join)(config.agentContextHome, ".update-state.json"),
|
|
8430
|
+
currentVersion: getThreadnoteVersion()
|
|
8431
|
+
});
|
|
8432
|
+
if (!result || !result.outdated) {
|
|
8433
|
+
return;
|
|
8434
|
+
}
|
|
8435
|
+
if (process.env.THREADNOTE_AUTO_UPDATE === "1") {
|
|
8436
|
+
process.stdout.write(
|
|
8437
|
+
`[threadnote] v${result.latestVersion} available (current v${result.currentVersion}). Auto-updating in the background; the new version takes effect next session.
|
|
8438
|
+
|
|
8439
|
+
`
|
|
8440
|
+
);
|
|
8441
|
+
spawnDetachedAutoUpdate();
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
process.stdout.write(
|
|
8445
|
+
`[threadnote] v${result.latestVersion} available (current v${result.currentVersion}). Run: threadnote update
|
|
8446
|
+
|
|
8447
|
+
`
|
|
8448
|
+
);
|
|
8449
|
+
} catch {
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8289
8452
|
|
|
8290
8453
|
// src/seeding.ts
|
|
8291
|
-
var
|
|
8292
|
-
var
|
|
8454
|
+
var import_promises7 = require("node:fs/promises");
|
|
8455
|
+
var import_node_path8 = require("node:path");
|
|
8293
8456
|
async function runSeed(config, options) {
|
|
8294
8457
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
8295
8458
|
const ignorePatterns = await loadIgnorePatterns();
|
|
@@ -8318,7 +8481,7 @@ async function runSeed(config, options) {
|
|
|
8318
8481
|
}
|
|
8319
8482
|
async function runInitManifest(config, options) {
|
|
8320
8483
|
const manifestPath = expandPath(
|
|
8321
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
8484
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path8.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
8322
8485
|
);
|
|
8323
8486
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
8324
8487
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -8361,9 +8524,9 @@ async function runInitManifest(config, options) {
|
|
|
8361
8524
|
console.log(output2.trimEnd());
|
|
8362
8525
|
return;
|
|
8363
8526
|
}
|
|
8364
|
-
await ensureDirectory((0,
|
|
8365
|
-
await (0,
|
|
8366
|
-
await (0,
|
|
8527
|
+
await ensureDirectory((0, import_node_path8.dirname)(manifestPath), false);
|
|
8528
|
+
await (0, import_promises7.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
8529
|
+
await (0, import_promises7.chmod)(manifestPath, 384);
|
|
8367
8530
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
8368
8531
|
console.log("Seed with:");
|
|
8369
8532
|
console.log(" threadnote seed --dry-run");
|
|
@@ -8393,13 +8556,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
8393
8556
|
async function projectIdentity(path) {
|
|
8394
8557
|
const expanded = expandPath(path);
|
|
8395
8558
|
try {
|
|
8396
|
-
return await (0,
|
|
8559
|
+
return await (0, import_promises7.realpath)(expanded);
|
|
8397
8560
|
} catch (_err) {
|
|
8398
8561
|
return expanded;
|
|
8399
8562
|
}
|
|
8400
8563
|
}
|
|
8401
8564
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
8402
|
-
const baseName = uriSegment((0,
|
|
8565
|
+
const baseName = uriSegment((0, import_node_path8.basename)(repoRoot));
|
|
8403
8566
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
8404
8567
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
8405
8568
|
let name = baseName;
|
|
@@ -8421,7 +8584,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8421
8584
|
for (const pattern of project.seed) {
|
|
8422
8585
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
8423
8586
|
for (const filePath of files) {
|
|
8424
|
-
const relativePath = toPosixPath((0,
|
|
8587
|
+
const relativePath = toPosixPath((0, import_node_path8.relative)(projectRoot, filePath));
|
|
8425
8588
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
8426
8589
|
continue;
|
|
8427
8590
|
}
|
|
@@ -8439,20 +8602,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8439
8602
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
8440
8603
|
const normalizedPattern = toPosixPath(pattern);
|
|
8441
8604
|
if (!hasGlob(normalizedPattern)) {
|
|
8442
|
-
const filePath = (0,
|
|
8605
|
+
const filePath = (0, import_node_path8.join)(projectRoot, normalizedPattern);
|
|
8443
8606
|
return await isFile(filePath) ? [filePath] : [];
|
|
8444
8607
|
}
|
|
8445
8608
|
const globBase = getGlobBase(normalizedPattern);
|
|
8446
|
-
const basePath = (0,
|
|
8609
|
+
const basePath = (0, import_node_path8.join)(projectRoot, globBase);
|
|
8447
8610
|
if (!await exists(basePath)) {
|
|
8448
8611
|
return [];
|
|
8449
8612
|
}
|
|
8450
8613
|
const regex = globToRegExp(normalizedPattern);
|
|
8451
8614
|
const files = await walkFiles(basePath);
|
|
8452
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
8615
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path8.relative)(projectRoot, filePath))));
|
|
8453
8616
|
}
|
|
8454
8617
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
8455
|
-
const content = await (0,
|
|
8618
|
+
const content = await (0, import_promises7.readFile)(candidate.filePath, "utf8");
|
|
8456
8619
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
8457
8620
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
8458
8621
|
if (secretMatches.length > 0) {
|
|
@@ -8464,14 +8627,14 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
8464
8627
|
if (redactedContent === content) {
|
|
8465
8628
|
return candidate.filePath;
|
|
8466
8629
|
}
|
|
8467
|
-
const redactedPath = (0,
|
|
8630
|
+
const redactedPath = (0, import_node_path8.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
8468
8631
|
if (dryRun) {
|
|
8469
8632
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
8470
8633
|
return redactedPath;
|
|
8471
8634
|
}
|
|
8472
|
-
await ensureDirectory((0,
|
|
8473
|
-
await (0,
|
|
8474
|
-
await (0,
|
|
8635
|
+
await ensureDirectory((0, import_node_path8.dirname)(redactedPath), false);
|
|
8636
|
+
await (0, import_promises7.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
8637
|
+
await (0, import_promises7.chmod)(redactedPath, 384);
|
|
8475
8638
|
return redactedPath;
|
|
8476
8639
|
}
|
|
8477
8640
|
async function collectSkillCandidates(config) {
|
|
@@ -8496,7 +8659,7 @@ async function collectSkillCandidates(config) {
|
|
|
8496
8659
|
for (const source of sources) {
|
|
8497
8660
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
8498
8661
|
for (const filePath of files) {
|
|
8499
|
-
const content = await (0,
|
|
8662
|
+
const content = await (0, import_promises7.readFile)(filePath, "utf8");
|
|
8500
8663
|
const matches = detectSecretMatches(content);
|
|
8501
8664
|
if (matches.length > 0) {
|
|
8502
8665
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -8527,10 +8690,10 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
8527
8690
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
8528
8691
|
}
|
|
8529
8692
|
function skillResourceUri(skill) {
|
|
8530
|
-
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0,
|
|
8693
|
+
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path8.basename)((0, import_node_path8.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
|
|
8531
8694
|
}
|
|
8532
8695
|
async function loadIgnorePatterns() {
|
|
8533
|
-
const raw = await (0,
|
|
8696
|
+
const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
8534
8697
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8535
8698
|
}
|
|
8536
8699
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -8601,14 +8764,16 @@ function detectSecretMatches(content) {
|
|
|
8601
8764
|
}
|
|
8602
8765
|
|
|
8603
8766
|
// src/share.ts
|
|
8604
|
-
var
|
|
8767
|
+
var import_promises8 = require("node:fs/promises");
|
|
8605
8768
|
var import_node_os4 = require("node:os");
|
|
8606
|
-
var
|
|
8769
|
+
var import_node_path9 = require("node:path");
|
|
8607
8770
|
var TEAMS_FILE_VERSION = 1;
|
|
8608
8771
|
var SHARED_SEGMENT = "shared";
|
|
8609
8772
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
8610
8773
|
var DEFAULT_GIT_REMOTE_NAME = "origin";
|
|
8611
8774
|
var SCRUBBER_PATTERNS = [
|
|
8775
|
+
// Credentials: never redactable. Blocking is the only safe response —
|
|
8776
|
+
// automated redaction risks false negatives that leave material in git.
|
|
8612
8777
|
{ name: "private key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
8613
8778
|
{ name: "API key (sk-...)", regex: /\bsk-[A-Za-z0-9_-]{16,}/ },
|
|
8614
8779
|
{ name: "GitHub token", regex: /\bgh[pousr]_[A-Za-z0-9_]{16,}/ },
|
|
@@ -8621,8 +8786,35 @@ var SCRUBBER_PATTERNS = [
|
|
|
8621
8786
|
{ name: "AWS access key", regex: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
8622
8787
|
// Slack tokens: xoxa/xoxb/xoxc (configuration)/xoxd (legacy user cookie)/
|
|
8623
8788
|
// xoxe (refresh)/xoxp/xoxr/xoxs, with optional -N- segment for the workspace tier.
|
|
8624
|
-
{ name: "Slack token", regex: /\bxox[abcdeprs](?:-\d-)?[A-Za-z0-9._-]{10,}/i }
|
|
8789
|
+
{ name: "Slack token", regex: /\bxox[abcdeprs](?:-\d-)?[A-Za-z0-9._-]{10,}/i },
|
|
8790
|
+
// Soft leaks: block by default (so the agent sees them and decides), but
|
|
8791
|
+
// allow opt-in redaction so curated memories with incidental matches can
|
|
8792
|
+
// ship without a manual rewrite. Local home paths are the recurring
|
|
8793
|
+
// real-world leak; the regexes greedily consume the whole path segment
|
|
8794
|
+
// (including subdirectories) up to whitespace or common closing punctuation
|
|
8795
|
+
// so redaction collapses an entire path to a single placeholder rather than
|
|
8796
|
+
// leaving the subpath visible.
|
|
8797
|
+
{ name: "macOS home path", placeholder: "<local-path>", regex: /\/Users\/[^\s)>"'`,]+/ },
|
|
8798
|
+
{ name: "linux home path", placeholder: "<local-path>", regex: /\b\/home\/[^\s)>"'`,]+/ }
|
|
8625
8799
|
];
|
|
8800
|
+
function applyScrubber(content, { redact }) {
|
|
8801
|
+
let cleaned = content;
|
|
8802
|
+
const redactions = [];
|
|
8803
|
+
for (const pattern of SCRUBBER_PATTERNS) {
|
|
8804
|
+
if (!pattern.regex.test(cleaned)) {
|
|
8805
|
+
continue;
|
|
8806
|
+
}
|
|
8807
|
+
if (!pattern.placeholder || !redact) {
|
|
8808
|
+
return { blocker: pattern.name, cleaned: content, redactions: [] };
|
|
8809
|
+
}
|
|
8810
|
+
const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`;
|
|
8811
|
+
const globalRegex = new RegExp(pattern.regex.source, flags);
|
|
8812
|
+
const matches = cleaned.match(globalRegex) ?? [];
|
|
8813
|
+
cleaned = cleaned.replace(globalRegex, pattern.placeholder);
|
|
8814
|
+
redactions.push({ count: matches.length, name: pattern.name });
|
|
8815
|
+
}
|
|
8816
|
+
return { cleaned, redactions };
|
|
8817
|
+
}
|
|
8626
8818
|
async function runShareInit(config, remoteUrl, options) {
|
|
8627
8819
|
if (!remoteUrl.trim()) {
|
|
8628
8820
|
throw new Error("Provide a git remote URL for the shared memories repo.");
|
|
@@ -8641,8 +8833,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8641
8833
|
if (await exists(gitdir)) {
|
|
8642
8834
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
8643
8835
|
}
|
|
8644
|
-
await ensureDirectory((0,
|
|
8645
|
-
await ensureDirectory((0,
|
|
8836
|
+
await ensureDirectory((0, import_node_path9.dirname)(worktree), dryRun);
|
|
8837
|
+
await ensureDirectory((0, import_node_path9.dirname)(gitdir), dryRun);
|
|
8646
8838
|
const git = await requiredExecutable("git");
|
|
8647
8839
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
8648
8840
|
const newConfig = {
|
|
@@ -8673,7 +8865,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8673
8865
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
8674
8866
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
8675
8867
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
8676
|
-
const gitignorePath = (0,
|
|
8868
|
+
const gitignorePath = (0, import_node_path9.join)(worktree, ".gitignore");
|
|
8677
8869
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
8678
8870
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
8679
8871
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -8692,7 +8884,7 @@ async function ensureSharedGitignore(worktree, git, push) {
|
|
|
8692
8884
|
segments.push(SHARED_GITIGNORE_HEADER, "\n");
|
|
8693
8885
|
}
|
|
8694
8886
|
segments.push(missingPatterns.join("\n"), "\n");
|
|
8695
|
-
await (0,
|
|
8887
|
+
await (0, import_promises8.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
|
|
8696
8888
|
console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
|
|
8697
8889
|
await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
|
|
8698
8890
|
const commitResult = await runCommand(
|
|
@@ -8757,7 +8949,7 @@ async function runShareSync(config, options) {
|
|
|
8757
8949
|
if (dryRun) {
|
|
8758
8950
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
8759
8951
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
8760
|
-
if (await exists((0,
|
|
8952
|
+
if (await exists((0, import_node_path9.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path9.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8761
8953
|
throw new Error(
|
|
8762
8954
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
8763
8955
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -8787,16 +8979,41 @@ async function runSharePublish(config, sourceUri, options) {
|
|
|
8787
8979
|
assertVikingUri(sourceUri);
|
|
8788
8980
|
const team = await resolveTeam(config, options.team);
|
|
8789
8981
|
const dryRun = options.dryRun === true;
|
|
8982
|
+
const preview = options.preview === true;
|
|
8790
8983
|
if (isInSharedNamespace(config, sourceUri)) {
|
|
8791
8984
|
throw new Error(`Memory ${sourceUri} is already in the shared namespace.`);
|
|
8792
8985
|
}
|
|
8793
8986
|
const ov = await openVikingCliForMode(dryRun);
|
|
8794
|
-
const
|
|
8795
|
-
const
|
|
8796
|
-
|
|
8797
|
-
throw new Error(`Refusing to publish ${sourceUri}: possible ${blocker}. Strip the sensitive value, then retry.`);
|
|
8798
|
-
}
|
|
8987
|
+
const rawContent = await readMemoryContent(config, ov, sourceUri, dryRun);
|
|
8988
|
+
const stripped = stripPersonalProvenance(rawContent);
|
|
8989
|
+
const scrub = applyScrubber(stripped, { redact: options.redact === true });
|
|
8799
8990
|
const targetUri = sharedUriFor(config, sourceUri, team.name);
|
|
8991
|
+
if (preview) {
|
|
8992
|
+
console.log(`PREVIEW source: ${sourceUri}`);
|
|
8993
|
+
console.log(`PREVIEW destination: ${targetUri}`);
|
|
8994
|
+
if (scrub.blocker) {
|
|
8995
|
+
console.log(
|
|
8996
|
+
`PREVIEW BLOCKED: ${scrub.blocker}. Strip the sensitive value or rerun with --redact for soft-leak patterns.`
|
|
8997
|
+
);
|
|
8998
|
+
return;
|
|
8999
|
+
}
|
|
9000
|
+
for (const redaction of scrub.redactions) {
|
|
9001
|
+
console.log(`PREVIEW redact: ${redaction.count}\xD7 ${redaction.name}`);
|
|
9002
|
+
}
|
|
9003
|
+
console.log("-----BEGIN PREVIEW-----");
|
|
9004
|
+
console.log(scrub.cleaned);
|
|
9005
|
+
console.log("-----END PREVIEW-----");
|
|
9006
|
+
return;
|
|
9007
|
+
}
|
|
9008
|
+
if (scrub.blocker) {
|
|
9009
|
+
throw new Error(
|
|
9010
|
+
`Refusing to publish ${sourceUri}: possible ${scrub.blocker}. Strip the sensitive value or pass --redact for soft-leak patterns.`
|
|
9011
|
+
);
|
|
9012
|
+
}
|
|
9013
|
+
for (const redaction of scrub.redactions) {
|
|
9014
|
+
console.log(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
|
|
9015
|
+
}
|
|
9016
|
+
const content = scrub.cleaned;
|
|
8800
9017
|
if (!dryRun && await vikingResourceExists2(ov, config, targetUri)) {
|
|
8801
9018
|
throw new Error(
|
|
8802
9019
|
`Refusing to publish: ${targetUri} already exists in the shared namespace. Inspect it via threadnote read; if it should be replaced, forget the existing shared copy first.`
|
|
@@ -8908,10 +9125,10 @@ function normalizeTeamName(input2) {
|
|
|
8908
9125
|
return candidate;
|
|
8909
9126
|
}
|
|
8910
9127
|
function teamsFilePath(config) {
|
|
8911
|
-
return (0,
|
|
9128
|
+
return (0, import_node_path9.join)(config.agentContextHome, "share", "teams.json");
|
|
8912
9129
|
}
|
|
8913
9130
|
function teamWorktreePath(config, team) {
|
|
8914
|
-
return (0,
|
|
9131
|
+
return (0, import_node_path9.join)(
|
|
8915
9132
|
config.agentContextHome,
|
|
8916
9133
|
"data",
|
|
8917
9134
|
"viking",
|
|
@@ -8924,7 +9141,7 @@ function teamWorktreePath(config, team) {
|
|
|
8924
9141
|
);
|
|
8925
9142
|
}
|
|
8926
9143
|
function teamGitdirPath(config, team) {
|
|
8927
|
-
return (0,
|
|
9144
|
+
return (0, import_node_path9.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
8928
9145
|
}
|
|
8929
9146
|
async function readTeamsFile(config) {
|
|
8930
9147
|
const path = teamsFilePath(config);
|
|
@@ -8967,13 +9184,13 @@ async function readTeamsFile(config) {
|
|
|
8967
9184
|
}
|
|
8968
9185
|
async function writeTeamsFile(config, contents) {
|
|
8969
9186
|
const path = teamsFilePath(config);
|
|
8970
|
-
await (0,
|
|
9187
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
|
|
8971
9188
|
const serializable = {
|
|
8972
9189
|
defaultTeam: contents.defaultTeam,
|
|
8973
9190
|
teams: contents.teams,
|
|
8974
9191
|
version: contents.version
|
|
8975
9192
|
};
|
|
8976
|
-
await (0,
|
|
9193
|
+
await (0, import_promises8.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
|
|
8977
9194
|
`, { encoding: "utf8", mode: 384 });
|
|
8978
9195
|
}
|
|
8979
9196
|
async function resolveTeam(config, requested) {
|
|
@@ -9003,7 +9220,7 @@ async function assertWorktreeUsable(worktree) {
|
|
|
9003
9220
|
if (!await isDirectory(worktree)) {
|
|
9004
9221
|
throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
|
|
9005
9222
|
}
|
|
9006
|
-
const entries = await (0,
|
|
9223
|
+
const entries = await (0, import_promises8.readdir)(worktree);
|
|
9007
9224
|
if (entries.length > 0) {
|
|
9008
9225
|
const preview = entries.slice(0, 5).join(", ");
|
|
9009
9226
|
const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
|
|
@@ -9027,7 +9244,7 @@ async function walkMemoryFiles(root) {
|
|
|
9027
9244
|
async function visit(path, depth) {
|
|
9028
9245
|
let entries;
|
|
9029
9246
|
try {
|
|
9030
|
-
entries = await (0,
|
|
9247
|
+
entries = await (0, import_promises8.readdir)(path, { withFileTypes: true });
|
|
9031
9248
|
} catch (err) {
|
|
9032
9249
|
console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
|
|
9033
9250
|
return;
|
|
@@ -9036,7 +9253,7 @@ async function walkMemoryFiles(root) {
|
|
|
9036
9253
|
if (entry.name === ".git") {
|
|
9037
9254
|
continue;
|
|
9038
9255
|
}
|
|
9039
|
-
const full = (0,
|
|
9256
|
+
const full = (0, import_node_path9.join)(path, entry.name);
|
|
9040
9257
|
if (entry.isDirectory()) {
|
|
9041
9258
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
9042
9259
|
continue;
|
|
@@ -9060,7 +9277,7 @@ async function walkMemoryFiles(root) {
|
|
|
9060
9277
|
return out;
|
|
9061
9278
|
}
|
|
9062
9279
|
function workfileToVikingUri(config, team, filePath) {
|
|
9063
|
-
const rel = (0,
|
|
9280
|
+
const rel = (0, import_node_path9.relative)(team.worktree, filePath).split(import_node_path9.sep).join("/");
|
|
9064
9281
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
9065
9282
|
}
|
|
9066
9283
|
function isInSharedNamespace(config, uri) {
|
|
@@ -9092,8 +9309,26 @@ function vikingUriToWorktreeRelative(config, uri, team) {
|
|
|
9092
9309
|
}
|
|
9093
9310
|
return uri.slice(prefix.length);
|
|
9094
9311
|
}
|
|
9095
|
-
function
|
|
9096
|
-
|
|
9312
|
+
function stripPersonalProvenance(content) {
|
|
9313
|
+
const lines = content.split("\n");
|
|
9314
|
+
let headerEnd = lines.length;
|
|
9315
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
9316
|
+
if (lines[index].trim() === "") {
|
|
9317
|
+
headerEnd = index;
|
|
9318
|
+
break;
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
const cleaned = [];
|
|
9322
|
+
for (let index = 0; index < headerEnd; index += 1) {
|
|
9323
|
+
if (/^(?:supersedes|archived_from):\s/.test(lines[index])) {
|
|
9324
|
+
continue;
|
|
9325
|
+
}
|
|
9326
|
+
cleaned.push(lines[index]);
|
|
9327
|
+
}
|
|
9328
|
+
for (let index = headerEnd; index < lines.length; index += 1) {
|
|
9329
|
+
cleaned.push(lines[index]);
|
|
9330
|
+
}
|
|
9331
|
+
return cleaned.join("\n");
|
|
9097
9332
|
}
|
|
9098
9333
|
async function readMemoryContent(config, ov, uri, dryRun) {
|
|
9099
9334
|
const args = withIdentity(config, ["read", uri]);
|
|
@@ -9154,13 +9389,13 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun) {
|
|
|
9154
9389
|
console.log(`Would run: ${formatShellCommand(ov, args)}`);
|
|
9155
9390
|
return;
|
|
9156
9391
|
}
|
|
9157
|
-
const stagingDir = await (0,
|
|
9158
|
-
const tempPath = (0,
|
|
9392
|
+
const stagingDir = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
|
|
9393
|
+
const tempPath = (0, import_node_path9.join)(stagingDir, "body.txt");
|
|
9159
9394
|
try {
|
|
9160
|
-
await (0,
|
|
9395
|
+
await (0, import_promises8.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
9161
9396
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode);
|
|
9162
9397
|
} finally {
|
|
9163
|
-
await (0,
|
|
9398
|
+
await (0, import_promises8.rm)(stagingDir, { force: true, recursive: true });
|
|
9164
9399
|
}
|
|
9165
9400
|
}
|
|
9166
9401
|
async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode) {
|
|
@@ -9218,7 +9453,7 @@ ${stdout}`.toLowerCase();
|
|
|
9218
9453
|
return output2.includes("resource is busy") || output2.includes("resource is being processed") || output2.includes("network error") || output2.includes("error sending request") || output2.includes("http request failed") || output2.includes("connection refused") || output2.includes("connection reset") || output2.includes("timed out");
|
|
9219
9454
|
}
|
|
9220
9455
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode) {
|
|
9221
|
-
const content = await (0,
|
|
9456
|
+
const content = await (0, import_promises8.readFile)(filePath, "utf8");
|
|
9222
9457
|
await writeMemoryFile(config, ov, uri, content, initialMode, false);
|
|
9223
9458
|
}
|
|
9224
9459
|
async function removeWithRollback(config, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
|
|
@@ -9260,7 +9495,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
|
|
|
9260
9495
|
if (!relative3) {
|
|
9261
9496
|
return;
|
|
9262
9497
|
}
|
|
9263
|
-
await (0,
|
|
9498
|
+
await (0, import_promises8.rm)((0, import_node_path9.join)(worktree, relative3), { force: true });
|
|
9264
9499
|
}
|
|
9265
9500
|
async function removeMemoryUri(config, ov, uri, dryRun) {
|
|
9266
9501
|
const args = withIdentity(config, ["rm", uri]);
|
|
@@ -9317,8 +9552,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9317
9552
|
const oldRel = entries[index + 1];
|
|
9318
9553
|
const newRel = entries[index + 2];
|
|
9319
9554
|
if (oldRel && newRel) {
|
|
9320
|
-
changes.push({ path: (0,
|
|
9321
|
-
changes.push({ path: (0,
|
|
9555
|
+
changes.push({ path: (0, import_node_path9.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
9556
|
+
changes.push({ path: (0, import_node_path9.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
9322
9557
|
}
|
|
9323
9558
|
index += 3;
|
|
9324
9559
|
continue;
|
|
@@ -9326,7 +9561,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9326
9561
|
const rel = entries[index + 1];
|
|
9327
9562
|
if (rel) {
|
|
9328
9563
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
9329
|
-
changes.push({ path: (0,
|
|
9564
|
+
changes.push({ path: (0, import_node_path9.join)(worktree, rel), relativePath: rel, status });
|
|
9330
9565
|
}
|
|
9331
9566
|
index += 2;
|
|
9332
9567
|
}
|
|
@@ -9350,32 +9585,32 @@ async function applyChangesToOpenViking(config, team, changes) {
|
|
|
9350
9585
|
if (!await isFile(change.path)) {
|
|
9351
9586
|
continue;
|
|
9352
9587
|
}
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
);
|
|
9588
|
+
const ovHasResource = await vikingResourceExists2(ov, config, uri);
|
|
9589
|
+
if (ovHasResource) {
|
|
9590
|
+
const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
|
|
9591
|
+
console.warn(`share sync: ${uri}: ${reason}.`);
|
|
9357
9592
|
}
|
|
9358
9593
|
await ensureSharedDirectoryChain(config, ov, uri, false);
|
|
9359
|
-
const writeMode =
|
|
9594
|
+
const writeMode = ovHasResource ? "replace" : "create";
|
|
9360
9595
|
await ingestSingleFile(ov, config, uri, change.path, writeMode);
|
|
9361
9596
|
}
|
|
9362
9597
|
}
|
|
9363
9598
|
|
|
9364
9599
|
// src/lifecycle.ts
|
|
9365
|
-
var
|
|
9366
|
-
var
|
|
9367
|
-
var
|
|
9600
|
+
var import_node_child_process3 = require("node:child_process");
|
|
9601
|
+
var import_node_fs6 = require("node:fs");
|
|
9602
|
+
var import_promises11 = require("node:fs/promises");
|
|
9368
9603
|
var import_node_os6 = require("node:os");
|
|
9369
|
-
var
|
|
9604
|
+
var import_node_path11 = require("node:path");
|
|
9370
9605
|
var import_node_process2 = require("node:process");
|
|
9371
|
-
var
|
|
9606
|
+
var import_promises12 = require("node:readline/promises");
|
|
9372
9607
|
|
|
9373
9608
|
// src/update.ts
|
|
9374
|
-
var
|
|
9375
|
-
var
|
|
9609
|
+
var import_node_fs5 = require("node:fs");
|
|
9610
|
+
var import_promises9 = require("node:fs/promises");
|
|
9376
9611
|
var import_node_os5 = require("node:os");
|
|
9377
|
-
var
|
|
9378
|
-
var
|
|
9612
|
+
var import_node_path10 = require("node:path");
|
|
9613
|
+
var import_promises10 = require("node:readline/promises");
|
|
9379
9614
|
var import_node_process = require("node:process");
|
|
9380
9615
|
var NPM_PACKAGE_NAME = "threadnote";
|
|
9381
9616
|
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -9423,7 +9658,7 @@ async function runUpdate(config, options) {
|
|
|
9423
9658
|
console.log(`Update available. Run: threadnote update`);
|
|
9424
9659
|
} else {
|
|
9425
9660
|
console.log(
|
|
9426
|
-
|
|
9661
|
+
compareVersions2(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
|
|
9427
9662
|
);
|
|
9428
9663
|
}
|
|
9429
9664
|
return;
|
|
@@ -9511,26 +9746,26 @@ async function maybeRunPostUpdateAfterRepair(config, options) {
|
|
|
9511
9746
|
async function getUpdateInfo(config, options) {
|
|
9512
9747
|
const currentVersion = await currentPackageVersion();
|
|
9513
9748
|
const cached = options.preferFresh ? void 0 : await readFreshCache(config, options.registry);
|
|
9514
|
-
const latestVersion = cached?.latestVersion ?? await
|
|
9749
|
+
const latestVersion = cached?.latestVersion ?? await fetchLatestVersion2(options.registry);
|
|
9515
9750
|
if (!cached && options.allowCacheWrite) {
|
|
9516
|
-
await
|
|
9751
|
+
await writeUpdateCache2(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
|
|
9517
9752
|
}
|
|
9518
9753
|
return {
|
|
9519
9754
|
currentVersion,
|
|
9520
|
-
isUpdateAvailable:
|
|
9755
|
+
isUpdateAvailable: compareVersions2(currentVersion, latestVersion) < 0,
|
|
9521
9756
|
latestVersion,
|
|
9522
9757
|
registry: options.registry
|
|
9523
9758
|
};
|
|
9524
9759
|
}
|
|
9525
9760
|
async function currentPackageVersion() {
|
|
9526
|
-
const rawPackage = await (0,
|
|
9761
|
+
const rawPackage = await (0, import_promises9.readFile)((0, import_node_path10.join)(toolRoot(), "package.json"), "utf8");
|
|
9527
9762
|
const parsed = JSON.parse(rawPackage);
|
|
9528
9763
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
9529
9764
|
throw new Error("Could not read current threadnote package version.");
|
|
9530
9765
|
}
|
|
9531
9766
|
return parsed.version;
|
|
9532
9767
|
}
|
|
9533
|
-
async function
|
|
9768
|
+
async function fetchLatestVersion2(registry) {
|
|
9534
9769
|
const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
|
|
9535
9770
|
const controller = new AbortController();
|
|
9536
9771
|
const timeout = setTimeout(() => {
|
|
@@ -9571,13 +9806,13 @@ async function readFreshCache(config, registry) {
|
|
|
9571
9806
|
return void 0;
|
|
9572
9807
|
}
|
|
9573
9808
|
}
|
|
9574
|
-
async function
|
|
9809
|
+
async function writeUpdateCache2(config, cache) {
|
|
9575
9810
|
await ensureDirectory(config.agentContextHome, false);
|
|
9576
|
-
await (0,
|
|
9811
|
+
await (0, import_promises9.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
9577
9812
|
`, { encoding: "utf8", mode: 384 });
|
|
9578
9813
|
}
|
|
9579
9814
|
function updateCachePath(config) {
|
|
9580
|
-
return (0,
|
|
9815
|
+
return (0, import_node_path10.join)(config.agentContextHome, "update-check.json");
|
|
9581
9816
|
}
|
|
9582
9817
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
9583
9818
|
const state = await readPostUpdateState(config);
|
|
@@ -9627,10 +9862,10 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
9627
9862
|
if (handled.has(migration.id)) {
|
|
9628
9863
|
continue;
|
|
9629
9864
|
}
|
|
9630
|
-
if (
|
|
9865
|
+
if (compareVersions2(options.fromVersion, migration.introducedIn) >= 0) {
|
|
9631
9866
|
continue;
|
|
9632
9867
|
}
|
|
9633
|
-
if (
|
|
9868
|
+
if (compareVersions2(migration.introducedIn, options.toVersion) > 0) {
|
|
9634
9869
|
continue;
|
|
9635
9870
|
}
|
|
9636
9871
|
if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
|
|
@@ -9641,7 +9876,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
9641
9876
|
return applicable;
|
|
9642
9877
|
}
|
|
9643
9878
|
async function readPostUpdateMigrations() {
|
|
9644
|
-
const raw = await readFileIfExists((0,
|
|
9879
|
+
const raw = await readFileIfExists((0, import_node_path10.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
9645
9880
|
if (!raw) {
|
|
9646
9881
|
return [];
|
|
9647
9882
|
}
|
|
@@ -9680,7 +9915,7 @@ function printPostUpdateMigration(migration) {
|
|
|
9680
9915
|
}
|
|
9681
9916
|
}
|
|
9682
9917
|
async function confirmPostUpdateMigration(prompt) {
|
|
9683
|
-
const readline = (0,
|
|
9918
|
+
const readline = (0, import_promises10.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
9684
9919
|
try {
|
|
9685
9920
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
9686
9921
|
return answer === "y" || answer === "yes";
|
|
@@ -9712,11 +9947,11 @@ async function readPostUpdateState(config) {
|
|
|
9712
9947
|
}
|
|
9713
9948
|
async function writePostUpdateState(config, state) {
|
|
9714
9949
|
await ensureDirectory(config.agentContextHome, false);
|
|
9715
|
-
await (0,
|
|
9950
|
+
await (0, import_promises9.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
9716
9951
|
`, { encoding: "utf8", mode: 384 });
|
|
9717
9952
|
}
|
|
9718
9953
|
function postUpdateStatePath(config) {
|
|
9719
|
-
return (0,
|
|
9954
|
+
return (0, import_node_path10.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
9720
9955
|
}
|
|
9721
9956
|
async function resolveUpdateRuntime(runtime) {
|
|
9722
9957
|
if (runtime !== "auto") {
|
|
@@ -9746,18 +9981,18 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
9746
9981
|
if (runtime === "npm") {
|
|
9747
9982
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
9748
9983
|
const prefix = result.stdout.trim();
|
|
9749
|
-
return prefix ? (0,
|
|
9984
|
+
return prefix ? (0, import_node_path10.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
9750
9985
|
}
|
|
9751
9986
|
if (runtime === "bun") {
|
|
9752
9987
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
9753
9988
|
const binDir = result.stdout.trim();
|
|
9754
|
-
return binDir ? (0,
|
|
9989
|
+
return binDir ? (0, import_node_path10.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
9755
9990
|
}
|
|
9756
|
-
return (0,
|
|
9991
|
+
return (0, import_node_path10.join)(process.env.DENO_INSTALL ?? (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
9757
9992
|
}
|
|
9758
9993
|
async function isExecutable(path) {
|
|
9759
9994
|
try {
|
|
9760
|
-
await (0,
|
|
9995
|
+
await (0, import_promises9.access)(path, import_node_fs5.constants.X_OK);
|
|
9761
9996
|
return true;
|
|
9762
9997
|
} catch (_err) {
|
|
9763
9998
|
return false;
|
|
@@ -9798,9 +10033,9 @@ function updateRegistry() {
|
|
|
9798
10033
|
function isUpdateNotificationDisabled() {
|
|
9799
10034
|
return process.env.CI !== void 0 || process.env.NO_UPDATE_NOTIFIER !== void 0 || process.env.THREADNOTE_NO_UPDATE_CHECK !== void 0;
|
|
9800
10035
|
}
|
|
9801
|
-
function
|
|
9802
|
-
const current =
|
|
9803
|
-
const latest =
|
|
10036
|
+
function compareVersions2(currentVersion, latestVersion) {
|
|
10037
|
+
const current = parseVersion2(currentVersion);
|
|
10038
|
+
const latest = parseVersion2(latestVersion);
|
|
9804
10039
|
for (let index = 0; index < 3; index += 1) {
|
|
9805
10040
|
const difference = current.numbers[index] - latest.numbers[index];
|
|
9806
10041
|
if (difference !== 0) {
|
|
@@ -9818,7 +10053,7 @@ function compareVersions(currentVersion, latestVersion) {
|
|
|
9818
10053
|
}
|
|
9819
10054
|
return current.prerelease.localeCompare(latest.prerelease);
|
|
9820
10055
|
}
|
|
9821
|
-
function
|
|
10056
|
+
function parseVersion2(version) {
|
|
9822
10057
|
const normalized = version.trim().replace(/^v/, "");
|
|
9823
10058
|
const [core2, prerelease] = normalized.split("-", 2);
|
|
9824
10059
|
const parts = core2.split(".").map((part) => Number(part));
|
|
@@ -9848,9 +10083,9 @@ async function runDoctor(config, options) {
|
|
|
9848
10083
|
checks.push(await commandShimCheck());
|
|
9849
10084
|
checks.push(...await userAgentInstructionsChecks());
|
|
9850
10085
|
checks.push(await manifestCheck(config.manifestPath));
|
|
9851
|
-
checks.push(await fileCheck((0,
|
|
9852
|
-
checks.push(await fileCheck((0,
|
|
9853
|
-
checks.push(await fileCheck((0,
|
|
10086
|
+
checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
10087
|
+
checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
10088
|
+
checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
9854
10089
|
checks.push(await healthCheck(config));
|
|
9855
10090
|
for (const check of checks) {
|
|
9856
10091
|
console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
|
|
@@ -9867,9 +10102,9 @@ async function runInstall(config, options) {
|
|
|
9867
10102
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
9868
10103
|
const dryRun = options.dryRun === true;
|
|
9869
10104
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
9870
|
-
await ensureDirectory((0,
|
|
9871
|
-
await ensureDirectory((0,
|
|
9872
|
-
await ensureDirectory((0,
|
|
10105
|
+
await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "logs"), dryRun);
|
|
10106
|
+
await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "redacted"), dryRun);
|
|
10107
|
+
await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "mcp"), dryRun);
|
|
9873
10108
|
await installCommandShim(dryRun);
|
|
9874
10109
|
await installUserAgentInstructions(dryRun);
|
|
9875
10110
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
@@ -9895,17 +10130,17 @@ async function runInstall(config, options) {
|
|
|
9895
10130
|
}
|
|
9896
10131
|
await writeTemplateIfMissing({
|
|
9897
10132
|
config,
|
|
9898
|
-
destinationPath: (0,
|
|
10133
|
+
destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ov.conf"),
|
|
9899
10134
|
dryRun,
|
|
9900
10135
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9901
|
-
templatePath: (0,
|
|
10136
|
+
templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
9902
10137
|
});
|
|
9903
10138
|
await writeTemplateIfMissing({
|
|
9904
10139
|
config,
|
|
9905
|
-
destinationPath: (0,
|
|
10140
|
+
destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ovcli.conf"),
|
|
9906
10141
|
dryRun,
|
|
9907
10142
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9908
|
-
templatePath: (0,
|
|
10143
|
+
templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
9909
10144
|
});
|
|
9910
10145
|
if (options.start !== false) {
|
|
9911
10146
|
const healthy = await repairServerHealth(config, dryRun);
|
|
@@ -9959,7 +10194,7 @@ async function runUninstall(config, options) {
|
|
|
9959
10194
|
}
|
|
9960
10195
|
console.log("Uninstalling local Threadnote setup.");
|
|
9961
10196
|
await runStop(config, { dryRun });
|
|
9962
|
-
await removePathIfExists((0,
|
|
10197
|
+
await removePathIfExists((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
9963
10198
|
await removeLaunchAgent(dryRun);
|
|
9964
10199
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
9965
10200
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -10016,16 +10251,16 @@ async function repairManifest(config, dryRun) {
|
|
|
10016
10251
|
console.log(output2.trimEnd());
|
|
10017
10252
|
return;
|
|
10018
10253
|
}
|
|
10019
|
-
await ensureDirectory((0,
|
|
10254
|
+
await ensureDirectory((0, import_node_path11.dirname)(config.manifestPath), false);
|
|
10020
10255
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
10021
10256
|
if (currentContent !== void 0) {
|
|
10022
10257
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
10023
|
-
await (0,
|
|
10024
|
-
await (0,
|
|
10258
|
+
await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
10259
|
+
await (0, import_promises11.chmod)(backupPath, 384);
|
|
10025
10260
|
console.log(`Backup: ${backupPath}`);
|
|
10026
10261
|
}
|
|
10027
|
-
await (0,
|
|
10028
|
-
await (0,
|
|
10262
|
+
await (0, import_promises11.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
10263
|
+
await (0, import_promises11.chmod)(config.manifestPath, 384);
|
|
10029
10264
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
10030
10265
|
}
|
|
10031
10266
|
async function repairServerHealth(config, dryRun) {
|
|
@@ -10066,16 +10301,16 @@ async function runStart(config, options) {
|
|
|
10066
10301
|
);
|
|
10067
10302
|
}
|
|
10068
10303
|
const logPath = openVikingLogPath(config);
|
|
10069
|
-
await ensureDirectory((0,
|
|
10304
|
+
await ensureDirectory((0, import_node_path11.dirname)(logPath), false);
|
|
10070
10305
|
if (options.foreground === true) {
|
|
10071
10306
|
const result = await runInteractive(server, args);
|
|
10072
10307
|
process.exitCode = result;
|
|
10073
10308
|
return;
|
|
10074
10309
|
}
|
|
10075
|
-
const logFd = (0,
|
|
10310
|
+
const logFd = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10076
10311
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
10077
10312
|
child.unref();
|
|
10078
|
-
await (0,
|
|
10313
|
+
await (0, import_promises11.writeFile)((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
10079
10314
|
`, "utf8");
|
|
10080
10315
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
10081
10316
|
if (health) {
|
|
@@ -10090,11 +10325,11 @@ function spawnDetachedServerWithLog(server, args, logFd) {
|
|
|
10090
10325
|
try {
|
|
10091
10326
|
return spawnDetachedServer(server, args, logFd);
|
|
10092
10327
|
} finally {
|
|
10093
|
-
(0,
|
|
10328
|
+
(0, import_node_fs6.closeSync)(logFd);
|
|
10094
10329
|
}
|
|
10095
10330
|
}
|
|
10096
10331
|
function spawnDetachedServer(server, args, logFd) {
|
|
10097
|
-
return (0,
|
|
10332
|
+
return (0, import_node_child_process3.spawn)(server, args, {
|
|
10098
10333
|
detached: true,
|
|
10099
10334
|
stdio: ["ignore", logFd, logFd]
|
|
10100
10335
|
});
|
|
@@ -10108,7 +10343,7 @@ async function runStop(config, options) {
|
|
|
10108
10343
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
10109
10344
|
}
|
|
10110
10345
|
}
|
|
10111
|
-
const pidPath = (0,
|
|
10346
|
+
const pidPath = (0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid");
|
|
10112
10347
|
const pidText = await readFileIfExists(pidPath);
|
|
10113
10348
|
if (!pidText) {
|
|
10114
10349
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -10204,7 +10439,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
10204
10439
|
};
|
|
10205
10440
|
}
|
|
10206
10441
|
async function commandShimCheck() {
|
|
10207
|
-
const shimPath = (0,
|
|
10442
|
+
const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
10208
10443
|
const content = await readFileIfExists(shimPath);
|
|
10209
10444
|
if (content === void 0) {
|
|
10210
10445
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -10270,11 +10505,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
10270
10505
|
async function siblingPythonForExecutable(executablePath) {
|
|
10271
10506
|
let resolvedPath;
|
|
10272
10507
|
try {
|
|
10273
|
-
resolvedPath = await (0,
|
|
10508
|
+
resolvedPath = await (0, import_promises11.realpath)(executablePath);
|
|
10274
10509
|
} catch (_err) {
|
|
10275
10510
|
return void 0;
|
|
10276
10511
|
}
|
|
10277
|
-
const pythonPath = (0,
|
|
10512
|
+
const pythonPath = (0, import_node_path11.join)((0, import_node_path11.dirname)(resolvedPath), "python");
|
|
10278
10513
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
10279
10514
|
}
|
|
10280
10515
|
async function manifestCheck(path) {
|
|
@@ -10345,7 +10580,7 @@ async function offerToInstallUv() {
|
|
|
10345
10580
|
);
|
|
10346
10581
|
return false;
|
|
10347
10582
|
}
|
|
10348
|
-
const readline = (0,
|
|
10583
|
+
const readline = (0, import_promises12.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
10349
10584
|
let answer;
|
|
10350
10585
|
try {
|
|
10351
10586
|
answer = (await readline.question(
|
|
@@ -10468,38 +10703,38 @@ function printInstallNextSteps(options) {
|
|
|
10468
10703
|
}
|
|
10469
10704
|
async function writeTemplateIfMissing(options) {
|
|
10470
10705
|
if (await exists(options.destinationPath)) {
|
|
10471
|
-
const currentContent = await (0,
|
|
10706
|
+
const currentContent = await (0, import_promises11.readFile)(options.destinationPath, "utf8");
|
|
10472
10707
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
10473
10708
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
10474
10709
|
return;
|
|
10475
10710
|
}
|
|
10476
|
-
const rendered2 = renderTemplate(await (0,
|
|
10711
|
+
const rendered2 = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
|
|
10477
10712
|
if (options.dryRun) {
|
|
10478
10713
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
10479
10714
|
return;
|
|
10480
10715
|
}
|
|
10481
10716
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
10482
|
-
await (0,
|
|
10483
|
-
await (0,
|
|
10484
|
-
await (0,
|
|
10485
|
-
await (0,
|
|
10717
|
+
await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
10718
|
+
await (0, import_promises11.chmod)(backupPath, 384);
|
|
10719
|
+
await (0, import_promises11.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
10720
|
+
await (0, import_promises11.chmod)(options.destinationPath, 384);
|
|
10486
10721
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
10487
10722
|
console.log(`Backup: ${backupPath}`);
|
|
10488
10723
|
return;
|
|
10489
10724
|
}
|
|
10490
|
-
const rendered = renderTemplate(await (0,
|
|
10725
|
+
const rendered = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
|
|
10491
10726
|
if (options.dryRun) {
|
|
10492
10727
|
console.log(`Would write ${options.destinationPath}`);
|
|
10493
10728
|
return;
|
|
10494
10729
|
}
|
|
10495
|
-
await ensureDirectory((0,
|
|
10496
|
-
await (0,
|
|
10497
|
-
await (0,
|
|
10730
|
+
await ensureDirectory((0, import_node_path11.dirname)(options.destinationPath), false);
|
|
10731
|
+
await (0, import_promises11.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
10732
|
+
await (0, import_promises11.chmod)(options.destinationPath, 384);
|
|
10498
10733
|
console.log(`Wrote ${options.destinationPath}`);
|
|
10499
10734
|
}
|
|
10500
10735
|
async function installCommandShim(dryRun) {
|
|
10501
10736
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
10502
|
-
const shimPath = (0,
|
|
10737
|
+
const shimPath = (0, import_node_path11.join)(binDir, "threadnote");
|
|
10503
10738
|
const existingContent = await readFileIfExists(shimPath);
|
|
10504
10739
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
10505
10740
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -10515,12 +10750,12 @@ async function installCommandShim(dryRun) {
|
|
|
10515
10750
|
return;
|
|
10516
10751
|
}
|
|
10517
10752
|
await ensureDirectory(binDir, false);
|
|
10518
|
-
await (0,
|
|
10519
|
-
await (0,
|
|
10753
|
+
await (0, import_promises11.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
10754
|
+
await (0, import_promises11.chmod)(shimPath, 493);
|
|
10520
10755
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
10521
10756
|
}
|
|
10522
10757
|
async function removeCommandShim(dryRun) {
|
|
10523
|
-
const shimPath = (0,
|
|
10758
|
+
const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
10524
10759
|
const content = await readFileIfExists(shimPath);
|
|
10525
10760
|
if (content === void 0) {
|
|
10526
10761
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -10554,8 +10789,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
10554
10789
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
10555
10790
|
continue;
|
|
10556
10791
|
}
|
|
10557
|
-
await ensureDirectory((0,
|
|
10558
|
-
await (0,
|
|
10792
|
+
await ensureDirectory((0, import_node_path11.dirname)(targetPath), false);
|
|
10793
|
+
await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10559
10794
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
10560
10795
|
}
|
|
10561
10796
|
}
|
|
@@ -10592,7 +10827,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
10592
10827
|
console.log(`Would update ${targetPath}`);
|
|
10593
10828
|
continue;
|
|
10594
10829
|
}
|
|
10595
|
-
await (0,
|
|
10830
|
+
await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10596
10831
|
console.log(`Updated ${targetPath}`);
|
|
10597
10832
|
}
|
|
10598
10833
|
}
|
|
@@ -10612,7 +10847,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
10612
10847
|
].join("\n");
|
|
10613
10848
|
}
|
|
10614
10849
|
async function renderUserAgentInstructionsBlock() {
|
|
10615
|
-
const instructions = (await (0,
|
|
10850
|
+
const instructions = (await (0, import_promises11.readFile)((0, import_node_path11.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
10616
10851
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
10617
10852
|
${instructions}
|
|
10618
10853
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -10690,9 +10925,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10690
10925
|
if ((0, import_node_os6.platform)() !== "darwin") {
|
|
10691
10926
|
throw new Error("launchd autostart is only supported on macOS.");
|
|
10692
10927
|
}
|
|
10693
|
-
const source = (0,
|
|
10928
|
+
const source = (0, import_node_path11.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
10694
10929
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
10695
|
-
const rendered = renderTemplate(await (0,
|
|
10930
|
+
const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config);
|
|
10696
10931
|
if (dryRun) {
|
|
10697
10932
|
console.log(`Would write ${destination}`);
|
|
10698
10933
|
console.log(`Would run: launchctl unload ${destination}`);
|
|
@@ -10700,9 +10935,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10700
10935
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
10701
10936
|
return;
|
|
10702
10937
|
}
|
|
10703
|
-
await ensureDirectory((0,
|
|
10704
|
-
await ensureDirectory((0,
|
|
10705
|
-
await (0,
|
|
10938
|
+
await ensureDirectory((0, import_node_path11.dirname)(destination), false);
|
|
10939
|
+
await ensureDirectory((0, import_node_path11.dirname)(openVikingLogPath(config)), false);
|
|
10940
|
+
await (0, import_promises11.writeFile)(destination, rendered, "utf8");
|
|
10706
10941
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
10707
10942
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
10708
10943
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -10765,7 +11000,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
10765
11000
|
if (typeof parsed.default_user !== "string") {
|
|
10766
11001
|
return false;
|
|
10767
11002
|
}
|
|
10768
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
11003
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path11.join)(config.agentContextHome, "data")) {
|
|
10769
11004
|
return false;
|
|
10770
11005
|
}
|
|
10771
11006
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -10911,7 +11146,13 @@ async function main() {
|
|
|
10911
11146
|
share.command("sync").description("Pull, reindex, and push the shared memories repo for a team").option("--team <name>", "Team name; defaults to the configured default team").option("--message <text>", "Commit message when auto-committing local edits").option("--no-auto-commit", "Refuse to sync if there are uncommitted local changes").option("--no-push", "Skip the push step after pulling and reindexing").option("--dry-run", "Print actions without running them").action(async (options) => {
|
|
10912
11147
|
await runShareSync(getRuntimeConfig(program2), options);
|
|
10913
11148
|
});
|
|
10914
|
-
share.command("publish").description("Move a personal memory into the shared team namespace, commit and push").argument("<viking-uri>", "viking:// memory URI to publish").option("--team <name>", "Team name; defaults to the configured default team").option("--message <text>", "Commit message override").option("--no-push", "Skip the push step").option("--dry-run", "Print actions without running them").
|
|
11149
|
+
share.command("publish").description("Move a personal memory into the shared team namespace, commit and push").argument("<viking-uri>", "viking:// memory URI to publish").option("--team <name>", "Team name; defaults to the configured default team").option("--message <text>", "Commit message override").option("--no-push", "Skip the push step").option("--dry-run", "Print actions without running them").option(
|
|
11150
|
+
"--preview",
|
|
11151
|
+
"Print the exact bytes that would land in the shared git repo (after frontmatter strip and scrubber redaction) without writing, committing, or pushing"
|
|
11152
|
+
).option(
|
|
11153
|
+
"--redact",
|
|
11154
|
+
"Replace soft-leak matches (local paths) with placeholders and continue; credentials still block"
|
|
11155
|
+
).action(async (uri, options) => {
|
|
10915
11156
|
await runSharePublish(getRuntimeConfig(program2), uri, options);
|
|
10916
11157
|
});
|
|
10917
11158
|
share.command("unpublish").description("Pull a shared memory back into the personal namespace, commit removal and push").argument("<viking-uri>", "viking:// memory URI inside a team shared subtree").option("--team <name>", "Team name; defaults to the configured default team").option("--message <text>", "Commit message override").option("--no-push", "Skip the push step").option("--dry-run", "Print actions without running them").action(async (uri, options) => {
|