threadnote 0.7.4 → 0.7.5

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.
@@ -6,7 +6,7 @@
6
6
  <string>io.threadnote.openviking</string>
7
7
  <key>ProgramArguments</key>
8
8
  <array>
9
- <string>openviking-server</string>
9
+ <string>{{OPENVIKING_SERVER_PATH}}</string>
10
10
  <string>--config</string>
11
11
  <string>{{THREADNOTE_HOME}}/ov.conf</string>
12
12
  <string>--host</string>
@@ -3850,6 +3850,27 @@ async function exists(path) {
3850
3850
  return false;
3851
3851
  }
3852
3852
  }
3853
+ async function isExecutable(path) {
3854
+ try {
3855
+ await (0, import_promises.access)(path, import_node_fs.constants.X_OK);
3856
+ return true;
3857
+ } catch (_err) {
3858
+ return false;
3859
+ }
3860
+ }
3861
+ function suggestedShellRc(shellPath, currentPlatform) {
3862
+ const shell = shellPath ?? "";
3863
+ if (shell.endsWith("/zsh")) {
3864
+ return "~/.zshrc";
3865
+ }
3866
+ if (shell.endsWith("/bash")) {
3867
+ return currentPlatform === "darwin" ? "~/.bash_profile" : "~/.bashrc";
3868
+ }
3869
+ if (shell.endsWith("/fish")) {
3870
+ return "~/.config/fish/config.fish";
3871
+ }
3872
+ return "your shell rc";
3873
+ }
3853
3874
  async function isFile(path) {
3854
3875
  try {
3855
3876
  return (await (0, import_promises.stat)(path)).isFile();
@@ -7258,8 +7279,12 @@ function openVikingServerArgs(config) {
7258
7279
  function withIdentity(config, args) {
7259
7280
  return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
7260
7281
  }
7261
- function renderTemplate(template, config) {
7262
- return template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
7282
+ function renderTemplate(template, config, extras = {}) {
7283
+ let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
7284
+ for (const [key, value] of Object.entries(extras)) {
7285
+ rendered = rendered.replaceAll(`{{${key}}}`, () => value);
7286
+ }
7287
+ return rendered;
7263
7288
  }
7264
7289
 
7265
7290
  // src/share.ts
@@ -10513,14 +10538,6 @@ async function runtimeThreadnoteBin(runtime) {
10513
10538
  }
10514
10539
  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);
10515
10540
  }
10516
- async function isExecutable(path) {
10517
- try {
10518
- await (0, import_promises9.access)(path, import_node_fs5.constants.X_OK);
10519
- return true;
10520
- } catch (_err) {
10521
- return false;
10522
- }
10523
- }
10524
10541
  function updatePackageCommand(runtime, registry) {
10525
10542
  if (runtime === "npm") {
10526
10543
  return { executable: "npm", args: ["install", "--global", `${NPM_PACKAGE_NAME}@latest`, `--registry=${registry}`] };
@@ -10564,7 +10581,7 @@ async function runDoctor(config, options) {
10564
10581
  checks.push({ name: "platform", status: (0, import_node_os6.platform)() === "darwin" ? "ok" : "warn", detail: (0, import_node_os6.platform)() });
10565
10582
  checks.push(await commandCheck("node", ["--version"]));
10566
10583
  checks.push(await commandCheck("python3", ["--version"]));
10567
- checks.push(await commandCheck("openviking-server", ["--help"]));
10584
+ checks.push(await openVikingServerCheck());
10568
10585
  checks.push(await firstCommandCheck("openviking cli", ["ov", "openviking"], ["--help"]));
10569
10586
  checks.push(await localEmbeddingCheck());
10570
10587
  checks.push(await pythonSystemCertificatesCheck());
@@ -10598,7 +10615,8 @@ async function runInstall(config, options) {
10598
10615
  await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "mcp"), dryRun);
10599
10616
  await installCommandShim(dryRun);
10600
10617
  await installUserAgentInstructions(dryRun);
10601
- const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
10618
+ const serverPath = await findOpenVikingServer();
10619
+ let serverInstallRan = false;
10602
10620
  if (serverPath) {
10603
10621
  console.log(`OpenViking server already installed: ${serverPath}`);
10604
10622
  const localEmbeddingMissing = await hasLocalEmbeddingDependency(serverPath) === false;
@@ -10606,6 +10624,7 @@ async function runInstall(config, options) {
10606
10624
  if (options.force === true) {
10607
10625
  console.log(`Reinstalling OpenViking at pinned version ${config.openVikingVersion} (--force).`);
10608
10626
  await runInstallCommands(config, options.packageManager, true, dryRun);
10627
+ serverInstallRan = true;
10609
10628
  } else if (localEmbeddingMissing) {
10610
10629
  const repairReasons = [];
10611
10630
  repairReasons.push("local embedding extra is missing");
@@ -10614,6 +10633,7 @@ async function runInstall(config, options) {
10614
10633
  }
10615
10634
  console.log(`OpenViking install needs repair: ${repairReasons.join("; ")}.`);
10616
10635
  await runInstallCommands(config, options.packageManager, true, dryRun);
10636
+ serverInstallRan = true;
10617
10637
  } else if (pythonSystemCertificatesMissing) {
10618
10638
  console.log("OpenViking install needs repair: Python system certificate bridge is missing.");
10619
10639
  const installCommand = await getPythonSystemCertificatesInstallCommand(serverPath);
@@ -10621,6 +10641,11 @@ async function runInstall(config, options) {
10621
10641
  }
10622
10642
  } else {
10623
10643
  await runInstallCommands(config, options.packageManager, false, dryRun);
10644
+ serverInstallRan = true;
10645
+ }
10646
+ const resolvedServerPath = serverInstallRan ? await findOpenVikingServer() : serverPath;
10647
+ if (resolvedServerPath && !dryRun) {
10648
+ await maybePrintOpenVikingPathHint(resolvedServerPath);
10624
10649
  }
10625
10650
  await writeTemplateIfMissing({
10626
10651
  config,
@@ -10757,6 +10782,64 @@ async function repairManifest(config, dryRun) {
10757
10782
  await (0, import_promises11.chmod)(config.manifestPath, 384);
10758
10783
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
10759
10784
  }
10785
+ async function findOpenVikingServer() {
10786
+ const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
10787
+ if (onPath) {
10788
+ return onPath;
10789
+ }
10790
+ for (const candidateDir of await openVikingServerCandidateDirs()) {
10791
+ const candidate = (0, import_node_path11.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
10792
+ if (await isExecutable(candidate)) {
10793
+ return candidate;
10794
+ }
10795
+ }
10796
+ return void 0;
10797
+ }
10798
+ var candidateDirsPromise;
10799
+ async function openVikingServerCandidateDirs() {
10800
+ if (!candidateDirsPromise) {
10801
+ candidateDirsPromise = computeOpenVikingServerCandidateDirs();
10802
+ }
10803
+ return candidateDirsPromise;
10804
+ }
10805
+ async function computeOpenVikingServerCandidateDirs() {
10806
+ const dirs = [];
10807
+ const uv = await findExecutable(["uv"]);
10808
+ if (uv) {
10809
+ const result = await runCommand(uv, ["tool", "dir", "--bin"], { allowFailure: true });
10810
+ if (result.exitCode === 0) {
10811
+ const dir = result.stdout.trim();
10812
+ if (dir) {
10813
+ dirs.push(dir);
10814
+ }
10815
+ }
10816
+ }
10817
+ if (process.env.UV_TOOL_BIN_DIR) {
10818
+ dirs.push(process.env.UV_TOOL_BIN_DIR);
10819
+ }
10820
+ dirs.push(expandPath("~/.local/bin"));
10821
+ return Array.from(new Set(dirs));
10822
+ }
10823
+ async function requireOpenVikingServer() {
10824
+ const resolved = await findOpenVikingServer();
10825
+ if (!resolved) {
10826
+ throw new Error(
10827
+ `${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
10828
+ );
10829
+ }
10830
+ return resolved;
10831
+ }
10832
+ async function maybePrintOpenVikingPathHint(serverPath) {
10833
+ const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
10834
+ if (onPath) {
10835
+ return;
10836
+ }
10837
+ const binDir = (0, import_node_path11.dirname)(serverPath);
10838
+ const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
10839
+ console.log(
10840
+ `Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
10841
+ );
10842
+ }
10760
10843
  async function repairServerHealth(config, dryRun) {
10761
10844
  const existingHealth = await readOpenVikingHealthIfAvailable(config, 800);
10762
10845
  if (existingHealth) {
@@ -10777,7 +10860,7 @@ async function runStart(config, options) {
10777
10860
  await installLaunchAgent(config, options.dryRun === true);
10778
10861
  return;
10779
10862
  }
10780
- const server = options.dryRun === true ? await findExecutable([OPENVIKING_SERVER_COMMAND]) ?? OPENVIKING_SERVER_COMMAND : await requiredExecutable(OPENVIKING_SERVER_COMMAND);
10863
+ const server = options.dryRun === true ? await findOpenVikingServer() ?? OPENVIKING_SERVER_COMMAND : await requireOpenVikingServer();
10781
10864
  const args = openVikingServerArgs(config);
10782
10865
  if (options.dryRun === true) {
10783
10866
  console.log(formatShellCommand(server, args));
@@ -10871,6 +10954,21 @@ async function commandCheck(name, args) {
10871
10954
  detail: firstLine(result.stdout || result.stderr) || executable
10872
10955
  };
10873
10956
  }
10957
+ async function openVikingServerCheck() {
10958
+ const name = OPENVIKING_SERVER_COMMAND;
10959
+ const executable = await findOpenVikingServer();
10960
+ if (!executable) {
10961
+ return { name, status: "fail", detail: "missing; install will fetch it via uv or pipx" };
10962
+ }
10963
+ const result = await runCommand(executable, ["--help"], { allowFailure: true });
10964
+ const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
10965
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path11.dirname)(executable)} to PATH)`;
10966
+ return {
10967
+ name,
10968
+ status: result.exitCode === 0 ? "ok" : "warn",
10969
+ detail: result.exitCode === 0 ? detail : firstLine(result.stderr || result.stdout) || detail
10970
+ };
10971
+ }
10874
10972
  async function commandPresenceCheck(name, args) {
10875
10973
  const executable = await findExecutable([name]);
10876
10974
  if (!executable) {
@@ -10899,7 +10997,7 @@ async function firstCommandCheck(name, commands, args) {
10899
10997
  return { name, status: "fail", detail: `none found: ${commands.join(", ")}` };
10900
10998
  }
10901
10999
  async function localEmbeddingCheck() {
10902
- const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
11000
+ const serverPath = await findOpenVikingServer();
10903
11001
  if (!serverPath) {
10904
11002
  return { name: "local embedding extra", status: "warn", detail: "openviking-server missing" };
10905
11003
  }
@@ -10918,7 +11016,7 @@ async function localEmbeddingCheck() {
10918
11016
  };
10919
11017
  }
10920
11018
  async function pythonSystemCertificatesCheck() {
10921
- const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
11019
+ const serverPath = await findOpenVikingServer();
10922
11020
  if (!serverPath) {
10923
11021
  return { name: "python system certs", status: "warn", detail: "openviking-server missing" };
10924
11022
  }
@@ -11099,6 +11197,7 @@ async function installUv() {
11099
11197
  console.log(result.stdout.trim());
11100
11198
  }
11101
11199
  if (await findExecutable(["uv"])) {
11200
+ candidateDirsPromise = void 0;
11102
11201
  return true;
11103
11202
  }
11104
11203
  } else {
@@ -11115,6 +11214,7 @@ async function installUv() {
11115
11214
  console.log(result.stdout.trim());
11116
11215
  }
11117
11216
  if (await findExecutable(["uv"])) {
11217
+ candidateDirsPromise = void 0;
11118
11218
  return true;
11119
11219
  }
11120
11220
  console.warn(
@@ -11419,10 +11519,20 @@ async function installLaunchAgent(config, dryRun) {
11419
11519
  if ((0, import_node_os6.platform)() !== "darwin") {
11420
11520
  throw new Error("launchd autostart is only supported on macOS.");
11421
11521
  }
11522
+ const resolvedServer = await findOpenVikingServer();
11523
+ if (!resolvedServer && !dryRun) {
11524
+ throw new Error(
11525
+ `Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
11526
+ );
11527
+ }
11422
11528
  const source = (0, import_node_path11.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
11423
11529
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
11424
- const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config);
11530
+ const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config, {
11531
+ OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
11532
+ });
11425
11533
  if (dryRun) {
11534
+ const resolutionDetail = resolvedServer ?? `<not found; would use bare \`${OPENVIKING_SERVER_COMMAND}\`>`;
11535
+ console.log(`Resolved openviking-server: ${resolutionDetail}`);
11426
11536
  console.log(`Would write ${destination}`);
11427
11537
  console.log(`Would run: launchctl unload ${destination}`);
11428
11538
  console.log(`Would run: launchctl load ${destination}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",
@@ -43,18 +43,21 @@
43
43
  "clean": "rm -rf dist",
44
44
  "dev": "tsx src/threadnote.ts",
45
45
  "dev:mcp-server": "tsx src/mcp_server.ts",
46
- "lint": "eslint \"src/**/*.ts\"",
47
- "lint:fix": "eslint \"src/**/*.ts\" --fix",
46
+ "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
47
+ "lint:fix": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix",
48
48
  "prepare": "husky",
49
- "precommit": "npm run lint && npm run prettier:check",
49
+ "precommit": "npm run lint && npm run prettier:check && npm run test",
50
50
  "prepack": "npm run build",
51
51
  "prettier:check": "prettier --check \"**/*.{ts,js,cjs,json,md,yaml,yml}\"",
52
52
  "prettier:write": "prettier --write \"**/*.{ts,js,cjs,json,md,yaml,yml}\"",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "test:coverage": "vitest run --coverage",
53
56
  "threadnote": "npm run build --silent && node ./bin/threadnote.cjs",
54
57
  "doctor": "npm run build --silent && node ./bin/threadnote.cjs doctor",
55
58
  "mcp-server": "npm run build --silent && node ./bin/threadnote-mcp-server.cjs",
56
59
  "pack:dry-run": "npm pack --dry-run",
57
- "typecheck": "tsc --noEmit"
60
+ "typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit"
58
61
  },
59
62
  "engines": {
60
63
  "node": ">=20"
@@ -64,6 +67,7 @@
64
67
  "@modelcontextprotocol/sdk": "^1.29.0",
65
68
  "@types/js-yaml": "^4.0.9",
66
69
  "@types/node": "^25.6.0",
70
+ "@vitest/coverage-v8": "^4.1.7",
67
71
  "commander": "^14.0.3",
68
72
  "esbuild": "^0.27.7",
69
73
  "eslint": "^10.3.0",
@@ -74,6 +78,7 @@
74
78
  "tsx": "^4.21.0",
75
79
  "typescript": "^6.0.3",
76
80
  "typescript-eslint": "^8.59.2",
81
+ "vitest": "^4.1.7",
77
82
  "zod": "^4.4.3"
78
83
  }
79
84
  }