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

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 (45) hide show
  1. package/README.md +295 -37
  2. package/SKILL.md +269 -0
  3. package/dist/chunk-2JRX7ZR4.js +247 -0
  4. package/dist/chunk-3EEFL6NJ.js +529 -0
  5. package/dist/chunk-4GLVJLEQ.js +55 -0
  6. package/dist/chunk-6IWNH776.js +77 -0
  7. package/dist/chunk-C7T7B45R.js +1400 -0
  8. package/dist/chunk-CUE2LKLR.js +155 -0
  9. package/dist/chunk-EZXEWFDG.js +210 -0
  10. package/dist/chunk-FI3LIRCB.js +262 -0
  11. package/dist/chunk-LEMX3IOG.js +79 -0
  12. package/dist/chunk-LMRYZL3D.js +209 -0
  13. package/dist/chunk-MAZWKJGL.js +18548 -0
  14. package/dist/chunk-PMRDE2DA.js +310 -0
  15. package/dist/chunk-PPQYQEIR.js +815 -0
  16. package/dist/chunk-R26A7TWY.js +36 -0
  17. package/dist/chunk-RO3F6JYV.js +126 -0
  18. package/dist/chunk-UZPBB7V4.js +505 -0
  19. package/dist/chunk-WB7AUZ4C.js +52 -0
  20. package/dist/chunk-YWO7O6QG.js +327 -0
  21. package/dist/chunk-Z7YPFWAZ.js +917 -0
  22. package/dist/daemon/server.js +21633 -0
  23. package/dist/hook-runtime/daemon/server.js +21633 -0
  24. package/dist/hook-runtime/hooks/post-commit.js +19258 -0
  25. package/dist/hook-runtime/hooks/post-rewrite.js +19273 -0
  26. package/dist/hook-runtime/hooks/post-tool-use.js +25846 -0
  27. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  28. package/dist/hook-runtime/hooks/pre-tool-use.js +25284 -0
  29. package/dist/hook-runtime/hooks/prim-hook.js +25870 -0
  30. package/dist/hook-runtime/hooks/session-end.js +495 -0
  31. package/dist/hook-runtime/hooks/session-start.js +28180 -0
  32. package/dist/hook-runtime/index.js +50516 -0
  33. package/dist/hook-runtime/statusline-main.js +765 -0
  34. package/dist/hooks/post-commit.js +188 -0
  35. package/dist/hooks/post-rewrite.js +201 -0
  36. package/dist/hooks/post-tool-use.js +263 -0
  37. package/dist/hooks/pre-commit.js +16 -101
  38. package/dist/hooks/pre-tool-use.js +367 -0
  39. package/dist/hooks/prim-hook.js +207 -0
  40. package/dist/hooks/session-end.js +70 -0
  41. package/dist/hooks/session-start.js +289 -0
  42. package/dist/index.js +9749 -425
  43. package/dist/statusline-main.js +765 -0
  44. package/package.json +35 -13
  45. package/dist/chunk-3APLWTLB.js +0 -165
@@ -0,0 +1,155 @@
1
+ import {
2
+ decodeJwtPayload,
3
+ primConfigDirectory,
4
+ resolveAuthCredential
5
+ } from "./chunk-3EEFL6NJ.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,210 @@
1
+ // src/lib/workspace-id.ts
2
+ import { execFileSync } from "child_process";
3
+ import { randomUUID } from "crypto";
4
+ import {
5
+ constants,
6
+ chmodSync,
7
+ closeSync,
8
+ fchmodSync,
9
+ fsyncSync,
10
+ linkSync,
11
+ lstatSync,
12
+ mkdirSync,
13
+ openSync,
14
+ readFileSync,
15
+ statSync,
16
+ unlinkSync,
17
+ writeFileSync
18
+ } from "fs";
19
+ import { dirname, isAbsolute, join, resolve } from "path";
20
+ var WORKSPACE_ID_RELATIVE_PATH = join("prim", "workspace-id");
21
+ var MAX_ANCESTORS = 64;
22
+ var MAX_GIT_FILE_BYTES = 4096;
23
+ var MAX_ID_FILE_BYTES = 128;
24
+ var GIT_FALLBACK_TIMEOUT_MS = 250;
25
+ var RACE_READ_ATTEMPTS = 5;
26
+ var RACE_READ_DELAY_MS = 5;
27
+ var CANONICAL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
28
+ var raceDelay = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
29
+ function isCanonicalWorkspaceId(value) {
30
+ return CANONICAL_UUID.test(value);
31
+ }
32
+ function inspectWorkspaceId(cwd = process.cwd()) {
33
+ const resolved = resolveWorkspaceIdPath(cwd);
34
+ if (resolved.status !== "resolved") return resolved;
35
+ return inspectPath(resolved.path);
36
+ }
37
+ function getOrCreateWorkspaceId(cwd = process.cwd()) {
38
+ const inspection = inspectWorkspaceId(cwd);
39
+ if (inspection.status !== "missing") return inspection;
40
+ return createWorkspaceId(inspection.path);
41
+ }
42
+ function resolveWorkspaceIdPath(cwd) {
43
+ if (!process.env.GIT_DIR && !process.env.GIT_WORK_TREE) {
44
+ const fromFilesystem = resolveFromFilesystem(cwd);
45
+ if (fromFilesystem) return { status: "resolved", path: fromFilesystem };
46
+ }
47
+ try {
48
+ const output = execFileSync(
49
+ "git",
50
+ ["rev-parse", "--is-inside-work-tree", "--git-path", WORKSPACE_ID_RELATIVE_PATH],
51
+ {
52
+ cwd,
53
+ encoding: "utf8",
54
+ maxBuffer: 4096,
55
+ stdio: ["ignore", "pipe", "ignore"],
56
+ timeout: GIT_FALLBACK_TIMEOUT_MS,
57
+ windowsHide: true
58
+ }
59
+ );
60
+ const [insideWorktree, gitPath, ...extra] = output.trim().split(/\r?\n/);
61
+ if (insideWorktree !== "true" || !gitPath || extra.length > 0 || gitPath.includes("\0")) {
62
+ return { status: "not_git" };
63
+ }
64
+ return {
65
+ status: "resolved",
66
+ path: isAbsolute(gitPath) ? resolve(gitPath) : resolve(cwd, gitPath)
67
+ };
68
+ } catch (error) {
69
+ const code = errorCode(error);
70
+ if (code === "ETIMEDOUT" || code === "EACCES" || code === "EPERM") {
71
+ return { status: "unavailable", operation: "resolve", errorCode: code };
72
+ }
73
+ return { status: "not_git" };
74
+ }
75
+ }
76
+ function resolveFromFilesystem(cwd) {
77
+ let current = resolve(cwd);
78
+ for (let depth = 0; depth < MAX_ANCESTORS; depth += 1) {
79
+ const dotGit = join(current, ".git");
80
+ try {
81
+ const stat = lstatSync(dotGit);
82
+ if (stat.isDirectory() && isGitDirectory(dotGit)) {
83
+ return join(dotGit, WORKSPACE_ID_RELATIVE_PATH);
84
+ }
85
+ if (stat.isFile() && stat.size <= MAX_GIT_FILE_BYTES) {
86
+ const contents = readFileSync(dotGit, "utf8");
87
+ const match = /^gitdir: (.+?)(?:\r?\n)?$/.exec(contents);
88
+ if (match?.[1]) {
89
+ const gitDirectory = isAbsolute(match[1]) ? resolve(match[1]) : resolve(current, match[1]);
90
+ if (isGitDirectory(gitDirectory)) {
91
+ return join(gitDirectory, WORKSPACE_ID_RELATIVE_PATH);
92
+ }
93
+ }
94
+ }
95
+ return null;
96
+ } catch (error) {
97
+ const code = errorCode(error);
98
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
99
+ return null;
100
+ }
101
+ }
102
+ const parent = dirname(current);
103
+ if (parent === current) break;
104
+ current = parent;
105
+ }
106
+ return null;
107
+ }
108
+ function isGitDirectory(path) {
109
+ try {
110
+ return statSync(join(path, "HEAD")).isFile();
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ function inspectPath(path) {
116
+ let stat;
117
+ try {
118
+ stat = lstatSync(path);
119
+ } catch (error) {
120
+ const code = errorCode(error);
121
+ if (code === "ENOENT" || code === "ENOTDIR") return { status: "missing", path };
122
+ return { status: "unavailable", path, operation: "read", errorCode: code };
123
+ }
124
+ if (!stat.isFile()) return { status: "corrupt", path, reason: "not_file" };
125
+ if (stat.size > MAX_ID_FILE_BYTES) {
126
+ return { status: "corrupt", path, reason: "invalid_content" };
127
+ }
128
+ try {
129
+ const contents = readFileSync(path, "utf8");
130
+ const workspaceId = contents.endsWith("\n") ? contents.slice(0, -1) : contents;
131
+ if ((contents === workspaceId || contents === `${workspaceId}
132
+ `) && isCanonicalWorkspaceId(workspaceId)) {
133
+ return { status: "ready", workspaceId, path, created: false };
134
+ }
135
+ return { status: "corrupt", path, reason: "invalid_content" };
136
+ } catch (error) {
137
+ return {
138
+ status: "unavailable",
139
+ path,
140
+ operation: "read",
141
+ errorCode: errorCode(error)
142
+ };
143
+ }
144
+ }
145
+ function createWorkspaceId(path) {
146
+ const parent = dirname(path);
147
+ const workspaceId = randomUUID();
148
+ const temporaryPath = join(parent, `.workspace-id-${process.pid}-${randomUUID()}.tmp`);
149
+ let temporaryCreated = false;
150
+ let publishing = false;
151
+ try {
152
+ mkdirSync(parent, { recursive: true, mode: 448 });
153
+ chmodSync(parent, 448);
154
+ const fd = openSync(
155
+ temporaryPath,
156
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
157
+ 384
158
+ );
159
+ temporaryCreated = true;
160
+ try {
161
+ writeFileSync(fd, `${workspaceId}
162
+ `, "utf8");
163
+ fchmodSync(fd, 384);
164
+ fsyncSync(fd);
165
+ } finally {
166
+ closeSync(fd);
167
+ }
168
+ publishing = true;
169
+ linkSync(temporaryPath, path);
170
+ return { status: "ready", workspaceId, path, created: true };
171
+ } catch (error) {
172
+ if (publishing && errorCode(error) === "EEXIST") return inspectAfterRace(path);
173
+ return {
174
+ status: "unavailable",
175
+ path,
176
+ operation: "create",
177
+ errorCode: errorCode(error)
178
+ };
179
+ } finally {
180
+ if (temporaryCreated) {
181
+ try {
182
+ unlinkSync(temporaryPath);
183
+ } catch {
184
+ }
185
+ }
186
+ }
187
+ }
188
+ function inspectAfterRace(path) {
189
+ let result = { status: "missing", path };
190
+ for (let attempt = 0; attempt < RACE_READ_ATTEMPTS; attempt += 1) {
191
+ result = inspectPath(path);
192
+ if (result.status === "ready") return result;
193
+ if (attempt + 1 < RACE_READ_ATTEMPTS) {
194
+ Atomics.wait(raceDelay, 0, 0, RACE_READ_DELAY_MS);
195
+ }
196
+ }
197
+ if (result.status === "missing") {
198
+ return { status: "unavailable", path, operation: "create", errorCode: "EEXIST" };
199
+ }
200
+ return result;
201
+ }
202
+ function errorCode(error) {
203
+ if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
204
+ return typeof error.code === "string" ? error.code : void 0;
205
+ }
206
+
207
+ export {
208
+ isCanonicalWorkspaceId,
209
+ getOrCreateWorkspaceId
210
+ };
@@ -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
+ };
@@ -0,0 +1,79 @@
1
+ import {
2
+ terminalSafeLine
3
+ } from "./chunk-Z7YPFWAZ.js";
4
+
5
+ // src/lib/ansi.ts
6
+ var ANSI_CODES = {
7
+ yellow: "\x1B[33m",
8
+ magenta: "\x1B[35m",
9
+ blue: "\x1B[34m",
10
+ cyan: "\x1B[36m",
11
+ green: "\x1B[32m",
12
+ red: "\x1B[31m",
13
+ // 256-color approximation of the orange trigger marker (xterm 208).
14
+ orange: "\x1B[38;5;208m",
15
+ gray: "\x1B[90m"
16
+ };
17
+ var ANSI_RESET = "\x1B[0m";
18
+ var ANSI_DIM = "\x1B[2m";
19
+ var ANSI_BOLD = "\x1B[1m";
20
+ function supportsColor() {
21
+ if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
22
+ return false;
23
+ }
24
+ return process.stderr.isTTY === true;
25
+ }
26
+ function color(text, c) {
27
+ if (!supportsColor()) {
28
+ return text;
29
+ }
30
+ return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
31
+ }
32
+ function dim(text) {
33
+ if (!supportsColor()) {
34
+ return text;
35
+ }
36
+ return `${ANSI_DIM}${text}${ANSI_RESET}`;
37
+ }
38
+ function bold(text) {
39
+ if (!supportsColor()) {
40
+ return text;
41
+ }
42
+ return `${ANSI_BOLD}${text}${ANSI_RESET}`;
43
+ }
44
+ var AREA_COLORS = {
45
+ auth: "yellow",
46
+ data: "magenta",
47
+ mobile: "blue",
48
+ infra: "cyan",
49
+ ui: "green",
50
+ billing: "orange",
51
+ api: "blue",
52
+ docs: "gray",
53
+ testing: "gray"
54
+ };
55
+ function colorForArea(area) {
56
+ if (!area) {
57
+ return "gray";
58
+ }
59
+ return AREA_COLORS[area] ?? "gray";
60
+ }
61
+ var MAX_HEALTH_ERROR_LENGTH = 240;
62
+ function boundedHealthError(value) {
63
+ if (!value) return void 0;
64
+ const clean = terminalSafeLine(value);
65
+ if (!clean) return void 0;
66
+ return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
67
+ }
68
+ function stripAnsi(text) {
69
+ return text.replace(/\u001b\[[0-9;]*m/g, "");
70
+ }
71
+
72
+ export {
73
+ color,
74
+ dim,
75
+ bold,
76
+ colorForArea,
77
+ boundedHealthError,
78
+ stripAnsi
79
+ };