@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,310 @@
1
+ import {
2
+ stripControlChars
3
+ } from "./chunk-Z7YPFWAZ.js";
4
+
5
+ // src/lib/bin-path.ts
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { dirname, isAbsolute, join } from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ // src/lib/semver.ts
11
+ function parseSemver(value) {
12
+ if (typeof value !== "string" || value.length > 256) return;
13
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(
14
+ value
15
+ );
16
+ if (!match) return;
17
+ const prerelease = match[4]?.split(".") ?? [];
18
+ if (prerelease.some((part) => /^\d+$/u.test(part) && part.length > 1 && part.startsWith("0"))) {
19
+ return;
20
+ }
21
+ return { core: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], prerelease };
22
+ }
23
+ function compareSemver(left, right) {
24
+ const a = parseSemver(left);
25
+ const b = parseSemver(right);
26
+ if (!a || !b) return;
27
+ for (let i = 0; i < a.core.length; i += 1) {
28
+ if (a.core[i] !== b.core[i]) return a.core[i] < b.core[i] ? -1 : 1;
29
+ }
30
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
31
+ return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length === 0 ? 1 : -1;
32
+ }
33
+ for (let i = 0; i < Math.max(a.prerelease.length, b.prerelease.length); i += 1) {
34
+ const x = a.prerelease[i];
35
+ const y = b.prerelease[i];
36
+ if (x === void 0 || y === void 0) return x === void 0 ? -1 : 1;
37
+ if (x === y) continue;
38
+ const xNumeric = /^\d+$/u.test(x);
39
+ const yNumeric = /^\d+$/u.test(y);
40
+ if (xNumeric && yNumeric) return BigInt(x) < BigInt(y) ? -1 : 1;
41
+ if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
42
+ return x < y ? -1 : 1;
43
+ }
44
+ return 0;
45
+ }
46
+
47
+ // src/lib/bin-path.ts
48
+ var PKG_NAME = "@primitive.ai/prim";
49
+ var ROOT_WALK_LIMIT = 6;
50
+ var STABLE_HOOK_LAUNCHER_NAME = "prim-hook-launcher-v1";
51
+ var resolvedRoot;
52
+ function locateRoot() {
53
+ if (resolvedRoot !== void 0) {
54
+ return resolvedRoot;
55
+ }
56
+ let dir = dirname(fileURLToPath(import.meta.url));
57
+ for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
58
+ const manifestPath = join(dir, "package.json");
59
+ if (existsSync(manifestPath)) {
60
+ try {
61
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
62
+ if (manifest.name === PKG_NAME && manifest.bin) {
63
+ resolvedRoot = { dir, version: manifest.version, bin: manifest.bin };
64
+ return resolvedRoot;
65
+ }
66
+ } catch {
67
+ }
68
+ }
69
+ const parent = dirname(dir);
70
+ if (parent === dir) {
71
+ break;
72
+ }
73
+ dir = parent;
74
+ }
75
+ resolvedRoot = null;
76
+ return resolvedRoot;
77
+ }
78
+ function binFile(bin) {
79
+ const root = locateRoot();
80
+ const rel = root?.bin[bin];
81
+ if (!root || !rel) {
82
+ return null;
83
+ }
84
+ return isAbsolute(rel) ? rel : join(root.dir, rel);
85
+ }
86
+ function packageVersion() {
87
+ return locateRoot()?.version ?? null;
88
+ }
89
+ function packageRoot() {
90
+ return locateRoot()?.dir ?? null;
91
+ }
92
+ function shellQuote(value) {
93
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
94
+ }
95
+ function shellWord(value) {
96
+ return /^[A-Za-z0-9_@%+=:,./-]+$/u.test(value) ? value : shellQuote(value);
97
+ }
98
+ function pinnedNpxArgs(bin, args = [], options = {}) {
99
+ const version = packageVersion();
100
+ if (!version) throw new Error("cannot determine Primitive package version");
101
+ return [
102
+ "--yes",
103
+ "--ignore-scripts",
104
+ ...options.preferOnline ? ["--prefer-online"] : [],
105
+ "-p",
106
+ `${PKG_NAME}@${version}`,
107
+ bin,
108
+ ...args
109
+ ];
110
+ }
111
+ function pinnedNpxCommand(bin, args = [], options = {}) {
112
+ return ["npx", ...pinnedNpxArgs(bin, args, options)].map(shellWord).join(" ");
113
+ }
114
+ function pinnedHookCommand(bin, args = "") {
115
+ const suffix = args ? ` ${args}` : "";
116
+ const fallback = `${pinnedNpxCommand(bin)}${suffix}`;
117
+ const file = binFile(bin);
118
+ if (!file) return fallback;
119
+ return `if [ -x ${shellQuote(process.execPath)} ] && [ -f ${shellQuote(file)} ]; then ${shellQuote(process.execPath)} ${shellQuote(file)}${suffix}; else ${fallback}; fi`;
120
+ }
121
+ var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
122
+ var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
123
+ function stableHookCommand(bin, args = "") {
124
+ if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
125
+ throw new Error("invalid stable hook command");
126
+ }
127
+ const resolver = [
128
+ // #242 and earlier identify managed commands by this package marker. Keep
129
+ // it as inert data during the rolling window so an older uninstall or
130
+ // reinstall can remove the stable entry without invoking npm or PATH.
131
+ `prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
132
+ 'prim_absolute() { case "$1" in /*) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*//*|*/./*|*/../*|*/.|*/..|?*/) return 1 ;; esac; }; ',
133
+ 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
134
+ 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
135
+ 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
136
+ "fi; fi; ",
137
+ `case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
138
+ 'exec "$prim_launcher" "$@"'
139
+ ].join("");
140
+ const suffix = args ? ` ${args}` : "";
141
+ return `/bin/sh -c ${shellQuote(resolver)} ${STABLE_HOOK_LAUNCHER_NAME} ${bin}${suffix}`;
142
+ }
143
+ function detachedHookShimCommand(bin, args = "") {
144
+ return `payload=$(cat); { trap '' HUP; printf '%s' "$payload" | { ${stableHookCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
145
+ }
146
+ function commandMatchesBin(command, bin) {
147
+ if (!command) {
148
+ return false;
149
+ }
150
+ const c = command.trim();
151
+ if (c === bin || c.startsWith(`${bin} `)) {
152
+ return true;
153
+ }
154
+ const exactBin = new RegExp(`(?:^|\\s)${bin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|;|$)`);
155
+ return c.includes(`command -v ${bin} `) || c.includes(`-p ${PKG_NAME}@`) && exactBin.test(c) || c.includes(STABLE_HOOK_LAUNCHER_NAME) && exactBin.test(c);
156
+ }
157
+ function exactPinnedNpxVersion(command, bin) {
158
+ const escapedBin = bin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
159
+ const shellQuoted = `'(?:[^']|'"'"')*'`;
160
+ const safeArgs = "(?: [A-Za-z0-9_@%+=:,./-]+)*";
161
+ const direct = new RegExp(
162
+ `^npx --yes --ignore-scripts -p @primitive\\.ai/prim@(?<version>[0-9A-Za-z.+-]+) ${escapedBin}${safeArgs}$`,
163
+ "u"
164
+ ).exec(command);
165
+ const wrapped = new RegExp(
166
+ `^if \\[ -x (?<node>${shellQuoted}) \\] && \\[ -f (?<entry>${shellQuoted}) \\]; then \\k<node> \\k<entry>(?<args>${safeArgs}); else npx --yes --ignore-scripts -p @primitive\\.ai/prim@(?<version>[0-9A-Za-z.+-]+) ${escapedBin}\\k<args>; fi$`,
167
+ "u"
168
+ ).exec(command);
169
+ const version = direct?.groups?.version ?? wrapped?.groups?.version;
170
+ return version && compareSemver(version, version) !== void 0 ? version : void 0;
171
+ }
172
+ function hookCommandResolution(command, bin) {
173
+ const trimmed = command?.trim();
174
+ if (!trimmed || !commandMatchesBin(trimmed, bin)) return void 0;
175
+ if (trimmed.includes(STABLE_HOOK_LAUNCHER_NAME)) return { kind: "stable_launcher" };
176
+ const version = exactPinnedNpxVersion(trimmed, bin);
177
+ return version ? { kind: "exact_npx_fallback", version } : { kind: "legacy_path" };
178
+ }
179
+ function hookCommandResolutions(commands, bins) {
180
+ const resolutions = [];
181
+ for (const command of commands) {
182
+ for (const bin of bins) {
183
+ const resolution = hookCommandResolution(command, bin);
184
+ if (resolution) resolutions.push(resolution);
185
+ }
186
+ }
187
+ return resolutions;
188
+ }
189
+
190
+ // src/lib/presence.ts
191
+ var DECISION_ORIGIN = "https://app.getprimitive.ai";
192
+ function validDecisionUrl(value) {
193
+ if (typeof value !== "string" || stripControlChars(value) !== value) {
194
+ return void 0;
195
+ }
196
+ try {
197
+ const url = new URL(value);
198
+ if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
199
+ return void 0;
200
+ }
201
+ return url.href;
202
+ } catch {
203
+ return void 0;
204
+ }
205
+ }
206
+ function decisionLink(label, decisionUrl, plain) {
207
+ const url = validDecisionUrl(decisionUrl);
208
+ if (plain || !url) {
209
+ return label;
210
+ }
211
+ return `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07`;
212
+ }
213
+ function formatLabeled(labels, cap) {
214
+ if (labels === void 0) {
215
+ return "\u2014";
216
+ }
217
+ if (labels.length === 0) {
218
+ return "just you";
219
+ }
220
+ if (labels.length <= cap) {
221
+ return labels.join(", ");
222
+ }
223
+ return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
224
+ }
225
+ function formatTeammates(names, cap) {
226
+ return formatLabeled(names?.map(stripControlChars), cap);
227
+ }
228
+ function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
229
+ return formatLabeled(
230
+ teammates?.map((t) => {
231
+ const name = stripControlChars(t.name);
232
+ const area = stripControlChars(t.area ?? "").trim();
233
+ const label = area ? `${name} - ${area}` : name;
234
+ return decisionLink(label, t.decisionUrl, plainLinks);
235
+ }),
236
+ cap
237
+ );
238
+ }
239
+
240
+ // src/lib/statusline-render.ts
241
+ function repositoryBindingDiagnosticLabel(state) {
242
+ if (state === "unbound") return "repository: unbound (enforcement not evaluating)";
243
+ if (state === "invalid") return "repository binding: invalid (run `prim doctor`)";
244
+ return void 0;
245
+ }
246
+ var STATUSLINE_NAME_CAP = 3;
247
+ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {}) {
248
+ const ingestionSuffix = (status) => status === void 0 ? "" : ` \xB7 Decision ingestion ${status}`;
249
+ const ingestionStatus = snapshot === null || snapshot.healthy === false ? options.includeIngestionWhenUnavailable ? resolveIngestionStatus() : void 0 : resolveIngestionStatus();
250
+ if (snapshot?.healthy === false && snapshot.needsReauth) {
251
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
252
+ }
253
+ const repositoryLabel = repositoryBindingDiagnosticLabel(
254
+ snapshot !== null && snapshot.healthy !== false || options.includeIngestionWhenUnavailable === true ? options.resolveRepositoryBindingState?.() : void 0
255
+ );
256
+ const repositorySuffix = repositoryLabel ? ` \xB7 ${repositoryLabel}` : "";
257
+ if (!snapshot) {
258
+ return `primitive ${version} (daemon: down${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
259
+ }
260
+ if (snapshot.healthy === false) {
261
+ if (snapshot.ingestion?.healthy === false) {
262
+ const pending = snapshot.ingestion.pendingCount;
263
+ const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
264
+ return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
265
+ }
266
+ if (snapshot.heartbeat?.healthy === false) {
267
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
268
+ }
269
+ return `primitive ${version} (daemon: starting${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
270
+ }
271
+ if (snapshot.envMismatch) {
272
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: other env)`;
273
+ }
274
+ if (snapshot.principalMismatch) {
275
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: other account)`;
276
+ }
277
+ if (snapshot.presenceStale) {
278
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 presence: stale)`;
279
+ }
280
+ let team;
281
+ if (snapshot.onlineTeammates !== void 0) {
282
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
283
+ } else if (snapshot.onlineNames !== void 0) {
284
+ team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
285
+ } else if (typeof snapshot.onlineCount === "number") {
286
+ team = `team: ${String(snapshot.onlineCount)} online`;
287
+ } else {
288
+ team = "team: \u2014";
289
+ }
290
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus}${repositorySuffix} \xB7 ${team})`;
291
+ }
292
+
293
+ export {
294
+ compareSemver,
295
+ STABLE_HOOK_LAUNCHER_NAME,
296
+ binFile,
297
+ packageVersion,
298
+ packageRoot,
299
+ pinnedNpxArgs,
300
+ pinnedNpxCommand,
301
+ pinnedHookCommand,
302
+ stableHookCommand,
303
+ detachedHookShimCommand,
304
+ commandMatchesBin,
305
+ hookCommandResolution,
306
+ hookCommandResolutions,
307
+ formatTeammates,
308
+ repositoryBindingDiagnosticLabel,
309
+ formatStatusline
310
+ };
@@ -199,85 +199,6 @@ function demoteForMode(verdict, mode) {
199
199
  return verdict;
200
200
  }
201
201
 
202
- // src/hooks/redact.ts
203
- import { existsSync, readFileSync } from "fs";
204
- import { join } from "path";
205
- var DEFAULT_RULES = [
206
- { pattern: /Bearer\s+[A-Za-z0-9._\-]+/g, reason: "bearer-token" },
207
- { pattern: /sk-[A-Za-z0-9]{32,}/g, reason: "sk-api-key" },
208
- {
209
- pattern: /-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+PRIVATE KEY-----/g,
210
- reason: "private-key"
211
- },
212
- { pattern: /xox[abprs]-[A-Za-z0-9-]{10,}/g, reason: "slack-token" },
213
- { pattern: /ghp_[A-Za-z0-9]{36,}/g, reason: "github-pat" }
214
- ];
215
- var MAX_SCRUB_LEN = 256e3;
216
- var WORKSPACE_FILE = ".prim/redaction.json";
217
- function debugRedaction(message, err) {
218
- if (!process.env.PRIM_HOOK_DEBUG) {
219
- return;
220
- }
221
- const detail = err instanceof Error ? err.message : String(err);
222
- process.stderr.write(`[prim-hook] redaction: ${message}: ${detail}
223
- `);
224
- }
225
- function loadWorkspaceRules(cwd) {
226
- const path = join(cwd, WORKSPACE_FILE);
227
- if (!existsSync(path)) {
228
- return [];
229
- }
230
- let cfg;
231
- try {
232
- cfg = JSON.parse(readFileSync(path, "utf-8"));
233
- } catch (err) {
234
- debugRedaction(`ignoring unparseable ${WORKSPACE_FILE}`, err);
235
- return [];
236
- }
237
- if (!cfg.rules) {
238
- return [];
239
- }
240
- const compiled = [];
241
- for (const r of cfg.rules) {
242
- try {
243
- compiled.push({ pattern: new RegExp(r.pattern, r.flags ?? "g"), reason: r.reason });
244
- } catch (err) {
245
- debugRedaction(`skipping invalid redaction rule "${r.reason}"`, err);
246
- }
247
- }
248
- return compiled;
249
- }
250
- function applyRules(input, rules) {
251
- if (input.length > MAX_SCRUB_LEN) {
252
- return "<REDACTED:oversized>";
253
- }
254
- let out = input;
255
- for (const rule of rules) {
256
- out = out.replace(rule.pattern, `<REDACTED:${rule.reason}>`);
257
- }
258
- return out;
259
- }
260
- function scrub(value, rules = DEFAULT_RULES) {
261
- if (typeof value === "string") {
262
- return applyRules(value, rules);
263
- }
264
- if (Array.isArray(value)) {
265
- return value.map((v) => scrub(v, rules));
266
- }
267
- if (value && typeof value === "object") {
268
- const out = {};
269
- for (const [k, v] of Object.entries(value)) {
270
- out[k] = scrub(v, rules);
271
- }
272
- return out;
273
- }
274
- return value;
275
- }
276
- function scrubFromCwd(value, cwd) {
277
- const rules = [...DEFAULT_RULES, ...loadWorkspaceRules(cwd)];
278
- return scrub(value, rules);
279
- }
280
-
281
202
  // src/hooks/shell-targets.ts
282
203
  import { parse } from "unbash";
283
204
  var OUTPUT_REDIRECTS = /* @__PURE__ */ new Set([">", ">>", ">|", "&>", "&>>"]);
@@ -846,8 +767,5 @@ export {
846
767
  extractFileTargets,
847
768
  readHookMode,
848
769
  demoteForMode,
849
- analyzeShellTargets,
850
- DEFAULT_RULES,
851
- scrub,
852
- scrubFromCwd
770
+ analyzeShellTargets
853
771
  };
@@ -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
+ };