@primitive.ai/prim 0.1.0-alpha.62 → 0.1.0-alpha.63

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/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  checkAffectedDecisions,
14
14
  daemonOrDirectGet,
15
15
  formatDecisionsWarning
16
- } from "./chunk-YITJ2H3B.js";
16
+ } from "./chunk-WS3MCNFD.js";
17
17
  import {
18
18
  JOURNAL_DIR,
19
19
  SESSIONS_DIR,
@@ -22,7 +22,7 @@ import {
22
22
  listFlushing,
23
23
  pendingJournalStats,
24
24
  readMovesFromPath
25
- } from "./chunk-S4LFRVGD.js";
25
+ } from "./chunk-HIMWM526.js";
26
26
  import {
27
27
  bindRepository,
28
28
  compareSemver,
@@ -34,10 +34,10 @@ import {
34
34
  registerSkillCommands,
35
35
  uninstallPostCommitHookAtPath,
36
36
  uninstallProjectPostCommitHook
37
- } from "./chunk-LOGW54W5.js";
37
+ } from "./chunk-WNIY6Z6N.js";
38
38
  import {
39
39
  fetchFeedbackCapability
40
- } from "./chunk-R4CUX6FY.js";
40
+ } from "./chunk-KHZNYEUV.js";
41
41
  import {
42
42
  inspectWorkspaceId
43
43
  } from "./chunk-UVIWZWM6.js";
@@ -73,7 +73,7 @@ import {
73
73
  resolveAuthCredential,
74
74
  setStoredToken,
75
75
  withFileLock
76
- } from "./chunk-I3HIPAIR.js";
76
+ } from "./chunk-CPX3BUOC.js";
77
77
  import {
78
78
  daemonIsLive,
79
79
  daemonRequest
@@ -104,6 +104,7 @@ async function applyActivation(active) {
104
104
  bound = await bindRepository(root);
105
105
  }
106
106
  setRepoActive(root, active);
107
+ await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
107
108
  process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
108
109
  `);
109
110
  printJson({ active, repo: root, ...bound, ...postCommitHook ? { postCommitHook } : {} });
@@ -671,7 +672,7 @@ function normalizeApiUrl(url) {
671
672
 
672
673
  // src/daemon/launchd.ts
673
674
  var LAUNCHD_LABEL = "ai.getprimitive.prim-daemon";
674
- var RUNTIME_SCHEMA_VERSION = 2;
675
+ var RUNTIME_SCHEMA_VERSION = 3;
675
676
  var RUNTIME_DIR_MODE = 448;
676
677
  var RUNTIME_FILE_MODE = 384;
677
678
  var RUNTIME_LAUNCHER_MODE = 448;
@@ -751,7 +752,7 @@ function findPackageVersion(entryFile) {
751
752
  function readRuntimeManifest(path) {
752
753
  try {
753
754
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
754
- if (parsed.schemaVersion !== RUNTIME_SCHEMA_VERSION || typeof parsed.version !== "string" || typeof parsed.nodePath !== "string" || parsed.daemonFile !== "prim-daemon-server" || typeof parsed.daemonSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(parsed.daemonSha256) || parsed.statuslineFile !== void 0 && parsed.statuslineFile !== "prim-statusline" || parsed.statuslineFile === void 0 !== (parsed.statuslineSha256 === void 0) || parsed.statuslineSha256 !== void 0 && !/^[a-f0-9]{64}$/u.test(parsed.statuslineSha256)) {
755
+ if (parsed.schemaVersion !== RUNTIME_SCHEMA_VERSION || typeof parsed.version !== "string" || typeof parsed.nodePath !== "string" || parsed.daemonFile !== "prim-daemon-server" || typeof parsed.daemonSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(parsed.daemonSha256)) {
755
756
  return null;
756
757
  }
757
758
  return parsed;
@@ -759,12 +760,12 @@ function readRuntimeManifest(path) {
759
760
  return null;
760
761
  }
761
762
  }
762
- function sameRuntime(current, desired, daemonPath, statuslinePath) {
763
- if (!current || current.version !== desired.version || current.nodePath !== desired.nodePath || current.daemonSha256 !== desired.daemonSha256 || current.statuslineFile !== desired.statuslineFile || current.statuslineSha256 !== desired.statuslineSha256) {
763
+ function sameRuntime(current, desired, daemonPath) {
764
+ if (!current || current.version !== desired.version || current.nodePath !== desired.nodePath || current.daemonSha256 !== desired.daemonSha256) {
764
765
  return false;
765
766
  }
766
767
  try {
767
- return sha256File(daemonPath) === desired.daemonSha256 && (desired.statuslineSha256 === void 0 || sha256File(statuslinePath) === desired.statuslineSha256);
768
+ return sha256File(daemonPath) === desired.daemonSha256;
768
769
  } catch {
769
770
  return false;
770
771
  }
@@ -788,8 +789,6 @@ function stageRuntime(options = {}) {
788
789
  if (!daemonSource || !existsSync2(daemonSource)) {
789
790
  throw new Error("cannot stage runtime: prim-daemon-server bundle is unavailable");
790
791
  }
791
- const configuredStatusline = options.statuslineSource === void 0 ? binFile("prim-statusline") : options.statuslineSource;
792
- const statuslineSource = configuredStatusline && existsSync2(configuredStatusline) ? configuredStatusline : null;
793
792
  const nodePath = resolve(options.nodePath ?? process.execPath);
794
793
  const version = options.version ?? findPackageVersion(daemonSource);
795
794
  const desired = {
@@ -798,10 +797,6 @@ function stageRuntime(options = {}) {
798
797
  nodePath,
799
798
  daemonFile: "prim-daemon-server",
800
799
  daemonSha256: sha256File(daemonSource),
801
- ...statuslineSource ? {
802
- statuslineFile: "prim-statusline",
803
- statuslineSha256: sha256File(statuslineSource)
804
- } : {},
805
800
  stagedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
806
801
  };
807
802
  let currentRelease = null;
@@ -811,19 +806,13 @@ function stageRuntime(options = {}) {
811
806
  }
812
807
  const current = currentRelease ? readRuntimeManifest(join(currentRelease, "manifest.json")) : null;
813
808
  const currentDaemon = currentRelease ? join(currentRelease, desired.daemonFile) : "";
814
- const currentStatusline = currentRelease ? join(currentRelease, "prim-statusline") : "";
815
- if (currentRelease && sameRuntime(current, desired, currentDaemon, currentStatusline)) {
809
+ if (currentRelease && sameRuntime(current, desired, currentDaemon)) {
816
810
  const manifest = current;
817
- const statuslinePath = manifest.statuslineFile ? currentStatusline : void 0;
818
- if (statuslinePath) {
819
- atomicWrite(
820
- paths.statuslineLauncher,
821
- statuslineLauncherContent(statuslinePath, manifest.nodePath),
822
- RUNTIME_LAUNCHER_MODE
823
- );
824
- } else {
825
- rmSync(paths.statuslineLauncher, { force: true });
826
- }
811
+ atomicWrite(
812
+ paths.statuslineLauncher,
813
+ statuslineLauncherContent(manifest.version, options.homeDir),
814
+ RUNTIME_LAUNCHER_MODE
815
+ );
827
816
  return {
828
817
  changed: false,
829
818
  manifest,
@@ -844,14 +833,6 @@ function stageRuntime(options = {}) {
844
833
  if (sha256File(daemonTarget) !== desired.daemonSha256) {
845
834
  throw new Error("cannot stage runtime: daemon bundle changed while it was copied");
846
835
  }
847
- if (statuslineSource && desired.statuslineFile) {
848
- const statuslineTarget = join(stagingDir, desired.statuslineFile);
849
- copyFileSync(statuslineSource, statuslineTarget);
850
- chmodSync(statuslineTarget, RUNTIME_FILE_MODE);
851
- if (sha256File(statuslineTarget) !== desired.statuslineSha256) {
852
- throw new Error("cannot stage runtime: statusline bundle changed while it was copied");
853
- }
854
- }
855
836
  const manifestTarget = join(stagingDir, "manifest.json");
856
837
  writeFileSync(manifestTarget, `${JSON.stringify(desired, null, 2)}
857
838
  `, {
@@ -859,23 +840,16 @@ function stageRuntime(options = {}) {
859
840
  mode: RUNTIME_FILE_MODE,
860
841
  flag: "wx"
861
842
  });
862
- const releaseName = `release-${createHash2("sha256").update(
863
- `${version}\0${nodePath}\0${desired.statuslineFile ?? ""}\0${desired.stagedAt}\0${process.pid}\0${Math.random()}`
864
- ).digest("hex").slice(0, 16)}`;
843
+ const releaseName = `release-${createHash2("sha256").update(`${version}\0${nodePath}\0${desired.stagedAt}\0${process.pid}\0${Math.random()}`).digest("hex").slice(0, 16)}`;
865
844
  releaseDir = join(paths.releasesDir, releaseName);
866
845
  renameSync(stagingDir, releaseDir);
867
846
  releaseDir = realpathSync(releaseDir);
868
847
  atomicSymlink(join("releases", releaseName), paths.currentLink);
869
- if (desired.statuslineFile) {
870
- const statuslinePath = join(releaseDir, desired.statuslineFile);
871
- atomicWrite(
872
- paths.statuslineLauncher,
873
- statuslineLauncherContent(statuslinePath, desired.nodePath),
874
- RUNTIME_LAUNCHER_MODE
875
- );
876
- } else {
877
- rmSync(paths.statuslineLauncher, { force: true });
878
- }
848
+ atomicWrite(
849
+ paths.statuslineLauncher,
850
+ statuslineLauncherContent(desired.version, options.homeDir),
851
+ RUNTIME_LAUNCHER_MODE
852
+ );
879
853
  } catch (error) {
880
854
  rmSync(stagingDir, { recursive: true, force: true });
881
855
  throw error;
@@ -890,9 +864,20 @@ function stageRuntime(options = {}) {
890
864
  function shellQuote(value) {
891
865
  return `'${value.replaceAll("'", `'"'"'`)}'`;
892
866
  }
893
- function statuslineLauncherContent(statuslinePath, nodePath) {
867
+ function statuslineLauncherContent(version, homeDir = homedir()) {
868
+ const socketPath = join(homeDir, ".config", "prim", "sock");
869
+ const fallback = `primitive ${version} (daemon: down)`;
894
870
  return `#!/bin/sh
895
- exec ${shellQuote(nodePath)} ${shellQuote(statuslinePath)}
871
+ response=$(
872
+ printf 'prim-statusline-v1\\000%s\\000%s\\000' "\${PWD:-/}" "\${PRIM_API_URL:-}" |
873
+ /usr/bin/nc -U -w 1 ${shellQuote(socketPath)} 2>/dev/null
874
+ )
875
+ status=$?
876
+ if [ "$status" -eq 0 ] && [ -n "$response" ]; then
877
+ printf '%s' "$response"
878
+ else
879
+ printf '%s' ${shellQuote(fallback)}
880
+ fi
896
881
  `;
897
882
  }
898
883
  function runtimeStatuslineCommand(options = {}) {
@@ -1728,10 +1713,7 @@ function performInstall(scope, force) {
1728
1713
  let statuslineCommand = STATUSLINE_COMMAND;
1729
1714
  if (!blockedByUserStatusline && process.platform === "darwin" && (!before.statusLine || isPrimStatusLine(before))) {
1730
1715
  try {
1731
- const runtime = stageRuntime();
1732
- if (!runtime.manifest.statuslineFile) {
1733
- throw new Error("standalone prim-statusline entry is unavailable");
1734
- }
1716
+ stageRuntime();
1735
1717
  statuslineCommand = runtimeStatuslineCommand();
1736
1718
  } catch (error) {
1737
1719
  const detail = error instanceof Error ? error.message : String(error);
@@ -2030,10 +2012,74 @@ ${line("project", result.project)}`);
2030
2012
  }
2031
2013
 
2032
2014
  // src/commands/daemon.ts
2033
- import { spawn } from "child_process";
2015
+ import { spawn as spawn2 } from "child_process";
2034
2016
  import { closeSync as closeSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
2017
+ import { homedir as homedir5 } from "os";
2018
+ import { join as join5 } from "path";
2019
+ import { Option } from "commander";
2020
+
2021
+ // src/daemon/latest-bootstrap.ts
2022
+ import { spawn } from "child_process";
2035
2023
  import { homedir as homedir4 } from "os";
2036
2024
  import { join as join4 } from "path";
2025
+ var LATEST_BOOTSTRAP_TIMEOUT_MS = 45e3;
2026
+ var LATEST_BOOTSTRAP_LOCK_NAME = "daemon-latest.lock";
2027
+ async function runLatestEnsure(options) {
2028
+ const env = {
2029
+ ...options.env ?? process.env,
2030
+ npm_config_prefer_online: "true",
2031
+ npm_config_fetch_retries: "1",
2032
+ npm_config_fetch_retry_mintimeout: "1000",
2033
+ npm_config_fetch_retry_maxtimeout: "5000",
2034
+ npm_config_fetch_timeout: "15000"
2035
+ };
2036
+ return await new Promise((resolve5) => {
2037
+ let settled = false;
2038
+ let child;
2039
+ try {
2040
+ child = (options.spawnProcess ?? spawn)(
2041
+ "npx",
2042
+ ["--yes", "--prefer-online", "-p", "@primitive.ai/prim@latest", "prim", "daemon", "ensure"],
2043
+ { env, stdio: "ignore" }
2044
+ );
2045
+ } catch {
2046
+ resolve5(false);
2047
+ return;
2048
+ }
2049
+ const timer = setTimeout(() => {
2050
+ child.kill("SIGTERM");
2051
+ finish(false);
2052
+ }, options.timeoutMs ?? LATEST_BOOTSTRAP_TIMEOUT_MS);
2053
+ const finish = (result) => {
2054
+ if (settled) return;
2055
+ settled = true;
2056
+ clearTimeout(timer);
2057
+ resolve5(result);
2058
+ };
2059
+ timer.unref();
2060
+ child.once("error", () => finish(false));
2061
+ child.once("exit", (code) => finish(code === 0));
2062
+ });
2063
+ }
2064
+ async function runLatestDaemonBootstrap(ensureCurrent, options = {}) {
2065
+ const lockPath = options.lockPath ?? join4(options.homeDir ?? homedir4(), ".config", "prim", LATEST_BOOTSTRAP_LOCK_NAME);
2066
+ try {
2067
+ return await withFileLock(
2068
+ lockPath,
2069
+ async () => {
2070
+ const current = await ensureCurrent();
2071
+ if (current.disabled) return true;
2072
+ return await runLatestEnsure(options);
2073
+ },
2074
+ // A concurrent SessionStart already owns the complete bootstrap. Do not
2075
+ // queue another npm lookup behind it; the short window only permits one
2076
+ // retry after recovering a stale lock.
2077
+ { timeoutMs: 100, pollMs: 25 }
2078
+ );
2079
+ } catch {
2080
+ return false;
2081
+ }
2082
+ }
2037
2083
 
2038
2084
  // src/lib/presence.ts
2039
2085
  var DECISION_ORIGIN = "https://app.getprimitive.ai";
@@ -2084,10 +2130,10 @@ function formatTeammatesWithArea(teammates, cap) {
2084
2130
 
2085
2131
  // src/commands/daemon.ts
2086
2132
  var DAEMON_BIN = "prim-daemon-server";
2087
- var CONFIG_DIR = join4(homedir4(), ".config", "prim");
2088
- var PID_PATH = join4(CONFIG_DIR, "daemon.pid");
2089
- var SOCK_PATH = join4(CONFIG_DIR, "sock");
2090
- var LOG_PATH = join4(CONFIG_DIR, "daemon.log");
2133
+ var CONFIG_DIR = join5(homedir5(), ".config", "prim");
2134
+ var PID_PATH = join5(CONFIG_DIR, "daemon.pid");
2135
+ var SOCK_PATH = join5(CONFIG_DIR, "sock");
2136
+ var LOG_PATH = join5(CONFIG_DIR, "daemon.log");
2091
2137
  var CONFIG_DIR_MODE = 448;
2092
2138
  var LOG_FILE_MODE = 384;
2093
2139
  var STOP_TIMEOUT_MS = 5e3;
@@ -2158,13 +2204,13 @@ function sleep(ms) {
2158
2204
  }
2159
2205
  function spawnDaemon(options) {
2160
2206
  const file = binFile(DAEMON_BIN);
2161
- return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
2207
+ return file ? spawn2(process.execPath, [file], options) : spawn2(DAEMON_BIN, [], options);
2162
2208
  }
2163
2209
  function openDaemonLog(configDir = CONFIG_DIR) {
2164
2210
  if (!existsSync4(configDir)) {
2165
2211
  mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
2166
2212
  }
2167
- return openSync2(join4(configDir, "daemon.log"), "a", LOG_FILE_MODE);
2213
+ return openSync2(join5(configDir, "daemon.log"), "a", LOG_FILE_MODE);
2168
2214
  }
2169
2215
  async function waitForReady() {
2170
2216
  const deadline = Date.now() + READY_TIMEOUT_MS;
@@ -2676,7 +2722,7 @@ async function daemonEnsure() {
2676
2722
  process.stderr.write("[prim] daemon remains explicitly disabled\n");
2677
2723
  console.log(JSON.stringify({ ensured: false, disabled: true, supervised: false }, null, 2));
2678
2724
  }
2679
- return;
2725
+ return { disabled };
2680
2726
  }
2681
2727
  const result = await ensureMacDaemon();
2682
2728
  if (result.state === "disabled") {
@@ -2703,6 +2749,7 @@ async function daemonEnsure() {
2703
2749
  2
2704
2750
  )
2705
2751
  );
2752
+ return { disabled: result.state === "disabled" };
2706
2753
  }
2707
2754
  function registerDaemonCommands(program2) {
2708
2755
  const daemon = program2.command("daemon").description("Manage the prim companion daemon (latency unlock + presence + broadcast)");
@@ -2718,13 +2765,17 @@ function registerDaemonCommands(program2) {
2718
2765
  daemon.command("restart").description("Restart the daemon and clear any explicit disable marker").option("--foreground", "Restart in the foreground").action(async (opts) => {
2719
2766
  await daemonRestart(opts);
2720
2767
  });
2721
- daemon.command("ensure").description("Idempotently install, upgrade, and heal the daemon unless explicitly disabled").action(async () => {
2768
+ daemon.command("ensure").description("Idempotently install, upgrade, and heal the daemon unless explicitly disabled").addOption(new Option("--latest-bootstrap").hideHelp()).action(async (opts) => {
2769
+ if (opts.latestBootstrap) {
2770
+ await runLatestDaemonBootstrap(daemonEnsure);
2771
+ return;
2772
+ }
2722
2773
  await daemonEnsure();
2723
2774
  });
2724
2775
  }
2725
2776
 
2726
2777
  // src/commands/decisions.ts
2727
- import { Option } from "commander";
2778
+ import { Option as Option2 } from "commander";
2728
2779
 
2729
2780
  // src/decisions/cascade-renderer.ts
2730
2781
  var DEPENDENTS_INLINE_LIMIT = 5;
@@ -3526,7 +3577,7 @@ function registerDecisionsCommands(program2) {
3526
3577
  }
3527
3578
  });
3528
3579
  decisions.command("create").description("Record a decision directly with its explicit user or agent origin").requiredOption("--intent <text>", "What was decided (required)").addOption(
3529
- new Option(
3580
+ new Option2(
3530
3581
  "--attribution <origin>",
3531
3582
  "Who originated the exact choice: user | agent (required)"
3532
3583
  ).choices(["user", "agent"]).makeOptionMandatory()
@@ -4061,8 +4112,8 @@ import {
4061
4112
  rmSync as rmSync2,
4062
4113
  writeFileSync as writeFileSync3
4063
4114
  } from "fs";
4064
- import { homedir as homedir5 } from "os";
4065
- import { dirname as dirname3, join as join5 } from "path";
4115
+ import { homedir as homedir6 } from "os";
4116
+ import { dirname as dirname3, join as join6 } from "path";
4066
4117
  import { Document, parseDocument, stringify } from "yaml";
4067
4118
  var CAPTURE_BIN3 = "prim-hook";
4068
4119
  var GATE_BIN3 = "prim-pre-tool-use";
@@ -4087,13 +4138,13 @@ var PINNED_CASES = PRIM_BINS3.map((bin) => {
4087
4138
  ;;`;
4088
4139
  }).join("\n");
4089
4140
  function hermesHome() {
4090
- return process.env.HERMES_HOME ?? join5(homedir5(), ".hermes");
4141
+ return process.env.HERMES_HOME ?? join6(homedir6(), ".hermes");
4091
4142
  }
4092
4143
  function configPath() {
4093
- return join5(hermesHome(), "config.yaml");
4144
+ return join6(hermesHome(), "config.yaml");
4094
4145
  }
4095
4146
  function shimPath() {
4096
- return join5(hermesHome(), "agent-hooks", "prim-shim.sh");
4147
+ return join6(hermesHome(), "agent-hooks", "prim-shim.sh");
4097
4148
  }
4098
4149
  var SHIM_SCRIPT = `#!/bin/sh
4099
4150
  # prim Hermes hook shim \u2014 managed by \`prim hermes install\`. Hermes runs hooks
@@ -4395,9 +4446,9 @@ function registerHermesCommands(program2) {
4395
4446
  // src/commands/hooks.ts
4396
4447
  import { execFileSync } from "child_process";
4397
4448
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
4398
- import { homedir as homedir6 } from "os";
4399
- import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
4400
- import { Option as Option2 } from "commander";
4449
+ import { homedir as homedir7 } from "os";
4450
+ import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
4451
+ import { Option as Option3 } from "commander";
4401
4452
  var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
4402
4453
  var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
4403
4454
  var HOOKS = [PRE_COMMIT, POST_COMMIT];
@@ -4544,9 +4595,9 @@ function projectHooksDir(gitRoot) {
4544
4595
  }
4545
4596
  return resolve2(gitRoot, commonDir, "hooks");
4546
4597
  }
4547
- var PRIM_GIT_HOOKS_DIR = join6(homedir6(), ".config", "prim", "git-hooks");
4598
+ var PRIM_GIT_HOOKS_DIR = join7(homedir7(), ".config", "prim", "git-hooks");
4548
4599
  function expandTilde(p) {
4549
- return p.startsWith("~/") ? join6(homedir6(), p.slice(2)) : p;
4600
+ return p.startsWith("~/") ? join7(homedir7(), p.slice(2)) : p;
4550
4601
  }
4551
4602
  function isOurHooksDir(value) {
4552
4603
  return value !== "" && expandTilde(value) === PRIM_GIT_HOOKS_DIR;
@@ -4739,12 +4790,12 @@ function registerHooksCommands(program2) {
4739
4790
  hooks.command("install").description(
4740
4791
  "Install the prim git hooks \u2014 pre-commit + post-commit (auto-detects Husky; use --target to override)"
4741
4792
  ).addOption(
4742
- new Option2("--target <where>", "install destination; bypasses Husky detection").choices([
4793
+ new Option3("--target <where>", "install destination; bypasses Husky detection").choices([
4743
4794
  "husky",
4744
4795
  "git-hooks"
4745
4796
  ])
4746
4797
  ).addOption(
4747
- new Option2(
4798
+ new Option3(
4748
4799
  "--scope <scope>",
4749
4800
  "project (default, this repo) or user (a global core.hooksPath capturing every repo)"
4750
4801
  ).choices(["project", "user"])
@@ -4784,7 +4835,7 @@ function registerHooksCommands(program2) {
4784
4835
  hooks.command("uninstall").description(
4785
4836
  "Remove the prim git hooks (.git/hooks, or the global core.hooksPath with --scope user)"
4786
4837
  ).addOption(
4787
- new Option2(
4838
+ new Option3(
4788
4839
  "--scope <scope>",
4789
4840
  "project (default, this repo) or user (global core.hooksPath)"
4790
4841
  ).choices(["project", "user"])
@@ -4821,11 +4872,11 @@ function registerHooksCommands(program2) {
4821
4872
 
4822
4873
  // src/commands/moves.ts
4823
4874
  import { existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
4824
- import { join as join8 } from "path";
4875
+ import { join as join9 } from "path";
4825
4876
 
4826
4877
  // src/flusher.ts
4827
4878
  import { createReadStream, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "fs";
4828
- import { dirname as dirname5, join as join7 } from "path";
4879
+ import { dirname as dirname5, join as join8 } from "path";
4829
4880
  import { createInterface } from "readline";
4830
4881
  var BATCH_SIZE = 500;
4831
4882
  var HTTP_TIMEOUT_MS = 1e4;
@@ -4949,7 +5000,7 @@ async function flushOnce() {
4949
5000
  return { flushed: total };
4950
5001
  }
4951
5002
  var flushInFlight;
4952
- var FLUSH_LOCK_PATH = join7(dirname5(JOURNAL_DIR), ".flush.lock");
5003
+ var FLUSH_LOCK_PATH = join8(dirname5(JOURNAL_DIR), ".flush.lock");
4953
5004
  var FLUSH_LOCK_TIMEOUT_MS = 250;
4954
5005
  function isFlushLockContended(error) {
4955
5006
  return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
@@ -5066,18 +5117,18 @@ function registerMovesCommands(program2) {
5066
5117
  }
5067
5118
  });
5068
5119
  moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
5069
- const dir = join8(process.cwd(), ".prim");
5120
+ const dir = join9(process.cwd(), ".prim");
5070
5121
  if (!existsSync8(dir)) {
5071
5122
  mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
5072
5123
  }
5073
- const file = join8(process.cwd(), WORKSPACE_FILE);
5124
+ const file = join9(process.cwd(), WORKSPACE_FILE);
5074
5125
  writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
5075
5126
  mode: FILE_MODE
5076
5127
  });
5077
5128
  console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
5078
5129
  });
5079
5130
  moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
5080
- const file = join8(process.cwd(), WORKSPACE_FILE);
5131
+ const file = join9(process.cwd(), WORKSPACE_FILE);
5081
5132
  if (!existsSync8(file)) {
5082
5133
  console.log("[prim] no workspace binding in cwd");
5083
5134
  return;
@@ -5176,7 +5227,7 @@ import {
5176
5227
  unlinkSync as unlinkSync5,
5177
5228
  writeFileSync as writeFileSync6
5178
5229
  } from "fs";
5179
- import { join as join9 } from "path";
5230
+ import { join as join10 } from "path";
5180
5231
  var DIR_MODE2 = 448;
5181
5232
  var FILE_MODE2 = 384;
5182
5233
  function ensureDir() {
@@ -5185,7 +5236,7 @@ function ensureDir() {
5185
5236
  }
5186
5237
  }
5187
5238
  function markerPath(sessionId) {
5188
- return join9(SESSIONS_DIR, `${sessionId}.json`);
5239
+ return join10(SESSIONS_DIR, `${sessionId}.json`);
5189
5240
  }
5190
5241
  function registerSessionCommands(program2) {
5191
5242
  const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
@@ -5213,7 +5264,7 @@ function registerSessionCommands(program2) {
5213
5264
  for (const f of files) {
5214
5265
  const sessionId = f.replace(/\.json$/, "");
5215
5266
  try {
5216
- const m = JSON.parse(readFileSync7(join9(SESSIONS_DIR, f), "utf-8"));
5267
+ const m = JSON.parse(readFileSync7(join10(SESSIONS_DIR, f), "utf-8"));
5217
5268
  console.log(`${sessionId} org=${m.orgId}`);
5218
5269
  } catch {
5219
5270
  }
@@ -5233,7 +5284,7 @@ function registerSessionCommands(program2) {
5233
5284
  // src/commands/setup.ts
5234
5285
  import { spawnSync as spawnSync2 } from "child_process";
5235
5286
  import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
5236
- import { join as join10 } from "path";
5287
+ import { join as join11 } from "path";
5237
5288
  var EXIT_INCOMPLETE = 1;
5238
5289
  var EXIT_USAGE3 = 2;
5239
5290
  var SESSION_LABELS = {
@@ -5314,7 +5365,7 @@ function detectProjectConflicts(agent, run) {
5314
5365
  }
5315
5366
  try {
5316
5367
  const root = gitToplevel();
5317
- const preCommit = root && join10(root, ".git", "hooks", "pre-commit");
5368
+ const preCommit = root && join11(root, ".git", "hooks", "pre-commit");
5318
5369
  if (preCommit && existsSync10(preCommit) && readFileSync8(preCommit, "utf-8").includes("prim-pre-commit")) {
5319
5370
  conflicts.push(CONFLICT_HOOKS);
5320
5371
  }
@@ -5473,14 +5524,55 @@ function registerSetupCommand(program2, dependencies = {}) {
5473
5524
  import { readFileSync as readFileSync9 } from "fs";
5474
5525
  import { dirname as dirname6, resolve as resolve3 } from "path";
5475
5526
  import { fileURLToPath } from "url";
5476
- var STATUSLINE_TIMEOUT_MS = 200;
5527
+
5528
+ // src/lib/statusline-render.ts
5477
5529
  var STATUSLINE_NAME_CAP = 3;
5530
+ function formatStatusline(version, snapshot, resolveIngestionStatus) {
5531
+ if (!snapshot) {
5532
+ return `primitive ${version} (daemon: down)`;
5533
+ }
5534
+ if (snapshot.healthy === false) {
5535
+ if (snapshot.needsReauth) {
5536
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
5537
+ }
5538
+ if (snapshot.ingestion?.healthy === false) {
5539
+ const pending = snapshot.ingestion.pendingCount;
5540
+ const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
5541
+ return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""})`;
5542
+ }
5543
+ if (snapshot.heartbeat?.healthy === false) {
5544
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
5545
+ }
5546
+ return `primitive ${version} (daemon: starting)`;
5547
+ }
5548
+ const ingestionStatus = resolveIngestionStatus();
5549
+ if (snapshot.envMismatch) {
5550
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
5551
+ }
5552
+ if (snapshot.presenceStale) {
5553
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
5554
+ }
5555
+ let team;
5556
+ if (snapshot.onlineTeammates !== void 0) {
5557
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
5558
+ } else if (snapshot.onlineNames !== void 0) {
5559
+ team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
5560
+ } else if (typeof snapshot.onlineCount === "number") {
5561
+ team = `team: ${String(snapshot.onlineCount)} online`;
5562
+ } else {
5563
+ team = "team: \u2014";
5564
+ }
5565
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
5566
+ }
5567
+
5568
+ // src/commands/statusline.ts
5569
+ var STATUSLINE_TIMEOUT_MS = 200;
5478
5570
  function readPackageVersion() {
5479
5571
  try {
5480
5572
  const here = dirname6(fileURLToPath(import.meta.url));
5481
5573
  const candidates = [
5482
- // The supervised runtime stages a tiny manifest beside the standalone
5483
- // statusline bundle, so it remains versioned without loading the package.
5574
+ // Schema-v2 runtimes staged a manifest beside this standalone bundle.
5575
+ // Keep resolving it while those legacy launchers transition to schema v3.
5484
5576
  resolve3(here, "manifest.json"),
5485
5577
  resolve3(here, "../../package.json"),
5486
5578
  resolve3(here, "../package.json")
@@ -5515,40 +5607,8 @@ async function renderStatusline() {
5515
5607
  );
5516
5608
  if (!snapshot) {
5517
5609
  debug("daemon snapshot missing");
5518
- return `primitive ${version} (daemon: down)`;
5519
5610
  }
5520
- if (snapshot.healthy === false) {
5521
- if (snapshot.needsReauth) {
5522
- return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
5523
- }
5524
- if (snapshot.ingestion?.healthy === false) {
5525
- const pending = snapshot.ingestion.pendingCount;
5526
- const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
5527
- return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""})`;
5528
- }
5529
- if (snapshot.heartbeat?.healthy === false) {
5530
- return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
5531
- }
5532
- return `primitive ${version} (daemon: starting)`;
5533
- }
5534
- const ingestionStatus = decisionIngestionStatus(process.cwd());
5535
- if (snapshot.envMismatch) {
5536
- return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
5537
- }
5538
- if (snapshot.presenceStale) {
5539
- return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
5540
- }
5541
- let team;
5542
- if (snapshot.onlineTeammates !== void 0) {
5543
- team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
5544
- } else if (snapshot.onlineNames !== void 0) {
5545
- team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
5546
- } else if (typeof snapshot.onlineCount === "number") {
5547
- team = `team: ${String(snapshot.onlineCount)} online`;
5548
- } else {
5549
- team = "team: \u2014";
5550
- }
5551
- return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
5611
+ return formatStatusline(version, snapshot, () => decisionIngestionStatus(process.cwd()));
5552
5612
  }
5553
5613
  function registerStatuslineCommands(program2) {
5554
5614
  program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {