@primitive.ai/prim 0.1.0-alpha.90 → 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.
- package/README.md +20 -12
- package/SKILL.md +6 -6
- package/dist/{chunk-LMRYZL3D.js → chunk-5AFRMOPX.js} +1 -1
- package/dist/{chunk-RO3F6JYV.js → chunk-5GYQ4J3D.js} +2 -2
- package/dist/{chunk-3EEFL6NJ.js → chunk-67VNLLBN.js} +261 -1
- package/dist/{chunk-LEMX3IOG.js → chunk-BTNNEH2W.js} +1 -1
- package/dist/chunk-EWZMDD44.js +1474 -0
- package/dist/{chunk-YWO7O6QG.js → chunk-FBAYCS3X.js} +2 -2
- package/dist/{chunk-R26A7TWY.js → chunk-FKXFUUWJ.js} +1 -1
- package/dist/{chunk-C7T7B45R.js → chunk-FOLKHXLJ.js} +150 -21
- package/dist/chunk-H4UNYFUC.js +196 -0
- package/dist/chunk-HU4PW6I6.js +264 -0
- package/dist/{chunk-CUE2LKLR.js → chunk-KFXF2V5G.js} +1 -1
- package/dist/{chunk-6IWNH776.js → chunk-LFFLHSQO.js} +4 -3
- 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-UZPBB7V4.js → chunk-VW3FDYRT.js} +60 -48
- package/dist/chunk-WX52U7Z2.js +112 -0
- package/dist/{chunk-PPQYQEIR.js → chunk-ZB4F75SZ.js} +28 -0
- package/dist/daemon/server.js +20 -20
- package/dist/hook-runtime/daemon/server.js +20 -20
- 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 +651 -132
- package/dist/hook-runtime/hooks/pre-tool-use.js +644 -96
- package/dist/hook-runtime/hooks/prim-hook.js +612 -96
- 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 +735 -264
- 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 +558 -1436
- 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
|
@@ -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
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeShellTargets,
|
|
3
3
|
extractFileTargets
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZB4F75SZ.js";
|
|
5
5
|
import {
|
|
6
6
|
canonicalRepositoryPath
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-H4UNYFUC.js";
|
|
8
8
|
|
|
9
9
|
// src/hooks/file-refs.ts
|
|
10
10
|
var MAX_PREFLIGHT_FILE_TARGETS = 25;
|
|
@@ -26,7 +26,8 @@ function preserveHookFileMetadata(payload, resolution) {
|
|
|
26
26
|
return { ...payload, primitive };
|
|
27
27
|
}
|
|
28
28
|
function resolveHookFileRefs(args) {
|
|
29
|
-
const
|
|
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;
|
|
30
31
|
const shell = command === void 0 ? void 0 : analyzeShellTargets(command);
|
|
31
32
|
const shellMutation = shell?.mutation === "none" ? "none" : shell?.coverage === "complete" ? "resolved" : shell ? "unresolved" : void 0;
|
|
32
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;
|