@primitive.ai/prim 0.1.0-alpha.9 → 0.1.0-alpha.91

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 (47) hide show
  1. package/README.md +303 -37
  2. package/SKILL.md +269 -0
  3. package/dist/chunk-4GLVJLEQ.js +55 -0
  4. package/dist/chunk-5AFRMOPX.js +209 -0
  5. package/dist/chunk-5GYQ4J3D.js +126 -0
  6. package/dist/chunk-67VNLLBN.js +789 -0
  7. package/dist/chunk-BTNNEH2W.js +79 -0
  8. package/dist/chunk-EWZMDD44.js +1474 -0
  9. package/dist/chunk-EZXEWFDG.js +210 -0
  10. package/dist/chunk-FBAYCS3X.js +327 -0
  11. package/dist/chunk-FKXFUUWJ.js +36 -0
  12. package/dist/chunk-FOLKHXLJ.js +1529 -0
  13. package/dist/chunk-H4UNYFUC.js +196 -0
  14. package/dist/chunk-HU4PW6I6.js +264 -0
  15. package/dist/chunk-KFXF2V5G.js +155 -0
  16. package/dist/chunk-LFFLHSQO.js +78 -0
  17. package/dist/chunk-M6RIJVRE.js +18358 -0
  18. package/dist/chunk-NAHHFSLH.js +258 -0
  19. package/dist/chunk-OAAP2MY6.js +262 -0
  20. package/dist/chunk-PCVGBE5B.js +659 -0
  21. package/dist/chunk-VW3FDYRT.js +517 -0
  22. package/dist/chunk-WX52U7Z2.js +112 -0
  23. package/dist/chunk-ZB4F75SZ.js +843 -0
  24. package/dist/daemon/server.js +21633 -0
  25. package/dist/hook-runtime/daemon/server.js +21633 -0
  26. package/dist/hook-runtime/hooks/post-commit.js +19258 -0
  27. package/dist/hook-runtime/hooks/post-rewrite.js +19273 -0
  28. package/dist/hook-runtime/hooks/post-tool-use.js +26365 -0
  29. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  30. package/dist/hook-runtime/hooks/pre-tool-use.js +25832 -0
  31. package/dist/hook-runtime/hooks/prim-hook.js +26386 -0
  32. package/dist/hook-runtime/hooks/session-end.js +1078 -0
  33. package/dist/hook-runtime/hooks/session-start.js +28779 -0
  34. package/dist/hook-runtime/index.js +50987 -0
  35. package/dist/hook-runtime/statusline-main.js +809 -0
  36. package/dist/hooks/post-commit.js +190 -0
  37. package/dist/hooks/post-rewrite.js +203 -0
  38. package/dist/hooks/post-tool-use.js +288 -0
  39. package/dist/hooks/pre-commit.js +16 -101
  40. package/dist/hooks/pre-tool-use.js +369 -0
  41. package/dist/hooks/prim-hook.js +229 -0
  42. package/dist/hooks/session-end.js +59 -0
  43. package/dist/hooks/session-start.js +316 -0
  44. package/dist/index.js +8875 -429
  45. package/dist/statusline-main.js +809 -0
  46. package/package.json +35 -13
  47. package/dist/chunk-3APLWTLB.js +0 -165
@@ -0,0 +1,196 @@
1
+ // src/lib/git.ts
2
+ import { execFileSync } from "child_process";
3
+ import { createHash } from "crypto";
4
+ import { lstatSync, realpathSync } from "fs";
5
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "path";
6
+ var GIT_TIMEOUT_MS = 1e3;
7
+ var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
8
+ var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
9
+ function gitToplevel(cwd) {
10
+ try {
11
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
12
+ cwd,
13
+ encoding: "utf-8",
14
+ stdio: ["ignore", "pipe", "ignore"],
15
+ timeout: GIT_TIMEOUT_MS
16
+ }).trim();
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function gitValue(cwd, args) {
22
+ try {
23
+ const value = execFileSync("git", args, {
24
+ cwd,
25
+ encoding: "utf-8",
26
+ stdio: ["ignore", "pipe", "ignore"],
27
+ timeout: GIT_TIMEOUT_MS
28
+ }).trim();
29
+ return value || void 0;
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ function currentBranch(cwd) {
35
+ return gitValue(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
36
+ }
37
+ function githubRepositoryFullName(cwd) {
38
+ const remote = gitValue(cwd, ["config", "--get", "remote.origin.url"]);
39
+ if (!remote || [...remote].some((char) => char < " " || char === "\x7F")) return null;
40
+ const match = /^(?:git@github\.com:([^?#]+)|(?:https|ssh):\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/([^?#]+))$/i.exec(
41
+ remote
42
+ );
43
+ const path = match?.[1] ?? match?.[2];
44
+ if (!path) return null;
45
+ const parts = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "").split("/");
46
+ if (parts.length !== 2 || !GITHUB_OWNER_PATTERN.test(parts[0] ?? "") || !GITHUB_REPOSITORY_PATTERN.test(parts[1] ?? "")) {
47
+ return null;
48
+ }
49
+ return `${parts[0]}/${parts[1]}`;
50
+ }
51
+ function canonicalGitRoot(cwd) {
52
+ try {
53
+ const top = gitToplevel(cwd);
54
+ return top ? realpathSync.native(top) : null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ function normalizeOriginRemote(remote) {
60
+ const value = remote.trim();
61
+ if (!value) return void 0;
62
+ const scp = !value.includes("://") ? /^(?:[^@/\s]+@)?(?<host>[^:/\s]+):(?<path>.+)$/.exec(value)?.groups : void 0;
63
+ if (scp?.host && scp.path) {
64
+ const path = scp.path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
65
+ return path ? `${scp.host.toLowerCase()}/${path}` : void 0;
66
+ }
67
+ try {
68
+ const url = new URL(value);
69
+ url.username = "";
70
+ url.password = "";
71
+ url.search = "";
72
+ url.hash = "";
73
+ const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
74
+ if (url.host && path) return `${url.host.toLowerCase()}/${path}`;
75
+ return void 0;
76
+ } catch {
77
+ return void 0;
78
+ }
79
+ }
80
+ function opaqueRepoKey(source, value) {
81
+ return `repo_v1_${createHash("sha256").update(`${source}\0${value}`).digest("hex")}`;
82
+ }
83
+ function resolveRepositoryContext(cwd = process.cwd()) {
84
+ const top = gitToplevel(cwd);
85
+ if (!top) return null;
86
+ let repoRoot;
87
+ try {
88
+ repoRoot = realpathSync.native(top);
89
+ } catch {
90
+ repoRoot = resolve(top);
91
+ }
92
+ const origin = gitValue(repoRoot, ["config", "--get", "remote.origin.url"]);
93
+ const repoFullName = githubRepositoryFullName(repoRoot) ?? void 0;
94
+ const normalizedOrigin = origin ? normalizeOriginRemote(origin) : void 0;
95
+ if (normalizedOrigin) {
96
+ return {
97
+ repoRoot,
98
+ repoKey: opaqueRepoKey("origin", normalizedOrigin),
99
+ identitySource: "origin",
100
+ repoFullName
101
+ };
102
+ }
103
+ const roots = gitValue(repoRoot, ["rev-list", "--max-parents=0", "HEAD"])?.split(/\s+/).filter(Boolean).sort();
104
+ if (roots && roots.length > 0) {
105
+ return {
106
+ repoRoot,
107
+ repoKey: opaqueRepoKey("root_commit", roots.join("\n")),
108
+ identitySource: "root_commit",
109
+ repoFullName
110
+ };
111
+ }
112
+ return { repoRoot, repoFullName };
113
+ }
114
+ function nearestExistingAncestor(path) {
115
+ const suffix = [];
116
+ let cursor = path;
117
+ while (true) {
118
+ try {
119
+ lstatSync(cursor);
120
+ break;
121
+ } catch (error) {
122
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
123
+ if (code !== "ENOENT" && code !== "ENOTDIR") return null;
124
+ const parent = dirname(cursor);
125
+ if (parent === cursor) return null;
126
+ suffix.unshift(cursor.slice(parent.length + (parent.endsWith(sep) ? 0 : 1)));
127
+ cursor = parent;
128
+ }
129
+ }
130
+ try {
131
+ return { real: realpathSync.native(cursor), suffix };
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ function canonicalRepositoryPathScoped(filePath, context, baseDir) {
137
+ if (!filePath || filePath !== filePath.trim() || filePath.includes("\0")) {
138
+ return { ok: false, reason: "invalid_path" };
139
+ }
140
+ const absolute = resolve(baseDir, filePath);
141
+ const resolved = nearestExistingAncestor(absolute);
142
+ if (!resolved) return { ok: false, reason: "invalid_path" };
143
+ const target = resolve(resolved.real, ...resolved.suffix);
144
+ const rel = relative(context.repoRoot, target);
145
+ if (!rel || rel === ".") return { ok: false, reason: "invalid_path" };
146
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) {
147
+ return { ok: false, reason: "outside_repository" };
148
+ }
149
+ return { ok: true, file: sep === "/" ? rel : rel.split(sep).join("/") };
150
+ }
151
+ function canonicalRepositoryPathLegacy(filePath, cwd, repositoryRoot = canonicalGitRoot(cwd)) {
152
+ if (!repositoryRoot || !filePath || [...filePath].some((char) => char < " " || char === "\x7F" || "~*?[]{}$`\\".includes(char)))
153
+ return null;
154
+ const absolute = resolve(cwd, filePath);
155
+ let target;
156
+ try {
157
+ const stat = lstatSync(absolute);
158
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
159
+ target = realpathSync.native(absolute);
160
+ } catch (error) {
161
+ if (!(error instanceof Error) || error.code !== "ENOENT") {
162
+ return null;
163
+ }
164
+ try {
165
+ const parent = realpathSync.native(dirname(absolute));
166
+ if (!lstatSync(parent).isDirectory()) return null;
167
+ target = resolve(parent, basename(absolute));
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+ const rel = relative(repositoryRoot, target);
173
+ if (!rel || isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) {
174
+ return null;
175
+ }
176
+ return sep === "/" ? rel : rel.split(sep).join("/");
177
+ }
178
+ function canonicalRepositoryPath(filePath, contextOrCwd, baseDirOrRoot) {
179
+ if (typeof contextOrCwd === "string") {
180
+ return canonicalRepositoryPathLegacy(filePath, contextOrCwd, baseDirOrRoot);
181
+ }
182
+ return canonicalRepositoryPathScoped(
183
+ filePath,
184
+ contextOrCwd,
185
+ baseDirOrRoot ?? contextOrCwd.repoRoot
186
+ );
187
+ }
188
+
189
+ export {
190
+ gitToplevel,
191
+ currentBranch,
192
+ githubRepositoryFullName,
193
+ canonicalGitRoot,
194
+ resolveRepositoryContext,
195
+ canonicalRepositoryPath
196
+ };
@@ -0,0 +1,264 @@
1
+ import {
2
+ cursorHooksPath,
3
+ hasExactCursorHandler,
4
+ readCursorHooks
5
+ } from "./chunk-EWZMDD44.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
+ };
@@ -0,0 +1,155 @@
1
+ import {
2
+ decodeJwtPayload,
3
+ primConfigDirectory,
4
+ resolveAuthCredential
5
+ } from "./chunk-67VNLLBN.js";
6
+
7
+ // src/daemon/client.ts
8
+ import { createConnection } from "net";
9
+ import { join } from "path";
10
+
11
+ // src/daemon/principal.ts
12
+ import { createHash } from "crypto";
13
+ var PRINCIPAL_COMPONENT_MAX_LENGTH = 512;
14
+ function boundedClaim(value) {
15
+ return typeof value === "string" && value.length > 0 && value.length <= PRINCIPAL_COMPONENT_MAX_LENGTH ? value : void 0;
16
+ }
17
+ function sha256(value) {
18
+ return createHash("sha256").update(value).digest("hex");
19
+ }
20
+ function resolveDaemonPrincipal(token = resolveAuthCredential()?.token) {
21
+ if (!token) return void 0;
22
+ const payload = decodeJwtPayload(token);
23
+ const subject = boundedClaim(payload?.sub);
24
+ const organizationId = boundedClaim(payload?.org_id);
25
+ if (!(subject && organizationId)) return void 0;
26
+ const issuer = boundedClaim(payload?.iss) ?? "unknown-issuer";
27
+ return {
28
+ principalId: sha256(`${issuer}\0${subject}`),
29
+ organizationId,
30
+ credentialFingerprint: sha256(token)
31
+ };
32
+ }
33
+ function daemonPrincipalsMatch(caller, daemon) {
34
+ return caller !== void 0 && daemon !== void 0 && caller.principalId === daemon.principalId && caller.organizationId === daemon.organizationId && caller.credentialFingerprint === daemon.credentialFingerprint;
35
+ }
36
+
37
+ // src/daemon/protocol.ts
38
+ var DAEMON_JSON_REQUEST_MAX_BYTES = 64 * 1024;
39
+ var DAEMON_JSON_RESPONSE_MAX_BYTES = 8 * 1024 * 1024;
40
+ function isRecord(value) {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+ function parseDaemonResponseEnvelope(value) {
44
+ if (!isRecord(value)) return void 0;
45
+ if (!(typeof value.id === "number" && Number.isSafeInteger(value.id) && value.id >= 0)) {
46
+ return void 0;
47
+ }
48
+ if (typeof value.ok !== "boolean") return void 0;
49
+ if (value.error !== void 0 && typeof value.error !== "string") return void 0;
50
+ return {
51
+ id: value.id,
52
+ ok: value.ok,
53
+ ...value.result === void 0 ? {} : { result: value.result },
54
+ ...value.error === void 0 ? {} : { error: value.error }
55
+ };
56
+ }
57
+
58
+ // src/daemon/client.ts
59
+ var SOCK_PATH = join(primConfigDirectory(), "sock");
60
+ var DEFAULT_TIMEOUT_MS = 250;
61
+ var nextRequestId = 1;
62
+ function daemonRequest(method, params = {}, opts = {}) {
63
+ const id = nextRequestId++;
64
+ const caller = resolveDaemonPrincipal();
65
+ const request = {
66
+ id,
67
+ method,
68
+ params,
69
+ ...caller ? { caller } : {}
70
+ };
71
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
72
+ return new Promise((resolve) => {
73
+ let settled = false;
74
+ let socket;
75
+ let buffer = Buffer.alloc(0);
76
+ const settle = (value) => {
77
+ if (settled) {
78
+ return;
79
+ }
80
+ settled = true;
81
+ if (socket) {
82
+ try {
83
+ socket.destroy();
84
+ } catch {
85
+ }
86
+ }
87
+ resolve(value);
88
+ };
89
+ const timer = setTimeout(() => settle(null), timeoutMs);
90
+ timer.unref();
91
+ try {
92
+ socket = createConnection(SOCK_PATH);
93
+ } catch {
94
+ clearTimeout(timer);
95
+ settle(null);
96
+ return;
97
+ }
98
+ socket.on("error", () => {
99
+ clearTimeout(timer);
100
+ settle(null);
101
+ });
102
+ socket.on("connect", () => {
103
+ try {
104
+ socket?.write(`${JSON.stringify(request)}
105
+ `);
106
+ } catch {
107
+ clearTimeout(timer);
108
+ settle(null);
109
+ }
110
+ });
111
+ socket.on("data", (chunk) => {
112
+ buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
113
+ if (buffer.length > DAEMON_JSON_RESPONSE_MAX_BYTES) {
114
+ clearTimeout(timer);
115
+ settle(null);
116
+ return;
117
+ }
118
+ const newlineIdx = buffer.indexOf(10);
119
+ if (newlineIdx === -1) {
120
+ return;
121
+ }
122
+ const line = buffer.subarray(0, newlineIdx).toString("utf8");
123
+ try {
124
+ const res = parseDaemonResponseEnvelope(JSON.parse(line));
125
+ if (!res || res.id !== id) {
126
+ clearTimeout(timer);
127
+ settle(null);
128
+ return;
129
+ }
130
+ clearTimeout(timer);
131
+ settle(res.ok && res.result !== void 0 ? res.result : null);
132
+ } catch {
133
+ clearTimeout(timer);
134
+ settle(null);
135
+ }
136
+ });
137
+ socket.on("end", () => {
138
+ if (!settled) {
139
+ clearTimeout(timer);
140
+ settle(null);
141
+ }
142
+ });
143
+ });
144
+ }
145
+ async function daemonIsLive(timeoutMs = DEFAULT_TIMEOUT_MS) {
146
+ const result = await daemonRequest("ping", {}, { timeoutMs });
147
+ return result?.pong === true;
148
+ }
149
+
150
+ export {
151
+ resolveDaemonPrincipal,
152
+ daemonPrincipalsMatch,
153
+ daemonRequest,
154
+ daemonIsLive
155
+ };
@@ -0,0 +1,78 @@
1
+ import {
2
+ analyzeShellTargets,
3
+ extractFileTargets
4
+ } from "./chunk-ZB4F75SZ.js";
5
+ import {
6
+ canonicalRepositoryPath
7
+ } from "./chunk-H4UNYFUC.js";
8
+
9
+ // src/hooks/file-refs.ts
10
+ var MAX_PREFLIGHT_FILE_TARGETS = 25;
11
+ function primitiveMetadataForResolution(resolution) {
12
+ const shouldAttach = resolution.fileRefs.length > 0 || resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation !== void 0;
13
+ if (!shouldAttach) return;
14
+ return {
15
+ fileRefs: [...resolution.fileRefs],
16
+ ...resolution.rejected.length > 0 || resolution.targetsIncomplete ? { fileRefsIncomplete: true } : {},
17
+ ...resolution.targetsTruncated ? { fileRefsTruncated: true } : {},
18
+ ...resolution.shellMutation !== void 0 ? { shellMutation: resolution.shellMutation } : {}
19
+ };
20
+ }
21
+ function preserveHookFileMetadata(payload, resolution) {
22
+ const primitive = primitiveMetadataForResolution(resolution);
23
+ if (!primitive || !payload || typeof payload !== "object" || Array.isArray(payload)) {
24
+ return payload;
25
+ }
26
+ return { ...payload, primitive };
27
+ }
28
+ function resolveHookFileRefs(args) {
29
+ const isShellTool = (args.agent === "claude_code" || args.agent === "codex") && args.toolName === "Bash" || args.agent === "cursor" && args.toolName === "Shell";
30
+ const command = isShellTool ? typeof args.toolInput === "string" ? args.toolInput : args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.command === "string" ? args.toolInput.command : void 0 : void 0;
31
+ const shell = command === void 0 ? void 0 : analyzeShellTargets(command);
32
+ const shellMutation = shell?.mutation === "none" ? "none" : shell?.coverage === "complete" ? "resolved" : shell ? "unresolved" : void 0;
33
+ const readPath = args.agent === "claude_code" && args.toolName === "Read" && args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.file_path === "string" ? [args.toolInput.file_path] : void 0;
34
+ const nativeTargets = shell ? null : readPath ? { paths: readPath, complete: true } : extractFileTargets(args.toolName, args.toolInput, args.agent);
35
+ const rawPaths = shell ? shellMutation === "unresolved" ? [] : shell.paths : nativeTargets?.paths ?? [];
36
+ const uniqueRawPaths = Array.from(new Set(rawPaths));
37
+ const targetsTruncated = uniqueRawPaths.length > MAX_PREFLIGHT_FILE_TARGETS;
38
+ const fileRefs = /* @__PURE__ */ new Set();
39
+ const rejected = [];
40
+ for (const path of uniqueRawPaths.slice(0, MAX_PREFLIGHT_FILE_TARGETS)) {
41
+ const canonical = canonicalRepositoryPath(path, args.repository, args.cwd);
42
+ if (canonical.ok) fileRefs.add(canonical.file);
43
+ else rejected.push({ path, reason: canonical.reason });
44
+ }
45
+ return {
46
+ fileRefs: [...fileRefs],
47
+ rejected,
48
+ targetsIncomplete: nativeTargets?.complete === false,
49
+ targetsTruncated,
50
+ ...shellMutation ? { shellMutation: targetsTruncated ? "unresolved" : shellMutation } : {}
51
+ };
52
+ }
53
+ function enrichHookPayloadWithFileRefs(args) {
54
+ const toolName = typeof args.parsed.tool_name === "string" ? args.parsed.tool_name : "";
55
+ const resolution = resolveHookFileRefs({
56
+ toolName,
57
+ toolInput: args.parsed.tool_input,
58
+ agent: args.agent,
59
+ cwd: args.cwd,
60
+ repository: args.repository
61
+ });
62
+ const primitive = primitiveMetadataForResolution(resolution);
63
+ if (!primitive) return { parsed: args.parsed, resolution };
64
+ return {
65
+ parsed: {
66
+ ...args.parsed,
67
+ // An explicit empty list is authoritative when every exposed target was
68
+ // rejected. It prevents backend fallback to unsafe raw tool input.
69
+ primitive
70
+ },
71
+ resolution
72
+ };
73
+ }
74
+
75
+ export {
76
+ preserveHookFileMetadata,
77
+ enrichHookPayloadWithFileRefs
78
+ };