@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.
- package/README.md +20 -12
- package/SKILL.md +6 -6
- package/dist/chunk-26P57LAR.js +1491 -0
- package/dist/{chunk-LMRYZL3D.js → chunk-5AFRMOPX.js} +1 -1
- package/dist/{chunk-RO3F6JYV.js → chunk-5GYQ4J3D.js} +2 -2
- package/dist/chunk-5HRR4AOR.js +264 -0
- package/dist/{chunk-3EEFL6NJ.js → chunk-67VNLLBN.js} +261 -1
- package/dist/{chunk-UZPBB7V4.js → chunk-ADUTW66W.js} +60 -48
- package/dist/{chunk-LEMX3IOG.js → chunk-BTNNEH2W.js} +1 -1
- package/dist/{chunk-YWO7O6QG.js → chunk-FBAYCS3X.js} +2 -2
- package/dist/{chunk-R26A7TWY.js → chunk-FKXFUUWJ.js} +1 -1
- package/dist/chunk-H4UNYFUC.js +196 -0
- package/dist/{chunk-CUE2LKLR.js → chunk-KFXF2V5G.js} +1 -1
- package/dist/{chunk-6IWNH776.js → chunk-LFFLHSQO.js} +4 -3
- package/dist/{chunk-C7T7B45R.js → chunk-LGGMJYY4.js} +150 -21
- package/dist/{chunk-MAZWKJGL.js → chunk-M6RIJVRE.js} +42 -232
- package/dist/{chunk-2JRX7ZR4.js → chunk-NAHHFSLH.js} +16 -5
- package/dist/{chunk-FI3LIRCB.js → chunk-OAAP2MY6.js} +2 -2
- package/dist/{chunk-Z7YPFWAZ.js → chunk-PCVGBE5B.js} +10 -268
- package/dist/chunk-WX52U7Z2.js +112 -0
- package/dist/{chunk-PPQYQEIR.js → chunk-ZB4F75SZ.js} +28 -0
- package/dist/daemon/server.js +30 -21
- package/dist/hook-runtime/daemon/server.js +30 -21
- package/dist/hook-runtime/hooks/post-commit.js +20 -20
- package/dist/hook-runtime/hooks/post-rewrite.js +20 -20
- package/dist/hook-runtime/hooks/post-tool-use.js +666 -147
- package/dist/hook-runtime/hooks/pre-tool-use.js +659 -111
- package/dist/hook-runtime/hooks/prim-hook.js +627 -111
- package/dist/hook-runtime/hooks/session-end.js +610 -27
- package/dist/hook-runtime/hooks/session-start.js +1132 -533
- package/dist/hook-runtime/index.js +764 -267
- package/dist/hook-runtime/statusline-main.js +48 -4
- package/dist/hooks/post-commit.js +11 -9
- package/dist/hooks/post-rewrite.js +11 -9
- package/dist/hooks/post-tool-use.js +73 -48
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +46 -44
- package/dist/hooks/prim-hook.js +47 -25
- package/dist/hooks/session-end.js +15 -26
- package/dist/hooks/session-start.js +59 -32
- package/dist/index.js +561 -1430
- package/dist/statusline-main.js +48 -4
- package/package.json +1 -1
- package/dist/chunk-PMRDE2DA.js +0 -310
- 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-
|
|
6
|
+
} from "./chunk-OAAP2MY6.js";
|
|
7
7
|
import {
|
|
8
8
|
isValidRepoSyncId
|
|
9
|
-
} from "./chunk-
|
|
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-
|
|
3
|
+
} from "./chunk-BTNNEH2W.js";
|
|
4
4
|
import {
|
|
5
5
|
terminalSafeLine
|
|
6
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
324
|
-
import { dirname
|
|
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 =
|
|
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 =
|
|
349
|
-
|
|
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 (!
|
|
358
|
-
|
|
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,
|