@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
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-EZXEWFDG.js";
4
4
  import {
5
5
  scrubEnvironmentPaths
6
- } from "./chunk-FI3LIRCB.js";
6
+ } from "./chunk-OAAP2MY6.js";
7
7
  import {
8
8
  isValidRepoSyncId
9
- } from "./chunk-MAZWKJGL.js";
9
+ } from "./chunk-M6RIJVRE.js";
10
10
 
11
11
  // src/hooks/prim-hook-core.ts
12
12
  import { createHash, randomUUID } from "crypto";
@@ -45,6 +45,17 @@ function toolOutcomeFor(parsed, agent) {
45
45
  if (agent === "codex") {
46
46
  return event === "PostToolUse" ? "returned" : void 0;
47
47
  }
48
+ if (agent === "cursor") {
49
+ if (event === "PostToolUse") return "succeeded";
50
+ if (event === "PostToolUseFailure") {
51
+ if (parsed.is_interrupt === true) return "interrupted";
52
+ const failureType = lowerString(parsed.failure_type);
53
+ if (failureType === "permission_denied") return "prevented";
54
+ if (failureType === "error" || failureType === "timeout") return "failed";
55
+ return "unknown";
56
+ }
57
+ return void 0;
58
+ }
48
59
  const extra = parsed.extra;
49
60
  if (event === "post_approval_response") {
50
61
  const choice = lowerString(extra?.choice);
@@ -75,7 +86,7 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
75
86
  let moveId;
76
87
  if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId) {
77
88
  moveId = `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}`;
78
- } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId) {
89
+ } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && agent !== "cursor") {
79
90
  moveId = `posttool:fallback:v1:${createHash("sha256").update(
80
91
  JSON.stringify([
81
92
  agent,
@@ -231,8 +242,8 @@ function toRewriteMove(rewrite, cliVersion, cwd, context) {
231
242
  }
232
243
  return moves;
233
244
  }
234
- function shouldFlushAfter(eventType) {
235
- return eventType === "SessionEnd";
245
+ function shouldFlushAfter(eventType, agent = "claude_code") {
246
+ return eventType === "SessionEnd" || agent === "cursor" && eventType === "Stop";
236
247
  }
237
248
 
238
249
  export {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  boundedHealthError
3
- } from "./chunk-LEMX3IOG.js";
3
+ } from "./chunk-BTNNEH2W.js";
4
4
  import {
5
5
  terminalSafeLine
6
- } from "./chunk-Z7YPFWAZ.js";
6
+ } from "./chunk-PCVGBE5B.js";
7
7
 
8
8
  // src/hooks/redact.ts
9
9
  import { existsSync, readFileSync, statSync } from "fs";
@@ -10,268 +10,16 @@ import {
10
10
  TOKEN_EXPIRES_PATH,
11
11
  TOKEN_FILE_PATH,
12
12
  WORKOS_CONNECT_RESPONSE_MAX_BYTES,
13
+ atomicWriteFile,
13
14
  jwtExpiresAt,
14
15
  parseWorkosConnectTokens,
15
16
  readBoundedJson,
16
17
  readCredentialMigrationState,
17
18
  readStoredCredentialMetadata,
18
19
  resolveAuthCredential,
20
+ withFileLock,
19
21
  workosConnectTokenEndpoint
20
- } from "./chunk-3EEFL6NJ.js";
21
-
22
- // src/lib/atomic-file.ts
23
- import { randomUUID } from "crypto";
24
- import {
25
- constants,
26
- closeSync,
27
- fchmodSync,
28
- fsyncSync,
29
- mkdirSync,
30
- openSync,
31
- renameSync,
32
- rmSync,
33
- writeFileSync
34
- } from "fs";
35
- import { dirname } from "path";
36
- var DEFAULT_FILE_MODE = 438;
37
- function syncDirectory(path) {
38
- const fd = openSync(path, constants.O_RDONLY);
39
- try {
40
- fsyncSync(fd);
41
- } finally {
42
- closeSync(fd);
43
- }
44
- }
45
- function syncFile(path) {
46
- const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
47
- try {
48
- fsyncSync(fd);
49
- } finally {
50
- closeSync(fd);
51
- }
52
- }
53
- function syncCreatedParentChain(firstCreatedParent, parent) {
54
- const createdDirectories = [];
55
- let current = parent;
56
- while (current !== firstCreatedParent) {
57
- createdDirectories.push(current);
58
- const next = dirname(current);
59
- if (next === current) {
60
- throw new Error("recursive mkdir returned a path outside the target parent chain");
61
- }
62
- current = next;
63
- }
64
- createdDirectories.push(firstCreatedParent);
65
- syncDirectory(dirname(firstCreatedParent));
66
- for (const directory of createdDirectories.reverse().slice(0, -1)) {
67
- syncDirectory(directory);
68
- }
69
- }
70
- function ensureDurableDirectory(path, mode) {
71
- const firstCreatedParent = mkdirSync(path, { recursive: true, mode });
72
- if (firstCreatedParent) {
73
- syncCreatedParentChain(firstCreatedParent, path);
74
- }
75
- }
76
- function atomicWriteFile(target, content, options = {}) {
77
- const parent = dirname(target);
78
- if (options.ensureParent) {
79
- ensureDurableDirectory(parent);
80
- }
81
- const temporaryPath = `${target}.${randomUUID()}.tmp`;
82
- let temporaryCreated = false;
83
- try {
84
- const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
85
- const fd = openSync(temporaryPath, flags, options.mode ?? DEFAULT_FILE_MODE);
86
- temporaryCreated = true;
87
- try {
88
- if (options.mode !== void 0) fchmodSync(fd, options.mode);
89
- writeFileSync(fd, content);
90
- fsyncSync(fd);
91
- } finally {
92
- closeSync(fd);
93
- }
94
- options.validate?.(temporaryPath);
95
- renameSync(temporaryPath, target);
96
- syncDirectory(parent);
97
- } catch (error) {
98
- if (temporaryCreated) {
99
- try {
100
- rmSync(temporaryPath, { force: true });
101
- } catch {
102
- }
103
- }
104
- throw error;
105
- }
106
- }
107
-
108
- // src/lib/process-liveness.ts
109
- function processIsAlive(pid, probe = process.kill) {
110
- if (!Number.isSafeInteger(pid) || pid <= 0) return false;
111
- try {
112
- probe(pid, 0);
113
- return true;
114
- } catch (error) {
115
- return error.code === "EPERM";
116
- }
117
- }
118
-
119
- // src/lib/file-lock.ts
120
- import { randomBytes } from "crypto";
121
- import { existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
122
- import { dirname as dirname2, join } from "path";
123
- var DEFAULT_TIMEOUT_MS = 3e4;
124
- var DEFAULT_POLL_MS = 50;
125
- var DEFAULT_INIT_GRACE_MS = 2e3;
126
- var DIRECTORY_MODE = 448;
127
- var FILE_MODE = 384;
128
- function ownerPath(lockDir) {
129
- return join(lockDir, "owner.json");
130
- }
131
- function readOwner(lockDir) {
132
- try {
133
- const owner = JSON.parse(readFileSync(ownerPath(lockDir), "utf8"));
134
- if (!Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || typeof owner.createdAt !== "number") {
135
- return null;
136
- }
137
- return owner;
138
- } catch {
139
- return null;
140
- }
141
- }
142
- function sameOwner(a, b) {
143
- return a?.pid === b.pid && a.nonce === b.nonce && a.createdAt === b.createdAt;
144
- }
145
- function tryTakeLock(lockDir, now) {
146
- try {
147
- mkdirSync2(lockDir, { mode: DIRECTORY_MODE });
148
- } catch (error) {
149
- if (error.code === "EEXIST") return null;
150
- throw error;
151
- }
152
- const owner = {
153
- pid: process.pid,
154
- nonce: randomBytes(16).toString("hex"),
155
- createdAt: now
156
- };
157
- try {
158
- writeFileSync2(ownerPath(lockDir), `${JSON.stringify(owner)}
159
- `, {
160
- encoding: "utf8",
161
- mode: FILE_MODE,
162
- flag: "wx"
163
- });
164
- return owner;
165
- } catch (error) {
166
- rmSync2(lockDir, { recursive: true, force: true });
167
- throw error;
168
- }
169
- }
170
- function recoverStaleLock(lockDir, now, alive, initGraceMs) {
171
- const owner = readOwner(lockDir);
172
- if (owner) {
173
- if (alive(owner.pid)) return false;
174
- if (!sameOwner(readOwner(lockDir), owner)) return false;
175
- rmSync2(lockDir, { recursive: true, force: true });
176
- return true;
177
- }
178
- try {
179
- const before = statSync(lockDir).mtimeMs;
180
- if (now - before < initGraceMs) return false;
181
- if (readOwner(lockDir)) return false;
182
- if (statSync(lockDir).mtimeMs !== before) return false;
183
- rmSync2(lockDir, { recursive: true, force: true });
184
- return true;
185
- } catch {
186
- return true;
187
- }
188
- }
189
- function abortError(signal) {
190
- if (signal.reason instanceof Error) return signal.reason;
191
- const error = new Error("The operation was aborted");
192
- error.name = "AbortError";
193
- return error;
194
- }
195
- function throwIfAborted(signal) {
196
- if (signal?.aborted) throw abortError(signal);
197
- }
198
- async function defaultSleep(ms, signal) {
199
- if (!signal) {
200
- await new Promise((resolve2) => setTimeout(resolve2, ms));
201
- return;
202
- }
203
- throwIfAborted(signal);
204
- await new Promise((resolve2, reject) => {
205
- const timer = setTimeout(() => {
206
- signal.removeEventListener("abort", onAbort);
207
- resolve2();
208
- }, ms);
209
- const onAbort = () => {
210
- clearTimeout(timer);
211
- reject(abortError(signal));
212
- };
213
- signal.addEventListener("abort", onAbort, { once: true });
214
- });
215
- }
216
- var syncSleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
217
- function defaultSleepSync(ms) {
218
- Atomics.wait(syncSleeper, 0, 0, ms);
219
- }
220
- function withFileLockSync(lockDir, operation, options = {}) {
221
- mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
222
- const nowMs = options.nowMs ?? Date.now;
223
- const alive = options.processAlive ?? processIsAlive;
224
- const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
225
- let owner = null;
226
- while (!owner) {
227
- const now = nowMs();
228
- owner = tryTakeLock(lockDir, now);
229
- if (owner) break;
230
- recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
231
- if (now >= deadline) {
232
- throw new Error(`timed out waiting for file lock ${lockDir}`);
233
- }
234
- (options.sleep ?? defaultSleepSync)(options.pollMs ?? DEFAULT_POLL_MS);
235
- }
236
- try {
237
- return operation();
238
- } finally {
239
- if (sameOwner(readOwner(lockDir), owner)) {
240
- rmSync2(lockDir, { recursive: true, force: true });
241
- }
242
- }
243
- }
244
- async function withFileLock(lockDir, operation, options = {}) {
245
- mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
246
- const nowMs = options.nowMs ?? Date.now;
247
- const alive = options.processAlive ?? processIsAlive;
248
- const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
249
- let owner = null;
250
- while (!owner) {
251
- throwIfAborted(options.signal);
252
- const now = nowMs();
253
- owner = tryTakeLock(lockDir, now);
254
- if (owner) break;
255
- recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
256
- if (now >= deadline) {
257
- throw new Error(`timed out waiting for file lock ${lockDir}`);
258
- }
259
- if (options.sleep) {
260
- await options.sleep(options.pollMs ?? DEFAULT_POLL_MS);
261
- throwIfAborted(options.signal);
262
- } else {
263
- await defaultSleep(options.pollMs ?? DEFAULT_POLL_MS, options.signal);
264
- }
265
- }
266
- try {
267
- throwIfAborted(options.signal);
268
- return await operation();
269
- } finally {
270
- if (sameOwner(readOwner(lockDir), owner)) {
271
- rmSync2(lockDir, { recursive: true, force: true });
272
- }
273
- }
274
- }
22
+ } from "./chunk-67VNLLBN.js";
275
23
 
276
24
  // src/lib/terminal-safe.ts
277
25
  function replaceIsolatedSurrogates(value) {
@@ -320,8 +68,8 @@ var stripControlChars = terminalSafeText;
320
68
 
321
69
  // src/client.ts
322
70
  import { createHash } from "crypto";
323
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, rmSync as rmSync3 } from "fs";
324
- import { dirname as dirname3 } from "path";
71
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
72
+ import { dirname } from "path";
325
73
  var CONFIG_DIR_MODE = 448;
326
74
  var CREDENTIAL_FILE_MODE = 384;
327
75
  var REFRESH_THRESHOLD_MS = 6e4;
@@ -329,7 +77,7 @@ var DEFAULT_API_URL = "https://api.getprimitive.ai";
329
77
  var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
330
78
  function readTrimmed(path) {
331
79
  try {
332
- const value = readFileSync2(path, "utf8").trim();
80
+ const value = readFileSync(path, "utf8").trim();
333
81
  return value || void 0;
334
82
  } catch {
335
83
  return void 0;
@@ -345,8 +93,8 @@ function expiresAtFor(token, expiresIn) {
345
93
  return expiresIn === void 0 ? jwtExpiresAt(token) : Date.now() + expiresIn * 1e3;
346
94
  }
347
95
  function ensureConfigDirectory() {
348
- const directory = dirname3(TOKEN_FILE_PATH);
349
- mkdirSync3(directory, { recursive: true, mode: CONFIG_DIR_MODE });
96
+ const directory = dirname(TOKEN_FILE_PATH);
97
+ mkdirSync(directory, { recursive: true, mode: CONFIG_DIR_MODE });
350
98
  chmodSync(directory, CONFIG_DIR_MODE);
351
99
  }
352
100
  function atomicWrite(path, content) {
@@ -354,8 +102,8 @@ function atomicWrite(path, content) {
354
102
  atomicWriteFile(path, content, { mode: CREDENTIAL_FILE_MODE });
355
103
  }
356
104
  function removeCredentialFile(path) {
357
- if (!existsSync2(path)) return false;
358
- rmSync3(path, { force: true });
105
+ if (!existsSync(path)) return false;
106
+ rmSync(path, { force: true });
359
107
  return true;
360
108
  }
361
109
  function getTokenExpiresAt() {
@@ -892,12 +640,6 @@ async function getPinnedManagementClient(options = {}) {
892
640
  }
893
641
 
894
642
  export {
895
- syncDirectory,
896
- syncFile,
897
- atomicWriteFile,
898
- processIsAlive,
899
- withFileLockSync,
900
- withFileLock,
901
643
  terminalSafeText,
902
644
  terminalSafeLine,
903
645
  isTerminalSafeText,
@@ -0,0 +1,112 @@
1
+ import {
2
+ stripControlChars
3
+ } from "./chunk-PCVGBE5B.js";
4
+
5
+ // src/lib/presence.ts
6
+ var DECISION_ORIGIN = "https://app.getprimitive.ai";
7
+ function validDecisionUrl(value) {
8
+ if (typeof value !== "string" || stripControlChars(value) !== value) {
9
+ return void 0;
10
+ }
11
+ try {
12
+ const url = new URL(value);
13
+ if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
14
+ return void 0;
15
+ }
16
+ return url.href;
17
+ } catch {
18
+ return void 0;
19
+ }
20
+ }
21
+ function decisionLink(label, decisionUrl, plain) {
22
+ const url = validDecisionUrl(decisionUrl);
23
+ if (plain || !url) {
24
+ return label;
25
+ }
26
+ return `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07`;
27
+ }
28
+ function formatLabeled(labels, cap) {
29
+ if (labels === void 0) {
30
+ return "\u2014";
31
+ }
32
+ if (labels.length === 0) {
33
+ return "just you";
34
+ }
35
+ if (labels.length <= cap) {
36
+ return labels.join(", ");
37
+ }
38
+ return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
39
+ }
40
+ function formatTeammates(names, cap) {
41
+ return formatLabeled(names?.map(stripControlChars), cap);
42
+ }
43
+ function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
44
+ return formatLabeled(
45
+ teammates?.map((t) => {
46
+ const name = stripControlChars(t.name);
47
+ const area = stripControlChars(t.area ?? "").trim();
48
+ const label = area ? `${name} - ${area}` : name;
49
+ return decisionLink(label, t.decisionUrl, plainLinks);
50
+ }),
51
+ cap
52
+ );
53
+ }
54
+
55
+ // src/lib/statusline-render.ts
56
+ function repositoryBindingDiagnosticLabel(state) {
57
+ if (state === "unbound") return "GitHub repo connection: required (run `prim github connect`)";
58
+ if (state === "invalid") return "GitHub repo connection: invalid (run `prim doctor`)";
59
+ return void 0;
60
+ }
61
+ var STATUSLINE_NAME_CAP = 3;
62
+ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {}) {
63
+ const ingestionSuffix = (status) => status === void 0 ? "" : ` \xB7 Decision ingestion ${status}`;
64
+ const ingestionStatus = snapshot === null || snapshot.healthy === false ? options.includeIngestionWhenUnavailable ? resolveIngestionStatus() : void 0 : resolveIngestionStatus();
65
+ if (snapshot?.healthy === false && snapshot.needsReauth) {
66
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
67
+ }
68
+ const repositoryLabel = repositoryBindingDiagnosticLabel(
69
+ snapshot !== null && snapshot.healthy !== false || options.includeIngestionWhenUnavailable === true ? options.resolveRepositoryBindingState?.() : void 0
70
+ );
71
+ const repositorySuffix = repositoryLabel ? ` \xB7 ${repositoryLabel}` : "";
72
+ if (!snapshot) {
73
+ return `primitive ${version} (daemon: down${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
74
+ }
75
+ if (snapshot.healthy === false) {
76
+ if (snapshot.ingestion?.healthy === false) {
77
+ const pending = snapshot.ingestion.pendingCount;
78
+ const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
79
+ return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
80
+ }
81
+ if (snapshot.heartbeat?.healthy === false) {
82
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
83
+ }
84
+ return `primitive ${version} (daemon: starting${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
85
+ }
86
+ if (snapshot.envMismatch) {
87
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: other env)`;
88
+ }
89
+ if (snapshot.principalMismatch) {
90
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: other account)`;
91
+ }
92
+ if (snapshot.presenceStale) {
93
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: stale)`;
94
+ }
95
+ let team;
96
+ if (snapshot.onlineTeammates !== void 0) {
97
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
98
+ } else if (snapshot.onlineNames !== void 0) {
99
+ team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
100
+ } else if (typeof snapshot.onlineCount === "number") {
101
+ team = `team: ${String(snapshot.onlineCount)} online`;
102
+ } else {
103
+ team = "team: \u2014";
104
+ }
105
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 ${team})`;
106
+ }
107
+
108
+ export {
109
+ formatTeammates,
110
+ repositoryBindingDiagnosticLabel,
111
+ formatStatusline
112
+ };
@@ -72,6 +72,14 @@ function buildCodexOutput(aggregate, results) {
72
72
  hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: notes }
73
73
  } : {};
74
74
  }
75
+ function buildCursorOutput(aggregate, results) {
76
+ if (aggregate === "deny" || aggregate === "ask") {
77
+ const reason = blockingReason(results);
78
+ return { permission: "deny", user_message: reason, agent_message: reason };
79
+ }
80
+ const notes = advisoryNote(aggregate, results);
81
+ return notes ? { permission: "allow", agent_message: notes } : { permission: "allow" };
82
+ }
75
83
  function failOpenOutput() {
76
84
  return {
77
85
  hookSpecificOutput: {
@@ -83,6 +91,9 @@ function failOpenOutput() {
83
91
  function failOpenCodex() {
84
92
  return {};
85
93
  }
94
+ function failOpenCursor() {
95
+ return { permission: "allow" };
96
+ }
86
97
  function buildHermesOutput(aggregate, results) {
87
98
  if (aggregate !== "deny" && aggregate !== "ask") {
88
99
  return {};
@@ -165,10 +176,25 @@ function extractHermesFileTargets(toolName, toolInput) {
165
176
  const mode = input.mode;
166
177
  return { paths, complete: paths.length > 0 && (mode === void 0 || mode === "replace") };
167
178
  }
179
+ function extractCursorFileTargets(toolName, toolInput) {
180
+ if (toolName !== "Write" && toolName !== "Delete") return null;
181
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
182
+ return { paths: [], complete: false };
183
+ }
184
+ const input = toolInput;
185
+ const candidates = [input.file_path, input.path].filter(
186
+ (value) => typeof value === "string" && value.length > 0
187
+ );
188
+ const paths = [...new Set(candidates)];
189
+ return { paths, complete: paths.length === 1 };
190
+ }
168
191
  function extractFileTargets(toolName, toolInput, agent = "claude_code") {
169
192
  if (agent === "codex") {
170
193
  return extractCodexFileTargets(toolName, toolInput);
171
194
  }
195
+ if (agent === "cursor") {
196
+ return extractCursorFileTargets(toolName, toolInput);
197
+ }
172
198
  if (agent === "hermes") {
173
199
  return extractHermesFileTargets(toolName, toolInput);
174
200
  }
@@ -804,8 +830,10 @@ function analyzeShellTargets(source) {
804
830
  export {
805
831
  buildHookOutput,
806
832
  buildCodexOutput,
833
+ buildCursorOutput,
807
834
  failOpenOutput,
808
835
  failOpenCodex,
836
+ failOpenCursor,
809
837
  buildHermesOutput,
810
838
  failOpenHermes,
811
839
  extractFileTargets,