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 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
 
@@ -3522,8 +3522,8 @@ var DEFAULT_SEED_PATTERNS = [
3522
3522
  ];
3523
3523
 
3524
3524
  // src/hooks.ts
3525
- var import_promises5 = require("node:fs/promises");
3526
- var import_node_path5 = require("node:path");
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, import_promises5.readFile)(path, "utf8") : "{}";
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, import_promises5.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
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, import_promises5.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
8166
- await (0, import_promises5.chmod)(path, 384);
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, import_promises5.readFile)(path, "utf8");
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, import_node_path5.basename)(repoRoot) : "general";
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, import_node_path5.basename)(repoRoot);
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 import_promises6 = require("node:fs/promises");
8306
- var import_node_path6 = require("node:path");
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, import_node_path6.join)(config.agentContextHome, USER_MANIFEST_NAME)
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, import_node_path6.dirname)(manifestPath), false);
8379
- await (0, import_promises6.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
8380
- await (0, import_promises6.chmod)(manifestPath, 384);
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, import_promises6.realpath)(expanded);
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, import_node_path6.basename)(repoRoot));
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, import_node_path6.relative)(projectRoot, filePath));
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, import_node_path6.join)(projectRoot, normalizedPattern);
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, import_node_path6.join)(projectRoot, globBase);
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, import_node_path6.relative)(projectRoot, filePath))));
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, import_promises6.readFile)(candidate.filePath, "utf8");
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, import_node_path6.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
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, import_node_path6.dirname)(redactedPath), false);
8487
- await (0, import_promises6.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
8488
- await (0, import_promises6.chmod)(redactedPath, 384);
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, import_promises6.readFile)(filePath, "utf8");
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, import_node_path6.basename)((0, import_node_path6.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
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, import_promises6.readFile)((0, import_node_path6.join)(toolRoot(), ".threadnoteignore"), "utf8");
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 import_promises7 = require("node:fs/promises");
8767
+ var import_promises8 = require("node:fs/promises");
8619
8768
  var import_node_os4 = require("node:os");
8620
- var import_node_path7 = require("node:path");
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, import_node_path7.dirname)(worktree), dryRun);
8688
- await ensureDirectory((0, import_node_path7.dirname)(gitdir), dryRun);
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, import_node_path7.join)(worktree, ".gitignore");
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, import_promises7.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
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, import_node_path7.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path7.join)(team.config.gitdir, "rebase-apply"))) {
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, import_node_path7.join)(config.agentContextHome, "share", "teams.json");
9128
+ return (0, import_node_path9.join)(config.agentContextHome, "share", "teams.json");
8980
9129
  }
8981
9130
  function teamWorktreePath(config, team) {
8982
- return (0, import_node_path7.join)(
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, import_node_path7.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
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, import_promises7.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
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, import_promises7.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
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, import_promises7.readdir)(worktree);
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, import_promises7.readdir)(path, { withFileTypes: true });
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, import_node_path7.join)(path, entry.name);
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, import_node_path7.relative)(team.worktree, filePath).split(import_node_path7.sep).join("/");
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, import_promises7.mkdtemp)((0, import_node_path7.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9244
- const tempPath = (0, import_node_path7.join)(stagingDir, "body.txt");
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, import_promises7.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
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, import_promises7.rm)(stagingDir, { force: true, recursive: true });
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, import_promises7.readFile)(filePath, "utf8");
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, import_promises7.rm)((0, import_node_path7.join)(worktree, relative3), { force: true });
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, import_node_path7.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9407
- changes.push({ path: (0, import_node_path7.join)(worktree, newRel), relativePath: newRel, status: "added" });
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, import_node_path7.join)(worktree, rel), relativePath: rel, status });
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
- if (change.status === "modified" && await vikingResourceExists2(ov, config, uri)) {
9440
- console.warn(
9441
- `share sync: overwriting local ${uri} with the upstream version (local edits to the shared subtree are not preserved across sync).`
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 = change.status === "modified" ? "replace" : "create";
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 import_node_child_process2 = require("node:child_process");
9452
- var import_node_fs5 = require("node:fs");
9453
- var import_promises10 = require("node:fs/promises");
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 import_node_path9 = require("node:path");
9604
+ var import_node_path11 = require("node:path");
9456
9605
  var import_node_process2 = require("node:process");
9457
- var import_promises11 = require("node:readline/promises");
9606
+ var import_promises12 = require("node:readline/promises");
9458
9607
 
9459
9608
  // src/update.ts
9460
- var import_node_fs4 = require("node:fs");
9461
- var import_promises8 = require("node:fs/promises");
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 import_node_path8 = require("node:path");
9464
- var import_promises9 = require("node:readline/promises");
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
- compareVersions(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
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 fetchLatestVersion(options.registry);
9749
+ const latestVersion = cached?.latestVersion ?? await fetchLatestVersion2(options.registry);
9601
9750
  if (!cached && options.allowCacheWrite) {
9602
- await writeUpdateCache(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
9751
+ await writeUpdateCache2(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
9603
9752
  }
9604
9753
  return {
9605
9754
  currentVersion,
9606
- isUpdateAvailable: compareVersions(currentVersion, latestVersion) < 0,
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, import_promises8.readFile)((0, import_node_path8.join)(toolRoot(), "package.json"), "utf8");
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 fetchLatestVersion(registry) {
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 writeUpdateCache(config, cache) {
9809
+ async function writeUpdateCache2(config, cache) {
9661
9810
  await ensureDirectory(config.agentContextHome, false);
9662
- await (0, import_promises8.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
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, import_node_path8.join)(config.agentContextHome, "update-check.json");
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 (compareVersions(options.fromVersion, migration.introducedIn) >= 0) {
9865
+ if (compareVersions2(options.fromVersion, migration.introducedIn) >= 0) {
9717
9866
  continue;
9718
9867
  }
9719
- if (compareVersions(migration.introducedIn, options.toVersion) > 0) {
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, import_node_path8.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
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, import_promises9.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
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, import_promises8.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
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, import_node_path8.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
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, import_node_path8.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 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, import_node_path8.join)(binDir, NPM_PACKAGE_NAME) : void 0;
9989
+ return binDir ? (0, import_node_path10.join)(binDir, NPM_PACKAGE_NAME) : void 0;
9841
9990
  }
9842
- return (0, import_node_path8.join)(process.env.DENO_INSTALL ?? (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
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, import_promises8.access)(path, import_node_fs4.constants.X_OK);
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 compareVersions(currentVersion, latestVersion) {
9888
- const current = parseVersion(currentVersion);
9889
- const latest = parseVersion(latestVersion);
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 parseVersion(version) {
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, import_node_path9.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
9938
- checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
9939
- checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
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, import_node_path9.join)(config.agentContextHome, "logs"), dryRun);
9957
- await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "redacted"), dryRun);
9958
- await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "mcp"), dryRun);
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, import_node_path9.join)(config.agentContextHome, "ov.conf"),
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, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json")
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, import_node_path9.join)(config.agentContextHome, "ovcli.conf"),
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, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json")
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, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
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, import_node_path9.dirname)(config.manifestPath), false);
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, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10110
- await (0, import_promises10.chmod)(backupPath, 384);
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, import_promises10.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
10114
- await (0, import_promises10.chmod)(config.manifestPath, 384);
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, import_node_path9.dirname)(logPath), false);
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, import_node_fs5.openSync)(logPath, "a");
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, import_promises10.writeFile)((0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
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, import_node_fs5.closeSync)(logFd);
10328
+ (0, import_node_fs6.closeSync)(logFd);
10180
10329
  }
10181
10330
  }
10182
10331
  function spawnDetachedServer(server, args, logFd) {
10183
- return (0, import_node_child_process2.spawn)(server, args, {
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, import_node_path9.join)(config.agentContextHome, "openviking-server.pid");
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, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
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, import_promises10.realpath)(executablePath);
10508
+ resolvedPath = await (0, import_promises11.realpath)(executablePath);
10360
10509
  } catch (_err) {
10361
10510
  return void 0;
10362
10511
  }
10363
- const pythonPath = (0, import_node_path9.join)((0, import_node_path9.dirname)(resolvedPath), "python");
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, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
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, import_promises10.readFile)(options.destinationPath, "utf8");
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, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
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, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10569
- await (0, import_promises10.chmod)(backupPath, 384);
10570
- await (0, import_promises10.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
10571
- await (0, import_promises10.chmod)(options.destinationPath, 384);
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, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
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, import_node_path9.dirname)(options.destinationPath), false);
10582
- await (0, import_promises10.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
10583
- await (0, import_promises10.chmod)(options.destinationPath, 384);
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, import_node_path9.join)(binDir, "threadnote");
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, import_promises10.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
10605
- await (0, import_promises10.chmod)(shimPath, 493);
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, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
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, import_node_path9.dirname)(targetPath), false);
10644
- await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
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, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
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, import_promises10.readFile)((0, import_node_path9.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
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, import_node_path9.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
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, import_promises10.readFile)(source, "utf8"), config);
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, import_node_path9.dirname)(destination), false);
10790
- await ensureDirectory((0, import_node_path9.dirname)(openVikingLogPath(config)), false);
10791
- await (0, import_promises10.writeFile)(destination, rendered, "utf8");
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, import_node_path9.join)(config.agentContextHome, "data")) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",