@primitive.ai/prim 0.1.0-alpha.90 → 0.1.0-alpha.92

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.
Files changed (45) hide show
  1. package/README.md +20 -12
  2. package/SKILL.md +6 -6
  3. package/dist/chunk-26P57LAR.js +1491 -0
  4. package/dist/{chunk-LMRYZL3D.js → chunk-5AFRMOPX.js} +1 -1
  5. package/dist/{chunk-RO3F6JYV.js → chunk-5GYQ4J3D.js} +2 -2
  6. package/dist/chunk-5HRR4AOR.js +264 -0
  7. package/dist/{chunk-3EEFL6NJ.js → chunk-67VNLLBN.js} +261 -1
  8. package/dist/{chunk-UZPBB7V4.js → chunk-ADUTW66W.js} +60 -48
  9. package/dist/{chunk-LEMX3IOG.js → chunk-BTNNEH2W.js} +1 -1
  10. package/dist/{chunk-YWO7O6QG.js → chunk-FBAYCS3X.js} +2 -2
  11. package/dist/{chunk-R26A7TWY.js → chunk-FKXFUUWJ.js} +1 -1
  12. package/dist/chunk-H4UNYFUC.js +196 -0
  13. package/dist/{chunk-CUE2LKLR.js → chunk-KFXF2V5G.js} +1 -1
  14. package/dist/{chunk-6IWNH776.js → chunk-LFFLHSQO.js} +4 -3
  15. package/dist/{chunk-C7T7B45R.js → chunk-LGGMJYY4.js} +150 -21
  16. package/dist/{chunk-MAZWKJGL.js → chunk-M6RIJVRE.js} +42 -232
  17. package/dist/{chunk-2JRX7ZR4.js → chunk-NAHHFSLH.js} +16 -5
  18. package/dist/{chunk-FI3LIRCB.js → chunk-OAAP2MY6.js} +2 -2
  19. package/dist/{chunk-Z7YPFWAZ.js → chunk-PCVGBE5B.js} +10 -268
  20. package/dist/chunk-WX52U7Z2.js +112 -0
  21. package/dist/{chunk-PPQYQEIR.js → chunk-ZB4F75SZ.js} +28 -0
  22. package/dist/daemon/server.js +30 -21
  23. package/dist/hook-runtime/daemon/server.js +30 -21
  24. package/dist/hook-runtime/hooks/post-commit.js +20 -20
  25. package/dist/hook-runtime/hooks/post-rewrite.js +20 -20
  26. package/dist/hook-runtime/hooks/post-tool-use.js +666 -147
  27. package/dist/hook-runtime/hooks/pre-tool-use.js +659 -111
  28. package/dist/hook-runtime/hooks/prim-hook.js +627 -111
  29. package/dist/hook-runtime/hooks/session-end.js +610 -27
  30. package/dist/hook-runtime/hooks/session-start.js +1132 -533
  31. package/dist/hook-runtime/index.js +764 -267
  32. package/dist/hook-runtime/statusline-main.js +48 -4
  33. package/dist/hooks/post-commit.js +11 -9
  34. package/dist/hooks/post-rewrite.js +11 -9
  35. package/dist/hooks/post-tool-use.js +73 -48
  36. package/dist/hooks/pre-commit.js +4 -4
  37. package/dist/hooks/pre-tool-use.js +46 -44
  38. package/dist/hooks/prim-hook.js +47 -25
  39. package/dist/hooks/session-end.js +15 -26
  40. package/dist/hooks/session-start.js +59 -32
  41. package/dist/index.js +561 -1430
  42. package/dist/statusline-main.js +48 -4
  43. package/package.json +1 -1
  44. package/dist/chunk-PMRDE2DA.js +0 -310
  45. package/dist/chunk-WB7AUZ4C.js +0 -52
package/dist/index.js CHANGED
@@ -17,18 +17,18 @@ import {
17
17
  uninstallPostRewriteHookAtPath,
18
18
  uninstallProjectPostCommitHook,
19
19
  uninstallProjectPostRewriteHook
20
- } from "./chunk-C7T7B45R.js";
20
+ } from "./chunk-LGGMJYY4.js";
21
21
  import {
22
22
  requireDurableIngestAcknowledgement
23
- } from "./chunk-R26A7TWY.js";
23
+ } from "./chunk-FKXFUUWJ.js";
24
24
  import {
25
25
  checkAffectedDecisions,
26
26
  daemonOrDirectGet,
27
27
  formatDecisionsWarning
28
- } from "./chunk-RO3F6JYV.js";
28
+ } from "./chunk-5GYQ4J3D.js";
29
29
  import {
30
30
  fetchFeedbackCapability
31
- } from "./chunk-LMRYZL3D.js";
31
+ } from "./chunk-5AFRMOPX.js";
32
32
  import {
33
33
  JOURNAL_DIR,
34
34
  SESSIONS_DIR,
@@ -37,24 +37,12 @@ import {
37
37
  listFlushing,
38
38
  pendingJournalStats,
39
39
  readMovesFromPath
40
- } from "./chunk-YWO7O6QG.js";
40
+ } from "./chunk-FBAYCS3X.js";
41
41
  import {
42
- STABLE_HOOK_LAUNCHER_NAME,
43
- binFile,
44
- commandMatchesBin,
45
- compareSemver,
46
- detachedHookShimCommand,
47
42
  formatStatusline,
48
43
  formatTeammates,
49
- hookCommandResolution,
50
- hookCommandResolutions,
51
- packageRoot,
52
- packageVersion,
53
- pinnedHookCommand,
54
- pinnedNpxArgs,
55
- repositoryBindingDiagnosticLabel,
56
- stableHookCommand
57
- } from "./chunk-PMRDE2DA.js";
44
+ repositoryBindingDiagnosticLabel
45
+ } from "./chunk-WX52U7Z2.js";
58
46
  import {
59
47
  bold,
60
48
  boundedHealthError,
@@ -62,13 +50,10 @@ import {
62
50
  colorForArea,
63
51
  dim,
64
52
  stripAnsi
65
- } from "./chunk-LEMX3IOG.js";
53
+ } from "./chunk-BTNNEH2W.js";
66
54
  import {
67
- canonicalGitRoot,
68
- canonicalRepositoryPath,
69
55
  decisionIngestionStatus,
70
56
  fetchAndCacheCollectScope,
71
- gitToplevel,
72
57
  isCliErrorResponse,
73
58
  isDecisionIdRequest,
74
59
  isDecisionRescopeRequest,
@@ -84,11 +69,10 @@ import {
84
69
  repoSyncId,
85
70
  repositoryBindingState,
86
71
  setRepoActive
87
- } from "./chunk-MAZWKJGL.js";
72
+ } from "./chunk-M6RIJVRE.js";
88
73
  import {
89
74
  CliManagementResponseError,
90
75
  HttpError,
91
- atomicWriteFile,
92
76
  clearStoredCredentials,
93
77
  commitCredentials,
94
78
  getClient,
@@ -97,34 +81,66 @@ import {
97
81
  getSiteUrl,
98
82
  getTokenExpiresAt,
99
83
  isSessionEnded,
100
- processIsAlive,
101
84
  refreshToken,
102
85
  setStoredToken,
103
86
  stripControlChars,
104
- syncDirectory,
105
- syncFile,
106
- terminalSafeLine,
107
- withFileLock,
108
- withFileLockSync
109
- } from "./chunk-Z7YPFWAZ.js";
87
+ terminalSafeLine
88
+ } from "./chunk-PCVGBE5B.js";
89
+ import {
90
+ assertOwnedHookRuntime,
91
+ atomicWrite,
92
+ binFile,
93
+ commandMatchesBin,
94
+ compareSemver,
95
+ ensureRegistration,
96
+ entryHasCommand,
97
+ hookCommandResolutions,
98
+ hookRuntimePaths,
99
+ inspectHookRuntime,
100
+ inspectHookRuntimeResolutions,
101
+ inspectHookRuntimeResolutions2,
102
+ makeRegistration,
103
+ packageVersion,
104
+ performStatus,
105
+ performStatus2,
106
+ pinnedHookCommand,
107
+ pinnedNpxArgs,
108
+ projectRoot,
109
+ readSettings,
110
+ registerClaudeCommands,
111
+ registerCursorCommands,
112
+ removeHookRuntime,
113
+ stableHookCommand,
114
+ stageHookRuntime,
115
+ stripCommand
116
+ } from "./chunk-26P57LAR.js";
117
+ import {
118
+ canonicalGitRoot,
119
+ canonicalRepositoryPath,
120
+ gitToplevel
121
+ } from "./chunk-H4UNYFUC.js";
110
122
  import {
111
123
  daemonIsLive,
112
124
  daemonRequest
113
- } from "./chunk-CUE2LKLR.js";
125
+ } from "./chunk-KFXF2V5G.js";
114
126
  import {
115
127
  REFRESH_TOKEN_PATH,
116
128
  TOKEN_FILE_PATH,
117
129
  WorkosConnectProtocolError,
130
+ atomicWriteFile,
118
131
  discoverWorkosConnect,
119
132
  pollWorkosDeviceAuthorization,
120
133
  primConfigDirectory,
134
+ processIsAlive,
121
135
  requestWorkosDeviceAuthorization,
122
- resolveAuthCredential
123
- } from "./chunk-3EEFL6NJ.js";
136
+ resolveAuthCredential,
137
+ syncDirectory,
138
+ withFileLock
139
+ } from "./chunk-67VNLLBN.js";
124
140
 
125
141
  // src/index.ts
126
- import { readFileSync as readFileSync11 } from "fs";
127
- import { dirname as dirname8, resolve as resolve5 } from "path";
142
+ import { readFileSync as readFileSync9 } from "fs";
143
+ import { dirname as dirname6, resolve as resolve4 } from "path";
128
144
  import { fileURLToPath as fileURLToPath2 } from "url";
129
145
  import { Command } from "commander";
130
146
  import updateNotifier from "update-notifier";
@@ -1007,8 +1023,8 @@ ${verificationUrl}`);
1007
1023
  const state = base64url(randomBytes2(16));
1008
1024
  const redirectUri = `http://${LOCALHOST}:${CALLBACK_PORT}/callback`;
1009
1025
  let settle;
1010
- const outcome = new Promise((resolve6) => {
1011
- settle = resolve6;
1026
+ const outcome = new Promise((resolve5) => {
1027
+ settle = resolve5;
1012
1028
  });
1013
1029
  const server = createServer((req, res) => {
1014
1030
  const url = new URL(req.url ?? "/", `http://${LOCALHOST}`);
@@ -1022,12 +1038,12 @@ ${verificationUrl}`);
1022
1038
  res.end(page.html, () => settle(page.result));
1023
1039
  });
1024
1040
  try {
1025
- await new Promise((resolve6, reject) => {
1041
+ await new Promise((resolve5, reject) => {
1026
1042
  const onError = (err) => reject(err);
1027
1043
  server.once("error", onError);
1028
1044
  server.listen(CALLBACK_PORT, LOCALHOST, () => {
1029
1045
  server.removeListener("error", onError);
1030
- resolve6();
1046
+ resolve5();
1031
1047
  });
1032
1048
  });
1033
1049
  } catch (err) {
@@ -1908,1041 +1924,84 @@ async function applyActivation(active, globals = {}) {
1908
1924
  if (active) {
1909
1925
  refreshOwnedGlobalHooks();
1910
1926
  postCommitHook = ensureEffectivePostCommitHook(root).path;
1911
- try {
1912
- postRewriteHook = ensureEffectivePostRewriteHook(root).path;
1913
- } catch (error) {
1914
- const detail = error instanceof Error ? error.message : String(error);
1915
- process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
1916
- `);
1917
- }
1918
- phase = "GitHub repo connection";
1919
- binding = await bindRepository(root);
1920
- }
1921
- if (active && binding?.status === "unbound") {
1922
- const connected = await maybeConnectRepository(root, globals);
1923
- if (!connected) {
1924
- process.stderr.write(`[prim] ${GITHUB_CONNECTION_REQUIRED}
1925
- `);
1926
- printJson({
1927
- active: false,
1928
- repo: root,
1929
- bindingStatus: binding.status,
1930
- repositoryFullName: binding.repositoryFullName,
1931
- ...postCommitHook ? { postCommitHook } : {},
1932
- ...postRewriteHook ? { postRewriteHook } : {}
1933
- });
1934
- process.exitCode = 1;
1935
- return;
1936
- }
1937
- binding = connected;
1938
- }
1939
- if (active) {
1940
- try {
1941
- await fetchAndCacheCollectScope(root);
1942
- } catch {
1943
- }
1944
- }
1945
- phase = "local activation";
1946
- setRepoActive(root, active);
1947
- await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
1948
- process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
1949
- `);
1950
- printJson({
1951
- active,
1952
- repo: root,
1953
- ...binding ? {
1954
- bindingStatus: binding.status,
1955
- repositoryFullName: binding.repositoryFullName,
1956
- ...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
1957
- } : {},
1958
- ...postCommitHook ? { postCommitHook } : {},
1959
- ...postRewriteHook ? { postRewriteHook } : {}
1960
- });
1961
- } catch (err) {
1962
- const detail = err instanceof Error ? err.message : String(err);
1963
- process.stderr.write(
1964
- active ? `[prim] failed to enable prim during ${phase}: ${detail}
1965
- ` : `[prim] failed to disable prim: ${detail}
1966
- `
1967
- );
1968
- process.exit(1);
1969
- }
1970
- }
1971
- function registerActivationCommands(program2) {
1972
- program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action((_opts, command) => applyActivation(true, command.optsWithGlobals()));
1973
- program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(
1974
- (_opts, command) => applyActivation(false, command.optsWithGlobals())
1975
- );
1976
- }
1977
-
1978
- // src/commands/claude-install.ts
1979
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1980
- import { homedir as homedir2 } from "os";
1981
- import { join as join3 } from "path";
1982
-
1983
- // src/lib/hook-runtime.ts
1984
- import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
1985
- import {
1986
- constants,
1987
- accessSync,
1988
- chmodSync,
1989
- copyFileSync,
1990
- existsSync as existsSync3,
1991
- lstatSync as lstatSync2,
1992
- mkdirSync as mkdirSync2,
1993
- mkdtempSync,
1994
- readFileSync as readFileSync3,
1995
- readdirSync as readdirSync2,
1996
- realpathSync,
1997
- renameSync,
1998
- rmSync,
1999
- statSync,
2000
- utimesSync,
2001
- writeFileSync as writeFileSync2
2002
- } from "fs";
2003
- import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "path";
2004
- var HOOK_RUNTIME_SCHEMA_VERSION = 1;
2005
- var DIRECTORY_MODE = 448;
2006
- var DATA_FILE_MODE = 384;
2007
- var LAUNCHER_MODE = 448;
2008
- var RELEASE_PREFIX = "release-";
2009
- var RELEASE_NAME_RE = /^release-[0-9a-f]{64}$/u;
2010
- var MAX_MANIFEST_BYTES = 64 * 1024;
2011
- var MAX_RELEASES = 8;
2012
- var MAX_RELEASE_DELETIONS_PER_STAGE = 8;
2013
- var RETAIN_PREDECESSORS = 2;
2014
- var RELEASE_GRACE_MS = 10 * 60 * 1e3;
2015
- var HOOK_RUNTIME_ENTRIES = {
2016
- // SessionStart self-healing resolves this exact CLI entry to run
2017
- // `daemon ensure`; it must travel with the immutable hook release.
2018
- prim: "dist/index.js",
2019
- "prim-hook": "dist/hooks/prim-hook.js",
2020
- "prim-pre-commit": "dist/hooks/pre-commit.js",
2021
- "prim-post-commit": "dist/hooks/post-commit.js",
2022
- "prim-post-rewrite": "dist/hooks/post-rewrite.js",
2023
- "prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
2024
- "prim-post-tool-use": "dist/hooks/post-tool-use.js",
2025
- "prim-session-start": "dist/hooks/session-start.js",
2026
- "prim-session-end": "dist/hooks/session-end.js",
2027
- "prim-daemon-server": "dist/daemon/server.js",
2028
- "prim-statusline": "dist/statusline-main.js"
2029
- };
2030
- var SOURCE_ENTRIES = Object.fromEntries(
2031
- Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, target]) => [
2032
- bin,
2033
- target.replace(/^dist\//u, "")
2034
- ])
2035
- );
2036
- function hookRuntimePaths(options = {}) {
2037
- const env = options.env ?? process.env;
2038
- const configDir = primConfigDirectory({
2039
- ...options,
2040
- env,
2041
- homeDir: options.homeDir ?? env.HOME ?? ""
2042
- });
2043
- const runtimeDir = join2(configDir, "hook-runtime");
2044
- return {
2045
- configDir,
2046
- launcher: join2(configDir, STABLE_HOOK_LAUNCHER_NAME),
2047
- runtimeDir,
2048
- releasesDir: join2(runtimeDir, "releases"),
2049
- current: join2(runtimeDir, "current"),
2050
- selectionLock: join2(runtimeDir, "selection.lock")
2051
- };
2052
- }
2053
- function sha256(path) {
2054
- return createHash2("sha256").update(readFileSync3(path)).digest("hex");
2055
- }
2056
- function exactManifest(manifest) {
2057
- return `${JSON.stringify(manifest, null, 2)}
2058
- `;
2059
- }
2060
- function exactKeys(value, expected) {
2061
- const actual = Object.keys(value).sort();
2062
- const wanted = [...expected].sort();
2063
- return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
2064
- }
2065
- function releaseId(manifest) {
2066
- return createHash2("sha256").update(exactManifest(manifest)).digest("hex");
2067
- }
2068
- function runtimePackageJson(manifest) {
2069
- return `${JSON.stringify(
2070
- {
2071
- name: "@primitive.ai/prim",
2072
- version: manifest.version,
2073
- type: "module",
2074
- bin: HOOK_RUNTIME_ENTRIES
2075
- },
2076
- null,
2077
- 2
2078
- )}
2079
- `;
2080
- }
2081
- function validateRelease(releaseDir, manifest) {
2082
- try {
2083
- if (!directReleaseLayout(releaseDir)) return false;
2084
- if (readFileSync3(join2(releaseDir, "manifest.json"), "utf8") !== exactManifest(manifest)) {
2085
- return false;
2086
- }
2087
- if (readFileSync3(join2(releaseDir, "package.json"), "utf8") !== runtimePackageJson(manifest)) {
2088
- return false;
2089
- }
2090
- if (readFileSync3(join2(releaseDir, "node"), "utf8") !== `${manifest.nodePath}
2091
- `) {
2092
- return false;
2093
- }
2094
- for (const [bin, relativePath] of Object.entries(HOOK_RUNTIME_ENTRIES)) {
2095
- const target = join2(releaseDir, relativePath);
2096
- if (sha256(target) !== manifest.files[bin]) return false;
2097
- }
2098
- return true;
2099
- } catch {
2100
- return false;
2101
- }
2102
- }
2103
- function directReleaseLayout(releaseDir) {
2104
- try {
2105
- const directories = /* @__PURE__ */ new Set([releaseDir]);
2106
- for (const relativePath of Object.values(HOOK_RUNTIME_ENTRIES)) {
2107
- for (let directory = dirname2(relativePath); directory !== "."; directory = dirname2(directory)) {
2108
- directories.add(join2(releaseDir, directory));
2109
- }
2110
- }
2111
- if (![...directories].every((directory) => lstatSync2(directory).isDirectory())) return false;
2112
- const files = ["manifest.json", "package.json", "node", ...Object.values(HOOK_RUNTIME_ENTRIES)];
2113
- return files.every((relativePath) => lstatSync2(join2(releaseDir, relativePath)).isFile());
2114
- } catch {
2115
- return false;
2116
- }
2117
- }
2118
- function readReleaseManifest(releaseDir, releaseName) {
2119
- try {
2120
- const manifestPath = join2(releaseDir, "manifest.json");
2121
- const manifestStat = statSync(manifestPath);
2122
- if (!manifestStat.isFile() || manifestStat.size > MAX_MANIFEST_BYTES) return null;
2123
- const raw = readFileSync3(manifestPath, "utf8");
2124
- const parsed = JSON.parse(raw);
2125
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2126
- if (!exactKeys(parsed, ["schemaVersion", "version", "nodePath", "files"])) return null;
2127
- const candidate = parsed;
2128
- if (candidate.schemaVersion !== HOOK_RUNTIME_SCHEMA_VERSION || typeof candidate.version !== "string" || compareSemver(candidate.version, candidate.version) !== 0 || typeof candidate.nodePath !== "string" || !isAbsolute(candidate.nodePath) || /[\r\n]/u.test(candidate.nodePath) || !candidate.files || typeof candidate.files !== "object" || Array.isArray(candidate.files) || !exactKeys(candidate.files, Object.keys(HOOK_RUNTIME_ENTRIES)) || Object.values(candidate.files).some(
2129
- (digest) => typeof digest !== "string" || !/^[0-9a-f]{64}$/u.test(digest)
2130
- )) {
2131
- return null;
2132
- }
2133
- const manifest = candidate;
2134
- if (raw !== exactManifest(manifest) || releaseName !== `${RELEASE_PREFIX}${releaseId(manifest)}` || !validateRelease(releaseDir, manifest)) {
2135
- return null;
2136
- }
2137
- return manifest;
2138
- } catch {
2139
- return null;
2140
- }
2141
- }
2142
- function readSelectedRelease(paths) {
2143
- let raw;
2144
- try {
2145
- raw = readFileSync3(paths.current, "utf8");
2146
- } catch (error) {
2147
- if (error.code === "ENOENT") return null;
2148
- throw error;
2149
- }
2150
- const name = raw.endsWith("\n") ? raw.slice(0, -1) : "";
2151
- if (`${name}
2152
- ` !== raw || !RELEASE_NAME_RE.test(name)) {
2153
- throw new Error("cannot stage hook runtime: selected release is malformed");
2154
- }
2155
- const dir = join2(paths.releasesDir, name);
2156
- const manifest = readReleaseManifest(dir, name);
2157
- if (!manifest) {
2158
- throw new Error("cannot stage hook runtime: immutable release is corrupt");
2159
- }
2160
- return { name, dir, manifest };
2161
- }
2162
- function releaseEntries(paths) {
2163
- return readdirSync2(paths.releasesDir, { withFileTypes: true }).flatMap((entry) => {
2164
- if (!entry.isDirectory() || !RELEASE_NAME_RE.test(entry.name)) return [];
2165
- const dir = join2(paths.releasesDir, entry.name);
2166
- try {
2167
- return [{ name: entry.name, dir, mtimeMs: statSync(dir).mtimeMs }];
2168
- } catch {
2169
- return [];
2170
- }
2171
- });
2172
- }
2173
- function pruneReleases(paths, protectedNames, reserveSlots, now) {
2174
- const entries = releaseEntries(paths).sort(
2175
- (left, right) => right.mtimeMs - left.mtimeMs || left.name.localeCompare(right.name)
2176
- );
2177
- const protectedWithPredecessors = new Set(protectedNames);
2178
- for (const entry of entries) {
2179
- if (protectedWithPredecessors.size >= protectedNames.size + RETAIN_PREDECESSORS) break;
2180
- if (!protectedWithPredecessors.has(entry.name)) protectedWithPredecessors.add(entry.name);
2181
- }
2182
- const candidates = entries.filter(
2183
- (entry) => !protectedWithPredecessors.has(entry.name) && now - entry.mtimeMs >= RELEASE_GRACE_MS
2184
- ).sort((left, right) => left.mtimeMs - right.mtimeMs || left.name.localeCompare(right.name));
2185
- let remaining = entries.length;
2186
- let deleted = 0;
2187
- for (const entry of candidates) {
2188
- if (remaining + reserveSlots <= MAX_RELEASES || deleted >= MAX_RELEASE_DELETIONS_PER_STAGE) {
2189
- break;
2190
- }
2191
- rmSync(entry.dir, { recursive: true, force: true });
2192
- remaining -= 1;
2193
- deleted += 1;
2194
- }
2195
- if (remaining + reserveSlots > MAX_RELEASES) {
2196
- throw new Error(
2197
- "cannot stage hook runtime: release retention is full while recent runtimes are protected"
2198
- );
2199
- }
2200
- }
2201
- function sourceManifest(sourceDir, version, nodePath) {
2202
- const files = {};
2203
- for (const [bin, relativePath] of Object.entries(SOURCE_ENTRIES)) {
2204
- const source = join2(sourceDir, relativePath);
2205
- if (!existsSync3(source) || !statSync(source).isFile()) {
2206
- throw new Error(`cannot stage hook runtime: missing self-contained ${bin} bundle`);
2207
- }
2208
- files[bin] = sha256(source);
2209
- }
2210
- return { schemaVersion: HOOK_RUNTIME_SCHEMA_VERSION, version, nodePath, files };
2211
- }
2212
- var STABLE_HOOK_LAUNCHER_CONTENT = `#!/bin/sh
2213
- set -eu
2214
- case "$0" in */*) prim_launcher_dir=\${0%/*} ;; *) exit 78 ;; esac
2215
- IFS= read -r prim_release_name < "$prim_launcher_dir/hook-runtime/current" || exit 69
2216
- case "$prim_release_name" in ${RELEASE_PREFIX}*) ;; *) exit 69 ;; esac
2217
- prim_release_id=\${prim_release_name#${RELEASE_PREFIX}}
2218
- case "$prim_release_id" in *[!0-9a-f]*|"") exit 69 ;; esac
2219
- [ "\${#prim_release_id}" -eq 64 ] || exit 69
2220
- prim_release="$prim_launcher_dir/hook-runtime/releases/$prim_release_name"
2221
- case "\${1-}" in
2222
- ${Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, entry]) => ` ${bin}) prim_entry="$prim_release/${entry}" ;;`).join("\n")}
2223
- *) exit 64 ;;
2224
- esac
2225
- shift
2226
- IFS= read -r prim_node < "$prim_release/node" || exit 69
2227
- case "$prim_node" in /*) ;; *) exit 69 ;; esac
2228
- [ -x "$prim_node" ] && [ -f "$prim_entry" ] || exit 69
2229
- exec "$prim_node" "$prim_entry" "$@"
2230
- `;
2231
- function privateDirectory(path) {
2232
- const stat = lstatIfPresent2(path);
2233
- if (!stat?.isDirectory()) return false;
2234
- if (process.platform === "win32") return true;
2235
- const uid = process.getuid?.();
2236
- return (Number(stat.mode) & 511) === DIRECTORY_MODE && (uid === void 0 || stat.uid === uid);
2237
- }
2238
- function privateFile(path, mode) {
2239
- const stat = lstatIfPresent2(path);
2240
- if (!stat?.isFile()) return false;
2241
- if (process.platform === "win32") return true;
2242
- const uid = process.getuid?.();
2243
- return (Number(stat.mode) & 511) === mode && (uid === void 0 || stat.uid === uid);
2244
- }
2245
- function hasSafeRuntimeLayout(paths, selected) {
2246
- const releaseDirectories = /* @__PURE__ */ new Set([selected.dir]);
2247
- for (const relativePath of Object.values(HOOK_RUNTIME_ENTRIES)) {
2248
- for (let directory = dirname2(relativePath); directory !== "."; directory = dirname2(directory)) {
2249
- releaseDirectories.add(join2(selected.dir, directory));
2250
- }
2251
- }
2252
- if (!privateDirectory(paths.configDir) || !privateDirectory(paths.runtimeDir) || !privateDirectory(paths.releasesDir) || ![...releaseDirectories].every(privateDirectory) || !privateFile(paths.launcher, LAUNCHER_MODE) || !privateFile(paths.current, DATA_FILE_MODE) || !privateFile(join2(selected.dir, "manifest.json"), DATA_FILE_MODE) || !privateFile(join2(selected.dir, "package.json"), DATA_FILE_MODE) || !privateFile(join2(selected.dir, "node"), DATA_FILE_MODE)) {
2253
- return false;
2254
- }
2255
- return Object.values(HOOK_RUNTIME_ENTRIES).every(
2256
- (relativePath) => privateFile(join2(selected.dir, relativePath), DATA_FILE_MODE)
2257
- );
2258
- }
2259
- function recordedNodeMatchesCurrentProcess(nodePath) {
2260
- try {
2261
- if (!statSync(nodePath).isFile()) return false;
2262
- accessSync(nodePath, constants.X_OK);
2263
- return realpathSync(nodePath) === realpathSync(process.execPath);
2264
- } catch {
2265
- return false;
2266
- }
2267
- }
2268
- function inspectHookRuntime(options = {}) {
2269
- let paths;
2270
- try {
2271
- paths = hookRuntimePaths(options);
2272
- } catch {
2273
- return { state: "invalid" };
2274
- }
2275
- const launcher = lstatIfPresent2(paths.launcher);
2276
- const selector = lstatIfPresent2(paths.current);
2277
- if (!(launcher || selector)) return { state: "missing" };
2278
- if (!(launcher && selector)) return { state: "invalid" };
2279
- try {
2280
- if (readFileSync3(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT) {
2281
- return { state: "invalid" };
2282
- }
2283
- const selected = readSelectedRelease(paths);
2284
- if (!selected) return { state: "invalid" };
2285
- assertOwnedHookRuntime(paths);
2286
- if (!hasSafeRuntimeLayout(paths, selected)) return { state: "invalid" };
2287
- return recordedNodeMatchesCurrentProcess(selected.manifest.nodePath) ? { state: "ready", version: selected.manifest.version } : { state: "invalid" };
2288
- } catch {
2289
- return { state: "invalid" };
2290
- }
2291
- }
2292
- function writeRelease(sourceDir, releaseDir, manifest, releasesDir) {
2293
- const stagingDir = mkdtempSync(join2(releasesDir, ".stage-"));
2294
- chmodSync(stagingDir, DIRECTORY_MODE);
2295
- try {
2296
- const directories = /* @__PURE__ */ new Set([stagingDir]);
2297
- const syncStagedFile = (path) => {
2298
- syncFile(path);
2299
- for (let directory = dirname2(path); ; directory = dirname2(directory)) {
2300
- directories.add(directory);
2301
- if (directory === stagingDir) break;
2302
- }
2303
- };
2304
- for (const [bin, targetRelative] of Object.entries(HOOK_RUNTIME_ENTRIES)) {
2305
- const source = join2(sourceDir, SOURCE_ENTRIES[bin]);
2306
- const target = join2(stagingDir, targetRelative);
2307
- mkdirSync2(dirname2(target), { recursive: true, mode: DIRECTORY_MODE });
2308
- chmodSync(dirname2(target), DIRECTORY_MODE);
2309
- copyFileSync(source, target);
2310
- chmodSync(target, DATA_FILE_MODE);
2311
- if (sha256(target) !== manifest.files[bin]) {
2312
- throw new Error(`cannot stage hook runtime: ${bin} changed while it was copied`);
2313
- }
2314
- syncStagedFile(target);
2315
- }
2316
- const nodePath = join2(stagingDir, "node");
2317
- writeFileSync2(nodePath, `${manifest.nodePath}
2318
- `, {
2319
- encoding: "utf8",
2320
- flag: "wx",
2321
- mode: DATA_FILE_MODE
2322
- });
2323
- syncStagedFile(nodePath);
2324
- const packageJsonPath = join2(stagingDir, "package.json");
2325
- writeFileSync2(packageJsonPath, runtimePackageJson(manifest), {
2326
- encoding: "utf8",
2327
- flag: "wx",
2328
- mode: DATA_FILE_MODE
2329
- });
2330
- syncStagedFile(packageJsonPath);
2331
- const manifestPath = join2(stagingDir, "manifest.json");
2332
- writeFileSync2(manifestPath, exactManifest(manifest), {
2333
- encoding: "utf8",
2334
- flag: "wx",
2335
- mode: DATA_FILE_MODE
2336
- });
2337
- syncStagedFile(manifestPath);
2338
- for (const directory of [...directories].sort((left, right) => right.length - left.length)) {
2339
- syncDirectory(directory);
2340
- }
2341
- try {
2342
- renameSync(stagingDir, releaseDir);
2343
- } catch (error) {
2344
- const code = error.code;
2345
- if (code !== "EEXIST" && code !== "ENOTEMPTY" || !validateRelease(releaseDir, manifest)) {
2346
- throw error;
2347
- }
2348
- }
2349
- } finally {
2350
- rmSync(stagingDir, { recursive: true, force: true });
2351
- }
2352
- }
2353
- function stageHookRuntime(options = {}) {
2354
- const root = packageRoot();
2355
- const sourceDir = options.sourceDir ?? (root ? join2(root, "dist", "hook-runtime") : null);
2356
- const version = options.version ?? packageVersion();
2357
- const nodePath = resolve2(options.nodePath ?? process.execPath);
2358
- if (!sourceDir || !version || compareSemver(version, version) !== 0 || !isAbsolute(nodePath) || /[\r\n]/u.test(nodePath)) {
2359
- throw new Error("cannot stage hook runtime: package identity is unavailable");
2360
- }
2361
- const manifest = sourceManifest(sourceDir, version, nodePath);
2362
- const paths = hookRuntimePaths(options);
2363
- const releaseName = `${RELEASE_PREFIX}${releaseId(manifest)}`;
2364
- const releaseDir = join2(paths.releasesDir, releaseName);
2365
- mkdirSync2(paths.releasesDir, { recursive: true, mode: DIRECTORY_MODE });
2366
- chmodSync(paths.configDir, DIRECTORY_MODE);
2367
- chmodSync(paths.runtimeDir, DIRECTORY_MODE);
2368
- chmodSync(paths.releasesDir, DIRECTORY_MODE);
2369
- return withFileLockSync(paths.selectionLock, () => {
2370
- const now = Date.now();
2371
- const selected = readSelectedRelease(paths);
2372
- if (selected) utimesSync(selected.dir, now / 1e3, now / 1e3);
2373
- const precedence = selected ? compareSemver(selected.manifest.version, manifest.version) : -1;
2374
- if (precedence === void 0) {
2375
- throw new Error("cannot stage hook runtime: selected release version is malformed");
2376
- }
2377
- const selectRequested = !selected || precedence < 0;
2378
- const requestedExists = existsSync3(releaseDir);
2379
- const protectedBefore = new Set(selected ? [selected.name] : []);
2380
- if (selectRequested && requestedExists) protectedBefore.add(releaseName);
2381
- pruneReleases(paths, protectedBefore, selectRequested && !requestedExists ? 1 : 0, now);
2382
- if (selectRequested && !validateRelease(releaseDir, manifest)) {
2383
- if (requestedExists) {
2384
- throw new Error("cannot stage hook runtime: immutable release is corrupt");
2385
- }
2386
- writeRelease(sourceDir, releaseDir, manifest, paths.releasesDir);
2387
- }
2388
- const finalRelease = selectRequested ? { name: releaseName, dir: releaseDir, manifest } : selected;
2389
- const launcherCurrent = (() => {
2390
- try {
2391
- return readFileSync3(paths.launcher, "utf8") === STABLE_HOOK_LAUNCHER_CONTENT;
2392
- } catch {
2393
- return false;
2394
- }
2395
- })();
2396
- const selectorCurrent = selected?.name === finalRelease.name;
2397
- if (!launcherCurrent) {
2398
- atomicWriteFile(paths.launcher, STABLE_HOOK_LAUNCHER_CONTENT, {
2399
- ensureParent: true,
2400
- mode: LAUNCHER_MODE
2401
- });
2402
- }
2403
- if (!selectorCurrent) {
2404
- syncDirectory(paths.releasesDir);
2405
- atomicWriteFile(paths.current, `${finalRelease.name}
2406
- `, {
2407
- ensureParent: true,
2408
- mode: DATA_FILE_MODE
2409
- });
2410
- }
2411
- utimesSync(finalRelease.dir, now / 1e3, now / 1e3);
2412
- pruneReleases(
2413
- paths,
2414
- /* @__PURE__ */ new Set([finalRelease.name, ...selected ? [selected.name] : []]),
2415
- 0,
2416
- now
2417
- );
2418
- return {
2419
- changed: !launcherCurrent || !selectorCurrent,
2420
- releaseDir: finalRelease.dir,
2421
- manifest: finalRelease.manifest,
2422
- paths
2423
- };
2424
- });
2425
- }
2426
- function lstatIfPresent2(path) {
2427
- try {
2428
- return lstatSync2(path);
2429
- } catch (error) {
2430
- const code = error.code;
2431
- if (code === "ENOENT" || code === "ENOTDIR") return void 0;
2432
- throw error;
2433
- }
2434
- }
2435
- function assertOwnedHookRuntime(paths) {
2436
- const launcher = lstatIfPresent2(paths.launcher);
2437
- if (launcher) {
2438
- if (!launcher.isFile() || readFileSync3(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT) {
2439
- throw new Error(`refusing to remove unrecognized hook launcher at ${paths.launcher}`);
2440
- }
2441
- }
2442
- const runtime = lstatIfPresent2(paths.runtimeDir);
2443
- if (!runtime) return;
2444
- if (!runtime.isDirectory()) {
2445
- throw new Error(`refusing to remove non-directory hook runtime at ${paths.runtimeDir}`);
2446
- }
2447
- const allowed = /* @__PURE__ */ new Set(["current", "releases", "selection.lock"]);
2448
- const unexpected = readdirSync2(paths.runtimeDir).filter((entry) => !allowed.has(entry));
2449
- if (unexpected.length > 0) {
2450
- throw new Error(
2451
- `refusing to remove hook runtime with unrecognized entries: ${unexpected.sort().join(", ")}`
2452
- );
2453
- }
2454
- const current = lstatIfPresent2(paths.current);
2455
- const releases = lstatIfPresent2(paths.releasesDir);
2456
- if (current) {
2457
- if (!current.isFile() || !releases || !readSelectedRelease(paths)) {
2458
- throw new Error(`refusing to remove malformed hook runtime selection at ${paths.current}`);
2459
- }
2460
- }
2461
- if (!releases) return;
2462
- if (!releases.isDirectory()) {
2463
- throw new Error(`refusing to remove non-directory hook releases at ${paths.releasesDir}`);
2464
- }
2465
- for (const entry of readdirSync2(paths.releasesDir, { withFileTypes: true })) {
2466
- const releaseDir = join2(paths.releasesDir, entry.name);
2467
- if (!entry.isDirectory() || !RELEASE_NAME_RE.test(entry.name) || !readReleaseManifest(releaseDir, entry.name)) {
2468
- throw new Error(`refusing to remove unrecognized hook release ${releaseDir}`);
2469
- }
2470
- }
2471
- }
2472
- function removeHookRuntime(options = {}) {
2473
- const paths = hookRuntimePaths(options);
2474
- const hadLauncher = lstatIfPresent2(paths.launcher) !== void 0;
2475
- const hadRuntime = lstatIfPresent2(paths.runtimeDir) !== void 0;
2476
- if (!hadLauncher && !hadRuntime) return { changed: false, paths };
2477
- assertOwnedHookRuntime(paths);
2478
- if (!hadRuntime) {
2479
- rmSync(paths.launcher, { force: true });
2480
- return { changed: hadLauncher, paths };
2481
- }
2482
- withFileLockSync(paths.selectionLock, () => {
2483
- assertOwnedHookRuntime(paths);
2484
- if (lstatIfPresent2(paths.launcher)) rmSync(paths.launcher, { force: true });
2485
- const quarantined = `${paths.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes3(8).toString("hex")}`;
2486
- renameSync(paths.runtimeDir, quarantined);
2487
- rmSync(quarantined, { recursive: true, force: true });
2488
- });
2489
- return { changed: true, paths };
2490
- }
2491
-
2492
- // src/commands/claude-install.ts
2493
- var CAPTURE_BIN = "prim-hook";
2494
- var GATE_BIN = "prim-pre-tool-use";
2495
- var POST_TOOL_USE_BIN = "prim-post-tool-use";
2496
- var SESSION_START_BIN = "prim-session-start";
2497
- var SESSION_END_BIN = "prim-session-end";
2498
- var STATUSLINE_BIN = "prim-statusline";
2499
- var STATUSLINE_ARGS = "";
2500
- var STATUSLINE_COMMAND = stableHookCommand(STATUSLINE_BIN, STATUSLINE_ARGS);
2501
- var STATUSLINE_REFRESH_SECONDS = 5;
2502
- var PRIM_BINS = [
2503
- CAPTURE_BIN,
2504
- GATE_BIN,
2505
- POST_TOOL_USE_BIN,
2506
- SESSION_START_BIN,
2507
- SESSION_END_BIN
2508
- ];
2509
- var JSON_INDENT = 2;
2510
- var USER_SCOPE_PATH = join3(homedir2(), ".claude", "settings.json");
2511
- function projectRoot() {
2512
- return gitToplevel() ?? process.cwd();
2513
- }
2514
- var projectScopePath = () => join3(projectRoot(), ".claude", "settings.json");
2515
- var CAPTURE_EVENTS = [
2516
- "SessionStart",
2517
- "UserPromptSubmit",
2518
- "PreToolUse",
2519
- "PostToolUse",
2520
- "PostToolUseFailure",
2521
- "Stop",
2522
- "SessionEnd",
2523
- "SubagentStop"
2524
- ];
2525
- function makeRegistration(event, matcher, bin, args = "") {
2526
- return {
2527
- event,
2528
- matcher,
2529
- bin,
2530
- command: stableHookCommand(bin, args)
2531
- };
2532
- }
2533
- function makeDetachedRegistration(event, matcher, bin, args = "") {
2534
- return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
2535
- }
2536
- var REGISTRATIONS = [
2537
- ...CAPTURE_EVENTS.map(
2538
- (event) => event === "SessionEnd" ? makeDetachedRegistration(event, "*", CAPTURE_BIN) : makeRegistration(event, "*", CAPTURE_BIN)
2539
- ),
2540
- makeRegistration("PreToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", GATE_BIN),
2541
- makeRegistration("PostToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", POST_TOOL_USE_BIN),
2542
- makeRegistration(
2543
- "PostToolUseFailure",
2544
- "Edit|Write|MultiEdit|NotebookEdit|Bash",
2545
- POST_TOOL_USE_BIN
2546
- ),
2547
- makeRegistration("SessionStart", "*", SESSION_START_BIN),
2548
- makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
2549
- ];
2550
- var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
2551
- var LEGACY_PRIM_PERMISSION_RULES = [
2552
- "Bash(npx --yes @primitive.ai/prim@latest:*)",
2553
- "Bash(npx --yes @primitive.ai/prim:*)"
2554
- ];
2555
- var ALL_PRIM_PERMISSION_RULES = /* @__PURE__ */ new Set([
2556
- PRIM_PERMISSION_RULE,
2557
- ...LEGACY_PRIM_PERMISSION_RULES
2558
- ]);
2559
- var PRIM_AUTOMODE_TRUST = "Primitive CLI (prim): the @primitive.ai/prim npm package \u2014 run via `npx --yes @primitive.ai/prim \u2026` \u2014 and its API at api.getprimitive.ai are trusted developer tooling this user installed for passive decision capture, conflict checks, and team presence. Running prim and its routine API/daemon calls is expected, not untrusted code execution or data exfiltration.";
2560
- var AUTOMODE_DEFAULTS = "$defaults";
2561
- function settingsPathFor(scope) {
2562
- return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
2563
- }
2564
- function readSettings(path) {
2565
- if (!existsSync4(path)) {
2566
- return {};
2567
- }
2568
- const raw = readFileSync4(path, "utf-8");
2569
- try {
2570
- return JSON.parse(raw);
2571
- } catch (err) {
2572
- const detail = err instanceof Error ? err.message : String(err);
2573
- throw new Error(`${path} is not valid JSON: ${detail}`);
2574
- }
2575
- }
2576
- function entryHasCommand(entry, bin) {
2577
- return entry.hooks?.some((h) => commandMatchesBin(h.command, bin)) ?? false;
2578
- }
2579
- function ownedRegistrationEntries(settings) {
2580
- return Object.entries(settings.hooks ?? {}).flatMap(
2581
- ([event, entries]) => (entries ?? []).filter((entry) => PRIM_BINS.some((bin) => entryHasCommand(entry, bin))).map((entry) => ({ event, entry }))
2582
- );
2583
- }
2584
- function hasAnyHookRegistration(settings) {
2585
- return ownedRegistrationEntries(settings).length > 0;
2586
- }
2587
- function entryMatchesRegistration(owned, registration) {
2588
- return owned.event === registration.event && owned.entry.matcher === registration.matcher && owned.entry.hooks?.length === 1 && owned.entry.hooks[0].type === "command" && owned.entry.hooks[0].command === registration.command;
2589
- }
2590
- function hasCompleteHookRegistration(settings) {
2591
- const owned = ownedRegistrationEntries(settings);
2592
- return owned.length === REGISTRATIONS.length && REGISTRATIONS.every(
2593
- (registration) => owned.filter((entry) => entryMatchesRegistration(entry, registration)).length === 1
2594
- );
2595
- }
2596
- function canonicalEntry(reg) {
2597
- return { matcher: reg.matcher, hooks: [{ type: "command", command: reg.command }] };
2598
- }
2599
- function stripCommand(list, bin) {
2600
- const out = [];
2601
- for (const e of list) {
2602
- const hooks = (e.hooks ?? []).filter((h) => !commandMatchesBin(h.command, bin));
2603
- if (hooks.length > 0) {
2604
- out.push({ ...e, hooks });
2605
- }
2606
- }
2607
- return out;
2608
- }
2609
- function ensureRegistration(list, reg, force) {
2610
- const isCanonical = (e) => e.matcher === reg.matcher && e.hooks?.length === 1 && e.hooks[0].command === reg.command;
2611
- const hasStray = (e) => !isCanonical(e) && (e.hooks ?? []).some((h) => commandMatchesBin(h.command, reg.bin));
2612
- if (list.some(isCanonical) && !list.some(hasStray) && !force) {
2613
- return list;
2614
- }
2615
- return [...stripCommand(list, reg.bin), canonicalEntry(reg)];
2616
- }
2617
- var LEGACY_STATUSLINE_COMMAND = "prim statusline";
2618
- var RUNTIME_STATUSLINE_SUFFIXES = [
2619
- "/prim/runtime/prim-statusline",
2620
- // One pre-launcher build persisted the JS entry directly. Keep recognizing
2621
- // it so reinstall migrates and uninstall removes that older staged form.
2622
- "/prim/runtime/current/prim-statusline"
2623
- ];
2624
- function isPrimStatusLine(settings) {
2625
- const s = settings.statusLine;
2626
- if (s?.type !== "command") {
2627
- return false;
2628
- }
2629
- const c = (s.command ?? "").trim();
2630
- return c === LEGACY_STATUSLINE_COMMAND || c.includes("@primitive.ai/prim") && c.includes("statusline") || c.includes("prim-hook-launcher-v1") && commandMatchesBin(c, STATUSLINE_BIN) || RUNTIME_STATUSLINE_SUFFIXES.some((suffix) => c.includes(suffix));
2631
- }
2632
- function hookRuntimeResolutions(settings) {
2633
- const resolutions = hookCommandResolutions(
2634
- Object.values(settings.hooks ?? {}).flatMap(
2635
- (entries) => (entries ?? []).flatMap((entry) => (entry.hooks ?? []).map((hook) => hook.command))
2636
- ),
2637
- PRIM_BINS
2638
- );
2639
- if (!isPrimStatusLine(settings)) return resolutions;
2640
- const statusline = hookCommandResolution(settings.statusLine?.command, STATUSLINE_BIN);
2641
- resolutions.push(statusline ?? { kind: "legacy_path" });
2642
- return resolutions;
2643
- }
2644
- function inspectHookRuntimeResolutions() {
2645
- return [
2646
- ...hookRuntimeResolutions(readSettings(USER_SCOPE_PATH)),
2647
- ...hookRuntimeResolutions(readSettings(projectScopePath()))
2648
- ];
2649
- }
2650
- function applyStatusLine(settings, command = STATUSLINE_COMMAND) {
2651
- if (command === false) {
2652
- return isPrimStatusLine(settings) ? { ...settings, statusLine: void 0 } : settings;
2653
- }
2654
- if (settings.statusLine && !isPrimStatusLine(settings)) {
2655
- return settings;
2656
- }
2657
- return {
2658
- ...settings,
2659
- statusLine: {
2660
- type: "command",
2661
- command,
2662
- refreshInterval: STATUSLINE_REFRESH_SECONDS
2663
- }
2664
- };
2665
- }
2666
- function applyPermissions(settings) {
2667
- const permissions = settings.permissions ?? {};
2668
- const allow = permissions.allow ?? [];
2669
- const hasCanonical = allow.includes(PRIM_PERMISSION_RULE);
2670
- const hasLegacy = allow.some((rule) => LEGACY_PRIM_PERMISSION_RULES.includes(rule));
2671
- if (hasCanonical && !hasLegacy) {
2672
- return settings;
2673
- }
2674
- const withoutPrim = allow.filter((rule) => !ALL_PRIM_PERMISSION_RULES.has(rule));
2675
- return {
2676
- ...settings,
2677
- permissions: { ...permissions, allow: [...withoutPrim, PRIM_PERMISSION_RULE] }
2678
- };
2679
- }
2680
- function removePrimPermission(settings) {
2681
- const permissions = settings.permissions;
2682
- if (!permissions?.allow?.some((rule) => ALL_PRIM_PERMISSION_RULES.has(rule))) {
2683
- return settings;
2684
- }
2685
- const allow = permissions.allow.filter((rule) => !ALL_PRIM_PERMISSION_RULES.has(rule));
2686
- const nextPermissions = {
2687
- ...permissions,
2688
- allow: allow.length > 0 ? allow : void 0
2689
- };
2690
- const hasOtherPerms = Object.values(nextPermissions).some((v) => v !== void 0);
2691
- return { ...settings, permissions: hasOtherPerms ? nextPermissions : void 0 };
2692
- }
2693
- function applyAutoModeTrust(settings) {
2694
- const autoMode = settings.autoMode ?? {};
2695
- const env = autoMode.environment;
2696
- if (env?.includes(PRIM_AUTOMODE_TRUST)) {
2697
- return settings;
2698
- }
2699
- const nextEnv = env ? [...env, PRIM_AUTOMODE_TRUST] : [AUTOMODE_DEFAULTS, PRIM_AUTOMODE_TRUST];
2700
- return { ...settings, autoMode: { ...autoMode, environment: nextEnv } };
2701
- }
2702
- function removeAutoModeTrust(settings) {
2703
- const autoMode = settings.autoMode;
2704
- if (!autoMode?.environment?.includes(PRIM_AUTOMODE_TRUST)) {
2705
- return settings;
2706
- }
2707
- const env = autoMode.environment.filter((e) => e !== PRIM_AUTOMODE_TRUST);
2708
- const onlyDefaultsLeft = env.length === 0 || env.length === 1 && env[0] === AUTOMODE_DEFAULTS;
2709
- const nextAutoMode = {
2710
- ...autoMode,
2711
- environment: onlyDefaultsLeft ? void 0 : env
2712
- };
2713
- const hasOther = Object.values(nextAutoMode).some((v) => v !== void 0);
2714
- return { ...settings, autoMode: hasOther ? nextAutoMode : void 0 };
2715
- }
2716
- function applyInstall(settings, options = {}) {
2717
- const hooks = { ...settings.hooks ?? {} };
2718
- for (const reg of REGISTRATIONS) {
2719
- hooks[reg.event] = ensureRegistration(hooks[reg.event] ?? [], reg, options.force ?? false);
2720
- }
2721
- return applyPermissions(
2722
- applyStatusLine(
2723
- { ...settings, hooks },
2724
- options.installStatusline === false ? false : options.statuslineCommand ?? STATUSLINE_COMMAND
2725
- )
2726
- );
2727
- }
2728
- function applyUninstall(settings) {
2729
- const source = settings.hooks ?? {};
2730
- const hooks = {};
2731
- for (const event of Object.keys(source)) {
2732
- let list = source[event] ?? [];
2733
- for (const bin of PRIM_BINS) {
2734
- list = stripCommand(list, bin);
2735
- }
2736
- if (list.length > 0) {
2737
- hooks[event] = list;
2738
- }
2739
- }
2740
- const next = { ...settings, hooks };
2741
- if (isPrimStatusLine(next)) {
2742
- next.statusLine = void 0;
2743
- }
2744
- return removeAutoModeTrust(removePrimPermission(next));
2745
- }
2746
- function captureInstalled(settings) {
2747
- return CAPTURE_EVENTS.some(
2748
- (event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e, CAPTURE_BIN))
2749
- );
2750
- }
2751
- function feedbackInstalled(settings) {
2752
- const stopCapture = (settings.hooks?.Stop ?? []).some(
2753
- (entry) => entryHasCommand(entry, CAPTURE_BIN)
2754
- );
2755
- const sessionStart = (settings.hooks?.SessionStart ?? []).some(
2756
- (entry) => entryHasCommand(entry, SESSION_START_BIN)
2757
- );
2758
- return stopCapture && sessionStart;
2759
- }
2760
- function statuslineInstalled(settings) {
2761
- return isPrimStatusLine(settings);
2762
- }
2763
- function statuslineState(settings) {
2764
- if (isPrimStatusLine(settings)) {
2765
- return "primitive";
2766
- }
2767
- return settings.statusLine ? "custom" : "empty";
2768
- }
2769
- function isGateInstalled(settings) {
2770
- return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN));
2771
- }
2772
- function atomicWrite(path, content) {
2773
- atomicWriteFile(path, `${JSON.stringify(content, null, JSON_INDENT)}
2774
- `, {
2775
- ensureParent: true
2776
- });
2777
- }
2778
- function userStatuslineBlocksProjectInstall(scope, userSettings) {
2779
- return scope === "project" && statuslineState(userSettings) === "custom";
2780
- }
2781
- function performInstall(scope, force) {
2782
- stageHookRuntime();
2783
- const path = settingsPathFor(scope);
2784
- const before = readSettings(path);
2785
- const blockedByUserStatusline = userStatuslineBlocksProjectInstall(
2786
- scope,
2787
- scope === "project" ? readSettings(USER_SCOPE_PATH) : {}
2788
- );
2789
- const after = applyInstall(before, {
2790
- force,
2791
- statuslineCommand: STATUSLINE_COMMAND,
2792
- installStatusline: !blockedByUserStatusline
2793
- });
2794
- const changed = JSON.stringify(before) !== JSON.stringify(after);
2795
- if (changed) {
2796
- atomicWrite(path, after);
2797
- }
2798
- return {
2799
- scope,
2800
- path,
2801
- gate: isGateInstalled(after),
2802
- capture: captureInstalled(after),
2803
- feedback: feedbackInstalled(after),
2804
- statusline: statuslineInstalled(after),
2805
- statuslineState: statuslineState(after),
2806
- changed
2807
- };
2808
- }
2809
- function performUninstall(scope) {
2810
- const path = settingsPathFor(scope);
2811
- const before = readSettings(path);
2812
- const after = applyUninstall(before);
2813
- const changed = JSON.stringify(before) !== JSON.stringify(after);
2814
- if (changed) {
2815
- atomicWrite(path, after);
2816
- }
2817
- return {
2818
- scope,
2819
- path,
2820
- gate: isGateInstalled(after),
2821
- capture: captureInstalled(after),
2822
- feedback: feedbackInstalled(after),
2823
- statusline: statuslineInstalled(after),
2824
- statuslineState: statuslineState(after),
2825
- changed
2826
- };
2827
- }
2828
- function performPermissionInstall(scope) {
2829
- const path = settingsPathFor(scope);
2830
- const before = readSettings(path);
2831
- const after = applyAutoModeTrust(applyPermissions(before));
2832
- const changed = JSON.stringify(before) !== JSON.stringify(after);
2833
- if (changed) {
2834
- atomicWrite(path, after);
2835
- }
2836
- return {
2837
- scope,
2838
- path,
2839
- allowed: (after.permissions?.allow ?? []).includes(PRIM_PERMISSION_RULE),
2840
- autoModeTrusted: (after.autoMode?.environment ?? []).includes(PRIM_AUTOMODE_TRUST),
2841
- changed
2842
- };
2843
- }
2844
- function performStatus() {
2845
- const statusFor = (path) => {
2846
- const settings = readSettings(path);
2847
- return {
2848
- path,
2849
- present: hasAnyHookRegistration(settings) || statuslineInstalled(settings),
2850
- gate: isGateInstalled(settings),
2851
- capture: captureInstalled(settings),
2852
- feedback: feedbackInstalled(settings),
2853
- complete: hasCompleteHookRegistration(settings),
2854
- statusline: statuslineInstalled(settings),
2855
- statuslineState: statuslineState(settings)
2856
- };
2857
- };
2858
- return { user: statusFor(USER_SCOPE_PATH), project: statusFor(projectScopePath()) };
2859
- }
2860
- function resolveScope(input) {
2861
- if (input === void 0 || input === "project") {
2862
- return "project";
2863
- }
2864
- if (input === "user") {
2865
- return "user";
2866
- }
2867
- console.error(`[prim] unknown --scope "${input}" (expected: user or project)`);
2868
- process.exit(1);
2869
- }
2870
- function registerClaudeCommands(program2) {
2871
- const claude = program2.command("claude").description("Manage the prim Claude Code integration (capture, gate, ingest, presence)");
2872
- claude.command("install").description("Register the prim hooks + statusline in Claude Code's settings.json").option(
2873
- "--scope <scope>",
2874
- "project (default, the repo's .claude/settings.json) or user (~/.claude/settings.json)"
2875
- ).option("--force", "Replace any drifted prim hook entries").action((opts) => {
2876
- const scope = resolveScope(opts.scope);
2877
- const result = performInstall(scope, opts.force ?? false);
2878
- if (result.changed) {
2879
- console.error(
2880
- `[prim] Claude Code integration installed (${scope} scope) at ${result.path}`
2881
- );
2882
- } else {
2883
- console.error(
2884
- `[prim] Claude Code integration already present at ${result.path} (no changes)`
2885
- );
1927
+ try {
1928
+ postRewriteHook = ensureEffectivePostRewriteHook(root).path;
1929
+ } catch (error) {
1930
+ const detail = error instanceof Error ? error.message : String(error);
1931
+ process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
1932
+ `);
1933
+ }
1934
+ phase = "GitHub repo connection";
1935
+ binding = await bindRepository(root);
2886
1936
  }
2887
- const projectState = scope === "user" ? performStatus().project.statuslineState : void 0;
2888
- const userState = scope === "project" ? performStatus().user.statuslineState : void 0;
2889
- if (result.statuslineState === "custom" || projectState === "custom" || userState === "custom") {
2890
- const owner = result.statuslineState === "custom" ? result.path : projectState === "custom" ? projectScopePath() : USER_SCOPE_PATH;
2891
- console.error(
2892
- `[prim] \u26A0 statusline skipped: an existing custom statusLine is preserved at ${owner}; run \`prim daemon status\` for Primitive health`
2893
- );
1937
+ if (active && binding?.status === "unbound") {
1938
+ const connected = await maybeConnectRepository(root, globals);
1939
+ if (!connected) {
1940
+ process.stderr.write(`[prim] ${GITHUB_CONNECTION_REQUIRED}
1941
+ `);
1942
+ printJson({
1943
+ active: false,
1944
+ repo: root,
1945
+ bindingStatus: binding.status,
1946
+ repositoryFullName: binding.repositoryFullName,
1947
+ ...postCommitHook ? { postCommitHook } : {},
1948
+ ...postRewriteHook ? { postRewriteHook } : {}
1949
+ });
1950
+ process.exitCode = 1;
1951
+ return;
1952
+ }
1953
+ binding = connected;
2894
1954
  }
2895
- console.log(JSON.stringify(result, null, JSON_INDENT));
2896
- });
2897
- claude.command("preauth").description(
2898
- "Pre-authorize prim's own calls (allow-rule + auto-mode trust, no hooks) so they never prompt"
2899
- ).option(
2900
- "--scope <scope>",
2901
- "user (default for preauth \u2014 covers every repo) or project (this repo's .claude/settings.json)"
2902
- ).action((opts) => {
2903
- const scope = resolveScope(opts.scope ?? "user");
2904
- const result = performPermissionInstall(scope);
2905
- console.error(
2906
- result.changed ? `[prim] prim pre-authorization written (${scope} scope) at ${result.path} (allow-rule + auto-mode trust)` : `[prim] prim pre-authorization already present at ${result.path} (no changes)`
2907
- );
2908
- console.log(JSON.stringify(result, null, JSON_INDENT));
2909
- });
2910
- claude.command("uninstall").description("Remove all prim hooks + the prim statusline from settings.json").option(
2911
- "--scope <scope>",
2912
- "project (default, the repo's .claude/settings.json) or user (~/.claude/settings.json)"
2913
- ).action((opts) => {
2914
- const scope = resolveScope(opts.scope);
2915
- const result = performUninstall(scope);
2916
- if (result.changed) {
2917
- console.error(`[prim] prim hooks removed from ${result.path}`);
2918
- } else {
2919
- console.error(`[prim] no prim hooks to remove at ${result.path} (nothing changed)`);
1955
+ if (active) {
1956
+ try {
1957
+ await fetchAndCacheCollectScope(root);
1958
+ } catch {
1959
+ }
2920
1960
  }
2921
- console.log(JSON.stringify(result, null, JSON_INDENT));
2922
- });
2923
- claude.command("status").description(
2924
- "Report whether each prim surface (gate, capture, feedback, statusline) is installed per scope"
2925
- ).action(() => {
2926
- const result = performStatus();
2927
- const mark = (b) => b ? "\u2713" : "\u2717";
2928
- const line = (label, s) => `[prim] ${label}: gate ${mark(s.gate)} \xB7 capture ${mark(s.capture)} \xB7 feedback ${mark(s.feedback)} \xB7 statusline ${s.statuslineState} (${s.path})`;
2929
- console.error(`${line("user", result.user)}
2930
- ${line("project", result.project)}`);
2931
- console.log(JSON.stringify(result, null, JSON_INDENT));
2932
- });
1961
+ phase = "local activation";
1962
+ setRepoActive(root, active);
1963
+ await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
1964
+ process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
1965
+ `);
1966
+ printJson({
1967
+ active,
1968
+ repo: root,
1969
+ ...binding ? {
1970
+ bindingStatus: binding.status,
1971
+ repositoryFullName: binding.repositoryFullName,
1972
+ ...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
1973
+ } : {},
1974
+ ...postCommitHook ? { postCommitHook } : {},
1975
+ ...postRewriteHook ? { postRewriteHook } : {}
1976
+ });
1977
+ } catch (err) {
1978
+ const detail = err instanceof Error ? err.message : String(err);
1979
+ process.stderr.write(
1980
+ active ? `[prim] failed to enable prim during ${phase}: ${detail}
1981
+ ` : `[prim] failed to disable prim: ${detail}
1982
+ `
1983
+ );
1984
+ process.exit(1);
1985
+ }
1986
+ }
1987
+ function registerActivationCommands(program2) {
1988
+ program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action((_opts, command) => applyActivation(true, command.optsWithGlobals()));
1989
+ program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(
1990
+ (_opts, command) => applyActivation(false, command.optsWithGlobals())
1991
+ );
2933
1992
  }
2934
1993
 
2935
1994
  // src/commands/codex-install.ts
2936
- import { homedir as homedir3 } from "os";
2937
- import { join as join4 } from "path";
2938
- var CAPTURE_BIN2 = "prim-hook";
2939
- var GATE_BIN2 = "prim-pre-tool-use";
2940
- var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
2941
- var SESSION_START_BIN2 = "prim-session-start";
1995
+ import { homedir as homedir2 } from "os";
1996
+ import { join as join2 } from "path";
1997
+ var CAPTURE_BIN = "prim-hook";
1998
+ var GATE_BIN = "prim-pre-tool-use";
1999
+ var POST_TOOL_USE_BIN = "prim-post-tool-use";
2000
+ var SESSION_START_BIN = "prim-session-start";
2942
2001
  var CODEX_ARGS = "--agent codex";
2943
2002
  var CODEX_EDIT_MATCHER = "apply_patch|Bash";
2944
2003
  var CODEX_POST_TOOL_MATCHER = "apply_patch|Bash";
2945
- var JSON_INDENT2 = 2;
2004
+ var JSON_INDENT = 2;
2946
2005
  var CODEX_CAPTURE_EVENTS = [
2947
2006
  "SessionStart",
2948
2007
  "UserPromptSubmit",
@@ -2951,31 +2010,31 @@ var CODEX_CAPTURE_EVENTS = [
2951
2010
  "Stop",
2952
2011
  "SubagentStop"
2953
2012
  ];
2954
- var PRIM_BINS2 = [CAPTURE_BIN2, GATE_BIN2, POST_TOOL_USE_BIN2, SESSION_START_BIN2];
2013
+ var PRIM_BINS = [CAPTURE_BIN, GATE_BIN, POST_TOOL_USE_BIN, SESSION_START_BIN];
2955
2014
  var CODEX_REGISTRATIONS = [
2956
- ...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
2957
- makeRegistration("PreToolUse", CODEX_EDIT_MATCHER, GATE_BIN2, CODEX_ARGS),
2958
- makeRegistration("PostToolUse", CODEX_POST_TOOL_MATCHER, POST_TOOL_USE_BIN2, CODEX_ARGS),
2959
- makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
2015
+ ...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN, CODEX_ARGS)),
2016
+ makeRegistration("PreToolUse", CODEX_EDIT_MATCHER, GATE_BIN, CODEX_ARGS),
2017
+ makeRegistration("PostToolUse", CODEX_POST_TOOL_MATCHER, POST_TOOL_USE_BIN, CODEX_ARGS),
2018
+ makeRegistration("SessionStart", "*", SESSION_START_BIN, CODEX_ARGS)
2960
2019
  ];
2961
- var USER_SCOPE_PATH2 = join4(homedir3(), ".codex", "hooks.json");
2962
- var projectScopePath2 = () => join4(projectRoot(), ".codex", "hooks.json");
2963
- function settingsPathFor2(scope) {
2964
- return scope === "user" ? USER_SCOPE_PATH2 : projectScopePath2();
2020
+ var USER_SCOPE_PATH = join2(homedir2(), ".codex", "hooks.json");
2021
+ var projectScopePath = () => join2(projectRoot(), ".codex", "hooks.json");
2022
+ function settingsPathFor(scope) {
2023
+ return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
2965
2024
  }
2966
- function applyInstall2(settings, options = {}) {
2025
+ function applyInstall(settings, options = {}) {
2967
2026
  const hooks = { ...settings.hooks ?? {} };
2968
2027
  for (const reg of CODEX_REGISTRATIONS) {
2969
2028
  hooks[reg.event] = ensureRegistration(hooks[reg.event] ?? [], reg, options.force ?? false);
2970
2029
  }
2971
2030
  return { ...settings, hooks };
2972
2031
  }
2973
- function applyUninstall2(settings) {
2032
+ function applyUninstall(settings) {
2974
2033
  const source = settings.hooks ?? {};
2975
2034
  const hooks = {};
2976
2035
  for (const event of Object.keys(source)) {
2977
2036
  let list = source[event] ?? [];
2978
- for (const bin of PRIM_BINS2) {
2037
+ for (const bin of PRIM_BINS) {
2979
2038
  list = stripCommand(list, bin);
2980
2039
  }
2981
2040
  if (list.length > 0) {
@@ -2984,89 +2043,89 @@ function applyUninstall2(settings) {
2984
2043
  }
2985
2044
  return { ...settings, hooks };
2986
2045
  }
2987
- function captureInstalled2(settings) {
2046
+ function captureInstalled(settings) {
2988
2047
  return CODEX_CAPTURE_EVENTS.some(
2989
- (event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e, CAPTURE_BIN2))
2048
+ (event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e, CAPTURE_BIN))
2990
2049
  );
2991
2050
  }
2992
- function ownedRegistrationEntries2(settings) {
2051
+ function ownedRegistrationEntries(settings) {
2993
2052
  return Object.entries(settings.hooks ?? {}).flatMap(
2994
- ([event, entries]) => (entries ?? []).filter((entry) => PRIM_BINS2.some((bin) => entryHasCommand(entry, bin))).map((entry) => ({ event, entry }))
2053
+ ([event, entries]) => (entries ?? []).filter((entry) => PRIM_BINS.some((bin) => entryHasCommand(entry, bin))).map((entry) => ({ event, entry }))
2995
2054
  );
2996
2055
  }
2997
- function hasAnyHookRegistration2(settings) {
2998
- return ownedRegistrationEntries2(settings).length > 0;
2056
+ function hasAnyHookRegistration(settings) {
2057
+ return ownedRegistrationEntries(settings).length > 0;
2999
2058
  }
3000
- function hookRuntimeResolutions2(settings) {
2059
+ function hookRuntimeResolutions(settings) {
3001
2060
  return hookCommandResolutions(
3002
2061
  Object.values(settings.hooks ?? {}).flatMap(
3003
2062
  (entries) => (entries ?? []).flatMap((entry) => (entry.hooks ?? []).map((hook) => hook.command))
3004
2063
  ),
3005
- PRIM_BINS2
2064
+ PRIM_BINS
3006
2065
  );
3007
2066
  }
3008
- function inspectHookRuntimeResolutions2() {
2067
+ function inspectHookRuntimeResolutions3() {
3009
2068
  return [
3010
- ...hookRuntimeResolutions2(readSettings(USER_SCOPE_PATH2)),
3011
- ...hookRuntimeResolutions2(readSettings(projectScopePath2()))
2069
+ ...hookRuntimeResolutions(readSettings(USER_SCOPE_PATH)),
2070
+ ...hookRuntimeResolutions(readSettings(projectScopePath()))
3012
2071
  ];
3013
2072
  }
3014
- function entryMatchesRegistration2(owned, registration) {
2073
+ function entryMatchesRegistration(owned, registration) {
3015
2074
  return owned.event === registration.event && owned.entry.matcher === registration.matcher && owned.entry.hooks?.length === 1 && owned.entry.hooks[0].type === "command" && owned.entry.hooks[0].command === registration.command;
3016
2075
  }
3017
- function hasCompleteHookRegistration2(settings) {
3018
- const owned = ownedRegistrationEntries2(settings);
2076
+ function hasCompleteHookRegistration(settings) {
2077
+ const owned = ownedRegistrationEntries(settings);
3019
2078
  return owned.length === CODEX_REGISTRATIONS.length && CODEX_REGISTRATIONS.every(
3020
- (registration) => owned.filter((entry) => entryMatchesRegistration2(entry, registration)).length === 1
2079
+ (registration) => owned.filter((entry) => entryMatchesRegistration(entry, registration)).length === 1
3021
2080
  );
3022
2081
  }
3023
- function isGateInstalled2(settings) {
3024
- return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN2));
2082
+ function isGateInstalled(settings) {
2083
+ return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN));
3025
2084
  }
3026
2085
  function resultFor(scope, path, after, changed) {
3027
2086
  return {
3028
2087
  scope,
3029
2088
  path,
3030
- gate: isGateInstalled2(after),
3031
- capture: captureInstalled2(after),
2089
+ gate: isGateInstalled(after),
2090
+ capture: captureInstalled(after),
3032
2091
  changed
3033
2092
  };
3034
2093
  }
3035
- function performInstall2(scope, force) {
2094
+ function performInstall(scope, force) {
3036
2095
  stageHookRuntime();
3037
- const path = settingsPathFor2(scope);
2096
+ const path = settingsPathFor(scope);
3038
2097
  const before = readSettings(path);
3039
- const after = applyInstall2(before, { force });
2098
+ const after = applyInstall(before, { force });
3040
2099
  const changed = JSON.stringify(before) !== JSON.stringify(after);
3041
2100
  if (changed) {
3042
2101
  atomicWrite(path, after);
3043
2102
  }
3044
2103
  return resultFor(scope, path, after, changed);
3045
2104
  }
3046
- function performUninstall2(scope) {
3047
- const path = settingsPathFor2(scope);
2105
+ function performUninstall(scope) {
2106
+ const path = settingsPathFor(scope);
3048
2107
  const before = readSettings(path);
3049
- const after = applyUninstall2(before);
2108
+ const after = applyUninstall(before);
3050
2109
  const changed = JSON.stringify(before) !== JSON.stringify(after);
3051
2110
  if (changed) {
3052
2111
  atomicWrite(path, after);
3053
2112
  }
3054
2113
  return resultFor(scope, path, after, changed);
3055
2114
  }
3056
- function performStatus2() {
2115
+ function performStatus3() {
3057
2116
  const statusFor = (path) => {
3058
2117
  const settings = readSettings(path);
3059
2118
  return {
3060
2119
  path,
3061
- present: hasAnyHookRegistration2(settings),
3062
- gate: isGateInstalled2(settings),
3063
- capture: captureInstalled2(settings),
3064
- complete: hasCompleteHookRegistration2(settings)
2120
+ present: hasAnyHookRegistration(settings),
2121
+ gate: isGateInstalled(settings),
2122
+ capture: captureInstalled(settings),
2123
+ complete: hasCompleteHookRegistration(settings)
3065
2124
  };
3066
2125
  };
3067
- return { user: statusFor(USER_SCOPE_PATH2), project: statusFor(projectScopePath2()) };
2126
+ return { user: statusFor(USER_SCOPE_PATH), project: statusFor(projectScopePath()) };
3068
2127
  }
3069
- function resolveScope2(input) {
2128
+ function resolveScope(input) {
3070
2129
  if (input === void 0 || input === "project") {
3071
2130
  return "project";
3072
2131
  }
@@ -3083,48 +2142,48 @@ function registerCodexCommands(program2) {
3083
2142
  "--scope <scope>",
3084
2143
  "project (default, the repo's .codex/hooks.json) or user (~/.codex/hooks.json)"
3085
2144
  ).option("--force", "Replace any drifted prim hook entries").action((opts) => {
3086
- const scope = resolveScope2(opts.scope);
3087
- const result = performInstall2(scope, opts.force ?? false);
2145
+ const scope = resolveScope(opts.scope);
2146
+ const result = performInstall(scope, opts.force ?? false);
3088
2147
  if (result.changed) {
3089
2148
  console.error(`[prim] Codex integration installed (${scope} scope) at ${result.path}`);
3090
2149
  } else {
3091
2150
  console.error(`[prim] Codex integration already present at ${result.path} (no changes)`);
3092
2151
  }
3093
2152
  console.error(TRUST_NOTICE);
3094
- console.log(JSON.stringify(result, null, JSON_INDENT2));
2153
+ console.log(JSON.stringify(result, null, JSON_INDENT));
3095
2154
  });
3096
2155
  codex.command("uninstall").description("Remove all prim hooks from Codex's hooks.json").option(
3097
2156
  "--scope <scope>",
3098
2157
  "project (default, the repo's .codex/hooks.json) or user (~/.codex/hooks.json)"
3099
2158
  ).action((opts) => {
3100
- const scope = resolveScope2(opts.scope);
3101
- const result = performUninstall2(scope);
2159
+ const scope = resolveScope(opts.scope);
2160
+ const result = performUninstall(scope);
3102
2161
  if (result.changed) {
3103
2162
  console.error(`[prim] prim hooks removed from ${result.path}`);
3104
2163
  } else {
3105
2164
  console.error(`[prim] no prim hooks to remove at ${result.path} (nothing changed)`);
3106
2165
  }
3107
- console.log(JSON.stringify(result, null, JSON_INDENT2));
2166
+ console.log(JSON.stringify(result, null, JSON_INDENT));
3108
2167
  });
3109
2168
  codex.command("status").description("Report whether each prim surface (gate, capture) is installed per scope").action(() => {
3110
- const result = performStatus2();
2169
+ const result = performStatus3();
3111
2170
  const mark = (b) => b ? "\u2713" : "\u2717";
3112
2171
  const line = (label, s) => `[prim] ${label}: gate ${mark(s.gate)} \xB7 capture ${mark(s.capture)} (${s.path})`;
3113
2172
  console.error(`${line("user", result.user)}
3114
2173
  ${line("project", result.project)}`);
3115
- console.log(JSON.stringify(result, null, JSON_INDENT2));
2174
+ console.log(JSON.stringify(result, null, JSON_INDENT));
3116
2175
  });
3117
2176
  }
3118
2177
 
3119
2178
  // src/commands/daemon.ts
3120
2179
  import { spawn as spawn2 } from "child_process";
3121
- import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
3122
- import { join as join7 } from "path";
2180
+ import { closeSync, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
2181
+ import { join as join5 } from "path";
3123
2182
  import { Option as Option2 } from "commander";
3124
2183
 
3125
2184
  // src/daemon/latest-bootstrap.ts
3126
2185
  import { spawn } from "child_process";
3127
- import { join as join5 } from "path";
2186
+ import { join as join3 } from "path";
3128
2187
  var LATEST_BOOTSTRAP_TIMEOUT_MS = 45e3;
3129
2188
  var LATEST_BOOTSTRAP_LOCK_NAME = "daemon-latest.lock";
3130
2189
  async function runPinnedEnsure(options) {
@@ -3136,7 +2195,7 @@ async function runPinnedEnsure(options) {
3136
2195
  npm_config_fetch_retry_maxtimeout: "5000",
3137
2196
  npm_config_fetch_timeout: "15000"
3138
2197
  };
3139
- return await new Promise((resolve6) => {
2198
+ return await new Promise((resolve5) => {
3140
2199
  let settled = false;
3141
2200
  let child;
3142
2201
  try {
@@ -3146,7 +2205,7 @@ async function runPinnedEnsure(options) {
3146
2205
  { env, stdio: "ignore" }
3147
2206
  );
3148
2207
  } catch {
3149
- resolve6(false);
2208
+ resolve5(false);
3150
2209
  return;
3151
2210
  }
3152
2211
  const timer = setTimeout(() => {
@@ -3157,7 +2216,7 @@ async function runPinnedEnsure(options) {
3157
2216
  if (settled) return;
3158
2217
  settled = true;
3159
2218
  clearTimeout(timer);
3160
- resolve6(result);
2219
+ resolve5(result);
3161
2220
  };
3162
2221
  timer.unref();
3163
2222
  child.once("error", () => finish(false));
@@ -3165,7 +2224,7 @@ async function runPinnedEnsure(options) {
3165
2224
  });
3166
2225
  }
3167
2226
  async function runLatestDaemonBootstrap(ensureCurrent, options = {}) {
3168
- const lockPath = options.lockPath ?? join5(
2227
+ const lockPath = options.lockPath ?? join3(
3169
2228
  primConfigDirectory({ env: options.env, homeDir: options.homeDir }),
3170
2229
  LATEST_BOOTSTRAP_LOCK_NAME
3171
2230
  );
@@ -3189,27 +2248,27 @@ async function runLatestDaemonBootstrap(ensureCurrent, options = {}) {
3189
2248
 
3190
2249
  // src/daemon/launchd.ts
3191
2250
  import { spawnSync } from "child_process";
3192
- import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
2251
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
3193
2252
  import {
3194
- constants as constants2,
3195
- accessSync as accessSync2,
3196
- chmodSync as chmodSync2,
3197
- copyFileSync as copyFileSync2,
3198
- existsSync as existsSync5,
3199
- lstatSync as lstatSync3,
3200
- mkdirSync as mkdirSync3,
3201
- mkdtempSync as mkdtempSync2,
3202
- readFileSync as readFileSync5,
3203
- readdirSync as readdirSync3,
2253
+ constants,
2254
+ accessSync,
2255
+ chmodSync,
2256
+ copyFileSync,
2257
+ existsSync as existsSync3,
2258
+ lstatSync as lstatSync2,
2259
+ mkdirSync as mkdirSync2,
2260
+ mkdtempSync,
2261
+ readFileSync as readFileSync3,
2262
+ readdirSync as readdirSync2,
3204
2263
  readlinkSync,
3205
- realpathSync as realpathSync2,
3206
- renameSync as renameSync2,
3207
- rmSync as rmSync2,
2264
+ realpathSync,
2265
+ renameSync,
2266
+ rmSync,
3208
2267
  symlinkSync,
3209
- writeFileSync as writeFileSync3
2268
+ writeFileSync as writeFileSync2
3210
2269
  } from "fs";
3211
- import { homedir as homedir4 } from "os";
3212
- import { basename, dirname as dirname4, isAbsolute as isAbsolute2, join as join6, resolve as resolve3 } from "path";
2270
+ import { homedir as homedir3 } from "os";
2271
+ import { basename, dirname as dirname2, isAbsolute, join as join4, resolve as resolve2 } from "path";
3213
2272
 
3214
2273
  // src/daemon/env-binding.ts
3215
2274
  function normalizeApiUrl(url) {
@@ -3248,49 +2307,49 @@ var LaunchctlError = class extends Error {
3248
2307
  }
3249
2308
  };
3250
2309
  function xdgDataHome(options) {
3251
- const home = options.homeDir ?? homedir4();
2310
+ const home = options.homeDir ?? homedir3();
3252
2311
  const configured = (options.env ?? process.env).XDG_DATA_HOME;
3253
- return configured && isAbsolute2(configured) ? configured : join6(home, ".local", "share");
2312
+ return configured && isAbsolute(configured) ? configured : join4(home, ".local", "share");
3254
2313
  }
3255
2314
  function runtimePaths(options = {}) {
3256
- const dataDir = join6(xdgDataHome(options), "prim");
3257
- const runtimeDir = join6(dataDir, "runtime");
3258
- const currentLink = join6(runtimeDir, "current");
2315
+ const dataDir = join4(xdgDataHome(options), "prim");
2316
+ const runtimeDir = join4(dataDir, "runtime");
2317
+ const currentLink = join4(runtimeDir, "current");
3259
2318
  return {
3260
2319
  dataDir,
3261
2320
  runtimeDir,
3262
- releasesDir: join6(runtimeDir, "releases"),
2321
+ releasesDir: join4(runtimeDir, "releases"),
3263
2322
  currentLink,
3264
- statuslineLauncher: join6(runtimeDir, "prim-statusline"),
3265
- disabledMarker: join6(dataDir, "daemon.disabled"),
3266
- lifecycleLockDir: join6(dataDir, "daemon.lifecycle.lock")
2323
+ statuslineLauncher: join4(runtimeDir, "prim-statusline"),
2324
+ disabledMarker: join4(dataDir, "daemon.disabled"),
2325
+ lifecycleLockDir: join4(dataDir, "daemon.lifecycle.lock")
3267
2326
  };
3268
2327
  }
3269
2328
  function daemonControlPaths(options = {}) {
3270
2329
  const configDir = primConfigDirectory(options);
3271
2330
  const legacy = runtimePaths(options);
3272
2331
  return {
3273
- launcher: join6(configDir, `prim-daemon-launcher-v${LAUNCHER_SCHEMA_VERSION}`),
3274
- disabledMarker: join6(configDir, "daemon.disabled"),
3275
- lifecycleLockDir: join6(configDir, "daemon.lifecycle.lock"),
2332
+ launcher: join4(configDir, `prim-daemon-launcher-v${LAUNCHER_SCHEMA_VERSION}`),
2333
+ disabledMarker: join4(configDir, "daemon.disabled"),
2334
+ lifecycleLockDir: join4(configDir, "daemon.lifecycle.lock"),
3276
2335
  legacyDisabledMarker: legacy.disabledMarker,
3277
2336
  legacyLifecycleLockDir: legacy.lifecycleLockDir
3278
2337
  };
3279
2338
  }
3280
2339
  function findPackageVersion(entryFile) {
3281
- let dir = dirname4(resolve3(entryFile));
2340
+ let dir = dirname2(resolve2(entryFile));
3282
2341
  for (let depth = 0; depth < 6; depth++) {
3283
- const packagePath = join6(dir, "package.json");
3284
- if (existsSync5(packagePath)) {
2342
+ const packagePath = join4(dir, "package.json");
2343
+ if (existsSync3(packagePath)) {
3285
2344
  try {
3286
- const manifest = JSON.parse(readFileSync5(packagePath, "utf8"));
2345
+ const manifest = JSON.parse(readFileSync3(packagePath, "utf8"));
3287
2346
  if (manifest.name === "@primitive.ai/prim" && manifest.version) {
3288
2347
  return manifest.version;
3289
2348
  }
3290
2349
  } catch {
3291
2350
  }
3292
2351
  }
3293
- const parent = dirname4(dir);
2352
+ const parent = dirname2(dir);
3294
2353
  if (parent === dir) break;
3295
2354
  dir = parent;
3296
2355
  }
@@ -3298,7 +2357,7 @@ function findPackageVersion(entryFile) {
3298
2357
  }
3299
2358
  function readRuntimeManifest(path) {
3300
2359
  try {
3301
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
2360
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
3302
2361
  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)) {
3303
2362
  return null;
3304
2363
  }
@@ -3308,19 +2367,19 @@ function readRuntimeManifest(path) {
3308
2367
  }
3309
2368
  }
3310
2369
  var RUNTIME_RELEASE_RE = /^release-[0-9a-f]{16}$/u;
3311
- function exactKeys2(value, expected) {
2370
+ function exactKeys(value, expected) {
3312
2371
  const actual = Object.keys(value).sort();
3313
2372
  const wanted = [...expected].sort();
3314
2373
  return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
3315
2374
  }
3316
2375
  function readOwnedRuntimeManifest(releaseDir) {
3317
- const manifestPath = join6(releaseDir, "manifest.json");
3318
- const daemonPath = join6(releaseDir, "prim-daemon-server");
2376
+ const manifestPath = join4(releaseDir, "manifest.json");
2377
+ const daemonPath = join4(releaseDir, "prim-daemon-server");
3319
2378
  try {
3320
- const raw = readFileSync5(manifestPath, "utf8");
2379
+ const raw = readFileSync3(manifestPath, "utf8");
3321
2380
  const parsed = JSON.parse(raw);
3322
2381
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3323
- if (!exactKeys2(parsed, [
2382
+ if (!exactKeys(parsed, [
3324
2383
  "schemaVersion",
3325
2384
  "version",
3326
2385
  "nodePath",
@@ -3331,11 +2390,11 @@ function readOwnedRuntimeManifest(releaseDir) {
3331
2390
  return null;
3332
2391
  }
3333
2392
  const manifest = parsed;
3334
- if (manifest.schemaVersion !== RUNTIME_SCHEMA_VERSION || compareSemver(manifest.version, manifest.version) !== 0 || !isAbsolute2(manifest.nodePath) || /[\r\n]/u.test(manifest.nodePath) || manifest.daemonFile !== "prim-daemon-server" || !/^[0-9a-f]{64}$/u.test(manifest.daemonSha256) || new Date(manifest.stagedAt).toISOString() !== manifest.stagedAt || raw !== `${JSON.stringify(manifest, null, 2)}
3335
- ` || !lstatSync3(daemonPath).isFile() || sha256File(daemonPath) !== manifest.daemonSha256) {
2393
+ if (manifest.schemaVersion !== RUNTIME_SCHEMA_VERSION || compareSemver(manifest.version, manifest.version) !== 0 || !isAbsolute(manifest.nodePath) || /[\r\n]/u.test(manifest.nodePath) || manifest.daemonFile !== "prim-daemon-server" || !/^[0-9a-f]{64}$/u.test(manifest.daemonSha256) || new Date(manifest.stagedAt).toISOString() !== manifest.stagedAt || raw !== `${JSON.stringify(manifest, null, 2)}
2394
+ ` || !lstatSync2(daemonPath).isFile() || sha256File(daemonPath) !== manifest.daemonSha256) {
3336
2395
  return null;
3337
2396
  }
3338
- const entries = readdirSync3(releaseDir).sort();
2397
+ const entries = readdirSync2(releaseDir).sort();
3339
2398
  return entries.length === 2 && entries[0] === "manifest.json" && entries[1] === "prim-daemon-server" ? manifest : null;
3340
2399
  } catch {
3341
2400
  return null;
@@ -3352,25 +2411,25 @@ function sameRuntime(current, desired, daemonPath) {
3352
2411
  }
3353
2412
  }
3354
2413
  function sha256File(path) {
3355
- return createHash3("sha256").update(readFileSync5(path)).digest("hex");
2414
+ return createHash2("sha256").update(readFileSync3(path)).digest("hex");
3356
2415
  }
3357
2416
  function atomicSymlink(target, linkPath) {
3358
- const tempLink = `${linkPath}.tmp-${process.pid}-${createHash3("sha256").update(`${target}\0${Date.now()}\0${Math.random()}`).digest("hex").slice(0, 12)}`;
2417
+ const tempLink = `${linkPath}.tmp-${process.pid}-${createHash2("sha256").update(`${target}\0${Date.now()}\0${Math.random()}`).digest("hex").slice(0, 12)}`;
3359
2418
  symlinkSync(target, tempLink);
3360
2419
  try {
3361
- renameSync2(tempLink, linkPath);
2420
+ renameSync(tempLink, linkPath);
3362
2421
  } catch (error) {
3363
- rmSync2(tempLink, { force: true });
2422
+ rmSync(tempLink, { force: true });
3364
2423
  throw error;
3365
2424
  }
3366
2425
  }
3367
2426
  function stageRuntime(options = {}) {
3368
2427
  const paths = runtimePaths(options);
3369
2428
  const daemonSource = options.daemonSource ?? binFile("prim-daemon-server");
3370
- if (!daemonSource || !existsSync5(daemonSource)) {
2429
+ if (!daemonSource || !existsSync3(daemonSource)) {
3371
2430
  throw new Error("cannot stage runtime: prim-daemon-server bundle is unavailable");
3372
2431
  }
3373
- const nodePath = resolve3(options.nodePath ?? process.execPath);
2432
+ const nodePath = resolve2(options.nodePath ?? process.execPath);
3374
2433
  const version = options.version ?? findPackageVersion(daemonSource);
3375
2434
  const desired = {
3376
2435
  schemaVersion: RUNTIME_SCHEMA_VERSION,
@@ -3382,11 +2441,11 @@ function stageRuntime(options = {}) {
3382
2441
  };
3383
2442
  let currentRelease = null;
3384
2443
  try {
3385
- currentRelease = realpathSync2(paths.currentLink);
2444
+ currentRelease = realpathSync(paths.currentLink);
3386
2445
  } catch {
3387
2446
  }
3388
- const current = currentRelease ? readRuntimeManifest(join6(currentRelease, "manifest.json")) : null;
3389
- const currentDaemon = currentRelease ? join6(currentRelease, desired.daemonFile) : "";
2447
+ const current = currentRelease ? readRuntimeManifest(join4(currentRelease, "manifest.json")) : null;
2448
+ const currentDaemon = currentRelease ? join4(currentRelease, desired.daemonFile) : "";
3390
2449
  if (currentRelease && sameRuntime(current, desired, currentDaemon)) {
3391
2450
  const manifest = current;
3392
2451
  atomicWrite2(
@@ -3401,52 +2460,52 @@ function stageRuntime(options = {}) {
3401
2460
  daemonPath: currentDaemon
3402
2461
  };
3403
2462
  }
3404
- mkdirSync3(paths.releasesDir, { recursive: true, mode: RUNTIME_DIR_MODE });
3405
- chmodSync2(paths.runtimeDir, RUNTIME_DIR_MODE);
3406
- chmodSync2(paths.releasesDir, RUNTIME_DIR_MODE);
3407
- const stagingDir = mkdtempSync2(join6(paths.releasesDir, ".stage-"));
3408
- chmodSync2(stagingDir, RUNTIME_DIR_MODE);
2463
+ mkdirSync2(paths.releasesDir, { recursive: true, mode: RUNTIME_DIR_MODE });
2464
+ chmodSync(paths.runtimeDir, RUNTIME_DIR_MODE);
2465
+ chmodSync(paths.releasesDir, RUNTIME_DIR_MODE);
2466
+ const stagingDir = mkdtempSync(join4(paths.releasesDir, ".stage-"));
2467
+ chmodSync(stagingDir, RUNTIME_DIR_MODE);
3409
2468
  let releaseDir;
3410
2469
  try {
3411
- const daemonTarget = join6(stagingDir, desired.daemonFile);
3412
- copyFileSync2(daemonSource, daemonTarget);
3413
- chmodSync2(daemonTarget, RUNTIME_FILE_MODE);
2470
+ const daemonTarget = join4(stagingDir, desired.daemonFile);
2471
+ copyFileSync(daemonSource, daemonTarget);
2472
+ chmodSync(daemonTarget, RUNTIME_FILE_MODE);
3414
2473
  if (sha256File(daemonTarget) !== desired.daemonSha256) {
3415
2474
  throw new Error("cannot stage runtime: daemon bundle changed while it was copied");
3416
2475
  }
3417
- const manifestTarget = join6(stagingDir, "manifest.json");
3418
- writeFileSync3(manifestTarget, `${JSON.stringify(desired, null, 2)}
2476
+ const manifestTarget = join4(stagingDir, "manifest.json");
2477
+ writeFileSync2(manifestTarget, `${JSON.stringify(desired, null, 2)}
3419
2478
  `, {
3420
2479
  encoding: "utf8",
3421
2480
  mode: RUNTIME_FILE_MODE,
3422
2481
  flag: "wx"
3423
2482
  });
3424
- const releaseName = `release-${createHash3("sha256").update(`${version}\0${nodePath}\0${desired.stagedAt}\0${process.pid}\0${Math.random()}`).digest("hex").slice(0, 16)}`;
3425
- releaseDir = join6(paths.releasesDir, releaseName);
3426
- renameSync2(stagingDir, releaseDir);
3427
- releaseDir = realpathSync2(releaseDir);
3428
- atomicSymlink(join6("releases", releaseName), paths.currentLink);
2483
+ const releaseName = `release-${createHash2("sha256").update(`${version}\0${nodePath}\0${desired.stagedAt}\0${process.pid}\0${Math.random()}`).digest("hex").slice(0, 16)}`;
2484
+ releaseDir = join4(paths.releasesDir, releaseName);
2485
+ renameSync(stagingDir, releaseDir);
2486
+ releaseDir = realpathSync(releaseDir);
2487
+ atomicSymlink(join4("releases", releaseName), paths.currentLink);
3429
2488
  atomicWrite2(
3430
2489
  paths.statuslineLauncher,
3431
2490
  statuslineLauncherContent(desired.version, options),
3432
2491
  RUNTIME_LAUNCHER_MODE
3433
2492
  );
3434
2493
  } catch (error) {
3435
- rmSync2(stagingDir, { recursive: true, force: true });
2494
+ rmSync(stagingDir, { recursive: true, force: true });
3436
2495
  throw error;
3437
2496
  }
3438
2497
  return {
3439
2498
  changed: true,
3440
2499
  manifest: desired,
3441
2500
  paths,
3442
- daemonPath: join6(releaseDir, desired.daemonFile)
2501
+ daemonPath: join4(releaseDir, desired.daemonFile)
3443
2502
  };
3444
2503
  }
3445
2504
  function shellQuote(value) {
3446
2505
  return `'${value.replaceAll("'", `'"'"'`)}'`;
3447
2506
  }
3448
2507
  function statuslineLauncherContent(version, options = {}) {
3449
- const socketPath = join6(primConfigDirectory(options), "sock");
2508
+ const socketPath = join4(primConfigDirectory(options), "sock");
3450
2509
  const fallback = `primitive ${version} (daemon: down)`;
3451
2510
  return `#!/bin/sh
3452
2511
  response=$(
@@ -3462,14 +2521,14 @@ fi
3462
2521
  `;
3463
2522
  }
3464
2523
  function assertRegularFile(path, label) {
3465
- const stat = lstatIfPresent3(path);
2524
+ const stat = lstatIfPresent2(path);
3466
2525
  if (stat && !stat.isFile()) {
3467
2526
  throw new Error(`refusing to remove non-file ${label} at ${path}`);
3468
2527
  }
3469
2528
  }
3470
- function lstatIfPresent3(path) {
2529
+ function lstatIfPresent2(path) {
3471
2530
  try {
3472
- return lstatSync3(path);
2531
+ return lstatSync2(path);
3473
2532
  } catch (error) {
3474
2533
  const code = error.code;
3475
2534
  if (code === "ENOENT" || code === "ENOTDIR") return void 0;
@@ -3477,9 +2536,9 @@ function lstatIfPresent3(path) {
3477
2536
  }
3478
2537
  }
3479
2538
  function assertExactFile(path, expected, label) {
3480
- if (!lstatIfPresent3(path)) return;
2539
+ if (!lstatIfPresent2(path)) return;
3481
2540
  assertRegularFile(path, label);
3482
- if (readFileSync5(path, "utf8") !== expected) {
2541
+ if (readFileSync3(path, "utf8") !== expected) {
3483
2542
  throw new Error(`refusing to remove unrecognized ${label} at ${path}`);
3484
2543
  }
3485
2544
  }
@@ -3488,18 +2547,18 @@ function assertOwnedDaemonRuntime(options) {
3488
2547
  const control = daemonControlPaths(options);
3489
2548
  const configDir = primConfigDirectory(options);
3490
2549
  const service = process.platform === "darwin" ? launchdPaths(options) : void 0;
3491
- const logPath = service?.logPath ?? join6(configDir, "daemon.log");
2550
+ const logPath = service?.logPath ?? join4(configDir, "daemon.log");
3492
2551
  for (const activeArtifact of [
3493
- join6(configDir, "daemon.pid"),
3494
- join6(configDir, "sock"),
3495
- join6(configDir, "daemon.lock"),
3496
- join6(configDir, "client-instance.lock")
2552
+ join4(configDir, "daemon.pid"),
2553
+ join4(configDir, "sock"),
2554
+ join4(configDir, "daemon.lock"),
2555
+ join4(configDir, "client-instance.lock")
3497
2556
  ]) {
3498
- if (lstatIfPresent3(activeArtifact)) {
2557
+ if (lstatIfPresent2(activeArtifact)) {
3499
2558
  throw new Error(`refusing runtime removal while daemon state remains at ${activeArtifact}`);
3500
2559
  }
3501
2560
  }
3502
- if (lstatIfPresent3(control.launcher)) {
2561
+ if (lstatIfPresent2(control.launcher)) {
3503
2562
  assertRegularFile(control.launcher, "daemon launcher");
3504
2563
  if (!readDaemonLauncher(control.launcher)) {
3505
2564
  throw new Error(`refusing to remove unrecognized daemon launcher at ${control.launcher}`);
@@ -3514,42 +2573,42 @@ function assertOwnedDaemonRuntime(options) {
3514
2573
  );
3515
2574
  }
3516
2575
  assertRegularFile(logPath, "daemon log");
3517
- assertRegularFile(join6(configDir, "daemon-health.json"), "daemon health snapshot");
3518
- assertRegularFile(join6(configDir, "client_instance_id"), "daemon client instance id");
3519
- if (service && lstatIfPresent3(service.plistPath)) {
2576
+ assertRegularFile(join4(configDir, "daemon-health.json"), "daemon health snapshot");
2577
+ assertRegularFile(join4(configDir, "client_instance_id"), "daemon client instance id");
2578
+ if (service && lstatIfPresent2(service.plistPath)) {
3520
2579
  assertRegularFile(service.plistPath, "launchd property list");
3521
2580
  const expected = generateLaunchAgentPlist({
3522
2581
  launcherPath: control.launcher,
3523
2582
  logPath,
3524
- workingDirectory: options.homeDir ?? homedir4(),
2583
+ workingDirectory: options.homeDir ?? homedir3(),
3525
2584
  label: options.label
3526
2585
  });
3527
- if (readFileSync5(service.plistPath, "utf8") !== expected) {
2586
+ if (readFileSync3(service.plistPath, "utf8") !== expected) {
3528
2587
  throw new Error(
3529
2588
  `refusing to remove unrecognized launchd property list at ${service.plistPath}`
3530
2589
  );
3531
2590
  }
3532
2591
  }
3533
- const runtimeDirectory = lstatIfPresent3(runtime.runtimeDir);
2592
+ const runtimeDirectory = lstatIfPresent2(runtime.runtimeDir);
3534
2593
  if (!runtimeDirectory) return;
3535
2594
  if (!runtimeDirectory.isDirectory()) {
3536
2595
  throw new Error(`refusing to remove non-directory daemon runtime at ${runtime.runtimeDir}`);
3537
2596
  }
3538
2597
  const allowed = /* @__PURE__ */ new Set(["current", "prim-statusline", "releases"]);
3539
- const unexpected = readdirSync3(runtime.runtimeDir).filter((entry) => !allowed.has(entry));
2598
+ const unexpected = readdirSync2(runtime.runtimeDir).filter((entry) => !allowed.has(entry));
3540
2599
  if (unexpected.length > 0) {
3541
2600
  throw new Error(
3542
2601
  `refusing to remove daemon runtime with unrecognized entries: ${unexpected.sort().join(", ")}`
3543
2602
  );
3544
2603
  }
3545
2604
  const manifests = /* @__PURE__ */ new Map();
3546
- const releasesDirectory = lstatIfPresent3(runtime.releasesDir);
2605
+ const releasesDirectory = lstatIfPresent2(runtime.releasesDir);
3547
2606
  if (releasesDirectory) {
3548
2607
  if (!releasesDirectory.isDirectory()) {
3549
2608
  throw new Error(`refusing to remove non-directory daemon releases at ${runtime.releasesDir}`);
3550
2609
  }
3551
- for (const entry of readdirSync3(runtime.releasesDir, { withFileTypes: true })) {
3552
- const releaseDir = join6(runtime.releasesDir, entry.name);
2610
+ for (const entry of readdirSync2(runtime.releasesDir, { withFileTypes: true })) {
2611
+ const releaseDir = join4(runtime.releasesDir, entry.name);
3553
2612
  const manifest = entry.isDirectory() && RUNTIME_RELEASE_RE.test(entry.name) ? readOwnedRuntimeManifest(releaseDir) : null;
3554
2613
  if (!manifest) {
3555
2614
  throw new Error(`refusing to remove unrecognized daemon release ${releaseDir}`);
@@ -3558,7 +2617,7 @@ function assertOwnedDaemonRuntime(options) {
3558
2617
  }
3559
2618
  }
3560
2619
  let selected;
3561
- const currentLink = lstatIfPresent3(runtime.currentLink);
2620
+ const currentLink = lstatIfPresent2(runtime.currentLink);
3562
2621
  if (currentLink) {
3563
2622
  if (!currentLink.isSymbolicLink()) {
3564
2623
  throw new Error(
@@ -3567,7 +2626,7 @@ function assertOwnedDaemonRuntime(options) {
3567
2626
  }
3568
2627
  const target = readlinkSync(runtime.currentLink);
3569
2628
  const releaseName = basename(target);
3570
- if (target !== join6("releases", releaseName) || !RUNTIME_RELEASE_RE.test(releaseName)) {
2629
+ if (target !== join4("releases", releaseName) || !RUNTIME_RELEASE_RE.test(releaseName)) {
3571
2630
  throw new Error(
3572
2631
  `refusing to remove malformed daemon runtime selector ${runtime.currentLink}`
3573
2632
  );
@@ -3577,7 +2636,7 @@ function assertOwnedDaemonRuntime(options) {
3577
2636
  throw new Error(`refusing to remove missing selected daemon release ${releaseName}`);
3578
2637
  }
3579
2638
  }
3580
- if (lstatIfPresent3(runtime.statuslineLauncher)) {
2639
+ if (lstatIfPresent2(runtime.statuslineLauncher)) {
3581
2640
  if (!selected) {
3582
2641
  throw new Error(
3583
2642
  `refusing to remove daemon statusline without a selected runtime at ${runtime.statuslineLauncher}`
@@ -3595,7 +2654,7 @@ async function removeDaemonRuntime(options = {}) {
3595
2654
  const control = daemonControlPaths(options);
3596
2655
  const configDir = primConfigDirectory(options);
3597
2656
  const service = process.platform === "darwin" ? launchdPaths(options) : void 0;
3598
- const logPath = service?.logPath ?? join6(configDir, "daemon.log");
2657
+ const logPath = service?.logPath ?? join4(configDir, "daemon.log");
3599
2658
  const ownedArtifacts = [
3600
2659
  runtime.runtimeDir,
3601
2660
  control.launcher,
@@ -3603,21 +2662,21 @@ async function removeDaemonRuntime(options = {}) {
3603
2662
  control.legacyDisabledMarker,
3604
2663
  ...service ? [service.plistPath] : [],
3605
2664
  logPath,
3606
- join6(configDir, "daemon-health.json"),
3607
- join6(configDir, "client_instance_id")
2665
+ join4(configDir, "daemon-health.json"),
2666
+ join4(configDir, "client_instance_id")
3608
2667
  ];
3609
- if (!ownedArtifacts.some((path) => lstatIfPresent3(path))) {
2668
+ if (!ownedArtifacts.some((path) => lstatIfPresent2(path))) {
3610
2669
  return { changed: false, runtimePaths: runtime };
3611
2670
  }
3612
2671
  return withDaemonLifecycleLock(async () => {
3613
2672
  assertOwnedDaemonRuntime(options);
3614
2673
  for (const path of ownedArtifacts.filter((candidate) => candidate !== runtime.runtimeDir)) {
3615
- rmSync2(path, { force: true });
2674
+ rmSync(path, { force: true });
3616
2675
  }
3617
- if (lstatIfPresent3(runtime.runtimeDir)) {
3618
- const quarantined = `${runtime.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes4(8).toString("hex")}`;
3619
- renameSync2(runtime.runtimeDir, quarantined);
3620
- rmSync2(quarantined, { recursive: true, force: true });
2676
+ if (lstatIfPresent2(runtime.runtimeDir)) {
2677
+ const quarantined = `${runtime.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes3(8).toString("hex")}`;
2678
+ renameSync(runtime.runtimeDir, quarantined);
2679
+ rmSync(quarantined, { recursive: true, force: true });
3621
2680
  }
3622
2681
  return { changed: true, runtimePaths: runtime };
3623
2682
  }, options);
@@ -3632,7 +2691,7 @@ function generateDaemonLauncher(config) {
3632
2691
  apiUrl: apiUrl ?? null,
3633
2692
  ...config.configDir ? { configDir: config.configDir } : {}
3634
2693
  };
3635
- const revision = createHash3("sha256").update(JSON.stringify(metadata)).digest("hex");
2694
+ const revision = createHash2("sha256").update(JSON.stringify(metadata)).digest("hex");
3636
2695
  const encoded = Buffer.from(JSON.stringify({ ...metadata, revision })).toString("base64url");
3637
2696
  return {
3638
2697
  revision,
@@ -3648,11 +2707,11 @@ ${config.configDir ? `export PRIM_CONFIG_DIR=${shellQuote(config.configDir)}
3648
2707
  }
3649
2708
  function readDaemonLauncher(path) {
3650
2709
  try {
3651
- const content = readFileSync5(path, "utf8");
2710
+ const content = readFileSync3(path, "utf8");
3652
2711
  const encoded = /^# prim-daemon-launcher: ([A-Za-z0-9_-]+)$/mu.exec(content)?.[1];
3653
2712
  if (!encoded) return null;
3654
2713
  const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
3655
- if (value.schemaVersion !== LAUNCHER_SCHEMA_VERSION || typeof value.nodePath !== "string" || typeof value.daemonPath !== "string" || typeof value.runtimeVersion !== "string" || value.apiUrl !== null && typeof value.apiUrl !== "string" || value.configDir !== void 0 && (typeof value.configDir !== "string" || !isAbsolute2(value.configDir)) || typeof value.revision !== "string") {
2714
+ if (value.schemaVersion !== LAUNCHER_SCHEMA_VERSION || typeof value.nodePath !== "string" || typeof value.daemonPath !== "string" || typeof value.runtimeVersion !== "string" || value.apiUrl !== null && typeof value.apiUrl !== "string" || value.configDir !== void 0 && (typeof value.configDir !== "string" || !isAbsolute(value.configDir)) || typeof value.revision !== "string") {
3656
2715
  return null;
3657
2716
  }
3658
2717
  const config = {
@@ -3671,9 +2730,9 @@ function readDaemonLauncher(path) {
3671
2730
  function readUsableDaemonConfig(path) {
3672
2731
  const config = readDaemonLauncher(path);
3673
2732
  if (!config) return null;
3674
- const manifest = readRuntimeManifest(join6(dirname4(config.daemonPath), "manifest.json"));
2733
+ const manifest = readRuntimeManifest(join4(dirname2(config.daemonPath), "manifest.json"));
3675
2734
  try {
3676
- accessSync2(config.nodePath, constants2.X_OK);
2735
+ accessSync(config.nodePath, constants.X_OK);
3677
2736
  return manifest?.version === config.runtimeVersion && manifest.nodePath === config.nodePath && manifest.daemonSha256 === sha256File(config.daemonPath) ? config : null;
3678
2737
  } catch {
3679
2738
  return null;
@@ -3716,7 +2775,7 @@ function generateLaunchAgentPlist(config) {
3716
2775
  `;
3717
2776
  }
3718
2777
  function launchdPaths(options = {}) {
3719
- const home = options.homeDir ?? homedir4();
2778
+ const home = options.homeDir ?? homedir3();
3720
2779
  const configDir = primConfigDirectory(options);
3721
2780
  const uid = options.uid ?? process.getuid?.();
3722
2781
  const label = options.label ?? LAUNCHD_LABEL;
@@ -3724,8 +2783,8 @@ function launchdPaths(options = {}) {
3724
2783
  return {
3725
2784
  domainTarget: `gui/${uid}`,
3726
2785
  serviceTarget: `gui/${uid}/${label}`,
3727
- plistPath: join6(home, "Library", "LaunchAgents", `${label}.plist`),
3728
- logPath: join6(configDir, "daemon.log")
2786
+ plistPath: join4(home, "Library", "LaunchAgents", `${label}.plist`),
2787
+ logPath: join4(configDir, "daemon.log")
3729
2788
  };
3730
2789
  }
3731
2790
  var runLaunchctl = (args, timeoutMs2 = COMMAND_TIMEOUT_MS) => {
@@ -3758,21 +2817,21 @@ function getLaunchdService(options = {}) {
3758
2817
  }
3759
2818
  function atomicWrite2(path, content, mode, validate) {
3760
2819
  try {
3761
- if (readFileSync5(path, "utf8") === content) {
3762
- chmodSync2(path, mode);
2820
+ if (readFileSync3(path, "utf8") === content) {
2821
+ chmodSync(path, mode);
3763
2822
  validate?.(path, COMMAND_TIMEOUT_MS);
3764
2823
  return false;
3765
2824
  }
3766
2825
  } catch {
3767
2826
  }
3768
- mkdirSync3(dirname4(path), { recursive: true, mode: RUNTIME_DIR_MODE });
2827
+ mkdirSync2(dirname2(path), { recursive: true, mode: RUNTIME_DIR_MODE });
3769
2828
  const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
3770
- writeFileSync3(temp, content, { encoding: "utf8", mode, flag: "wx" });
2829
+ writeFileSync2(temp, content, { encoding: "utf8", mode, flag: "wx" });
3771
2830
  try {
3772
2831
  validate?.(temp, COMMAND_TIMEOUT_MS);
3773
- renameSync2(temp, path);
2832
+ renameSync(temp, path);
3774
2833
  } catch (error) {
3775
- rmSync2(temp, { force: true });
2834
+ rmSync(temp, { force: true });
3776
2835
  throw error;
3777
2836
  }
3778
2837
  return true;
@@ -3799,17 +2858,17 @@ async function inspectDaemon(timeoutMs2) {
3799
2858
  }
3800
2859
  function canonicalProspectivePath(path) {
3801
2860
  const suffix = [];
3802
- let ancestor = resolve3(path);
3803
- while (!existsSync5(ancestor)) {
3804
- const parent = dirname4(ancestor);
3805
- if (parent === ancestor) return resolve3(path);
2861
+ let ancestor = resolve2(path);
2862
+ while (!existsSync3(ancestor)) {
2863
+ const parent = dirname2(ancestor);
2864
+ if (parent === ancestor) return resolve2(path);
3806
2865
  suffix.unshift(basename(ancestor));
3807
2866
  ancestor = parent;
3808
2867
  }
3809
2868
  try {
3810
- return join6(realpathSync2(ancestor), ...suffix);
2869
+ return join4(realpathSync(ancestor), ...suffix);
3811
2870
  } catch {
3812
- return resolve3(path);
2871
+ return resolve2(path);
3813
2872
  }
3814
2873
  }
3815
2874
  async function withDaemonLifecycleLock(operation, options = {}) {
@@ -3822,10 +2881,10 @@ async function withDaemonLifecycleLock(operation, options = {}) {
3822
2881
  );
3823
2882
  }
3824
2883
  async function stopVerifiedLegacyDaemon(options, deadlineMs, nowMs, sleep2) {
3825
- const pidPath = join6(primConfigDirectory(options), "daemon.pid");
2884
+ const pidPath = join4(primConfigDirectory(options), "daemon.pid");
3826
2885
  let pid;
3827
2886
  try {
3828
- pid = Number(readFileSync5(pidPath, "utf8").trim());
2887
+ pid = Number(readFileSync3(pidPath, "utf8").trim());
3829
2888
  } catch {
3830
2889
  throw new Error("an unsupervised daemon answered, but it has no verifiable pidfile");
3831
2890
  }
@@ -4002,13 +3061,13 @@ async function waitForAbsence(context) {
4002
3061
  }
4003
3062
  function daemonExplicitlyDisabled(options = {}) {
4004
3063
  const paths = daemonControlPaths(options);
4005
- return existsSync5(paths.disabledMarker) || existsSync5(paths.legacyDisabledMarker);
3064
+ return existsSync3(paths.disabledMarker) || existsSync3(paths.legacyDisabledMarker);
4006
3065
  }
4007
3066
  function setDaemonExplicitlyDisabled(disabled, options = {}) {
4008
3067
  const paths = daemonControlPaths(options);
4009
3068
  if (!disabled) {
4010
- rmSync2(paths.disabledMarker, { force: true });
4011
- rmSync2(paths.legacyDisabledMarker, { force: true });
3069
+ rmSync(paths.disabledMarker, { force: true });
3070
+ rmSync(paths.legacyDisabledMarker, { force: true });
4012
3071
  return;
4013
3072
  }
4014
3073
  const content = DAEMON_DISABLED_CONTENT;
@@ -4025,7 +3084,7 @@ async function ensureMacDaemonLocked(options) {
4025
3084
  let service = getLaunchdService({ ...options, runner });
4026
3085
  if (!options.explicitlyStarted && daemonExplicitlyDisabled(options)) {
4027
3086
  const control2 = daemonControlPaths(options);
4028
- if (!existsSync5(control2.disabledMarker)) {
3087
+ if (!existsSync3(control2.disabledMarker)) {
4029
3088
  atomicWrite2(control2.disabledMarker, "disabled by `prim daemon stop`\n", RUNTIME_FILE_MODE);
4030
3089
  }
4031
3090
  return {
@@ -4052,7 +3111,7 @@ async function ensureMacDaemonLocked(options) {
4052
3111
  const configuredApiUrl = (options.env ?? process.env).PRIM_API_URL;
4053
3112
  const apiUrl = configuredApiUrl ? normalizeApiUrl(configuredApiUrl) || void 0 : void 0;
4054
3113
  const configDir = primConfigDirectory(options);
4055
- const legacyDefaultConfigDir = join6(options.homeDir ?? homedir4(), ".config", "prim");
3114
+ const legacyDefaultConfigDir = join4(options.homeDir ?? homedir3(), ".config", "prim");
4056
3115
  const launcherConfigDir = configDir === legacyDefaultConfigDir ? void 0 : configDir;
4057
3116
  if (retainSelected && selected) {
4058
3117
  const usable = readUsableDaemonConfig(control.launcher);
@@ -4079,15 +3138,15 @@ async function ensureMacDaemonLocked(options) {
4079
3138
  };
4080
3139
  }
4081
3140
  const servicePaths = launchdPaths(options);
4082
- mkdirSync3(dirname4(servicePaths.logPath), { recursive: true, mode: RUNTIME_DIR_MODE });
4083
- writeFileSync3(servicePaths.logPath, "", { mode: RUNTIME_FILE_MODE, flag: "a" });
4084
- chmodSync2(servicePaths.logPath, RUNTIME_FILE_MODE);
3141
+ mkdirSync2(dirname2(servicePaths.logPath), { recursive: true, mode: RUNTIME_DIR_MODE });
3142
+ writeFileSync2(servicePaths.logPath, "", { mode: RUNTIME_FILE_MODE, flag: "a" });
3143
+ chmodSync(servicePaths.logPath, RUNTIME_FILE_MODE);
4085
3144
  const launcher = generateDaemonLauncher(daemonConfig);
4086
3145
  atomicWrite2(control.launcher, launcher.content, RUNTIME_LAUNCHER_MODE);
4087
3146
  const plist = generateLaunchAgentPlist({
4088
3147
  launcherPath: control.launcher,
4089
3148
  logPath: servicePaths.logPath,
4090
- workingDirectory: options.homeDir ?? homedir4(),
3149
+ workingDirectory: options.homeDir ?? homedir3(),
4091
3150
  label: options.label
4092
3151
  });
4093
3152
  const plistChanged = atomicWrite2(
@@ -4159,7 +3218,7 @@ async function bootoutMacDaemonLocked(options) {
4159
3218
  const wasLoaded = service.loaded;
4160
3219
  if (wasLoaded) {
4161
3220
  const control = daemonControlPaths(options);
4162
- const launcher = lstatIfPresent3(control.launcher);
3221
+ const launcher = lstatIfPresent2(control.launcher);
4163
3222
  if (service.program !== control.launcher || !launcher?.isFile() || readDaemonLauncher(control.launcher) === null) {
4164
3223
  throw new Error(
4165
3224
  `refusing to stop unrecognized launchd program ${service.program ?? "(missing)"}`
@@ -4207,9 +3266,9 @@ async function bootoutMacDaemonLocked(options) {
4207
3266
  // src/commands/daemon.ts
4208
3267
  var DAEMON_BIN = "prim-daemon-server";
4209
3268
  var CONFIG_DIR = primConfigDirectory();
4210
- var PID_PATH = join7(CONFIG_DIR, "daemon.pid");
4211
- var SOCK_PATH = join7(CONFIG_DIR, "sock");
4212
- var LOG_PATH = join7(CONFIG_DIR, "daemon.log");
3269
+ var PID_PATH = join5(CONFIG_DIR, "daemon.pid");
3270
+ var SOCK_PATH = join5(CONFIG_DIR, "sock");
3271
+ var LOG_PATH = join5(CONFIG_DIR, "daemon.log");
4213
3272
  var CONFIG_DIR_MODE = 448;
4214
3273
  var LOG_FILE_MODE = 384;
4215
3274
  var STOP_TIMEOUT_MS = 5e3;
@@ -4247,10 +3306,10 @@ function daemonDegradedReason(snapshot) {
4247
3306
  return "health checks have not recovered";
4248
3307
  }
4249
3308
  function readPidfile() {
4250
- if (!existsSync6(PID_PATH)) {
3309
+ if (!existsSync4(PID_PATH)) {
4251
3310
  return null;
4252
3311
  }
4253
- const raw = readFileSync6(PID_PATH, "utf-8").trim();
3312
+ const raw = readFileSync4(PID_PATH, "utf-8").trim();
4254
3313
  const pid = Number(raw);
4255
3314
  if (!Number.isInteger(pid) || pid <= 0) {
4256
3315
  return null;
@@ -4268,17 +3327,17 @@ function clearStaleArtifacts() {
4268
3327
  }
4269
3328
  }
4270
3329
  function sleep(ms) {
4271
- return new Promise((resolve6) => setTimeout(resolve6, ms));
3330
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
4272
3331
  }
4273
3332
  function spawnDaemon(options) {
4274
3333
  const file = binFile(DAEMON_BIN);
4275
3334
  return file ? spawn2(process.execPath, [file], options) : spawn2(DAEMON_BIN, [], options);
4276
3335
  }
4277
3336
  function openDaemonLog(configDir = CONFIG_DIR) {
4278
- if (!existsSync6(configDir)) {
4279
- mkdirSync4(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
3337
+ if (!existsSync4(configDir)) {
3338
+ mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
4280
3339
  }
4281
- return openSync(join7(configDir, "daemon.log"), "a", LOG_FILE_MODE);
3340
+ return openSync(join5(configDir, "daemon.log"), "a", LOG_FILE_MODE);
4282
3341
  }
4283
3342
  async function waitForReady() {
4284
3343
  const deadline = Date.now() + READY_TIMEOUT_MS;
@@ -4412,7 +3471,7 @@ async function detachedDaemonStop() {
4412
3471
  {},
4413
3472
  { timeoutMs: STATUS_PROBE_TIMEOUT_MS }
4414
3473
  );
4415
- if (existsSync6(PID_PATH) || existsSync6(SOCK_PATH) || socketOwner) {
3474
+ if (existsSync4(PID_PATH) || existsSync4(SOCK_PATH) || socketOwner) {
4416
3475
  process.stderr.write(
4417
3476
  `[prim] refusing to verify daemon absence: ${socketOwner ? `socket is owned by pid=${socketOwner.pid}` : "daemon artifacts remain"}
4418
3477
  `
@@ -4444,7 +3503,7 @@ async function detachedDaemonStop() {
4444
3503
  {},
4445
3504
  { timeoutMs: STATUS_PROBE_TIMEOUT_MS }
4446
3505
  );
4447
- if (socketOwner || existsSync6(SOCK_PATH)) {
3506
+ if (socketOwner || existsSync4(SOCK_PATH)) {
4448
3507
  process.stderr.write(
4449
3508
  `[prim] refusing to clear stale pidfile: ${socketOwner ? `socket is owned by pid=${socketOwner.pid}` : "socket artifact remains"}
4450
3509
  `
@@ -5212,6 +4271,8 @@ function authorLabel(row) {
5212
4271
  return "Your Claude Code";
5213
4272
  case "codex":
5214
4273
  return "Your Codex";
4274
+ case "cursor":
4275
+ return "Your Cursor";
5215
4276
  case "hermes":
5216
4277
  return "Your Hermes";
5217
4278
  case "chat":
@@ -6644,6 +5705,7 @@ function describeAudienceMember(member) {
6644
5705
  return {
6645
5706
  claude_code: "Claude Code agent",
6646
5707
  codex: "Codex agent",
5708
+ cursor: "Cursor agent",
6647
5709
  hermes: "Hermes agent"
6648
5710
  }[member.agent] ?? terminalSafeLine(member.agent);
6649
5711
  case "credential":
@@ -6767,7 +5829,7 @@ var UserScopeInputError = class extends Error {
6767
5829
  }
6768
5830
  };
6769
5831
  var USER_SCOPE_ROLES = /* @__PURE__ */ new Set(["owner", "admin", "member"]);
6770
- var USER_SCOPE_AGENTS = /* @__PURE__ */ new Set(["claude_code", "codex", "hermes"]);
5832
+ var USER_SCOPE_AGENTS = /* @__PURE__ */ new Set(["claude_code", "codex", "cursor", "hermes"]);
6771
5833
  var USER_SCOPE_CREDENTIALS = /* @__PURE__ */ new Set(["workos_jwt", "workos_api_key", "service_token"]);
6772
5834
  function requiredScopeValue(flag, value) {
6773
5835
  const trimmed = value.trim();
@@ -6795,9 +5857,12 @@ function userScopeFromOptions(opts) {
6795
5857
  for (const value of agents) {
6796
5858
  const agent = requiredScopeValue("--for-agent", value);
6797
5859
  if (!USER_SCOPE_AGENTS.has(agent)) {
6798
- throw new UserScopeInputError("--for-agent must be claude_code, codex, or hermes");
5860
+ throw new UserScopeInputError("--for-agent must be claude_code, codex, cursor, or hermes");
6799
5861
  }
6800
- members.push({ kind: "agent", agent });
5862
+ members.push({
5863
+ kind: "agent",
5864
+ agent
5865
+ });
6801
5866
  }
6802
5867
  for (const value of credentials) {
6803
5868
  const credential = requiredScopeValue("--for-credential", value);
@@ -7004,7 +6069,7 @@ function registerDecisionsCommands(program2) {
7004
6069
  []
7005
6070
  ).option(
7006
6071
  "--for-agent <agent>",
7007
- "Include one agent: claude_code | codex | hermes (repeatable)",
6072
+ "Include one agent: claude_code | codex | cursor | hermes (repeatable)",
7008
6073
  collectItem,
7009
6074
  []
7010
6075
  ).option(
@@ -7160,7 +6225,7 @@ function registerDecisionsCommands(program2) {
7160
6225
  []
7161
6226
  ).option(
7162
6227
  "--for-agent <agent>",
7163
- "Set one agent: claude_code | codex | hermes (repeatable)",
6228
+ "Set one agent: claude_code | codex | cursor | hermes (repeatable)",
7164
6229
  collectItem,
7165
6230
  []
7166
6231
  ).option(
@@ -7240,7 +6305,7 @@ function registerDecisionsCommands(program2) {
7240
6305
  }
7241
6306
 
7242
6307
  // src/commands/doctor.ts
7243
- import { existsSync as existsSync8 } from "fs";
6308
+ import { existsSync as existsSync6 } from "fs";
7244
6309
 
7245
6310
  // src/protocol/decision-lifecycle.ts
7246
6311
  var DECISION_LIFECYCLE_PROTOCOL_VERSION = 2;
@@ -7358,28 +6423,28 @@ async function inspectJournalDelivery(buckets, options = {}) {
7358
6423
  }
7359
6424
 
7360
6425
  // src/commands/hermes-install.ts
7361
- import { existsSync as existsSync7, readFileSync as readFileSync7, rmSync as rmSync3, statSync as statSync2 } from "fs";
7362
- import { homedir as homedir5 } from "os";
7363
- import { join as join8 } from "path";
6426
+ import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync } from "fs";
6427
+ import { homedir as homedir4 } from "os";
6428
+ import { join as join6 } from "path";
7364
6429
  import { Document, isMap, isSeq, parseAllDocuments, stringify, visit } from "yaml";
7365
- var CAPTURE_BIN3 = "prim-hook";
7366
- var GATE_BIN3 = "prim-pre-tool-use";
7367
- var POST_TOOL_USE_BIN3 = "prim-post-tool-use";
7368
- var SESSION_START_BIN3 = "prim-session-start";
7369
- var SESSION_END_BIN2 = "prim-session-end";
6430
+ var CAPTURE_BIN2 = "prim-hook";
6431
+ var GATE_BIN2 = "prim-pre-tool-use";
6432
+ var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
6433
+ var SESSION_START_BIN2 = "prim-session-start";
6434
+ var SESSION_END_BIN = "prim-session-end";
7370
6435
  var HERMES_ARGS = "--agent hermes";
7371
6436
  var EDIT_MATCHER = "write_file|patch";
7372
- var JSON_INDENT3 = 2;
6437
+ var JSON_INDENT2 = 2;
7373
6438
  var CONFIG_MODE = 384;
7374
- var PRIM_BINS3 = [CAPTURE_BIN3, GATE_BIN3, POST_TOOL_USE_BIN3, SESSION_START_BIN3, SESSION_END_BIN2];
6439
+ var PRIM_BINS2 = [CAPTURE_BIN2, GATE_BIN2, POST_TOOL_USE_BIN2, SESSION_START_BIN2, SESSION_END_BIN];
7375
6440
  function hermesHome() {
7376
- return process.env.HERMES_HOME ?? join8(homedir5(), ".hermes");
6441
+ return process.env.HERMES_HOME ?? join6(homedir4(), ".hermes");
7377
6442
  }
7378
6443
  function configPath() {
7379
- return join8(hermesHome(), "config.yaml");
6444
+ return join6(hermesHome(), "config.yaml");
7380
6445
  }
7381
6446
  function legacyShimPath() {
7382
- return join8(hermesHome(), "agent-hooks", "prim-shim.sh");
6447
+ return join6(hermesHome(), "agent-hooks", "prim-shim.sh");
7383
6448
  }
7384
6449
  function commandFor(bin) {
7385
6450
  return stableHookCommand(bin, HERMES_ARGS);
@@ -7390,7 +6455,7 @@ function legacyCommandFor(bin) {
7390
6455
  function commandUsesBin(command, bin) {
7391
6456
  return command === commandFor(bin) || command === legacyCommandFor(bin);
7392
6457
  }
7393
- var CAPTURE_EVENTS2 = [
6458
+ var CAPTURE_EVENTS = [
7394
6459
  "on_session_start",
7395
6460
  "pre_llm_call",
7396
6461
  "pre_tool_call",
@@ -7400,13 +6465,13 @@ var CAPTURE_EVENTS2 = [
7400
6465
  "subagent_stop"
7401
6466
  ];
7402
6467
  var GATE_TIMEOUT_SECONDS = 10;
7403
- var REGISTRATIONS2 = [
7404
- ...CAPTURE_EVENTS2.map((event) => ({ event, bin: CAPTURE_BIN3 })),
7405
- { event: "pre_tool_call", matcher: EDIT_MATCHER, bin: GATE_BIN3, timeout: GATE_TIMEOUT_SECONDS },
7406
- { event: "post_tool_call", matcher: EDIT_MATCHER, bin: POST_TOOL_USE_BIN3 },
7407
- { event: "post_approval_response", bin: POST_TOOL_USE_BIN3 },
7408
- { event: "on_session_start", bin: SESSION_START_BIN3 },
7409
- { event: "on_session_end", bin: SESSION_END_BIN2 }
6468
+ var REGISTRATIONS = [
6469
+ ...CAPTURE_EVENTS.map((event) => ({ event, bin: CAPTURE_BIN2 })),
6470
+ { event: "pre_tool_call", matcher: EDIT_MATCHER, bin: GATE_BIN2, timeout: GATE_TIMEOUT_SECONDS },
6471
+ { event: "post_tool_call", matcher: EDIT_MATCHER, bin: POST_TOOL_USE_BIN2 },
6472
+ { event: "post_approval_response", bin: POST_TOOL_USE_BIN2 },
6473
+ { event: "on_session_start", bin: SESSION_START_BIN2 },
6474
+ { event: "on_session_end", bin: SESSION_END_BIN }
7410
6475
  ];
7411
6476
  function yamlValue(node) {
7412
6477
  return typeof node?.toJSON === "function" ? node.toJSON?.() : node;
@@ -7436,18 +6501,18 @@ function ensureReg(list, reg, force) {
7436
6501
  }
7437
6502
  return [...stripBin(list, reg.bin), entry];
7438
6503
  }
7439
- function applyInstall3(hooks, force) {
6504
+ function applyInstall2(hooks, force) {
7440
6505
  const next = { ...hooks };
7441
- for (const reg of REGISTRATIONS2) {
6506
+ for (const reg of REGISTRATIONS) {
7442
6507
  next[reg.event] = ensureReg(next[reg.event] ?? [], reg, force);
7443
6508
  }
7444
6509
  return next;
7445
6510
  }
7446
- function applyUninstall3(hooks) {
6511
+ function applyUninstall2(hooks) {
7447
6512
  const next = {};
7448
6513
  for (const [event, list] of Object.entries(hooks)) {
7449
6514
  let kept = list;
7450
- for (const bin of PRIM_BINS3) {
6515
+ for (const bin of PRIM_BINS2) {
7451
6516
  kept = stripBin(kept, bin);
7452
6517
  }
7453
6518
  if (kept.length > 0) {
@@ -7456,54 +6521,54 @@ function applyUninstall3(hooks) {
7456
6521
  }
7457
6522
  return next;
7458
6523
  }
7459
- function isGateInstalled3(hooks) {
7460
- return (hooks.pre_tool_call ?? []).some((e) => commandUsesBin(e.command, GATE_BIN3));
6524
+ function isGateInstalled2(hooks) {
6525
+ return (hooks.pre_tool_call ?? []).some((e) => commandUsesBin(e.command, GATE_BIN2));
7461
6526
  }
7462
6527
  function isCaptureInstalled(hooks) {
7463
- return CAPTURE_EVENTS2.some(
7464
- (event) => (hooks[event] ?? []).some((e) => commandUsesBin(e.command, CAPTURE_BIN3))
6528
+ return CAPTURE_EVENTS.some(
6529
+ (event) => (hooks[event] ?? []).some((e) => commandUsesBin(e.command, CAPTURE_BIN2))
7465
6530
  );
7466
6531
  }
7467
6532
  function entryUsesOwnedBin(entry) {
7468
- return PRIM_BINS3.some(
6533
+ return PRIM_BINS2.some(
7469
6534
  (bin) => commandUsesBin(entry.command, bin) || commandMatchesBin(entry.command, bin)
7470
6535
  );
7471
6536
  }
7472
- function ownedRegistrationEntries3(hooks) {
6537
+ function ownedRegistrationEntries2(hooks) {
7473
6538
  return Object.entries(hooks).flatMap(
7474
6539
  ([event, entries]) => entries.filter(entryUsesOwnedBin).map((entry) => ({ event, entry }))
7475
6540
  );
7476
6541
  }
7477
- function hasAnyHookRegistration3(hooks) {
7478
- return ownedRegistrationEntries3(hooks).length > 0;
6542
+ function hasAnyHookRegistration2(hooks) {
6543
+ return ownedRegistrationEntries2(hooks).length > 0;
7479
6544
  }
7480
- function hookRuntimeResolutions3(hooks) {
6545
+ function hookRuntimeResolutions2(hooks) {
7481
6546
  const resolutions = hookCommandResolutions(
7482
6547
  Object.values(hooks).flatMap((entries) => entries.map((entry) => entry.command)),
7483
- PRIM_BINS3
6548
+ PRIM_BINS2
7484
6549
  );
7485
6550
  for (const entries of Object.values(hooks)) {
7486
6551
  for (const entry of entries) {
7487
- for (const bin of PRIM_BINS3) {
6552
+ for (const bin of PRIM_BINS2) {
7488
6553
  if (entry.command === legacyCommandFor(bin)) resolutions.push({ kind: "legacy_path" });
7489
6554
  }
7490
6555
  }
7491
6556
  }
7492
6557
  return resolutions;
7493
6558
  }
7494
- function inspectHookRuntimeResolutions3() {
6559
+ function inspectHookRuntimeResolutions4() {
7495
6560
  const path = configPath();
7496
- if (!existsSync7(path)) return [];
7497
- return hookRuntimeResolutions3(readHooks(parseHermesDocument(readFileSync7(path, "utf8"))));
6561
+ if (!existsSync5(path)) return [];
6562
+ return hookRuntimeResolutions2(readHooks(parseHermesDocument(readFileSync5(path, "utf8"))));
7498
6563
  }
7499
- function entryMatchesRegistration3(owned, registration) {
6564
+ function entryMatchesRegistration2(owned, registration) {
7500
6565
  const expected = entryFor(registration);
7501
6566
  return owned.event === registration.event && owned.entry.command === expected.command && (owned.entry.matcher ?? "") === (expected.matcher ?? "") && (owned.entry.timeout ?? null) === (expected.timeout ?? null);
7502
6567
  }
7503
- function hasCompleteHookRegistration3(hooks) {
7504
- const owned = ownedRegistrationEntries3(hooks);
7505
- return owned.length === REGISTRATIONS2.length && REGISTRATIONS2.every(
7506
- (registration) => owned.filter((entry) => entryMatchesRegistration3(entry, registration)).length === 1
6568
+ function hasCompleteHookRegistration2(hooks) {
6569
+ const owned = ownedRegistrationEntries2(hooks);
6570
+ return owned.length === REGISTRATIONS.length && REGISTRATIONS.every(
6571
+ (registration) => owned.filter((entry) => entryMatchesRegistration2(entry, registration)).length === 1
7507
6572
  );
7508
6573
  }
7509
6574
  var INVALID_CONFIG_MESSAGE = "Hermes config must be valid, single-document YAML with a top-level mapping";
@@ -7578,7 +6643,7 @@ function serializeHooks(hooks) {
7578
6643
  return stringify({ hooks }, { lineWidth: 0 });
7579
6644
  }
7580
6645
  function isPrimEntry(entry) {
7581
- return PRIM_BINS3.some((bin) => commandUsesBin(entry.command, bin));
6646
+ return PRIM_BINS2.some((bin) => commandUsesBin(entry.command, bin));
7582
6647
  }
7583
6648
  function entryKey(entry) {
7584
6649
  return JSON.stringify(entry);
@@ -7763,11 +6828,11 @@ function assertMergeValid(before, after, expectedHooks) {
7763
6828
  }
7764
6829
  }
7765
6830
  function readConfigSnapshot(path) {
7766
- if (!existsSync7(path)) return { exists: false, mode: CONFIG_MODE, raw: "" };
6831
+ if (!existsSync5(path)) return { exists: false, mode: CONFIG_MODE, raw: "" };
7767
6832
  return {
7768
6833
  exists: true,
7769
- mode: statSync2(path).mode & 511,
7770
- raw: readFileSync7(path, "utf8")
6834
+ mode: statSync(path).mode & 511,
6835
+ raw: readFileSync5(path, "utf8")
7771
6836
  };
7772
6837
  }
7773
6838
  var CONCURRENT_CONFIG_MESSAGE = "Hermes config changed during update; concurrent changes were preserved, retry the command";
@@ -7776,7 +6841,7 @@ function writeHermesConfigSnapshot(path, before, after, expectedHooks) {
7776
6841
  ensureParent: true,
7777
6842
  mode: before.mode,
7778
6843
  validate(temporaryPath) {
7779
- const flushed = readFileSync7(temporaryPath, "utf8");
6844
+ const flushed = readFileSync5(temporaryPath, "utf8");
7780
6845
  if (!mergePreservesHermesSemantics(before.raw, flushed, expectedHooks)) {
7781
6846
  throw new Error(INVALID_MERGE_MESSAGE);
7782
6847
  }
@@ -7788,7 +6853,7 @@ function writeHermesConfigSnapshot(path, before, after, expectedHooks) {
7788
6853
  });
7789
6854
  }
7790
6855
  function removeLegacyShim() {
7791
- rmSync3(legacyShimPath(), { force: true });
6856
+ rmSync2(legacyShimPath(), { force: true });
7792
6857
  }
7793
6858
  function autoAcceptOf(raw) {
7794
6859
  if (raw.trim().length === 0) {
@@ -7799,13 +6864,13 @@ function autoAcceptOf(raw) {
7799
6864
  function readHooksFromRaw(raw) {
7800
6865
  return readHooks(parseHermesDocument(raw));
7801
6866
  }
7802
- function performInstall3(opts) {
6867
+ function performInstall2(opts) {
7803
6868
  (opts.stageRuntime ?? stageHookRuntime)();
7804
6869
  const path = configPath();
7805
6870
  const snapshot = readConfigSnapshot(path);
7806
6871
  const raw = snapshot.raw;
7807
6872
  const existing = readHooksFromRaw(raw);
7808
- const desired = applyInstall3(existing, opts.force);
6873
+ const desired = applyInstall2(existing, opts.force);
7809
6874
  let next = raw;
7810
6875
  if (JSON.stringify(existing) !== JSON.stringify(desired)) {
7811
6876
  next = spliceHooks(next, desired);
@@ -7821,22 +6886,22 @@ function performInstall3(opts) {
7821
6886
  removeLegacyShim();
7822
6887
  return {
7823
6888
  path,
7824
- gate: isGateInstalled3(desired),
6889
+ gate: isGateInstalled2(desired),
7825
6890
  capture: isCaptureInstalled(desired),
7826
6891
  autoAccept: autoAcceptOf(next),
7827
6892
  changed
7828
6893
  };
7829
6894
  }
7830
- function performUninstall3() {
6895
+ function performUninstall2() {
7831
6896
  const path = configPath();
7832
- if (!existsSync7(path)) {
6897
+ if (!existsSync5(path)) {
7833
6898
  removeLegacyShim();
7834
6899
  return { path, gate: false, capture: false, autoAccept: false, changed: false };
7835
6900
  }
7836
6901
  const snapshot = readConfigSnapshot(path);
7837
6902
  const raw = snapshot.raw;
7838
6903
  const existing = readHooksFromRaw(raw);
7839
- const remaining = applyUninstall3(existing);
6904
+ const remaining = applyUninstall2(existing);
7840
6905
  const next = JSON.stringify(existing) === JSON.stringify(remaining) ? raw : spliceHooks(raw, remaining);
7841
6906
  const changed = next !== raw;
7842
6907
  if (changed) {
@@ -7846,22 +6911,22 @@ function performUninstall3() {
7846
6911
  removeLegacyShim();
7847
6912
  return {
7848
6913
  path,
7849
- gate: isGateInstalled3(remaining),
6914
+ gate: isGateInstalled2(remaining),
7850
6915
  capture: isCaptureInstalled(remaining),
7851
6916
  autoAccept: autoAcceptOf(next),
7852
6917
  changed
7853
6918
  };
7854
6919
  }
7855
- function performStatus3() {
6920
+ function performStatus4() {
7856
6921
  const path = configPath();
7857
- const raw = existsSync7(path) ? readFileSync7(path, "utf8") : "";
6922
+ const raw = existsSync5(path) ? readFileSync5(path, "utf8") : "";
7858
6923
  const hooks = raw ? readHooks(parseHermesDocument(raw)) : {};
7859
6924
  return {
7860
6925
  path,
7861
- present: hasAnyHookRegistration3(hooks),
7862
- gate: isGateInstalled3(hooks),
6926
+ present: hasAnyHookRegistration2(hooks),
6927
+ gate: isGateInstalled2(hooks),
7863
6928
  capture: isCaptureInstalled(hooks),
7864
- complete: hasCompleteHookRegistration3(hooks),
6929
+ complete: hasCompleteHookRegistration2(hooks),
7865
6930
  autoAccept: raw ? autoAcceptOf(raw) : false
7866
6931
  };
7867
6932
  }
@@ -7878,7 +6943,7 @@ function registerHermesCommands(program2) {
7878
6943
  const hermes = program2.command("hermes").description("Manage the prim Hermes Agent integration (capture, gate, ingest, presence)");
7879
6944
  hermes.command("install").description("Register the prim hooks in Hermes's ~/.hermes/config.yaml").option("--scope <scope>", "user (default; Hermes config is global-only)").option("--force", "Replace any drifted prim hook entries").option("--auto-accept", "Also set hooks_auto_accept: true so the hooks need no TTY consent").action((opts) => {
7880
6945
  rejectProjectScope(opts.scope);
7881
- const result = performInstall3({
6946
+ const result = performInstall2({
7882
6947
  force: opts.force ?? false,
7883
6948
  autoAccept: opts.autoAccept ?? false
7884
6949
  });
@@ -7886,22 +6951,22 @@ function registerHermesCommands(program2) {
7886
6951
  result.changed ? `[prim] Hermes integration installed at ${result.path}` : `[prim] Hermes integration already present at ${result.path} (no changes)`
7887
6952
  );
7888
6953
  console.error(TRUST_NOTICE2);
7889
- console.log(JSON.stringify(result, null, JSON_INDENT3));
6954
+ console.log(JSON.stringify(result, null, JSON_INDENT2));
7890
6955
  });
7891
6956
  hermes.command("uninstall").description("Remove all prim hooks from Hermes's ~/.hermes/config.yaml").action(() => {
7892
- const result = performUninstall3();
6957
+ const result = performUninstall2();
7893
6958
  console.error(
7894
6959
  result.changed ? `[prim] prim hooks removed from ${result.path}` : `[prim] no prim hooks to remove at ${result.path} (nothing changed)`
7895
6960
  );
7896
- console.log(JSON.stringify(result, null, JSON_INDENT3));
6961
+ console.log(JSON.stringify(result, null, JSON_INDENT2));
7897
6962
  });
7898
6963
  hermes.command("status").description("Report whether each prim surface (gate, capture) is installed").action(() => {
7899
- const result = performStatus3();
6964
+ const result = performStatus4();
7900
6965
  const mark = (b) => b ? "\u2713" : "\u2717";
7901
6966
  console.error(
7902
6967
  `[prim] hermes: gate ${mark(result.gate)} \xB7 capture ${mark(result.capture)} (${result.path})`
7903
6968
  );
7904
- console.log(JSON.stringify(result, null, JSON_INDENT3));
6969
+ console.log(JSON.stringify(result, null, JSON_INDENT2));
7905
6970
  });
7906
6971
  }
7907
6972
 
@@ -7944,7 +7009,7 @@ function checkAuth() {
7944
7009
  return classifyAuthCredential(
7945
7010
  credential,
7946
7011
  storedCredential ? getTokenExpiresAt() : void 0,
7947
- storedCredential && existsSync8(REFRESH_TOKEN_PATH)
7012
+ storedCredential && existsSync6(REFRESH_TOKEN_PATH)
7948
7013
  );
7949
7014
  }
7950
7015
  function classifyDaemonHealth(snapshot, options = {}) {
@@ -8288,6 +7353,34 @@ function classifyCodexHooks(statuses) {
8288
7353
  detail: "installed; Codex trust is not machine-readable \u2014 verify with `/hooks`"
8289
7354
  };
8290
7355
  }
7356
+ function classifyCursorHooks(statuses) {
7357
+ const installed = statuses.filter((status) => status.present);
7358
+ if (installed.length === 0)
7359
+ return { name: "cursor-hooks", status: "ok", detail: "not installed" };
7360
+ if (installed.some((status) => !status.complete)) {
7361
+ return {
7362
+ name: "cursor-hooks",
7363
+ status: "fail",
7364
+ detail: "incomplete or drifted native lifecycle \u2014 run `prim cursor install --force`"
7365
+ };
7366
+ }
7367
+ const user = statuses.find((status) => status.footer !== void 0);
7368
+ if (user?.present && user.footer === false && !user.footerPreservedCustom) {
7369
+ return {
7370
+ name: "cursor-hooks",
7371
+ status: "fail",
7372
+ detail: "native lifecycle ready but Cursor CLI footer is missing \u2014 run `prim cursor install --scope user --force`"
7373
+ };
7374
+ }
7375
+ if (statuses.some((status) => status.footerPreservedCustom)) {
7376
+ return {
7377
+ name: "cursor-hooks",
7378
+ status: "warn",
7379
+ detail: "native lifecycle ready; custom Cursor CLI footer preserved"
7380
+ };
7381
+ }
7382
+ return { name: "cursor-hooks", status: "ok", detail: "complete native lifecycle ready" };
7383
+ }
8291
7384
  function classifyHermesHooks(status) {
8292
7385
  if (!status.present) {
8293
7386
  return { name: "hermes-hooks", status: "ok", detail: "not installed" };
@@ -8308,7 +7401,7 @@ function classifyHermesHooks(status) {
8308
7401
  function checkAgentHooks() {
8309
7402
  const checks = [];
8310
7403
  try {
8311
- const status = performStatus2();
7404
+ const status = performStatus3();
8312
7405
  checks.push(classifyCodexHooks([status.project, status.user]));
8313
7406
  } catch (error) {
8314
7407
  const detail = boundedHealthError(error instanceof Error ? error.message : String(error));
@@ -8319,7 +7412,18 @@ function checkAgentHooks() {
8319
7412
  });
8320
7413
  }
8321
7414
  try {
8322
- checks.push(classifyHermesHooks(performStatus3()));
7415
+ const status = performStatus2();
7416
+ checks.push(classifyCursorHooks([status.project, status.user]));
7417
+ } catch (error) {
7418
+ const detail = boundedHealthError(error instanceof Error ? error.message : String(error));
7419
+ checks.push({
7420
+ name: "cursor-hooks",
7421
+ status: "fail",
7422
+ detail: detail ?? "hook configuration is unreadable"
7423
+ });
7424
+ }
7425
+ try {
7426
+ checks.push(classifyHermesHooks(performStatus4()));
8323
7427
  } catch (error) {
8324
7428
  const detail = boundedHealthError(error instanceof Error ? error.message : String(error));
8325
7429
  checks.push({
@@ -8428,8 +7532,9 @@ function checkHookRuntime() {
8428
7532
  return diagnoseRegisteredHookRuntime(
8429
7533
  [
8430
7534
  ...inspectHookRuntimeResolutions(),
7535
+ ...inspectHookRuntimeResolutions3(),
8431
7536
  ...inspectHookRuntimeResolutions2(),
8432
- ...inspectHookRuntimeResolutions3()
7537
+ ...inspectHookRuntimeResolutions4()
8433
7538
  ],
8434
7539
  inspectHookRuntime,
8435
7540
  packageVersion
@@ -8564,70 +7669,70 @@ function registerDoctorCommands(program2) {
8564
7669
  }
8565
7670
 
8566
7671
  // src/commands/moves.ts
8567
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
8568
- import { join as join11 } from "path";
7672
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "fs";
7673
+ import { join as join9 } from "path";
8569
7674
 
8570
7675
  // src/flusher.ts
8571
- import { createReadStream, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "fs";
8572
- import { dirname as dirname6, join as join10 } from "path";
7676
+ import { createReadStream, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
7677
+ import { dirname as dirname4, join as join8 } from "path";
8573
7678
 
8574
7679
  // src/dead-letter.ts
8575
- import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
7680
+ import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
8576
7681
  import {
8577
- constants as constants3,
8578
- chmodSync as chmodSync3,
7682
+ constants as constants2,
7683
+ chmodSync as chmodSync2,
8579
7684
  closeSync as closeSync2,
8580
- existsSync as existsSync9,
7685
+ existsSync as existsSync7,
8581
7686
  fchmodSync,
8582
7687
  fsyncSync,
8583
- mkdirSync as mkdirSync5,
7688
+ mkdirSync as mkdirSync4,
8584
7689
  openSync as openSync2,
8585
- renameSync as renameSync3,
8586
- rmSync as rmSync4,
8587
- writeFileSync as writeFileSync4
7690
+ renameSync as renameSync2,
7691
+ rmSync as rmSync3,
7692
+ writeFileSync as writeFileSync3
8588
7693
  } from "fs";
8589
- import { dirname as dirname5, join as join9 } from "path";
8590
- var DIRECTORY_MODE2 = 448;
7694
+ import { dirname as dirname3, join as join7 } from "path";
7695
+ var DIRECTORY_MODE = 448;
8591
7696
  var FILE_MODE = 384;
8592
7697
  var DEAD_LETTER_DIRNAME = "dead-letter";
8593
7698
  function deadLetterDirectoryForRotation(flushingPath) {
8594
- return join9(dirname5(flushingPath), DEAD_LETTER_DIRNAME);
7699
+ return join7(dirname3(flushingPath), DEAD_LETTER_DIRNAME);
8595
7700
  }
8596
7701
  function deadLetterPathForRawLine(flushingPath, rawLine) {
8597
- const quarantineId = createHash4("sha256").update(rawLine).digest("hex");
8598
- return join9(deadLetterDirectoryForRotation(flushingPath), `raw-${quarantineId}.json`);
7702
+ const quarantineId = createHash3("sha256").update(rawLine).digest("hex");
7703
+ return join7(deadLetterDirectoryForRotation(flushingPath), `raw-${quarantineId}.json`);
8599
7704
  }
8600
7705
  function ensureDeadLetterDirectory(flushingPath, persistDirectory) {
8601
7706
  const directory = deadLetterDirectoryForRotation(flushingPath);
8602
- mkdirSync5(directory, { recursive: true, mode: DIRECTORY_MODE2 });
8603
- chmodSync3(directory, DIRECTORY_MODE2);
8604
- persistDirectory(dirname5(directory));
7707
+ mkdirSync4(directory, { recursive: true, mode: DIRECTORY_MODE });
7708
+ chmodSync2(directory, DIRECTORY_MODE);
7709
+ persistDirectory(dirname3(directory));
8605
7710
  return directory;
8606
7711
  }
8607
7712
  function persistDeadLetter(directory, path, quarantineId, record2, persistDirectory) {
8608
- if (existsSync9(path)) {
7713
+ if (existsSync7(path)) {
8609
7714
  persistDirectory(directory);
8610
7715
  return;
8611
7716
  }
8612
- const temporaryPath = join9(
7717
+ const temporaryPath = join7(
8613
7718
  directory,
8614
- `.${quarantineId}.${String(process.pid)}.${randomBytes5(8).toString("hex")}.tmp`
7719
+ `.${quarantineId}.${String(process.pid)}.${randomBytes4(8).toString("hex")}.tmp`
8615
7720
  );
8616
- const flags = constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY | constants3.O_NOFOLLOW;
7721
+ const flags = constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY | constants2.O_NOFOLLOW;
8617
7722
  try {
8618
7723
  const fd = openSync2(temporaryPath, flags, FILE_MODE);
8619
7724
  try {
8620
7725
  fchmodSync(fd, FILE_MODE);
8621
- writeFileSync4(fd, `${JSON.stringify(record2)}
7726
+ writeFileSync3(fd, `${JSON.stringify(record2)}
8622
7727
  `, "utf8");
8623
7728
  fsyncSync(fd);
8624
7729
  } finally {
8625
7730
  closeSync2(fd);
8626
7731
  }
8627
- renameSync3(temporaryPath, path);
7732
+ renameSync2(temporaryPath, path);
8628
7733
  persistDirectory(directory);
8629
7734
  } finally {
8630
- rmSync4(temporaryPath, { force: true });
7735
+ rmSync3(temporaryPath, { force: true });
8631
7736
  }
8632
7737
  }
8633
7738
  function quarantineMove(flushingPath, move, reason, now = Date.now, options = {}) {
@@ -8636,12 +7741,12 @@ function quarantineMove(flushingPath, move, reason, now = Date.now, options = {}
8636
7741
  const serializedMove = JSON.stringify(move);
8637
7742
  const record2 = {
8638
7743
  version: 1,
8639
- quarantineId: createHash4("sha256").update(serializedMove).digest("hex"),
7744
+ quarantineId: createHash3("sha256").update(serializedMove).digest("hex"),
8640
7745
  quarantinedAt: now(),
8641
7746
  reason,
8642
7747
  move
8643
7748
  };
8644
- const path = join9(directory, `${record2.quarantineId}.json`);
7749
+ const path = join7(directory, `${record2.quarantineId}.json`);
8645
7750
  persistDeadLetter(directory, path, record2.quarantineId, record2, persistDirectory);
8646
7751
  return record2;
8647
7752
  }
@@ -8652,7 +7757,7 @@ function quarantineRawLine(flushingPath, rawLine, now = Date.now, options = {})
8652
7757
  const record2 = {
8653
7758
  version: 1,
8654
7759
  recordKind: "raw_line_v1",
8655
- quarantineId: createHash4("sha256").update(bytes).digest("hex"),
7760
+ quarantineId: createHash3("sha256").update(bytes).digest("hex"),
8656
7761
  quarantinedAt: now(),
8657
7762
  reason: "invalid_move",
8658
7763
  rawLineEncoding: "base64",
@@ -8717,6 +7822,12 @@ function deadLetterReason(error) {
8717
7822
  }
8718
7823
  return void 0;
8719
7824
  }
7825
+ function mayBeBatchSizeFailure(error) {
7826
+ if (error instanceof HttpError) {
7827
+ return error.status >= 500 && error.status !== 503;
7828
+ }
7829
+ return error instanceof Error && error.name === "TimeoutError";
7830
+ }
8720
7831
  async function* rawJournalLines(input) {
8721
7832
  let pending = [];
8722
7833
  let pendingBytes = 0;
@@ -8758,7 +7869,7 @@ async function drainFlushingPath(flushingPath, client, binding, options = {}) {
8758
7869
  return { flushed: batch2.length, quarantined: 0 };
8759
7870
  } catch (error) {
8760
7871
  const reason = deadLetterReason(error);
8761
- if (!reason) {
7872
+ if (!(reason || mayBeBatchSizeFailure(error))) {
8762
7873
  throw error;
8763
7874
  }
8764
7875
  if (batch2.length > 1) {
@@ -8767,6 +7878,9 @@ async function drainFlushingPath(flushingPath, client, binding, options = {}) {
8767
7878
  const right = await postBatch(batch2.slice(midpoint));
8768
7879
  return addDrainCounts(left, right);
8769
7880
  }
7881
+ if (!reason) {
7882
+ throw error;
7883
+ }
8770
7884
  const [move] = batch2;
8771
7885
  const quarantined = quarantineMove(
8772
7886
  flushingPath,
@@ -8825,7 +7939,7 @@ async function drainFlushingPath(flushingPath, client, binding, options = {}) {
8825
7939
  async function drainPath(path, client, binding) {
8826
7940
  const tmpPath = `${path}.flushing.${String(Date.now())}.${String(process.pid)}`;
8827
7941
  try {
8828
- renameSync4(path, tmpPath);
7942
+ renameSync3(path, tmpPath);
8829
7943
  } catch (err) {
8830
7944
  if (err.code === "ENOENT") {
8831
7945
  return { flushed: 0, quarantined: 0 };
@@ -8924,7 +8038,7 @@ async function flushOnce() {
8924
8038
  return inspection.retainedBuckets.length > 0 ? { flushed: total, quarantined, retained: inspection.retainedBuckets } : { flushed: total, quarantined };
8925
8039
  }
8926
8040
  var flushInFlight;
8927
- var FLUSH_LOCK_PATH = join10(dirname6(JOURNAL_DIR), ".flush.lock");
8041
+ var FLUSH_LOCK_PATH = join8(dirname4(JOURNAL_DIR), ".flush.lock");
8928
8042
  var FLUSH_LOCK_TIMEOUT_MS = 250;
8929
8043
  function isFlushLockContended(error) {
8930
8044
  return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
@@ -9058,19 +8172,19 @@ function registerMovesCommands(program2) {
9058
8172
  }
9059
8173
  });
9060
8174
  moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
9061
- const dir = join11(process.cwd(), ".prim");
9062
- if (!existsSync10(dir)) {
9063
- mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
8175
+ const dir = join9(process.cwd(), ".prim");
8176
+ if (!existsSync8(dir)) {
8177
+ mkdirSync5(dir, { recursive: true, mode: DIR_MODE });
9064
8178
  }
9065
- const file = join11(process.cwd(), WORKSPACE_FILE);
9066
- writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
8179
+ const file = join9(process.cwd(), WORKSPACE_FILE);
8180
+ writeFileSync4(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
9067
8181
  mode: FILE_MODE2
9068
8182
  });
9069
8183
  console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
9070
8184
  });
9071
8185
  moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
9072
- const file = join11(process.cwd(), WORKSPACE_FILE);
9073
- if (!existsSync10(file)) {
8186
+ const file = join9(process.cwd(), WORKSPACE_FILE);
8187
+ if (!existsSync8(file)) {
9074
8188
  console.log("[prim] no workspace binding in cwd");
9075
8189
  return;
9076
8190
  }
@@ -9161,23 +8275,23 @@ function registerReconcileCommands(program2) {
9161
8275
 
9162
8276
  // src/commands/session.ts
9163
8277
  import {
9164
- existsSync as existsSync11,
9165
- mkdirSync as mkdirSync7,
9166
- readFileSync as readFileSync8,
9167
- readdirSync as readdirSync4,
8278
+ existsSync as existsSync9,
8279
+ mkdirSync as mkdirSync6,
8280
+ readFileSync as readFileSync6,
8281
+ readdirSync as readdirSync3,
9168
8282
  unlinkSync as unlinkSync5,
9169
- writeFileSync as writeFileSync6
8283
+ writeFileSync as writeFileSync5
9170
8284
  } from "fs";
9171
- import { join as join12 } from "path";
8285
+ import { join as join10 } from "path";
9172
8286
  var DIR_MODE2 = 448;
9173
8287
  var FILE_MODE3 = 384;
9174
8288
  function ensureDir() {
9175
- if (!existsSync11(SESSIONS_DIR)) {
9176
- mkdirSync7(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
8289
+ if (!existsSync9(SESSIONS_DIR)) {
8290
+ mkdirSync6(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
9177
8291
  }
9178
8292
  }
9179
8293
  function markerPath(sessionId) {
9180
- return join12(SESSIONS_DIR, `${sessionId}.json`);
8294
+ return join10(SESSIONS_DIR, `${sessionId}.json`);
9181
8295
  }
9182
8296
  function registerSessionCommands(program2) {
9183
8297
  const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
@@ -9187,17 +8301,17 @@ function registerSessionCommands(program2) {
9187
8301
  orgId: opts.orgId,
9188
8302
  startedAt: Date.now()
9189
8303
  };
9190
- writeFileSync6(markerPath(sessionId), JSON.stringify(marker, null, 2), {
8304
+ writeFileSync5(markerPath(sessionId), JSON.stringify(marker, null, 2), {
9191
8305
  mode: FILE_MODE3
9192
8306
  });
9193
8307
  console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
9194
8308
  });
9195
8309
  session.command("list").description("List active session markers").action(() => {
9196
- if (!existsSync11(SESSIONS_DIR)) {
8310
+ if (!existsSync9(SESSIONS_DIR)) {
9197
8311
  console.log("[prim] no session markers");
9198
8312
  return;
9199
8313
  }
9200
- const files = readdirSync4(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
8314
+ const files = readdirSync3(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
9201
8315
  if (files.length === 0) {
9202
8316
  console.log("[prim] no session markers");
9203
8317
  return;
@@ -9205,7 +8319,7 @@ function registerSessionCommands(program2) {
9205
8319
  for (const f of files) {
9206
8320
  const sessionId = f.replace(/\.json$/, "");
9207
8321
  try {
9208
- const m = JSON.parse(readFileSync8(join12(SESSIONS_DIR, f), "utf-8"));
8322
+ const m = JSON.parse(readFileSync6(join10(SESSIONS_DIR, f), "utf-8"));
9209
8323
  console.log(`${sessionId} org=${m.orgId}`);
9210
8324
  } catch {
9211
8325
  }
@@ -9213,7 +8327,7 @@ function registerSessionCommands(program2) {
9213
8327
  });
9214
8328
  session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
9215
8329
  const p = markerPath(sessionId);
9216
- if (!existsSync11(p)) {
8330
+ if (!existsSync9(p)) {
9217
8331
  console.log(`[prim] no marker for session ${sessionId}`);
9218
8332
  return;
9219
8333
  }
@@ -9224,13 +8338,14 @@ function registerSessionCommands(program2) {
9224
8338
 
9225
8339
  // src/commands/setup.ts
9226
8340
  import { spawnSync as spawnSync2 } from "child_process";
9227
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
9228
- import { join as join13 } from "path";
8341
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
8342
+ import { join as join11 } from "path";
9229
8343
  var EXIT_INCOMPLETE = 1;
9230
8344
  var EXIT_USAGE3 = 2;
9231
8345
  var SESSION_LABELS = {
9232
8346
  claude: "Claude Code integration",
9233
8347
  codex: "Codex integration",
8348
+ cursor: "Cursor integration",
9234
8349
  hermes: "Hermes integration"
9235
8350
  };
9236
8351
  function planSetupSteps(opts) {
@@ -9306,8 +8421,8 @@ function detectProjectConflicts(agent, run) {
9306
8421
  }
9307
8422
  try {
9308
8423
  const root = gitToplevel();
9309
- const preCommit = root && join13(root, ".git", "hooks", "pre-commit");
9310
- if (preCommit && existsSync12(preCommit) && readFileSync9(preCommit, "utf-8").includes("prim-pre-commit")) {
8424
+ const preCommit = root && join11(root, ".git", "hooks", "pre-commit");
8425
+ if (preCommit && existsSync10(preCommit) && readFileSync7(preCommit, "utf-8").includes("prim-pre-commit")) {
9311
8426
  conflicts.push(CONFLICT_HOOKS);
9312
8427
  }
9313
8428
  } catch {
@@ -9325,6 +8440,9 @@ function detectAgent(env) {
9325
8440
  if (env.HERMES_INTERACTIVE) {
9326
8441
  return "hermes";
9327
8442
  }
8443
+ if (env.CURSOR_AGENT) {
8444
+ return "cursor";
8445
+ }
9328
8446
  return "claude";
9329
8447
  }
9330
8448
  function resolveAgent(agentFlag, env) {
@@ -9346,7 +8464,7 @@ function parseSetupAuthStatus(result) {
9346
8464
  function registerSetupCommand(program2, dependencies = {}) {
9347
8465
  program2.command("setup").description(
9348
8466
  "Install everything in one shot (auth, session + git hooks, daemon, skill, welcome)"
9349
- ).option("--agent <agent>", "claude, codex, or hermes (auto-detected when omitted)").option(
8467
+ ).option("--agent <agent>", "claude, codex, cursor, or hermes (auto-detected when omitted)").option(
9350
8468
  "--scope <scope>",
9351
8469
  "user (default \u2014 install once for every repo) or project (this repo only)",
9352
8470
  "user"
@@ -9355,9 +8473,9 @@ function registerSetupCommand(program2, dependencies = {}) {
9355
8473
  "with the default user scope, remove any project-scoped prim config in this repo (else just warn)"
9356
8474
  ).option("--no-daemon", "stop and disable the companion daemon").action((opts) => {
9357
8475
  const { agent: agentInput, detected } = resolveAgent(opts.agent, process.env);
9358
- if (agentInput !== "claude" && agentInput !== "codex" && agentInput !== "hermes") {
8476
+ if (agentInput !== "claude" && agentInput !== "codex" && agentInput !== "cursor" && agentInput !== "hermes") {
9359
8477
  process.stderr.write(
9360
- `[prim] unknown --agent "${agentInput}" (expected claude, codex, or hermes)
8478
+ `[prim] unknown --agent "${agentInput}" (expected claude, codex, cursor, or hermes)
9361
8479
  `
9362
8480
  );
9363
8481
  (dependencies.exit ?? process.exit)(EXIT_USAGE3);
@@ -9468,23 +8586,23 @@ function registerSetupCommand(program2, dependencies = {}) {
9468
8586
  }
9469
8587
 
9470
8588
  // src/commands/statusline.ts
9471
- import { readFileSync as readFileSync10 } from "fs";
9472
- import { dirname as dirname7, resolve as resolve4 } from "path";
8589
+ import { readFileSync as readFileSync8 } from "fs";
8590
+ import { dirname as dirname5, resolve as resolve3 } from "path";
9473
8591
  import { fileURLToPath } from "url";
9474
8592
  var STATUSLINE_TIMEOUT_MS = 200;
9475
8593
  function readPackageVersion() {
9476
8594
  try {
9477
- const here = dirname7(fileURLToPath(import.meta.url));
8595
+ const here = dirname5(fileURLToPath(import.meta.url));
9478
8596
  const candidates = [
9479
8597
  // Schema-v2 runtimes staged a manifest beside this standalone bundle.
9480
8598
  // Keep resolving it while those legacy launchers transition to schema v3.
9481
- resolve4(here, "manifest.json"),
9482
- resolve4(here, "../../package.json"),
9483
- resolve4(here, "../package.json")
8599
+ resolve3(here, "manifest.json"),
8600
+ resolve3(here, "../../package.json"),
8601
+ resolve3(here, "../package.json")
9484
8602
  ];
9485
8603
  for (const path of candidates) {
9486
8604
  try {
9487
- const pkg2 = JSON.parse(readFileSync10(path, "utf-8"));
8605
+ const pkg2 = JSON.parse(readFileSync8(path, "utf-8"));
9488
8606
  if (pkg2.version) {
9489
8607
  return pkg2.version;
9490
8608
  }
@@ -9501,9 +8619,8 @@ function debug(msg) {
9501
8619
  `);
9502
8620
  }
9503
8621
  }
9504
- async function renderStatusline() {
8622
+ async function renderStatusline(cwd = process.cwd()) {
9505
8623
  const version = readPackageVersion();
9506
- const cwd = process.cwd();
9507
8624
  let ingestion;
9508
8625
  const resolveIngestion = () => {
9509
8626
  ingestion ??= decisionIngestionStatus(cwd);
@@ -9551,6 +8668,11 @@ function planUninstallSteps(inRepository) {
9551
8668
  label: "Codex project integration",
9552
8669
  args: ["codex", "uninstall", "--scope", "project"]
9553
8670
  },
8671
+ {
8672
+ key: "cursor-project",
8673
+ label: "Cursor project integration",
8674
+ args: ["cursor", "uninstall", "--scope", "project"]
8675
+ },
9554
8676
  {
9555
8677
  key: "hooks-project",
9556
8678
  label: "project Git hooks",
@@ -9570,6 +8692,11 @@ function planUninstallSteps(inRepository) {
9570
8692
  label: "Codex user integration",
9571
8693
  args: ["codex", "uninstall", "--scope", "user"]
9572
8694
  },
8695
+ {
8696
+ key: "cursor-user",
8697
+ label: "Cursor user integration",
8698
+ args: ["cursor", "uninstall", "--scope", "user"]
8699
+ },
9573
8700
  { key: "hermes-user", label: "Hermes integration", args: ["hermes", "uninstall"] },
9574
8701
  {
9575
8702
  key: "hooks-user",
@@ -9588,7 +8715,7 @@ function uninstallStepSucceeded(step, result) {
9588
8715
  return false;
9589
8716
  }
9590
8717
  }
9591
- if (!step.key.startsWith("claude-") && !step.key.startsWith("codex-") && step.key !== "hermes-user") {
8718
+ if (!step.key.startsWith("claude-") && !step.key.startsWith("codex-") && !step.key.startsWith("cursor-") && step.key !== "hermes-user") {
9592
8719
  return true;
9593
8720
  }
9594
8721
  try {
@@ -9596,7 +8723,7 @@ function uninstallStepSucceeded(step, result) {
9596
8723
  if (step.key.startsWith("claude-")) {
9597
8724
  return parsed.gate === false && parsed.capture === false && parsed.feedback === false && parsed.statusline === false;
9598
8725
  }
9599
- return parsed.gate === false && parsed.capture === false;
8726
+ return parsed.gate === false && parsed.capture === false && (!step.key.startsWith("cursor-user") || parsed.footer === false);
9600
8727
  } catch {
9601
8728
  return false;
9602
8729
  }
@@ -9736,7 +8863,7 @@ function registerUninstallCommand(program2, dependencies = {}) {
9736
8863
  // src/commands/welcome.ts
9737
8864
  var CMD_GUTTER = 38;
9738
8865
  var RECENT_LIMIT = 5;
9739
- var WELCOME_AGENTS = ["claude", "codex", "hermes"];
8866
+ var WELCOME_AGENTS = ["claude", "codex", "cursor", "hermes"];
9740
8867
  function resolveWelcomeAgent(agentFlag, env) {
9741
8868
  if (agentFlag !== void 0 && WELCOME_AGENTS.includes(agentFlag)) {
9742
8869
  return agentFlag;
@@ -9744,6 +8871,9 @@ function resolveWelcomeAgent(agentFlag, env) {
9744
8871
  if (agentFlag === void 0 && env.HERMES_INTERACTIVE) {
9745
8872
  return "hermes";
9746
8873
  }
8874
+ if (agentFlag === void 0 && env.CURSOR_AGENT) {
8875
+ return "cursor";
8876
+ }
9747
8877
  return void 0;
9748
8878
  }
9749
8879
  var CAPTURE_LINES = {
@@ -9752,6 +8882,7 @@ var CAPTURE_LINES = {
9752
8882
  "Capture starts once you trust the prim hooks \u2014 run /hooks in Codex;",
9753
8883
  "until then nothing is recorded."
9754
8884
  ],
8885
+ cursor: ["Capture is automatic in trusted Cursor workspaces; restart Cursor CLI for the footer."],
9755
8886
  hermes: [
9756
8887
  "Capture starts once you approve the prim hooks when Hermes prompts;",
9757
8888
  "until then nothing is recorded."
@@ -9767,6 +8898,7 @@ var PASSIVE_LINES = {
9767
8898
  "Once you trust the prim hooks (/hooks in Codex), Primitive captures",
9768
8899
  "decisions passively while you work."
9769
8900
  ],
8901
+ cursor: ["In trusted Cursor workspaces, Primitive captures decisions passively while you work."],
9770
8902
  hermes: [
9771
8903
  "Once you approve the prim hooks, Primitive captures decisions",
9772
8904
  "passively while you work."
@@ -9779,6 +8911,7 @@ var PASSIVE_LINES = {
9779
8911
  var GUIDANCE_PASSIVE = {
9780
8912
  claude: "Otherwise, Primitive passively captures decisions in the background while you work.",
9781
8913
  codex: "Otherwise, once you trust the prim hooks (run /hooks in Codex), Primitive passively captures decisions in the background while you work.",
8914
+ cursor: "Otherwise, Primitive passively captures decisions in trusted Cursor workspaces while you work.",
9782
8915
  hermes: "Otherwise, once you approve the prim hooks when Hermes prompts, Primitive passively captures decisions in the background while you work.",
9783
8916
  generic: "Otherwise, once your agent's prim hooks are active, Primitive passively captures decisions in the background while you work."
9784
8917
  };
@@ -9873,10 +9006,7 @@ function welcomeJson(state, agent) {
9873
9006
  return { welcomed: true, org: "unknown" };
9874
9007
  }
9875
9008
  function registerWelcomeCommand(program2, deps = { getClient }) {
9876
- program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").option(
9877
- "--agent <agent>",
9878
- "claude, codex, or hermes \u2014 tailors the capture guidance (Hermes auto-detected)"
9879
- ).action(async (options) => {
9009
+ program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").option("--agent <agent>", "claude, codex, cursor, or hermes \u2014 tailors the capture guidance").action(async (options) => {
9880
9010
  const agent = resolveWelcomeAgent(options.agent, process.env);
9881
9011
  const result = await fetchRecent({ limit: RECENT_LIMIT }, deps);
9882
9012
  const state = welcomeStateFromRecent(result);
@@ -9903,8 +9033,8 @@ function runStartupBackgroundWork(argv, env, work) {
9903
9033
  }
9904
9034
 
9905
9035
  // src/index.ts
9906
- var __dirname = dirname8(fileURLToPath2(import.meta.url));
9907
- var pkg = JSON.parse(readFileSync11(resolve5(__dirname, "../package.json"), "utf-8"));
9036
+ var __dirname = dirname6(fileURLToPath2(import.meta.url));
9037
+ var pkg = JSON.parse(readFileSync9(resolve4(__dirname, "../package.json"), "utf-8"));
9908
9038
  runStartupBackgroundWork(process.argv.slice(2), process.env, {
9909
9039
  notify: () => updateNotifier({ pkg }).notify(),
9910
9040
  flush: () => flushIfNeeded()
@@ -9923,6 +9053,7 @@ registerSessionCommands(program);
9923
9053
  registerDecisionsCommands(program);
9924
9054
  registerClaudeCommands(program);
9925
9055
  registerCodexCommands(program);
9056
+ registerCursorCommands(program);
9926
9057
  registerHermesCommands(program);
9927
9058
  registerDaemonCommands(program);
9928
9059
  registerGithubCommands(program);