@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
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getClient,
|
|
3
3
|
getSiteUrl,
|
|
4
4
|
terminalSafeLine
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PCVGBE5B.js";
|
|
6
6
|
import {
|
|
7
7
|
daemonRequest
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KFXF2V5G.js";
|
|
9
9
|
|
|
10
10
|
// src/daemon/proxy.ts
|
|
11
11
|
var DAEMON_HTTP_TIMEOUT_MS = 1e4;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cursorHooksPath,
|
|
3
|
+
hasExactCursorHandler,
|
|
4
|
+
readCursorHooks
|
|
5
|
+
} from "./chunk-26P57LAR.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveRepositoryContext
|
|
8
|
+
} from "./chunk-H4UNYFUC.js";
|
|
9
|
+
|
|
10
|
+
// src/hooks/agent.ts
|
|
11
|
+
function parseAgent(argv) {
|
|
12
|
+
const i = argv.indexOf("--agent");
|
|
13
|
+
const value = i !== -1 ? argv[i + 1] : void 0;
|
|
14
|
+
if (value === "codex" || value === "cursor" || value === "hermes") {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return "claude_code";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/hooks/cursor-coexistence.ts
|
|
21
|
+
var CURSOR_EVENTS = /* @__PURE__ */ new Set([
|
|
22
|
+
"sessionStart",
|
|
23
|
+
"beforeSubmitPrompt",
|
|
24
|
+
"preToolUse",
|
|
25
|
+
"postToolUse",
|
|
26
|
+
"postToolUseFailure",
|
|
27
|
+
"afterAgentResponse",
|
|
28
|
+
"subagentStop",
|
|
29
|
+
"stop",
|
|
30
|
+
"sessionEnd"
|
|
31
|
+
]);
|
|
32
|
+
function cursorEvidence(parsed) {
|
|
33
|
+
return typeof parsed.cursor_version === "string" && parsed.cursor_version.length > 0 && typeof parsed.conversation_id === "string" && parsed.conversation_id.length > 0 && typeof parsed.generation_id === "string" && parsed.generation_id.length > 0 && Array.isArray(parsed.workspace_roots) && typeof parsed.hook_event_name === "string" && CURSOR_EVENTS.has(parsed.hook_event_name);
|
|
34
|
+
}
|
|
35
|
+
function uniqueProjectRoot(parsed) {
|
|
36
|
+
if (!Array.isArray(parsed.workspace_roots)) return;
|
|
37
|
+
const roots = new Set(
|
|
38
|
+
parsed.workspace_roots.filter((value) => typeof value === "string" && value.length > 0).map((value) => resolveRepositoryContext(value)?.repoRoot).filter((value) => !!value)
|
|
39
|
+
);
|
|
40
|
+
return roots.size === 1 ? [...roots][0] : void 0;
|
|
41
|
+
}
|
|
42
|
+
function shouldSuppressImportedCursorHandler(parsed, bin) {
|
|
43
|
+
if (!cursorEvidence(parsed)) return false;
|
|
44
|
+
const project = uniqueProjectRoot(parsed);
|
|
45
|
+
if (project) {
|
|
46
|
+
try {
|
|
47
|
+
if (hasExactCursorHandler(
|
|
48
|
+
readCursorHooks(cursorHooksPath("project", project)),
|
|
49
|
+
parsed.hook_event_name,
|
|
50
|
+
bin,
|
|
51
|
+
"project"
|
|
52
|
+
)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return hasExactCursorHandler(
|
|
60
|
+
readCursorHooks(cursorHooksPath("user")),
|
|
61
|
+
parsed.hook_event_name,
|
|
62
|
+
bin,
|
|
63
|
+
"user"
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hooks/hook-stdin.ts
|
|
71
|
+
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
72
|
+
function readHookStdin(timeoutMs = 1e3, maxBytes = DEFAULT_MAX_BYTES) {
|
|
73
|
+
return new Promise((resolve2, reject) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
let settled = false;
|
|
77
|
+
const finish = (error) => {
|
|
78
|
+
if (settled) return;
|
|
79
|
+
settled = true;
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
process.stdin.removeAllListeners("data");
|
|
82
|
+
process.stdin.removeAllListeners("end");
|
|
83
|
+
process.stdin.removeAllListeners("error");
|
|
84
|
+
if (error) reject(error);
|
|
85
|
+
else resolve2(Buffer.concat(chunks, bytes).toString("utf-8"));
|
|
86
|
+
};
|
|
87
|
+
const timer = setTimeout(() => finish(new Error("stdin read timeout")), timeoutMs);
|
|
88
|
+
process.stdin.on("data", (chunk) => {
|
|
89
|
+
bytes += chunk.length;
|
|
90
|
+
if (bytes > maxBytes) {
|
|
91
|
+
finish(new Error("stdin envelope exceeds size limit"));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
chunks.push(chunk);
|
|
95
|
+
});
|
|
96
|
+
process.stdin.on("end", () => finish());
|
|
97
|
+
process.stdin.on("error", (error) => finish(error));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/hooks/normalize.ts
|
|
102
|
+
import { createHash } from "crypto";
|
|
103
|
+
import { realpathSync } from "fs";
|
|
104
|
+
import { isAbsolute, resolve } from "path";
|
|
105
|
+
var HERMES_EVENT_MAP = {
|
|
106
|
+
on_session_start: "SessionStart",
|
|
107
|
+
on_session_end: "SessionEnd",
|
|
108
|
+
pre_llm_call: "UserPromptSubmit",
|
|
109
|
+
post_llm_call: "Stop",
|
|
110
|
+
pre_tool_call: "PreToolUse",
|
|
111
|
+
post_tool_call: "PostToolUse",
|
|
112
|
+
subagent_stop: "SubagentStop"
|
|
113
|
+
};
|
|
114
|
+
var CURSOR_EVENT_MAP = {
|
|
115
|
+
sessionStart: "SessionStart",
|
|
116
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
117
|
+
preToolUse: "PreToolUse",
|
|
118
|
+
postToolUse: "PostToolUse",
|
|
119
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
120
|
+
afterAgentResponse: "AssistantResponse",
|
|
121
|
+
subagentStop: "SubagentStop",
|
|
122
|
+
stop: "Stop",
|
|
123
|
+
sessionEnd: "SessionEnd"
|
|
124
|
+
};
|
|
125
|
+
var CURSOR_TOOL_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse", "PostToolUseFailure"]);
|
|
126
|
+
var SAFE_CORRELATION_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
127
|
+
var CURSOR_LOCAL_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
128
|
+
"conversation_id",
|
|
129
|
+
"generation_id",
|
|
130
|
+
"user_email",
|
|
131
|
+
"account_email",
|
|
132
|
+
"transcript_path",
|
|
133
|
+
"transcript",
|
|
134
|
+
"thought",
|
|
135
|
+
"thoughts",
|
|
136
|
+
"thinking",
|
|
137
|
+
"tool_output"
|
|
138
|
+
]);
|
|
139
|
+
function requiredId(value, field) {
|
|
140
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
141
|
+
throw new TypeError(`Cursor hook envelope is missing ${field}`);
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
function cursorToolUseId(value) {
|
|
146
|
+
if (SAFE_CORRELATION_ID_RE.test(value)) return value;
|
|
147
|
+
return `cursor:tool:v1:${createHash("sha256").update(value).digest("hex")}`;
|
|
148
|
+
}
|
|
149
|
+
function workspaceCandidates(value) {
|
|
150
|
+
if (!Array.isArray(value)) return [];
|
|
151
|
+
const roots = value.filter((item) => typeof item === "string" && item.length > 0);
|
|
152
|
+
if (roots.some((root) => !isAbsolute(root))) {
|
|
153
|
+
throw new TypeError("Cursor workspace roots must be absolute");
|
|
154
|
+
}
|
|
155
|
+
return roots;
|
|
156
|
+
}
|
|
157
|
+
function pathIdentity(path) {
|
|
158
|
+
try {
|
|
159
|
+
return realpathSync.native(path);
|
|
160
|
+
} catch {
|
|
161
|
+
return resolve(path);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function resolveCursorCwd(parsed) {
|
|
165
|
+
const roots = workspaceCandidates(parsed.workspace_roots).map(pathIdentity);
|
|
166
|
+
const rootRepositories = roots.map((root) => resolveRepositoryContext(root)?.repoRoot);
|
|
167
|
+
const repositoryRoots = new Set(rootRepositories.filter((root) => !!root));
|
|
168
|
+
if (repositoryRoots.size > 1) {
|
|
169
|
+
throw new TypeError("Cursor hook envelope spans multiple repositories");
|
|
170
|
+
}
|
|
171
|
+
if (roots.length > 1 && rootRepositories.some((root) => root === void 0)) {
|
|
172
|
+
throw new TypeError("Cursor hook envelope has ambiguous workspace roots");
|
|
173
|
+
}
|
|
174
|
+
const expectedRepo = [...repositoryRoots][0];
|
|
175
|
+
const rawCwd = typeof parsed.cwd === "string" && parsed.cwd.length > 0 ? parsed.cwd : void 0;
|
|
176
|
+
if (rawCwd && !isAbsolute(rawCwd)) {
|
|
177
|
+
throw new TypeError("Cursor hook cwd must be absolute");
|
|
178
|
+
}
|
|
179
|
+
const envelopeCwd = rawCwd ? pathIdentity(rawCwd) : void 0;
|
|
180
|
+
const input = typeof parsed.tool_input === "object" && parsed.tool_input !== null && !Array.isArray(parsed.tool_input) ? parsed.tool_input : void 0;
|
|
181
|
+
const rawToolCwd = parsed.tool_name === "Shell" && typeof input?.working_directory === "string" ? input.working_directory : void 0;
|
|
182
|
+
const base = envelopeCwd ?? (roots.length === 1 ? roots[0] : expectedRepo);
|
|
183
|
+
if (rawToolCwd && !isAbsolute(rawToolCwd) && !base) {
|
|
184
|
+
throw new TypeError("relative Cursor tool cwd has no workspace base");
|
|
185
|
+
}
|
|
186
|
+
const toolCwd = rawToolCwd ? pathIdentity(isAbsolute(rawToolCwd) ? rawToolCwd : resolve(base, rawToolCwd)) : void 0;
|
|
187
|
+
const selected = toolCwd ?? envelopeCwd ?? (roots.length === 1 ? roots[0] : expectedRepo);
|
|
188
|
+
if (!selected) {
|
|
189
|
+
throw new TypeError("Cursor hook envelope has no unambiguous workspace");
|
|
190
|
+
}
|
|
191
|
+
const selectedRepo = resolveRepositoryContext(selected)?.repoRoot;
|
|
192
|
+
if (expectedRepo && selectedRepo !== expectedRepo) {
|
|
193
|
+
throw new TypeError("Cursor hook cwd conflicts with workspace repository");
|
|
194
|
+
}
|
|
195
|
+
return selected;
|
|
196
|
+
}
|
|
197
|
+
function normalizeCursorEnvelope(parsed) {
|
|
198
|
+
const rawEvent = requiredId(parsed.hook_event_name, "hook_event_name");
|
|
199
|
+
const event = CURSOR_EVENT_MAP[rawEvent];
|
|
200
|
+
if (!event) throw new TypeError(`unsupported Cursor hook event: ${rawEvent}`);
|
|
201
|
+
const sessionId = requiredId(parsed.conversation_id, "conversation_id");
|
|
202
|
+
const turnId = requiredId(parsed.generation_id, "generation_id");
|
|
203
|
+
if (parsed.session_id !== void 0 && parsed.session_id !== sessionId) {
|
|
204
|
+
throw new TypeError("Cursor conversation_id conflicts with session_id");
|
|
205
|
+
}
|
|
206
|
+
if (parsed.turn_id !== void 0 && parsed.turn_id !== turnId) {
|
|
207
|
+
throw new TypeError("Cursor generation_id conflicts with turn_id");
|
|
208
|
+
}
|
|
209
|
+
const rawToolUseId = CURSOR_TOOL_EVENTS.has(event) ? requiredId(parsed.tool_use_id, "tool_use_id") : void 0;
|
|
210
|
+
const normalized = {
|
|
211
|
+
...Object.fromEntries(
|
|
212
|
+
Object.entries(parsed).filter(([key]) => !CURSOR_LOCAL_ONLY_FIELDS.has(key))
|
|
213
|
+
),
|
|
214
|
+
hook_event_name: event,
|
|
215
|
+
session_id: sessionId,
|
|
216
|
+
turn_id: turnId,
|
|
217
|
+
cwd: resolveCursorCwd(parsed),
|
|
218
|
+
...rawToolUseId === void 0 ? {} : { tool_use_id: cursorToolUseId(rawToolUseId) }
|
|
219
|
+
};
|
|
220
|
+
if (typeof parsed.tool_output === "string") {
|
|
221
|
+
try {
|
|
222
|
+
normalized.tool_response = JSON.parse(parsed.tool_output);
|
|
223
|
+
} catch {
|
|
224
|
+
normalized.tool_response = parsed.tool_output;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
229
|
+
function normalizeEnvelope(parsed, agent) {
|
|
230
|
+
if (agent === "cursor") {
|
|
231
|
+
return normalizeCursorEnvelope(parsed);
|
|
232
|
+
}
|
|
233
|
+
if (agent === "codex") {
|
|
234
|
+
const event = parsed.hook_event_name;
|
|
235
|
+
if ((event === "PreToolUse" || event === "PostToolUse") && (typeof parsed.tool_use_id !== "string" || parsed.tool_use_id.length === 0)) {
|
|
236
|
+
const stableToolIdentity = JSON.stringify([
|
|
237
|
+
parsed.session_id ?? null,
|
|
238
|
+
parsed.turn_id ?? null,
|
|
239
|
+
parsed.tool_name ?? null,
|
|
240
|
+
parsed.tool_input ?? null
|
|
241
|
+
]);
|
|
242
|
+
return {
|
|
243
|
+
...parsed,
|
|
244
|
+
tool_use_id: `codex:fallback:v1:${createHash("sha256").update(stableToolIdentity).digest("hex")}`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return parsed;
|
|
248
|
+
}
|
|
249
|
+
if (agent !== "hermes") {
|
|
250
|
+
return parsed;
|
|
251
|
+
}
|
|
252
|
+
const raw = parsed.hook_event_name;
|
|
253
|
+
if (typeof raw === "string" && raw in HERMES_EVENT_MAP) {
|
|
254
|
+
return { ...parsed, hook_event_name: HERMES_EVENT_MAP[raw] };
|
|
255
|
+
}
|
|
256
|
+
return parsed;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
parseAgent,
|
|
261
|
+
shouldSuppressImportedCursorHandler,
|
|
262
|
+
readHookStdin,
|
|
263
|
+
normalizeEnvelope
|
|
264
|
+
};
|
|
@@ -498,6 +498,260 @@ function jwtOrganizationId(token) {
|
|
|
498
498
|
return typeof orgId === "string" && orgId.length > 0 ? orgId : void 0;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
// src/lib/atomic-file.ts
|
|
502
|
+
import { randomUUID } from "crypto";
|
|
503
|
+
import {
|
|
504
|
+
constants,
|
|
505
|
+
closeSync,
|
|
506
|
+
fchmodSync,
|
|
507
|
+
fsyncSync,
|
|
508
|
+
mkdirSync,
|
|
509
|
+
openSync,
|
|
510
|
+
renameSync,
|
|
511
|
+
rmSync,
|
|
512
|
+
writeFileSync
|
|
513
|
+
} from "fs";
|
|
514
|
+
import { dirname } from "path";
|
|
515
|
+
var DEFAULT_FILE_MODE = 438;
|
|
516
|
+
function syncDirectory(path) {
|
|
517
|
+
const fd = openSync(path, constants.O_RDONLY);
|
|
518
|
+
try {
|
|
519
|
+
fsyncSync(fd);
|
|
520
|
+
} finally {
|
|
521
|
+
closeSync(fd);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function syncFile(path) {
|
|
525
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
526
|
+
try {
|
|
527
|
+
fsyncSync(fd);
|
|
528
|
+
} finally {
|
|
529
|
+
closeSync(fd);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function syncCreatedParentChain(firstCreatedParent, parent) {
|
|
533
|
+
const createdDirectories = [];
|
|
534
|
+
let current = parent;
|
|
535
|
+
while (current !== firstCreatedParent) {
|
|
536
|
+
createdDirectories.push(current);
|
|
537
|
+
const next = dirname(current);
|
|
538
|
+
if (next === current) {
|
|
539
|
+
throw new Error("recursive mkdir returned a path outside the target parent chain");
|
|
540
|
+
}
|
|
541
|
+
current = next;
|
|
542
|
+
}
|
|
543
|
+
createdDirectories.push(firstCreatedParent);
|
|
544
|
+
syncDirectory(dirname(firstCreatedParent));
|
|
545
|
+
for (const directory of createdDirectories.reverse().slice(0, -1)) {
|
|
546
|
+
syncDirectory(directory);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function ensureDurableDirectory(path, mode) {
|
|
550
|
+
const firstCreatedParent = mkdirSync(path, { recursive: true, mode });
|
|
551
|
+
if (firstCreatedParent) {
|
|
552
|
+
syncCreatedParentChain(firstCreatedParent, path);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function atomicWriteFile(target, content, options = {}) {
|
|
556
|
+
const parent = dirname(target);
|
|
557
|
+
if (options.ensureParent) {
|
|
558
|
+
ensureDurableDirectory(parent);
|
|
559
|
+
}
|
|
560
|
+
const temporaryPath = `${target}.${randomUUID()}.tmp`;
|
|
561
|
+
let temporaryCreated = false;
|
|
562
|
+
try {
|
|
563
|
+
const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
|
|
564
|
+
const fd = openSync(temporaryPath, flags, options.mode ?? DEFAULT_FILE_MODE);
|
|
565
|
+
temporaryCreated = true;
|
|
566
|
+
try {
|
|
567
|
+
if (options.mode !== void 0) fchmodSync(fd, options.mode);
|
|
568
|
+
writeFileSync(fd, content);
|
|
569
|
+
fsyncSync(fd);
|
|
570
|
+
} finally {
|
|
571
|
+
closeSync(fd);
|
|
572
|
+
}
|
|
573
|
+
options.validate?.(temporaryPath);
|
|
574
|
+
renameSync(temporaryPath, target);
|
|
575
|
+
syncDirectory(parent);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (temporaryCreated) {
|
|
578
|
+
try {
|
|
579
|
+
rmSync(temporaryPath, { force: true });
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/lib/process-liveness.ts
|
|
588
|
+
function processIsAlive(pid, probe = process.kill) {
|
|
589
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
590
|
+
try {
|
|
591
|
+
probe(pid, 0);
|
|
592
|
+
return true;
|
|
593
|
+
} catch (error) {
|
|
594
|
+
return error.code === "EPERM";
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/lib/file-lock.ts
|
|
599
|
+
import { randomBytes } from "crypto";
|
|
600
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
601
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
602
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
603
|
+
var DEFAULT_POLL_MS = 50;
|
|
604
|
+
var DEFAULT_INIT_GRACE_MS = 2e3;
|
|
605
|
+
var DIRECTORY_MODE = 448;
|
|
606
|
+
var FILE_MODE = 384;
|
|
607
|
+
function ownerPath(lockDir) {
|
|
608
|
+
return join3(lockDir, "owner.json");
|
|
609
|
+
}
|
|
610
|
+
function readOwner(lockDir) {
|
|
611
|
+
try {
|
|
612
|
+
const owner = JSON.parse(readFileSync2(ownerPath(lockDir), "utf8"));
|
|
613
|
+
if (!Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || typeof owner.createdAt !== "number") {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
return owner;
|
|
617
|
+
} catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function sameOwner(a, b) {
|
|
622
|
+
return a?.pid === b.pid && a.nonce === b.nonce && a.createdAt === b.createdAt;
|
|
623
|
+
}
|
|
624
|
+
function tryTakeLock(lockDir, now) {
|
|
625
|
+
try {
|
|
626
|
+
mkdirSync2(lockDir, { mode: DIRECTORY_MODE });
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if (error.code === "EEXIST") return null;
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
const owner = {
|
|
632
|
+
pid: process.pid,
|
|
633
|
+
nonce: randomBytes(16).toString("hex"),
|
|
634
|
+
createdAt: now
|
|
635
|
+
};
|
|
636
|
+
try {
|
|
637
|
+
writeFileSync2(ownerPath(lockDir), `${JSON.stringify(owner)}
|
|
638
|
+
`, {
|
|
639
|
+
encoding: "utf8",
|
|
640
|
+
mode: FILE_MODE,
|
|
641
|
+
flag: "wx"
|
|
642
|
+
});
|
|
643
|
+
return owner;
|
|
644
|
+
} catch (error) {
|
|
645
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function recoverStaleLock(lockDir, now, alive, initGraceMs) {
|
|
650
|
+
const owner = readOwner(lockDir);
|
|
651
|
+
if (owner) {
|
|
652
|
+
if (alive(owner.pid)) return false;
|
|
653
|
+
if (!sameOwner(readOwner(lockDir), owner)) return false;
|
|
654
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
const before = statSync(lockDir).mtimeMs;
|
|
659
|
+
if (now - before < initGraceMs) return false;
|
|
660
|
+
if (readOwner(lockDir)) return false;
|
|
661
|
+
if (statSync(lockDir).mtimeMs !== before) return false;
|
|
662
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
663
|
+
return true;
|
|
664
|
+
} catch {
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function abortError(signal) {
|
|
669
|
+
if (signal.reason instanceof Error) return signal.reason;
|
|
670
|
+
const error = new Error("The operation was aborted");
|
|
671
|
+
error.name = "AbortError";
|
|
672
|
+
return error;
|
|
673
|
+
}
|
|
674
|
+
function throwIfAborted(signal) {
|
|
675
|
+
if (signal?.aborted) throw abortError(signal);
|
|
676
|
+
}
|
|
677
|
+
async function defaultSleep2(ms, signal) {
|
|
678
|
+
if (!signal) {
|
|
679
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
throwIfAborted(signal);
|
|
683
|
+
await new Promise((resolve, reject) => {
|
|
684
|
+
const timer = setTimeout(() => {
|
|
685
|
+
signal.removeEventListener("abort", onAbort);
|
|
686
|
+
resolve();
|
|
687
|
+
}, ms);
|
|
688
|
+
const onAbort = () => {
|
|
689
|
+
clearTimeout(timer);
|
|
690
|
+
reject(abortError(signal));
|
|
691
|
+
};
|
|
692
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
var syncSleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
696
|
+
function defaultSleepSync(ms) {
|
|
697
|
+
Atomics.wait(syncSleeper, 0, 0, ms);
|
|
698
|
+
}
|
|
699
|
+
function withFileLockSync(lockDir, operation, options = {}) {
|
|
700
|
+
mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
|
|
701
|
+
const nowMs = options.nowMs ?? Date.now;
|
|
702
|
+
const alive = options.processAlive ?? processIsAlive;
|
|
703
|
+
const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
704
|
+
let owner = null;
|
|
705
|
+
while (!owner) {
|
|
706
|
+
const now = nowMs();
|
|
707
|
+
owner = tryTakeLock(lockDir, now);
|
|
708
|
+
if (owner) break;
|
|
709
|
+
recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
|
|
710
|
+
if (now >= deadline) {
|
|
711
|
+
throw new Error(`timed out waiting for file lock ${lockDir}`);
|
|
712
|
+
}
|
|
713
|
+
(options.sleep ?? defaultSleepSync)(options.pollMs ?? DEFAULT_POLL_MS);
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
return operation();
|
|
717
|
+
} finally {
|
|
718
|
+
if (sameOwner(readOwner(lockDir), owner)) {
|
|
719
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async function withFileLock(lockDir, operation, options = {}) {
|
|
724
|
+
mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
|
|
725
|
+
const nowMs = options.nowMs ?? Date.now;
|
|
726
|
+
const alive = options.processAlive ?? processIsAlive;
|
|
727
|
+
const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
728
|
+
let owner = null;
|
|
729
|
+
while (!owner) {
|
|
730
|
+
throwIfAborted(options.signal);
|
|
731
|
+
const now = nowMs();
|
|
732
|
+
owner = tryTakeLock(lockDir, now);
|
|
733
|
+
if (owner) break;
|
|
734
|
+
recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
|
|
735
|
+
if (now >= deadline) {
|
|
736
|
+
throw new Error(`timed out waiting for file lock ${lockDir}`);
|
|
737
|
+
}
|
|
738
|
+
if (options.sleep) {
|
|
739
|
+
await options.sleep(options.pollMs ?? DEFAULT_POLL_MS);
|
|
740
|
+
throwIfAborted(options.signal);
|
|
741
|
+
} else {
|
|
742
|
+
await defaultSleep2(options.pollMs ?? DEFAULT_POLL_MS, options.signal);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
throwIfAborted(options.signal);
|
|
747
|
+
return await operation();
|
|
748
|
+
} finally {
|
|
749
|
+
if (sameOwner(readOwner(lockDir), owner)) {
|
|
750
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
501
755
|
export {
|
|
502
756
|
__commonJS,
|
|
503
757
|
__toESM,
|
|
@@ -525,5 +779,11 @@ export {
|
|
|
525
779
|
readStoredCredentialMetadata,
|
|
526
780
|
decodeJwtPayload,
|
|
527
781
|
jwtExpiresAt,
|
|
528
|
-
jwtOrganizationId
|
|
782
|
+
jwtOrganizationId,
|
|
783
|
+
syncDirectory,
|
|
784
|
+
syncFile,
|
|
785
|
+
atomicWriteFile,
|
|
786
|
+
processIsAlive,
|
|
787
|
+
withFileLockSync,
|
|
788
|
+
withFileLock
|
|
529
789
|
};
|