@primitive.ai/prim 0.1.0-alpha.68 → 0.1.0-alpha.69

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 (44) hide show
  1. package/README.md +39 -1
  2. package/SKILL.md +1 -1
  3. package/dist/{chunk-XPVNH3J6.js → chunk-2KW7DVFJ.js} +121 -130
  4. package/dist/chunk-3EEFL6NJ.js +529 -0
  5. package/dist/chunk-4A2XB2PC.js +310 -0
  6. package/dist/{chunk-G5D3FF3D.js → chunk-BQUJ4AFJ.js} +1 -83
  7. package/dist/chunk-CUE2LKLR.js +155 -0
  8. package/dist/chunk-FI3LIRCB.js +262 -0
  9. package/dist/{chunk-CPSE3Y3T.js → chunk-H64F6QHV.js} +27 -8
  10. package/dist/{chunk-I5BHJQH2.js → chunk-KKP2NYZ3.js} +206 -30
  11. package/dist/chunk-LEMX3IOG.js +79 -0
  12. package/dist/{chunk-U6L2RU67.js → chunk-LMRYZL3D.js} +52 -45
  13. package/dist/{chunk-XUAP572Z.js → chunk-RO3F6JYV.js} +8 -7
  14. package/dist/{chunk-TDRXHBEX.js → chunk-TJTVHDJ5.js} +41 -18
  15. package/dist/{chunk-TCDUH7AN.js → chunk-WB7AUZ4C.js} +17 -0
  16. package/dist/{chunk-UN5O5SYG.js → chunk-YWO7O6QG.js} +14 -53
  17. package/dist/chunk-Z7YPFWAZ.js +917 -0
  18. package/dist/{chunk-BDGQEJA5.js → chunk-ZOSNEO5N.js} +2 -2
  19. package/dist/daemon/server.js +11634 -391
  20. package/dist/hook-runtime/daemon/server.js +13353 -0
  21. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  22. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  23. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  24. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  25. package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
  26. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  27. package/dist/hook-runtime/hooks/session-end.js +495 -0
  28. package/dist/hook-runtime/hooks/session-start.js +11284 -0
  29. package/dist/hook-runtime/index.js +42775 -0
  30. package/dist/hook-runtime/statusline-main.js +765 -0
  31. package/dist/hooks/post-commit.js +7 -4
  32. package/dist/hooks/post-rewrite.js +7 -4
  33. package/dist/hooks/post-tool-use.js +21 -19
  34. package/dist/hooks/pre-commit.js +4 -3
  35. package/dist/hooks/pre-tool-use.js +59 -18
  36. package/dist/hooks/prim-hook.js +39 -56
  37. package/dist/hooks/session-end.js +3 -2
  38. package/dist/hooks/session-start.js +15 -12
  39. package/dist/index.js +15050 -2479
  40. package/dist/statusline-main.js +477 -89
  41. package/package.json +11 -5
  42. package/dist/chunk-K3AYW6VZ.js +0 -300
  43. package/dist/chunk-RTLFE6EZ.js +0 -518
  44. package/dist/chunk-UTKQTZHL.js +0 -88
@@ -0,0 +1,262 @@
1
+ import {
2
+ boundedHealthError
3
+ } from "./chunk-LEMX3IOG.js";
4
+ import {
5
+ terminalSafeLine
6
+ } from "./chunk-Z7YPFWAZ.js";
7
+
8
+ // src/hooks/redact.ts
9
+ import { existsSync, readFileSync, statSync } from "fs";
10
+ import { join } from "path";
11
+ import { Worker } from "worker_threads";
12
+ var USER_HOME_RULES = [
13
+ {
14
+ pattern: /\/Users\/[^/\\\s"'`]+/g,
15
+ reason: "user-home-path",
16
+ replacement: "/Users/__redacted_user__"
17
+ },
18
+ {
19
+ pattern: /\/home\/[^/\\\s"'`]+/g,
20
+ reason: "user-home-path",
21
+ replacement: "/home/__redacted_user__"
22
+ },
23
+ {
24
+ pattern: /([A-Za-z]:\\Users\\)[^/\\\s"'`]+/gi,
25
+ reason: "user-home-path",
26
+ replacement: "$1__redacted_user__"
27
+ },
28
+ {
29
+ pattern: /\/root(?=\/|$)/g,
30
+ reason: "user-home-path",
31
+ replacement: "/__redacted_user__"
32
+ }
33
+ ];
34
+ var DEFAULT_RULES = [
35
+ { pattern: /Bearer\s+[A-Za-z0-9._~+/=-]+/gi, reason: "bearer-token" },
36
+ {
37
+ pattern: /\bsk-(?:(?:ant|proj)-)?[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
38
+ reason: "sk-api-key"
39
+ },
40
+ {
41
+ pattern: /-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+PRIVATE KEY-----/g,
42
+ reason: "private-key"
43
+ },
44
+ { pattern: /xox[abprs]-[A-Za-z0-9-]{10,}/g, reason: "slack-token" },
45
+ {
46
+ pattern: /\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{50,})\b/g,
47
+ reason: "github-pat"
48
+ },
49
+ { pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, reason: "aws-access-key" },
50
+ {
51
+ pattern: /\bAWS_(?:SECRET_ACCESS_KEY|SESSION_TOKEN)"?\s*[:=]\s*["']?[A-Za-z0-9/+=]{20,}["']?/gi,
52
+ reason: "aws-secret"
53
+ },
54
+ { pattern: /\bAIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])/g, reason: "gcp-api-key" },
55
+ {
56
+ pattern: /\bya29\.[0-9A-Za-z_-]{20,}(?![0-9A-Za-z_-])/g,
57
+ reason: "gcp-oauth-token"
58
+ },
59
+ {
60
+ pattern: /\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*(?![A-Za-z0-9_-])/g,
61
+ reason: "jwt"
62
+ },
63
+ { pattern: /\bBasic\s+[A-Za-z0-9+/]+={0,2}/gi, reason: "basic-auth" },
64
+ { pattern: /\bhttps?:\/\/[^/\s:@]+:[^/\s@]+@/gi, reason: "basic-auth" },
65
+ ...USER_HOME_RULES
66
+ ];
67
+ var MAX_SCRUB_LEN = 256e3;
68
+ var MAX_WORKSPACE_CONFIG_BYTES = 64 * 1024;
69
+ var MAX_WORKSPACE_RULES = 32;
70
+ var MAX_WORKSPACE_PATTERN_LENGTH = 512;
71
+ var MAX_WORKSPACE_REASON_LENGTH = 64;
72
+ var WORKSPACE_RULE_TIMEOUT_MS = 250;
73
+ var WORKSPACE_FILE = ".prim/redaction.json";
74
+ var SAFE_REASON_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
75
+ var SAFE_WORKSPACE_FLAGS_RE = /^[gimsu]*$/u;
76
+ function debugRedaction(message) {
77
+ writeHookDebug(`redaction: ${message}`);
78
+ }
79
+ function normalizedWorkspaceFlags(value) {
80
+ if (value !== void 0 && typeof value !== "string") return null;
81
+ const requested = value ?? "";
82
+ if (!SAFE_WORKSPACE_FLAGS_RE.test(requested)) return null;
83
+ return [...new Set(`g${requested}`)].sort().join("");
84
+ }
85
+ function loadWorkspaceRules(cwd) {
86
+ const path = join(cwd, WORKSPACE_FILE);
87
+ if (!existsSync(path)) return [];
88
+ let cfg;
89
+ try {
90
+ if (statSync(path).size > MAX_WORKSPACE_CONFIG_BYTES) {
91
+ debugRedaction(`ignoring oversized ${WORKSPACE_FILE}`);
92
+ return [];
93
+ }
94
+ cfg = JSON.parse(readFileSync(path, "utf-8"));
95
+ } catch {
96
+ debugRedaction(`ignoring unparseable ${WORKSPACE_FILE}`);
97
+ return [];
98
+ }
99
+ if (!Array.isArray(cfg.rules)) return [];
100
+ const compiled = [];
101
+ for (const candidate of cfg.rules.slice(0, MAX_WORKSPACE_RULES)) {
102
+ if (!candidate || typeof candidate !== "object") continue;
103
+ const { pattern, reason } = candidate;
104
+ const flags = normalizedWorkspaceFlags(candidate.flags);
105
+ if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > MAX_WORKSPACE_PATTERN_LENGTH || typeof reason !== "string" || reason.length === 0 || reason.length > MAX_WORKSPACE_REASON_LENGTH || !SAFE_REASON_RE.test(reason) || flags === null) {
106
+ debugRedaction("skipping invalid workspace rule metadata");
107
+ continue;
108
+ }
109
+ try {
110
+ compiled.push({ pattern: new RegExp(pattern, flags), reason });
111
+ } catch {
112
+ debugRedaction(`skipping invalid workspace rule ${reason}`);
113
+ }
114
+ }
115
+ return compiled;
116
+ }
117
+ function applyRules(input, rules) {
118
+ if (input.length > MAX_SCRUB_LEN) return "<REDACTED:oversized>";
119
+ let out = input;
120
+ for (const rule of rules) {
121
+ out = out.replace(rule.pattern, rule.replacement ?? `<REDACTED:${rule.reason}>`);
122
+ }
123
+ return out;
124
+ }
125
+ var REDACTED_OBJECT_KEY = "<REDACTED:object-key>";
126
+ function redactedObjectKey(input, transform) {
127
+ return transform(input) === input ? input : REDACTED_OBJECT_KEY;
128
+ }
129
+ function uniqueObjectKey(output, key) {
130
+ let candidate = key;
131
+ let suffix = 2;
132
+ while (Object.hasOwn(output, candidate)) candidate = `${key}_${suffix++}`;
133
+ return candidate;
134
+ }
135
+ function mapStrings(value, transform) {
136
+ if (typeof value === "string") return transform(value);
137
+ if (Array.isArray(value)) return value.map((item) => mapStrings(item, transform));
138
+ if (value && typeof value === "object") {
139
+ const out = /* @__PURE__ */ Object.create(null);
140
+ for (const [key, item] of Object.entries(value)) {
141
+ out[uniqueObjectKey(out, redactedObjectKey(key, transform))] = mapStrings(item, transform);
142
+ }
143
+ return out;
144
+ }
145
+ return value;
146
+ }
147
+ function scrub(value, rules = DEFAULT_RULES) {
148
+ return mapStrings(value, (input) => applyRules(input, rules));
149
+ }
150
+ function debugDetail(value) {
151
+ try {
152
+ return String(value instanceof Error ? value.message : value);
153
+ } catch {
154
+ return "unprintable detail";
155
+ }
156
+ }
157
+ function writeHookDebug(context, ...details) {
158
+ if (!process.env.PRIM_HOOK_DEBUG) return;
159
+ const raw = details.length > 0 ? `${context}: ${details.map(debugDetail).join(": ")}` : context;
160
+ const normalized = terminalSafeLine(raw);
161
+ const message = boundedHealthError(scrub(normalized, DEFAULT_RULES)) ?? context;
162
+ process.stderr.write(`[prim-hook] ${message === `${context}:` ? context : message}
163
+ `);
164
+ }
165
+ function redactEveryString(value, reason) {
166
+ return mapStrings(value, () => `<REDACTED:${reason}>`);
167
+ }
168
+ var WORKSPACE_REDACTION_WORKER = String.raw`
169
+ const { parentPort, workerData } = require("node:worker_threads");
170
+ const REDACTED_OBJECT_KEY = "<REDACTED:object-key>";
171
+ const rules = workerData.rules.map((rule) => ({
172
+ pattern: new RegExp(rule.source, rule.flags),
173
+ reason: rule.reason,
174
+ }));
175
+ function redact(input) {
176
+ let output = input;
177
+ for (const rule of rules) {
178
+ output = output.replace(rule.pattern, "<REDACTED:" + rule.reason + ">");
179
+ }
180
+ return output;
181
+ }
182
+ function objectKey(input) {
183
+ return redact(input) === input ? input : REDACTED_OBJECT_KEY;
184
+ }
185
+ function uniqueKey(output, key) {
186
+ let candidate = key;
187
+ let suffix = 2;
188
+ while (Object.hasOwn(output, candidate)) candidate = key + "_" + suffix++;
189
+ return candidate;
190
+ }
191
+ function walk(value) {
192
+ if (typeof value === "string") {
193
+ return redact(value);
194
+ }
195
+ if (Array.isArray(value)) return value.map(walk);
196
+ if (value && typeof value === "object") {
197
+ const output = Object.create(null);
198
+ for (const [key, item] of Object.entries(value)) {
199
+ output[uniqueKey(output, objectKey(key))] = walk(item);
200
+ }
201
+ return output;
202
+ }
203
+ return value;
204
+ }
205
+ parentPort.postMessage(walk(workerData.value));
206
+ `;
207
+ function applyWorkspaceRules(value, rules) {
208
+ const serialized = rules.map(({ pattern, reason }) => ({
209
+ source: pattern.source,
210
+ flags: pattern.flags,
211
+ reason
212
+ }));
213
+ return new Promise((resolve) => {
214
+ let worker;
215
+ try {
216
+ worker = new Worker(WORKSPACE_REDACTION_WORKER, {
217
+ eval: true,
218
+ workerData: { value, rules: serialized }
219
+ });
220
+ } catch {
221
+ resolve(redactEveryString(value, "workspace-rule-failed"));
222
+ return;
223
+ }
224
+ let settled = false;
225
+ const finish = (result) => {
226
+ if (settled) return;
227
+ settled = true;
228
+ clearTimeout(timer);
229
+ void worker.terminate();
230
+ resolve(result);
231
+ };
232
+ const failClosed = () => finish(redactEveryString(value, "workspace-rule-failed"));
233
+ const timer = setTimeout(failClosed, WORKSPACE_RULE_TIMEOUT_MS);
234
+ worker.once("message", finish);
235
+ worker.once("error", failClosed);
236
+ worker.once("exit", (code) => {
237
+ if (code !== 0) failClosed();
238
+ });
239
+ });
240
+ }
241
+ async function scrubFromCwd(value, cwd) {
242
+ const defaultScrubbed = scrub(value, DEFAULT_RULES);
243
+ const workspaceRules = loadWorkspaceRules(cwd);
244
+ return workspaceRules.length === 0 ? defaultScrubbed : await applyWorkspaceRules(defaultScrubbed, workspaceRules);
245
+ }
246
+ function scrubEnvironmentPaths(environment) {
247
+ const scrubPath = (value) => value === void 0 ? void 0 : applyRules(value, USER_HOME_RULES);
248
+ return {
249
+ ...environment,
250
+ cwd: scrubPath(environment.cwd) ?? environment.cwd,
251
+ ...environment.repoRoot === void 0 ? {} : { repoRoot: scrubPath(environment.repoRoot) },
252
+ ...environment.gitRoot === void 0 ? {} : { gitRoot: scrubPath(environment.gitRoot) }
253
+ };
254
+ }
255
+
256
+ export {
257
+ DEFAULT_RULES,
258
+ scrub,
259
+ writeHookDebug,
260
+ scrubFromCwd,
261
+ scrubEnvironmentPaths
262
+ };
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  isCanonicalWorkspaceId
3
3
  } from "./chunk-UVIWZWM6.js";
4
+ import {
5
+ scrubEnvironmentPaths
6
+ } from "./chunk-FI3LIRCB.js";
4
7
  import {
5
8
  isValidRepoSyncId
6
- } from "./chunk-TDRXHBEX.js";
9
+ } from "./chunk-TJTVHDJ5.js";
7
10
 
8
11
  // src/hooks/prim-hook-core.ts
9
12
  import { createHash, randomUUID } from "crypto";
@@ -69,14 +72,30 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
69
72
  const sessionId = parsed.session_id ?? "";
70
73
  const eventType = parsed.hook_event_name ?? "unknown";
71
74
  const toolOutcome = toolOutcomeFor(parsed, agent);
72
- const moveId = CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId ? `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}` : randomUUID();
75
+ let moveId;
76
+ if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId) {
77
+ moveId = `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}`;
78
+ } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId) {
79
+ moveId = `posttool:fallback:v1:${createHash("sha256").update(
80
+ JSON.stringify([
81
+ agent,
82
+ sessionId,
83
+ eventType,
84
+ parsed.turn_id ?? null,
85
+ parsed.tool_name ?? null,
86
+ parsed.tool_input ?? null
87
+ ])
88
+ ).digest("hex")}`;
89
+ } else {
90
+ moveId = randomUUID();
91
+ }
73
92
  return {
74
93
  moveId,
75
94
  capturedAt: Date.now(),
76
95
  sessionId,
77
96
  eventType,
78
97
  payload: parsed,
79
- env: {
98
+ env: scrubEnvironmentPaths({
80
99
  cwd: parsed.cwd ?? process.cwd(),
81
100
  cliVersion,
82
101
  osPlatform: platform(),
@@ -88,7 +107,7 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
88
107
  repoSyncId: repository.repoSyncId
89
108
  } : {},
90
109
  ...workspaceId ? { workspaceId } : {}
91
- },
110
+ }),
92
111
  envelopeVersion: AGENT_ENVELOPE_VERSION,
93
112
  producer: agent,
94
113
  ...invocationId ? { invocationId } : {},
@@ -135,7 +154,7 @@ function toCommitMove(commit, cliVersion, cwd, context = {}) {
135
154
  changedFiles: commit.files,
136
155
  changedFilesComplete: commit.filesComplete
137
156
  },
138
- env: {
157
+ env: scrubEnvironmentPaths({
139
158
  cwd,
140
159
  cliVersion,
141
160
  osPlatform: platform(),
@@ -147,7 +166,7 @@ function toCommitMove(commit, cliVersion, cwd, context = {}) {
147
166
  ...repoFullName ? { repoFullName } : {},
148
167
  ...validatedRepoSyncId ? { repoSyncId: validatedRepoSyncId } : {},
149
168
  ...validatedWorkspaceId ? { workspaceId: validatedWorkspaceId } : {}
150
- }
169
+ })
151
170
  };
152
171
  return validatedAttribution ? {
153
172
  ...base,
@@ -194,7 +213,7 @@ function toRewriteMove(rewrite, cliVersion, cwd, context) {
194
213
  pairs: sortedPairs,
195
214
  ...branch ? { branch } : {}
196
215
  },
197
- env: {
216
+ env: scrubEnvironmentPaths({
198
217
  cwd,
199
218
  cliVersion,
200
219
  osPlatform: platform(),
@@ -206,7 +225,7 @@ function toRewriteMove(rewrite, cliVersion, cwd, context) {
206
225
  ...repoFullName ? { repoFullName } : {},
207
226
  repoSyncId,
208
227
  workspaceId
209
- },
228
+ }),
210
229
  envelopeVersion: ENVELOPE_VERSION
211
230
  });
212
231
  }