threadnote 0.6.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 +5 -1
- package/dist/threadnote.cjs +290 -141
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,11 @@ For deterministic moments where the agent shouldn't have to remember, Threadnote
|
|
|
240
240
|
- **`PreCompact`** auto-stores a handoff snapshot for the current repo right before Claude compacts the conversation,
|
|
241
241
|
so the next turn can recall it even if the agent forgot to write a handoff manually.
|
|
242
242
|
- **`SessionStart`** preloads the latest threadnote handoff/feature memory for the current repo into the new session
|
|
243
|
-
context, so the first turn already knows where you left off.
|
|
243
|
+
context, so the first turn already knows where you left off. The same hook also runs a daily-cached check against
|
|
244
|
+
the npm registry and prints a one-line `[threadnote] vX.Y.Z available; run threadnote update` banner when the
|
|
245
|
+
installed version is behind, so you stop missing releases. Set `THREADNOTE_AUTO_UPDATE=1` to skip the nag and have
|
|
246
|
+
the hook spawn `threadnote update --yes` in the background instead — the new version takes effect on the next
|
|
247
|
+
session.
|
|
244
248
|
|
|
245
249
|
Install them per agent (Codex / Cursor / Copilot are no-ops today — Threadnote prints a clear explanation):
|
|
246
250
|
|
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");
|
|
@@ -8109,6 +8109,128 @@ function formatBlock(value, emptyValue) {
|
|
|
8109
8109
|
return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
|
|
8110
8110
|
}
|
|
8111
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
|
+
|
|
8112
8234
|
// src/hooks.ts
|
|
8113
8235
|
var MANAGED_HOOKS = [
|
|
8114
8236
|
{
|
|
@@ -8142,7 +8264,7 @@ async function runHooksInstall(config, agent, options) {
|
|
|
8142
8264
|
}
|
|
8143
8265
|
async function runClaudeHooksInstall(options) {
|
|
8144
8266
|
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
8145
|
-
const existingRaw = await exists(path) ? await (0,
|
|
8267
|
+
const existingRaw = await exists(path) ? await (0, import_promises6.readFile)(path, "utf8") : "{}";
|
|
8146
8268
|
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
8147
8269
|
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
8148
8270
|
const before = JSON.stringify(parsed);
|
|
@@ -8159,11 +8281,11 @@ async function runClaudeHooksInstall(options) {
|
|
|
8159
8281
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
8160
8282
|
return;
|
|
8161
8283
|
}
|
|
8162
|
-
await (0,
|
|
8284
|
+
await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
|
|
8163
8285
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
8164
8286
|
`;
|
|
8165
|
-
await (0,
|
|
8166
|
-
await (0,
|
|
8287
|
+
await (0, import_promises6.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
8288
|
+
await (0, import_promises6.chmod)(path, 384);
|
|
8167
8289
|
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
8168
8290
|
}
|
|
8169
8291
|
function withThreadnoteHooks(input2) {
|
|
@@ -8241,7 +8363,7 @@ async function hasManagedClaudeHooks() {
|
|
|
8241
8363
|
if (!await exists(path)) {
|
|
8242
8364
|
return false;
|
|
8243
8365
|
}
|
|
8244
|
-
const raw = await (0,
|
|
8366
|
+
const raw = await (0, import_promises6.readFile)(path, "utf8");
|
|
8245
8367
|
const parsed = parseJsonConfigObject(raw);
|
|
8246
8368
|
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
8247
8369
|
return false;
|
|
@@ -8259,7 +8381,7 @@ async function hasManagedClaudeHooks() {
|
|
|
8259
8381
|
async function runPreCompactHook(config, options = {}) {
|
|
8260
8382
|
try {
|
|
8261
8383
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
8262
|
-
const project = repoRoot ? (0,
|
|
8384
|
+
const project = repoRoot ? (0, import_node_path7.basename)(repoRoot) : "general";
|
|
8263
8385
|
await runHandoff(config, {
|
|
8264
8386
|
blockers: "- none recorded",
|
|
8265
8387
|
dryRun: options.dryRun === true,
|
|
@@ -8283,7 +8405,8 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
8283
8405
|
if (!repoRoot) {
|
|
8284
8406
|
return;
|
|
8285
8407
|
}
|
|
8286
|
-
const project = (0,
|
|
8408
|
+
const project = (0, import_node_path7.basename)(repoRoot);
|
|
8409
|
+
await emitUpdateBannerIfOutdated(config);
|
|
8287
8410
|
process.stdout.write(`## Threadnote \u2014 latest context for ${project}
|
|
8288
8411
|
|
|
8289
8412
|
`);
|
|
@@ -8300,10 +8423,36 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
8300
8423
|
);
|
|
8301
8424
|
}
|
|
8302
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
|
+
}
|
|
8303
8452
|
|
|
8304
8453
|
// src/seeding.ts
|
|
8305
|
-
var
|
|
8306
|
-
var
|
|
8454
|
+
var import_promises7 = require("node:fs/promises");
|
|
8455
|
+
var import_node_path8 = require("node:path");
|
|
8307
8456
|
async function runSeed(config, options) {
|
|
8308
8457
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
8309
8458
|
const ignorePatterns = await loadIgnorePatterns();
|
|
@@ -8332,7 +8481,7 @@ async function runSeed(config, options) {
|
|
|
8332
8481
|
}
|
|
8333
8482
|
async function runInitManifest(config, options) {
|
|
8334
8483
|
const manifestPath = expandPath(
|
|
8335
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
8484
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path8.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
8336
8485
|
);
|
|
8337
8486
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
8338
8487
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -8375,9 +8524,9 @@ async function runInitManifest(config, options) {
|
|
|
8375
8524
|
console.log(output2.trimEnd());
|
|
8376
8525
|
return;
|
|
8377
8526
|
}
|
|
8378
|
-
await ensureDirectory((0,
|
|
8379
|
-
await (0,
|
|
8380
|
-
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);
|
|
8381
8530
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
8382
8531
|
console.log("Seed with:");
|
|
8383
8532
|
console.log(" threadnote seed --dry-run");
|
|
@@ -8407,13 +8556,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
8407
8556
|
async function projectIdentity(path) {
|
|
8408
8557
|
const expanded = expandPath(path);
|
|
8409
8558
|
try {
|
|
8410
|
-
return await (0,
|
|
8559
|
+
return await (0, import_promises7.realpath)(expanded);
|
|
8411
8560
|
} catch (_err) {
|
|
8412
8561
|
return expanded;
|
|
8413
8562
|
}
|
|
8414
8563
|
}
|
|
8415
8564
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
8416
|
-
const baseName = uriSegment((0,
|
|
8565
|
+
const baseName = uriSegment((0, import_node_path8.basename)(repoRoot));
|
|
8417
8566
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
8418
8567
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
8419
8568
|
let name = baseName;
|
|
@@ -8435,7 +8584,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8435
8584
|
for (const pattern of project.seed) {
|
|
8436
8585
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
8437
8586
|
for (const filePath of files) {
|
|
8438
|
-
const relativePath = toPosixPath((0,
|
|
8587
|
+
const relativePath = toPosixPath((0, import_node_path8.relative)(projectRoot, filePath));
|
|
8439
8588
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
8440
8589
|
continue;
|
|
8441
8590
|
}
|
|
@@ -8453,20 +8602,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
8453
8602
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
8454
8603
|
const normalizedPattern = toPosixPath(pattern);
|
|
8455
8604
|
if (!hasGlob(normalizedPattern)) {
|
|
8456
|
-
const filePath = (0,
|
|
8605
|
+
const filePath = (0, import_node_path8.join)(projectRoot, normalizedPattern);
|
|
8457
8606
|
return await isFile(filePath) ? [filePath] : [];
|
|
8458
8607
|
}
|
|
8459
8608
|
const globBase = getGlobBase(normalizedPattern);
|
|
8460
|
-
const basePath = (0,
|
|
8609
|
+
const basePath = (0, import_node_path8.join)(projectRoot, globBase);
|
|
8461
8610
|
if (!await exists(basePath)) {
|
|
8462
8611
|
return [];
|
|
8463
8612
|
}
|
|
8464
8613
|
const regex = globToRegExp(normalizedPattern);
|
|
8465
8614
|
const files = await walkFiles(basePath);
|
|
8466
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
8615
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path8.relative)(projectRoot, filePath))));
|
|
8467
8616
|
}
|
|
8468
8617
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
8469
|
-
const content = await (0,
|
|
8618
|
+
const content = await (0, import_promises7.readFile)(candidate.filePath, "utf8");
|
|
8470
8619
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
8471
8620
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
8472
8621
|
if (secretMatches.length > 0) {
|
|
@@ -8478,14 +8627,14 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
8478
8627
|
if (redactedContent === content) {
|
|
8479
8628
|
return candidate.filePath;
|
|
8480
8629
|
}
|
|
8481
|
-
const redactedPath = (0,
|
|
8630
|
+
const redactedPath = (0, import_node_path8.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
8482
8631
|
if (dryRun) {
|
|
8483
8632
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
8484
8633
|
return redactedPath;
|
|
8485
8634
|
}
|
|
8486
|
-
await ensureDirectory((0,
|
|
8487
|
-
await (0,
|
|
8488
|
-
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);
|
|
8489
8638
|
return redactedPath;
|
|
8490
8639
|
}
|
|
8491
8640
|
async function collectSkillCandidates(config) {
|
|
@@ -8510,7 +8659,7 @@ async function collectSkillCandidates(config) {
|
|
|
8510
8659
|
for (const source of sources) {
|
|
8511
8660
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
8512
8661
|
for (const filePath of files) {
|
|
8513
|
-
const content = await (0,
|
|
8662
|
+
const content = await (0, import_promises7.readFile)(filePath, "utf8");
|
|
8514
8663
|
const matches = detectSecretMatches(content);
|
|
8515
8664
|
if (matches.length > 0) {
|
|
8516
8665
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -8541,10 +8690,10 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
8541
8690
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
8542
8691
|
}
|
|
8543
8692
|
function skillResourceUri(skill) {
|
|
8544
|
-
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`;
|
|
8545
8694
|
}
|
|
8546
8695
|
async function loadIgnorePatterns() {
|
|
8547
|
-
const raw = await (0,
|
|
8696
|
+
const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
8548
8697
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8549
8698
|
}
|
|
8550
8699
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -8615,9 +8764,9 @@ function detectSecretMatches(content) {
|
|
|
8615
8764
|
}
|
|
8616
8765
|
|
|
8617
8766
|
// src/share.ts
|
|
8618
|
-
var
|
|
8767
|
+
var import_promises8 = require("node:fs/promises");
|
|
8619
8768
|
var import_node_os4 = require("node:os");
|
|
8620
|
-
var
|
|
8769
|
+
var import_node_path9 = require("node:path");
|
|
8621
8770
|
var TEAMS_FILE_VERSION = 1;
|
|
8622
8771
|
var SHARED_SEGMENT = "shared";
|
|
8623
8772
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -8684,8 +8833,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8684
8833
|
if (await exists(gitdir)) {
|
|
8685
8834
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
8686
8835
|
}
|
|
8687
|
-
await ensureDirectory((0,
|
|
8688
|
-
await ensureDirectory((0,
|
|
8836
|
+
await ensureDirectory((0, import_node_path9.dirname)(worktree), dryRun);
|
|
8837
|
+
await ensureDirectory((0, import_node_path9.dirname)(gitdir), dryRun);
|
|
8689
8838
|
const git = await requiredExecutable("git");
|
|
8690
8839
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
8691
8840
|
const newConfig = {
|
|
@@ -8716,7 +8865,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8716
8865
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
8717
8866
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
8718
8867
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
8719
|
-
const gitignorePath = (0,
|
|
8868
|
+
const gitignorePath = (0, import_node_path9.join)(worktree, ".gitignore");
|
|
8720
8869
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
8721
8870
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
8722
8871
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -8735,7 +8884,7 @@ async function ensureSharedGitignore(worktree, git, push) {
|
|
|
8735
8884
|
segments.push(SHARED_GITIGNORE_HEADER, "\n");
|
|
8736
8885
|
}
|
|
8737
8886
|
segments.push(missingPatterns.join("\n"), "\n");
|
|
8738
|
-
await (0,
|
|
8887
|
+
await (0, import_promises8.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
|
|
8739
8888
|
console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
|
|
8740
8889
|
await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
|
|
8741
8890
|
const commitResult = await runCommand(
|
|
@@ -8800,7 +8949,7 @@ async function runShareSync(config, options) {
|
|
|
8800
8949
|
if (dryRun) {
|
|
8801
8950
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
8802
8951
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
8803
|
-
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"))) {
|
|
8804
8953
|
throw new Error(
|
|
8805
8954
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
8806
8955
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -8976,10 +9125,10 @@ function normalizeTeamName(input2) {
|
|
|
8976
9125
|
return candidate;
|
|
8977
9126
|
}
|
|
8978
9127
|
function teamsFilePath(config) {
|
|
8979
|
-
return (0,
|
|
9128
|
+
return (0, import_node_path9.join)(config.agentContextHome, "share", "teams.json");
|
|
8980
9129
|
}
|
|
8981
9130
|
function teamWorktreePath(config, team) {
|
|
8982
|
-
return (0,
|
|
9131
|
+
return (0, import_node_path9.join)(
|
|
8983
9132
|
config.agentContextHome,
|
|
8984
9133
|
"data",
|
|
8985
9134
|
"viking",
|
|
@@ -8992,7 +9141,7 @@ function teamWorktreePath(config, team) {
|
|
|
8992
9141
|
);
|
|
8993
9142
|
}
|
|
8994
9143
|
function teamGitdirPath(config, team) {
|
|
8995
|
-
return (0,
|
|
9144
|
+
return (0, import_node_path9.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
8996
9145
|
}
|
|
8997
9146
|
async function readTeamsFile(config) {
|
|
8998
9147
|
const path = teamsFilePath(config);
|
|
@@ -9035,13 +9184,13 @@ async function readTeamsFile(config) {
|
|
|
9035
9184
|
}
|
|
9036
9185
|
async function writeTeamsFile(config, contents) {
|
|
9037
9186
|
const path = teamsFilePath(config);
|
|
9038
|
-
await (0,
|
|
9187
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
|
|
9039
9188
|
const serializable = {
|
|
9040
9189
|
defaultTeam: contents.defaultTeam,
|
|
9041
9190
|
teams: contents.teams,
|
|
9042
9191
|
version: contents.version
|
|
9043
9192
|
};
|
|
9044
|
-
await (0,
|
|
9193
|
+
await (0, import_promises8.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
|
|
9045
9194
|
`, { encoding: "utf8", mode: 384 });
|
|
9046
9195
|
}
|
|
9047
9196
|
async function resolveTeam(config, requested) {
|
|
@@ -9071,7 +9220,7 @@ async function assertWorktreeUsable(worktree) {
|
|
|
9071
9220
|
if (!await isDirectory(worktree)) {
|
|
9072
9221
|
throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
|
|
9073
9222
|
}
|
|
9074
|
-
const entries = await (0,
|
|
9223
|
+
const entries = await (0, import_promises8.readdir)(worktree);
|
|
9075
9224
|
if (entries.length > 0) {
|
|
9076
9225
|
const preview = entries.slice(0, 5).join(", ");
|
|
9077
9226
|
const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
|
|
@@ -9095,7 +9244,7 @@ async function walkMemoryFiles(root) {
|
|
|
9095
9244
|
async function visit(path, depth) {
|
|
9096
9245
|
let entries;
|
|
9097
9246
|
try {
|
|
9098
|
-
entries = await (0,
|
|
9247
|
+
entries = await (0, import_promises8.readdir)(path, { withFileTypes: true });
|
|
9099
9248
|
} catch (err) {
|
|
9100
9249
|
console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
|
|
9101
9250
|
return;
|
|
@@ -9104,7 +9253,7 @@ async function walkMemoryFiles(root) {
|
|
|
9104
9253
|
if (entry.name === ".git") {
|
|
9105
9254
|
continue;
|
|
9106
9255
|
}
|
|
9107
|
-
const full = (0,
|
|
9256
|
+
const full = (0, import_node_path9.join)(path, entry.name);
|
|
9108
9257
|
if (entry.isDirectory()) {
|
|
9109
9258
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
9110
9259
|
continue;
|
|
@@ -9128,7 +9277,7 @@ async function walkMemoryFiles(root) {
|
|
|
9128
9277
|
return out;
|
|
9129
9278
|
}
|
|
9130
9279
|
function workfileToVikingUri(config, team, filePath) {
|
|
9131
|
-
const rel = (0,
|
|
9280
|
+
const rel = (0, import_node_path9.relative)(team.worktree, filePath).split(import_node_path9.sep).join("/");
|
|
9132
9281
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
9133
9282
|
}
|
|
9134
9283
|
function isInSharedNamespace(config, uri) {
|
|
@@ -9240,13 +9389,13 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun) {
|
|
|
9240
9389
|
console.log(`Would run: ${formatShellCommand(ov, args)}`);
|
|
9241
9390
|
return;
|
|
9242
9391
|
}
|
|
9243
|
-
const stagingDir = await (0,
|
|
9244
|
-
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");
|
|
9245
9394
|
try {
|
|
9246
|
-
await (0,
|
|
9395
|
+
await (0, import_promises8.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
9247
9396
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode);
|
|
9248
9397
|
} finally {
|
|
9249
|
-
await (0,
|
|
9398
|
+
await (0, import_promises8.rm)(stagingDir, { force: true, recursive: true });
|
|
9250
9399
|
}
|
|
9251
9400
|
}
|
|
9252
9401
|
async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode) {
|
|
@@ -9304,7 +9453,7 @@ ${stdout}`.toLowerCase();
|
|
|
9304
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");
|
|
9305
9454
|
}
|
|
9306
9455
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode) {
|
|
9307
|
-
const content = await (0,
|
|
9456
|
+
const content = await (0, import_promises8.readFile)(filePath, "utf8");
|
|
9308
9457
|
await writeMemoryFile(config, ov, uri, content, initialMode, false);
|
|
9309
9458
|
}
|
|
9310
9459
|
async function removeWithRollback(config, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
|
|
@@ -9346,7 +9495,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
|
|
|
9346
9495
|
if (!relative3) {
|
|
9347
9496
|
return;
|
|
9348
9497
|
}
|
|
9349
|
-
await (0,
|
|
9498
|
+
await (0, import_promises8.rm)((0, import_node_path9.join)(worktree, relative3), { force: true });
|
|
9350
9499
|
}
|
|
9351
9500
|
async function removeMemoryUri(config, ov, uri, dryRun) {
|
|
9352
9501
|
const args = withIdentity(config, ["rm", uri]);
|
|
@@ -9403,8 +9552,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9403
9552
|
const oldRel = entries[index + 1];
|
|
9404
9553
|
const newRel = entries[index + 2];
|
|
9405
9554
|
if (oldRel && newRel) {
|
|
9406
|
-
changes.push({ path: (0,
|
|
9407
|
-
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" });
|
|
9408
9557
|
}
|
|
9409
9558
|
index += 3;
|
|
9410
9559
|
continue;
|
|
@@ -9412,7 +9561,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9412
9561
|
const rel = entries[index + 1];
|
|
9413
9562
|
if (rel) {
|
|
9414
9563
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
9415
|
-
changes.push({ path: (0,
|
|
9564
|
+
changes.push({ path: (0, import_node_path9.join)(worktree, rel), relativePath: rel, status });
|
|
9416
9565
|
}
|
|
9417
9566
|
index += 2;
|
|
9418
9567
|
}
|
|
@@ -9436,32 +9585,32 @@ async function applyChangesToOpenViking(config, team, changes) {
|
|
|
9436
9585
|
if (!await isFile(change.path)) {
|
|
9437
9586
|
continue;
|
|
9438
9587
|
}
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
);
|
|
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}.`);
|
|
9443
9592
|
}
|
|
9444
9593
|
await ensureSharedDirectoryChain(config, ov, uri, false);
|
|
9445
|
-
const writeMode =
|
|
9594
|
+
const writeMode = ovHasResource ? "replace" : "create";
|
|
9446
9595
|
await ingestSingleFile(ov, config, uri, change.path, writeMode);
|
|
9447
9596
|
}
|
|
9448
9597
|
}
|
|
9449
9598
|
|
|
9450
9599
|
// src/lifecycle.ts
|
|
9451
|
-
var
|
|
9452
|
-
var
|
|
9453
|
-
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");
|
|
9454
9603
|
var import_node_os6 = require("node:os");
|
|
9455
|
-
var
|
|
9604
|
+
var import_node_path11 = require("node:path");
|
|
9456
9605
|
var import_node_process2 = require("node:process");
|
|
9457
|
-
var
|
|
9606
|
+
var import_promises12 = require("node:readline/promises");
|
|
9458
9607
|
|
|
9459
9608
|
// src/update.ts
|
|
9460
|
-
var
|
|
9461
|
-
var
|
|
9609
|
+
var import_node_fs5 = require("node:fs");
|
|
9610
|
+
var import_promises9 = require("node:fs/promises");
|
|
9462
9611
|
var import_node_os5 = require("node:os");
|
|
9463
|
-
var
|
|
9464
|
-
var
|
|
9612
|
+
var import_node_path10 = require("node:path");
|
|
9613
|
+
var import_promises10 = require("node:readline/promises");
|
|
9465
9614
|
var import_node_process = require("node:process");
|
|
9466
9615
|
var NPM_PACKAGE_NAME = "threadnote";
|
|
9467
9616
|
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -9509,7 +9658,7 @@ async function runUpdate(config, options) {
|
|
|
9509
9658
|
console.log(`Update available. Run: threadnote update`);
|
|
9510
9659
|
} else {
|
|
9511
9660
|
console.log(
|
|
9512
|
-
|
|
9661
|
+
compareVersions2(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
|
|
9513
9662
|
);
|
|
9514
9663
|
}
|
|
9515
9664
|
return;
|
|
@@ -9597,26 +9746,26 @@ async function maybeRunPostUpdateAfterRepair(config, options) {
|
|
|
9597
9746
|
async function getUpdateInfo(config, options) {
|
|
9598
9747
|
const currentVersion = await currentPackageVersion();
|
|
9599
9748
|
const cached = options.preferFresh ? void 0 : await readFreshCache(config, options.registry);
|
|
9600
|
-
const latestVersion = cached?.latestVersion ?? await
|
|
9749
|
+
const latestVersion = cached?.latestVersion ?? await fetchLatestVersion2(options.registry);
|
|
9601
9750
|
if (!cached && options.allowCacheWrite) {
|
|
9602
|
-
await
|
|
9751
|
+
await writeUpdateCache2(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
|
|
9603
9752
|
}
|
|
9604
9753
|
return {
|
|
9605
9754
|
currentVersion,
|
|
9606
|
-
isUpdateAvailable:
|
|
9755
|
+
isUpdateAvailable: compareVersions2(currentVersion, latestVersion) < 0,
|
|
9607
9756
|
latestVersion,
|
|
9608
9757
|
registry: options.registry
|
|
9609
9758
|
};
|
|
9610
9759
|
}
|
|
9611
9760
|
async function currentPackageVersion() {
|
|
9612
|
-
const rawPackage = await (0,
|
|
9761
|
+
const rawPackage = await (0, import_promises9.readFile)((0, import_node_path10.join)(toolRoot(), "package.json"), "utf8");
|
|
9613
9762
|
const parsed = JSON.parse(rawPackage);
|
|
9614
9763
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
9615
9764
|
throw new Error("Could not read current threadnote package version.");
|
|
9616
9765
|
}
|
|
9617
9766
|
return parsed.version;
|
|
9618
9767
|
}
|
|
9619
|
-
async function
|
|
9768
|
+
async function fetchLatestVersion2(registry) {
|
|
9620
9769
|
const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
|
|
9621
9770
|
const controller = new AbortController();
|
|
9622
9771
|
const timeout = setTimeout(() => {
|
|
@@ -9657,13 +9806,13 @@ async function readFreshCache(config, registry) {
|
|
|
9657
9806
|
return void 0;
|
|
9658
9807
|
}
|
|
9659
9808
|
}
|
|
9660
|
-
async function
|
|
9809
|
+
async function writeUpdateCache2(config, cache) {
|
|
9661
9810
|
await ensureDirectory(config.agentContextHome, false);
|
|
9662
|
-
await (0,
|
|
9811
|
+
await (0, import_promises9.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
9663
9812
|
`, { encoding: "utf8", mode: 384 });
|
|
9664
9813
|
}
|
|
9665
9814
|
function updateCachePath(config) {
|
|
9666
|
-
return (0,
|
|
9815
|
+
return (0, import_node_path10.join)(config.agentContextHome, "update-check.json");
|
|
9667
9816
|
}
|
|
9668
9817
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
9669
9818
|
const state = await readPostUpdateState(config);
|
|
@@ -9713,10 +9862,10 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
9713
9862
|
if (handled.has(migration.id)) {
|
|
9714
9863
|
continue;
|
|
9715
9864
|
}
|
|
9716
|
-
if (
|
|
9865
|
+
if (compareVersions2(options.fromVersion, migration.introducedIn) >= 0) {
|
|
9717
9866
|
continue;
|
|
9718
9867
|
}
|
|
9719
|
-
if (
|
|
9868
|
+
if (compareVersions2(migration.introducedIn, options.toVersion) > 0) {
|
|
9720
9869
|
continue;
|
|
9721
9870
|
}
|
|
9722
9871
|
if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
|
|
@@ -9727,7 +9876,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
9727
9876
|
return applicable;
|
|
9728
9877
|
}
|
|
9729
9878
|
async function readPostUpdateMigrations() {
|
|
9730
|
-
const raw = await readFileIfExists((0,
|
|
9879
|
+
const raw = await readFileIfExists((0, import_node_path10.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
9731
9880
|
if (!raw) {
|
|
9732
9881
|
return [];
|
|
9733
9882
|
}
|
|
@@ -9766,7 +9915,7 @@ function printPostUpdateMigration(migration) {
|
|
|
9766
9915
|
}
|
|
9767
9916
|
}
|
|
9768
9917
|
async function confirmPostUpdateMigration(prompt) {
|
|
9769
|
-
const readline = (0,
|
|
9918
|
+
const readline = (0, import_promises10.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
|
|
9770
9919
|
try {
|
|
9771
9920
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
9772
9921
|
return answer === "y" || answer === "yes";
|
|
@@ -9798,11 +9947,11 @@ async function readPostUpdateState(config) {
|
|
|
9798
9947
|
}
|
|
9799
9948
|
async function writePostUpdateState(config, state) {
|
|
9800
9949
|
await ensureDirectory(config.agentContextHome, false);
|
|
9801
|
-
await (0,
|
|
9950
|
+
await (0, import_promises9.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
9802
9951
|
`, { encoding: "utf8", mode: 384 });
|
|
9803
9952
|
}
|
|
9804
9953
|
function postUpdateStatePath(config) {
|
|
9805
|
-
return (0,
|
|
9954
|
+
return (0, import_node_path10.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
9806
9955
|
}
|
|
9807
9956
|
async function resolveUpdateRuntime(runtime) {
|
|
9808
9957
|
if (runtime !== "auto") {
|
|
@@ -9832,18 +9981,18 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
9832
9981
|
if (runtime === "npm") {
|
|
9833
9982
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
9834
9983
|
const prefix = result.stdout.trim();
|
|
9835
|
-
return prefix ? (0,
|
|
9984
|
+
return prefix ? (0, import_node_path10.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
9836
9985
|
}
|
|
9837
9986
|
if (runtime === "bun") {
|
|
9838
9987
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
9839
9988
|
const binDir = result.stdout.trim();
|
|
9840
|
-
return binDir ? (0,
|
|
9989
|
+
return binDir ? (0, import_node_path10.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
9841
9990
|
}
|
|
9842
|
-
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);
|
|
9843
9992
|
}
|
|
9844
9993
|
async function isExecutable(path) {
|
|
9845
9994
|
try {
|
|
9846
|
-
await (0,
|
|
9995
|
+
await (0, import_promises9.access)(path, import_node_fs5.constants.X_OK);
|
|
9847
9996
|
return true;
|
|
9848
9997
|
} catch (_err) {
|
|
9849
9998
|
return false;
|
|
@@ -9884,9 +10033,9 @@ function updateRegistry() {
|
|
|
9884
10033
|
function isUpdateNotificationDisabled() {
|
|
9885
10034
|
return process.env.CI !== void 0 || process.env.NO_UPDATE_NOTIFIER !== void 0 || process.env.THREADNOTE_NO_UPDATE_CHECK !== void 0;
|
|
9886
10035
|
}
|
|
9887
|
-
function
|
|
9888
|
-
const current =
|
|
9889
|
-
const latest =
|
|
10036
|
+
function compareVersions2(currentVersion, latestVersion) {
|
|
10037
|
+
const current = parseVersion2(currentVersion);
|
|
10038
|
+
const latest = parseVersion2(latestVersion);
|
|
9890
10039
|
for (let index = 0; index < 3; index += 1) {
|
|
9891
10040
|
const difference = current.numbers[index] - latest.numbers[index];
|
|
9892
10041
|
if (difference !== 0) {
|
|
@@ -9904,7 +10053,7 @@ function compareVersions(currentVersion, latestVersion) {
|
|
|
9904
10053
|
}
|
|
9905
10054
|
return current.prerelease.localeCompare(latest.prerelease);
|
|
9906
10055
|
}
|
|
9907
|
-
function
|
|
10056
|
+
function parseVersion2(version) {
|
|
9908
10057
|
const normalized = version.trim().replace(/^v/, "");
|
|
9909
10058
|
const [core2, prerelease] = normalized.split("-", 2);
|
|
9910
10059
|
const parts = core2.split(".").map((part) => Number(part));
|
|
@@ -9934,9 +10083,9 @@ async function runDoctor(config, options) {
|
|
|
9934
10083
|
checks.push(await commandShimCheck());
|
|
9935
10084
|
checks.push(...await userAgentInstructionsChecks());
|
|
9936
10085
|
checks.push(await manifestCheck(config.manifestPath));
|
|
9937
|
-
checks.push(await fileCheck((0,
|
|
9938
|
-
checks.push(await fileCheck((0,
|
|
9939
|
-
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"));
|
|
9940
10089
|
checks.push(await healthCheck(config));
|
|
9941
10090
|
for (const check of checks) {
|
|
9942
10091
|
console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
|
|
@@ -9953,9 +10102,9 @@ async function runInstall(config, options) {
|
|
|
9953
10102
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
9954
10103
|
const dryRun = options.dryRun === true;
|
|
9955
10104
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
9956
|
-
await ensureDirectory((0,
|
|
9957
|
-
await ensureDirectory((0,
|
|
9958
|
-
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);
|
|
9959
10108
|
await installCommandShim(dryRun);
|
|
9960
10109
|
await installUserAgentInstructions(dryRun);
|
|
9961
10110
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
@@ -9981,17 +10130,17 @@ async function runInstall(config, options) {
|
|
|
9981
10130
|
}
|
|
9982
10131
|
await writeTemplateIfMissing({
|
|
9983
10132
|
config,
|
|
9984
|
-
destinationPath: (0,
|
|
10133
|
+
destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ov.conf"),
|
|
9985
10134
|
dryRun,
|
|
9986
10135
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9987
|
-
templatePath: (0,
|
|
10136
|
+
templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
9988
10137
|
});
|
|
9989
10138
|
await writeTemplateIfMissing({
|
|
9990
10139
|
config,
|
|
9991
|
-
destinationPath: (0,
|
|
10140
|
+
destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ovcli.conf"),
|
|
9992
10141
|
dryRun,
|
|
9993
10142
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
9994
|
-
templatePath: (0,
|
|
10143
|
+
templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
9995
10144
|
});
|
|
9996
10145
|
if (options.start !== false) {
|
|
9997
10146
|
const healthy = await repairServerHealth(config, dryRun);
|
|
@@ -10045,7 +10194,7 @@ async function runUninstall(config, options) {
|
|
|
10045
10194
|
}
|
|
10046
10195
|
console.log("Uninstalling local Threadnote setup.");
|
|
10047
10196
|
await runStop(config, { dryRun });
|
|
10048
|
-
await removePathIfExists((0,
|
|
10197
|
+
await removePathIfExists((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
10049
10198
|
await removeLaunchAgent(dryRun);
|
|
10050
10199
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
10051
10200
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -10102,16 +10251,16 @@ async function repairManifest(config, dryRun) {
|
|
|
10102
10251
|
console.log(output2.trimEnd());
|
|
10103
10252
|
return;
|
|
10104
10253
|
}
|
|
10105
|
-
await ensureDirectory((0,
|
|
10254
|
+
await ensureDirectory((0, import_node_path11.dirname)(config.manifestPath), false);
|
|
10106
10255
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
10107
10256
|
if (currentContent !== void 0) {
|
|
10108
10257
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
10109
|
-
await (0,
|
|
10110
|
-
await (0,
|
|
10258
|
+
await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
10259
|
+
await (0, import_promises11.chmod)(backupPath, 384);
|
|
10111
10260
|
console.log(`Backup: ${backupPath}`);
|
|
10112
10261
|
}
|
|
10113
|
-
await (0,
|
|
10114
|
-
await (0,
|
|
10262
|
+
await (0, import_promises11.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
10263
|
+
await (0, import_promises11.chmod)(config.manifestPath, 384);
|
|
10115
10264
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
10116
10265
|
}
|
|
10117
10266
|
async function repairServerHealth(config, dryRun) {
|
|
@@ -10152,16 +10301,16 @@ async function runStart(config, options) {
|
|
|
10152
10301
|
);
|
|
10153
10302
|
}
|
|
10154
10303
|
const logPath = openVikingLogPath(config);
|
|
10155
|
-
await ensureDirectory((0,
|
|
10304
|
+
await ensureDirectory((0, import_node_path11.dirname)(logPath), false);
|
|
10156
10305
|
if (options.foreground === true) {
|
|
10157
10306
|
const result = await runInteractive(server, args);
|
|
10158
10307
|
process.exitCode = result;
|
|
10159
10308
|
return;
|
|
10160
10309
|
}
|
|
10161
|
-
const logFd = (0,
|
|
10310
|
+
const logFd = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10162
10311
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
10163
10312
|
child.unref();
|
|
10164
|
-
await (0,
|
|
10313
|
+
await (0, import_promises11.writeFile)((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
10165
10314
|
`, "utf8");
|
|
10166
10315
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
10167
10316
|
if (health) {
|
|
@@ -10176,11 +10325,11 @@ function spawnDetachedServerWithLog(server, args, logFd) {
|
|
|
10176
10325
|
try {
|
|
10177
10326
|
return spawnDetachedServer(server, args, logFd);
|
|
10178
10327
|
} finally {
|
|
10179
|
-
(0,
|
|
10328
|
+
(0, import_node_fs6.closeSync)(logFd);
|
|
10180
10329
|
}
|
|
10181
10330
|
}
|
|
10182
10331
|
function spawnDetachedServer(server, args, logFd) {
|
|
10183
|
-
return (0,
|
|
10332
|
+
return (0, import_node_child_process3.spawn)(server, args, {
|
|
10184
10333
|
detached: true,
|
|
10185
10334
|
stdio: ["ignore", logFd, logFd]
|
|
10186
10335
|
});
|
|
@@ -10194,7 +10343,7 @@ async function runStop(config, options) {
|
|
|
10194
10343
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
10195
10344
|
}
|
|
10196
10345
|
}
|
|
10197
|
-
const pidPath = (0,
|
|
10346
|
+
const pidPath = (0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid");
|
|
10198
10347
|
const pidText = await readFileIfExists(pidPath);
|
|
10199
10348
|
if (!pidText) {
|
|
10200
10349
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -10290,7 +10439,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
10290
10439
|
};
|
|
10291
10440
|
}
|
|
10292
10441
|
async function commandShimCheck() {
|
|
10293
|
-
const shimPath = (0,
|
|
10442
|
+
const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
10294
10443
|
const content = await readFileIfExists(shimPath);
|
|
10295
10444
|
if (content === void 0) {
|
|
10296
10445
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -10356,11 +10505,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
10356
10505
|
async function siblingPythonForExecutable(executablePath) {
|
|
10357
10506
|
let resolvedPath;
|
|
10358
10507
|
try {
|
|
10359
|
-
resolvedPath = await (0,
|
|
10508
|
+
resolvedPath = await (0, import_promises11.realpath)(executablePath);
|
|
10360
10509
|
} catch (_err) {
|
|
10361
10510
|
return void 0;
|
|
10362
10511
|
}
|
|
10363
|
-
const pythonPath = (0,
|
|
10512
|
+
const pythonPath = (0, import_node_path11.join)((0, import_node_path11.dirname)(resolvedPath), "python");
|
|
10364
10513
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
10365
10514
|
}
|
|
10366
10515
|
async function manifestCheck(path) {
|
|
@@ -10431,7 +10580,7 @@ async function offerToInstallUv() {
|
|
|
10431
10580
|
);
|
|
10432
10581
|
return false;
|
|
10433
10582
|
}
|
|
10434
|
-
const readline = (0,
|
|
10583
|
+
const readline = (0, import_promises12.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
10435
10584
|
let answer;
|
|
10436
10585
|
try {
|
|
10437
10586
|
answer = (await readline.question(
|
|
@@ -10554,38 +10703,38 @@ function printInstallNextSteps(options) {
|
|
|
10554
10703
|
}
|
|
10555
10704
|
async function writeTemplateIfMissing(options) {
|
|
10556
10705
|
if (await exists(options.destinationPath)) {
|
|
10557
|
-
const currentContent = await (0,
|
|
10706
|
+
const currentContent = await (0, import_promises11.readFile)(options.destinationPath, "utf8");
|
|
10558
10707
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
10559
10708
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
10560
10709
|
return;
|
|
10561
10710
|
}
|
|
10562
|
-
const rendered2 = renderTemplate(await (0,
|
|
10711
|
+
const rendered2 = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
|
|
10563
10712
|
if (options.dryRun) {
|
|
10564
10713
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
10565
10714
|
return;
|
|
10566
10715
|
}
|
|
10567
10716
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
10568
|
-
await (0,
|
|
10569
|
-
await (0,
|
|
10570
|
-
await (0,
|
|
10571
|
-
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);
|
|
10572
10721
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
10573
10722
|
console.log(`Backup: ${backupPath}`);
|
|
10574
10723
|
return;
|
|
10575
10724
|
}
|
|
10576
|
-
const rendered = renderTemplate(await (0,
|
|
10725
|
+
const rendered = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
|
|
10577
10726
|
if (options.dryRun) {
|
|
10578
10727
|
console.log(`Would write ${options.destinationPath}`);
|
|
10579
10728
|
return;
|
|
10580
10729
|
}
|
|
10581
|
-
await ensureDirectory((0,
|
|
10582
|
-
await (0,
|
|
10583
|
-
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);
|
|
10584
10733
|
console.log(`Wrote ${options.destinationPath}`);
|
|
10585
10734
|
}
|
|
10586
10735
|
async function installCommandShim(dryRun) {
|
|
10587
10736
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
10588
|
-
const shimPath = (0,
|
|
10737
|
+
const shimPath = (0, import_node_path11.join)(binDir, "threadnote");
|
|
10589
10738
|
const existingContent = await readFileIfExists(shimPath);
|
|
10590
10739
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
10591
10740
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -10601,12 +10750,12 @@ async function installCommandShim(dryRun) {
|
|
|
10601
10750
|
return;
|
|
10602
10751
|
}
|
|
10603
10752
|
await ensureDirectory(binDir, false);
|
|
10604
|
-
await (0,
|
|
10605
|
-
await (0,
|
|
10753
|
+
await (0, import_promises11.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
10754
|
+
await (0, import_promises11.chmod)(shimPath, 493);
|
|
10606
10755
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
10607
10756
|
}
|
|
10608
10757
|
async function removeCommandShim(dryRun) {
|
|
10609
|
-
const shimPath = (0,
|
|
10758
|
+
const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
10610
10759
|
const content = await readFileIfExists(shimPath);
|
|
10611
10760
|
if (content === void 0) {
|
|
10612
10761
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -10640,8 +10789,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
10640
10789
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
10641
10790
|
continue;
|
|
10642
10791
|
}
|
|
10643
|
-
await ensureDirectory((0,
|
|
10644
|
-
await (0,
|
|
10792
|
+
await ensureDirectory((0, import_node_path11.dirname)(targetPath), false);
|
|
10793
|
+
await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10645
10794
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
10646
10795
|
}
|
|
10647
10796
|
}
|
|
@@ -10678,7 +10827,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
10678
10827
|
console.log(`Would update ${targetPath}`);
|
|
10679
10828
|
continue;
|
|
10680
10829
|
}
|
|
10681
|
-
await (0,
|
|
10830
|
+
await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
10682
10831
|
console.log(`Updated ${targetPath}`);
|
|
10683
10832
|
}
|
|
10684
10833
|
}
|
|
@@ -10698,7 +10847,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
10698
10847
|
].join("\n");
|
|
10699
10848
|
}
|
|
10700
10849
|
async function renderUserAgentInstructionsBlock() {
|
|
10701
|
-
const instructions = (await (0,
|
|
10850
|
+
const instructions = (await (0, import_promises11.readFile)((0, import_node_path11.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
10702
10851
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
10703
10852
|
${instructions}
|
|
10704
10853
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -10776,9 +10925,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10776
10925
|
if ((0, import_node_os6.platform)() !== "darwin") {
|
|
10777
10926
|
throw new Error("launchd autostart is only supported on macOS.");
|
|
10778
10927
|
}
|
|
10779
|
-
const source = (0,
|
|
10928
|
+
const source = (0, import_node_path11.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
10780
10929
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
10781
|
-
const rendered = renderTemplate(await (0,
|
|
10930
|
+
const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config);
|
|
10782
10931
|
if (dryRun) {
|
|
10783
10932
|
console.log(`Would write ${destination}`);
|
|
10784
10933
|
console.log(`Would run: launchctl unload ${destination}`);
|
|
@@ -10786,9 +10935,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
10786
10935
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
10787
10936
|
return;
|
|
10788
10937
|
}
|
|
10789
|
-
await ensureDirectory((0,
|
|
10790
|
-
await ensureDirectory((0,
|
|
10791
|
-
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");
|
|
10792
10941
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
10793
10942
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
10794
10943
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -10851,7 +11000,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
10851
11000
|
if (typeof parsed.default_user !== "string") {
|
|
10852
11001
|
return false;
|
|
10853
11002
|
}
|
|
10854
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
11003
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path11.join)(config.agentContextHome, "data")) {
|
|
10855
11004
|
return false;
|
|
10856
11005
|
}
|
|
10857
11006
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|