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

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.
@@ -0,0 +1,329 @@
1
+ import {
2
+ formatStatusline,
3
+ packageVersion,
4
+ stripControlChars
5
+ } from "./chunk-K3AYW6VZ.js";
6
+ import {
7
+ decisionIngestionStatus,
8
+ gitToplevel
9
+ } from "./chunk-TDRXHBEX.js";
10
+ import {
11
+ getSiteUrl,
12
+ isSessionEnded,
13
+ withFileLock
14
+ } from "./chunk-RTLFE6EZ.js";
15
+ import {
16
+ daemonRequest
17
+ } from "./chunk-UTKQTZHL.js";
18
+
19
+ // src/hooks/codex-context.ts
20
+ import { createHash, randomBytes } from "crypto";
21
+ import {
22
+ chmodSync,
23
+ existsSync,
24
+ mkdirSync,
25
+ readFileSync,
26
+ readdirSync,
27
+ renameSync,
28
+ rmSync,
29
+ statSync,
30
+ unlinkSync,
31
+ writeFileSync
32
+ } from "fs";
33
+ import { homedir } from "os";
34
+ import { join } from "path";
35
+
36
+ // src/daemon/decision-digest-cache.ts
37
+ var DECISION_DIGEST_CACHE_LIMIT = 100;
38
+ var DECISION_DIGEST_CACHE_WINDOW = "24h";
39
+ var DECISION_DIGEST_CACHE_PATH = `/api/cli/decisions/recent?limit=${String(DECISION_DIGEST_CACHE_LIMIT)}&since=${DECISION_DIGEST_CACHE_WINDOW}`;
40
+
41
+ // src/hooks/codex-context.ts
42
+ var CODEX_CONTEXT_TIMEOUT_MS = 250;
43
+ var CODEX_DIGEST_LIMIT = DECISION_DIGEST_CACHE_LIMIT;
44
+ var CODEX_DIGEST_VISIBLE_CAP = 3;
45
+ var CODEX_DIGEST_OVERLAP_MS = 6e4;
46
+ var CODEX_DIGEST_MAX_SEEN_IDS = 128;
47
+ var CODEX_DIGEST_STATE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
48
+ var CODEX_DIGEST_STATE_MAX_FILES = 256;
49
+ var STATE_VERSION = 1;
50
+ var STATE_DIRECTORY = [".config", "prim", "codex", "decision-digests"];
51
+ var NO_CURSOR = -1;
52
+ function hasVisibleCodexMessage(output) {
53
+ if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
54
+ const candidate = output;
55
+ return Boolean(
56
+ candidate.systemMessage?.trim() || candidate.hookSpecificOutput?.permissionDecisionReason?.trim() || candidate.hookSpecificOutput?.additionalContext?.trim()
57
+ );
58
+ }
59
+ function appendContext(existing, context) {
60
+ return existing ? `${existing}
61
+
62
+ ${context}` : context;
63
+ }
64
+ function appendCodexContext(output, context) {
65
+ if (!context) return output;
66
+ const candidate = output;
67
+ const next = { ...output };
68
+ if (candidate.systemMessage) {
69
+ next.systemMessage = appendContext(candidate.systemMessage, context);
70
+ } else if (candidate.hookSpecificOutput?.permissionDecisionReason) {
71
+ next.systemMessage = context;
72
+ } else if (candidate.hookSpecificOutput?.additionalContext) {
73
+ next.hookSpecificOutput = {
74
+ ...candidate.hookSpecificOutput,
75
+ additionalContext: appendContext(candidate.hookSpecificOutput.additionalContext, context)
76
+ };
77
+ } else {
78
+ next.systemMessage = context;
79
+ }
80
+ return next;
81
+ }
82
+ function stateRoot() {
83
+ return join(homedir(), ...STATE_DIRECTORY);
84
+ }
85
+ function workspaceFor(cwd) {
86
+ try {
87
+ return gitToplevel(cwd) ?? cwd;
88
+ } catch {
89
+ return cwd;
90
+ }
91
+ }
92
+ function statePath(args) {
93
+ const identity = `${args.siteUrl}\0${workspaceFor(args.cwd)}\0${args.sessionId}`;
94
+ const key = createHash("sha256").update(identity).digest("hex");
95
+ return join(stateRoot(), `${key}.json`);
96
+ }
97
+ function parseState(value) {
98
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
99
+ const record = value;
100
+ if (record.version !== STATE_VERSION || typeof record.sessionId !== "string" || typeof record.siteUrl !== "string" || typeof record.workspace !== "string" || typeof record.startedAt !== "number" || typeof record.watermarkMs !== "number" || !Array.isArray(record.seenIds) || !record.seenIds.every((id) => typeof id === "string") || typeof record.updatedAt !== "number") {
101
+ return void 0;
102
+ }
103
+ return {
104
+ version: STATE_VERSION,
105
+ sessionId: record.sessionId,
106
+ siteUrl: record.siteUrl,
107
+ workspace: record.workspace,
108
+ startedAt: record.startedAt,
109
+ watermarkMs: record.watermarkMs,
110
+ seenIds: record.seenIds.slice(-CODEX_DIGEST_MAX_SEEN_IDS),
111
+ lastReport: typeof record.lastReport === "string" ? record.lastReport : void 0,
112
+ updatedAt: record.updatedAt
113
+ };
114
+ }
115
+ function readState(path) {
116
+ if (!existsSync(path)) return void 0;
117
+ try {
118
+ return parseState(JSON.parse(readFileSync(path, "utf8")));
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ }
123
+ function writeState(path, state) {
124
+ const directory = stateRoot();
125
+ mkdirSync(directory, { recursive: true, mode: 448 });
126
+ chmodSync(directory, 448);
127
+ const temporary = join(
128
+ directory,
129
+ `.${path.slice(directory.length + 1)}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`
130
+ );
131
+ try {
132
+ writeFileSync(temporary, `${JSON.stringify(state)}
133
+ `, {
134
+ encoding: "utf8",
135
+ mode: 384,
136
+ flag: "wx"
137
+ });
138
+ chmodSync(temporary, 384);
139
+ renameSync(temporary, path);
140
+ } finally {
141
+ try {
142
+ unlinkSync(temporary);
143
+ } catch {
144
+ }
145
+ }
146
+ }
147
+ function cleanupStateFiles(now) {
148
+ const directory = stateRoot();
149
+ if (!existsSync(directory)) return;
150
+ let names;
151
+ try {
152
+ names = readdirSync(directory);
153
+ } catch {
154
+ return;
155
+ }
156
+ const files = [];
157
+ const residue = [];
158
+ for (const name of names) {
159
+ const path = join(directory, name);
160
+ try {
161
+ const mtimeMs = statSync(path).mtimeMs;
162
+ if (name.endsWith(".json")) {
163
+ files.push({ path, mtimeMs });
164
+ } else if ((name.includes(".json.tmp-") || name.endsWith(".json.lock")) && now - mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS) {
165
+ residue.push(path);
166
+ }
167
+ } catch {
168
+ }
169
+ }
170
+ const stale = files.filter((file) => now - file.mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS);
171
+ const excess = files.filter((file) => !stale.some((candidate) => candidate.path === file.path)).sort((a, b) => a.mtimeMs - b.mtimeMs).slice(0, Math.max(0, files.length - CODEX_DIGEST_STATE_MAX_FILES));
172
+ for (const file of [...stale, ...excess]) {
173
+ try {
174
+ unlinkSync(file.path);
175
+ } catch {
176
+ }
177
+ }
178
+ for (const path of residue) {
179
+ try {
180
+ if (now - statSync(path).mtimeMs > CODEX_DIGEST_STATE_RETENTION_MS) {
181
+ rmSync(path, { recursive: true, force: true });
182
+ }
183
+ } catch {
184
+ }
185
+ }
186
+ }
187
+ function isChangeDecision(row) {
188
+ return row.intentKind === void 0 || row.intentKind === "change";
189
+ }
190
+ function safeInline(value, fallback) {
191
+ const clean = stripControlChars(value ?? "").replace(/\s+/gu, " ").trim();
192
+ if (!clean) return fallback;
193
+ return clean.length <= 240 ? clean : `${clean.slice(0, 239)}\u2026`;
194
+ }
195
+ function renderDecisionDigest(rows, options = {}) {
196
+ if (rows.length === 0) return void 0;
197
+ const visible = rows.slice(0, CODEX_DIGEST_VISIBLE_CAP).map((row) => {
198
+ const author = safeInline(row.authorName, "(unknown)").replace(/[;;;‒-―−]/gu, "-");
199
+ const intent = safeInline(row.intent, "(untitled Decision)").replace(/[“-‟″‶〝〞]/gu, "'");
200
+ return `${author} \u2014 \u201C${intent}\u201D`;
201
+ });
202
+ const overflow = rows.length > CODEX_DIGEST_VISIBLE_CAP ? ` +${String(rows.length - CODEX_DIGEST_VISIBLE_CAP)}${options.pageTruncated === true ? "+" : ""}` : "";
203
+ return `[prim] Decisions captured since last message: ${visible.join("; ")}${overflow}`;
204
+ }
205
+ function reauthSnapshot(sessionId) {
206
+ return {
207
+ pid: 0,
208
+ uptimeMs: 0,
209
+ sessionId,
210
+ healthy: false,
211
+ needsReauth: true
212
+ };
213
+ }
214
+ function mergeState(latest, args) {
215
+ const seen = new Set(latest?.seenIds ?? []);
216
+ for (const id of args.seenIds) seen.add(id);
217
+ return {
218
+ version: STATE_VERSION,
219
+ sessionId: args.sessionId,
220
+ siteUrl: args.siteUrl,
221
+ workspace: args.workspace,
222
+ startedAt: latest?.startedAt ?? args.startedAt,
223
+ // Advance only on an observed page (server classifiedAt time), never the
224
+ // client clock; an unavailable feed keeps the previous cursor — or the
225
+ // NO_CURSOR sentinel, so the startup backlog survives until the first
226
+ // successful fetch.
227
+ watermarkMs: args.feedAvailable ? Math.max(latest?.watermarkMs ?? NO_CURSOR, args.watermarkMs) : latest?.watermarkMs ?? NO_CURSOR,
228
+ seenIds: [...seen].slice(-CODEX_DIGEST_MAX_SEEN_IDS),
229
+ lastReport: args.report,
230
+ updatedAt: args.now
231
+ };
232
+ }
233
+ async function commitState(path, args) {
234
+ try {
235
+ await withFileLock(
236
+ `${path}.lock`,
237
+ () => {
238
+ const latest = readState(path);
239
+ writeState(path, mergeState(latest, args));
240
+ cleanupStateFiles(args.now);
241
+ },
242
+ { timeoutMs: 50, pollMs: 10 }
243
+ );
244
+ } catch {
245
+ }
246
+ }
247
+ async function prepareCodexContext(options) {
248
+ const siteUrl = getSiteUrl();
249
+ const workspace = workspaceFor(options.cwd);
250
+ const path = statePath({ cwd: options.cwd, sessionId: options.sessionId, siteUrl });
251
+ const previous = readState(path);
252
+ const startup = options.startup === true;
253
+ const includeDigest = options.includeDigest !== false;
254
+ const startedAt = previous?.startedAt ?? Date.now();
255
+ const terminalAuth = isSessionEnded();
256
+ let snapshot = null;
257
+ let recent = null;
258
+ if (terminalAuth) {
259
+ snapshot = reauthSnapshot(options.sessionId);
260
+ } else {
261
+ [snapshot, recent] = await Promise.all([
262
+ daemonRequest(
263
+ "status_snapshot",
264
+ { callerEnv: siteUrl },
265
+ { timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
266
+ ),
267
+ includeDigest ? daemonRequest(
268
+ "decision_digest_snapshot",
269
+ { callerEnv: siteUrl },
270
+ { timeoutMs: CODEX_CONTEXT_TIMEOUT_MS }
271
+ ) : Promise.resolve(null)
272
+ ]);
273
+ }
274
+ const report = formatStatusline(
275
+ packageVersion() ?? "0.0.0",
276
+ snapshot,
277
+ () => decisionIngestionStatus(options.cwd),
278
+ { includeIngestionWhenUnavailable: true, plainLinks: true }
279
+ );
280
+ const reportChanged = startup || previous?.lastReport !== report;
281
+ let feedAvailable = false;
282
+ let freshRows = [];
283
+ let pageTruncated = false;
284
+ let pageWatermark = NO_CURSOR;
285
+ if (!terminalAuth && includeDigest && recent !== null) {
286
+ feedAvailable = recent.unavailable === void 0 && Array.isArray(recent.decisions);
287
+ if (feedAvailable) {
288
+ const seen = new Set(previous?.seenIds ?? []);
289
+ const lowerBound = previous !== void 0 && previous.watermarkMs >= 0 ? Math.max(0, previous.watermarkMs - CODEX_DIGEST_OVERLAP_MS) : 0;
290
+ freshRows = recent.decisions.filter(
291
+ (row) => isChangeDecision(row) && !seen.has(row.id) && typeof row.classifiedAt === "number" && row.classifiedAt >= lowerBound
292
+ );
293
+ pageTruncated = recent.decisions.length >= CODEX_DIGEST_LIMIT;
294
+ pageWatermark = recent.decisions.reduce(
295
+ (max, row) => typeof row.classifiedAt === "number" && row.classifiedAt > max ? row.classifiedAt : max,
296
+ NO_CURSOR
297
+ );
298
+ }
299
+ }
300
+ const digest = renderDecisionDigest(freshRows, { pageTruncated });
301
+ const context = [reportChanged ? report : void 0, digest].filter((value) => value !== void 0).join("\n\n") || void 0;
302
+ let acknowledged = false;
303
+ return {
304
+ context,
305
+ decisionDigest: digest,
306
+ feedAvailable,
307
+ acknowledge: async (handedOff) => {
308
+ if (!handedOff || acknowledged) return;
309
+ acknowledged = true;
310
+ await commitState(path, {
311
+ sessionId: options.sessionId,
312
+ siteUrl,
313
+ workspace,
314
+ startedAt,
315
+ watermarkMs: pageWatermark,
316
+ seenIds: freshRows.map((row) => row.id),
317
+ report,
318
+ feedAvailable,
319
+ now: Date.now()
320
+ });
321
+ }
322
+ };
323
+ }
324
+
325
+ export {
326
+ hasVisibleCodexMessage,
327
+ appendCodexContext,
328
+ prepareCodexContext
329
+ };
@@ -0,0 +1,300 @@
1
+ // src/lib/bin-path.ts
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { dirname, isAbsolute, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var PKG_NAME = "@primitive.ai/prim";
6
+ var ROOT_WALK_LIMIT = 6;
7
+ var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
8
+ var resolvedRoot;
9
+ function locateRoot() {
10
+ if (resolvedRoot !== void 0) {
11
+ return resolvedRoot;
12
+ }
13
+ let dir = dirname(fileURLToPath(import.meta.url));
14
+ for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
15
+ const manifestPath = join(dir, "package.json");
16
+ if (existsSync(manifestPath)) {
17
+ try {
18
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
19
+ if (manifest.name === PKG_NAME && manifest.bin) {
20
+ resolvedRoot = { dir, version: manifest.version, bin: manifest.bin };
21
+ return resolvedRoot;
22
+ }
23
+ } catch {
24
+ }
25
+ }
26
+ const parent = dirname(dir);
27
+ if (parent === dir) {
28
+ break;
29
+ }
30
+ dir = parent;
31
+ }
32
+ resolvedRoot = null;
33
+ return resolvedRoot;
34
+ }
35
+ function binFile(bin) {
36
+ const root = locateRoot();
37
+ const rel = root?.bin[bin];
38
+ if (!root || !rel) {
39
+ return null;
40
+ }
41
+ return isAbsolute(rel) ? rel : join(root.dir, rel);
42
+ }
43
+ function packageVersion() {
44
+ return locateRoot()?.version ?? null;
45
+ }
46
+ function shellQuote(value) {
47
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
48
+ }
49
+ function pinnedHookCommand(bin, args = "") {
50
+ const version = packageVersion();
51
+ if (!version) throw new Error("cannot determine Primitive package version");
52
+ const suffix = args ? ` ${args}` : "";
53
+ const fallback = `npx --yes -p ${PKG_NAME}@${version} ${bin}${suffix}`;
54
+ const file = binFile(bin);
55
+ if (!file) return fallback;
56
+ return `if [ -x ${shellQuote(process.execPath)} ] && [ -f ${shellQuote(file)} ]; then ${shellQuote(process.execPath)} ${shellQuote(file)}${suffix}; else ${fallback}; fi`;
57
+ }
58
+ function detachedHookShimCommand(bin, args = "") {
59
+ return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${pinnedHookCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
60
+ }
61
+ function commandMatchesBin(command, bin) {
62
+ if (!command) {
63
+ return false;
64
+ }
65
+ const c = command.trim();
66
+ if (c === bin || c.startsWith(`${bin} `)) {
67
+ return true;
68
+ }
69
+ const exactBin = new RegExp(`(?:^|\\s)${bin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|;|$)`);
70
+ return c.includes(`command -v ${bin} `) || c.includes(`-p ${PKG_NAME}@`) && exactBin.test(c);
71
+ }
72
+
73
+ // src/lib/bin-cache.ts
74
+ import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
75
+ import { homedir } from "os";
76
+ import { join as join2 } from "path";
77
+ var CACHED_BINS = [
78
+ "prim",
79
+ "prim-hook",
80
+ "prim-pre-tool-use",
81
+ "prim-post-tool-use",
82
+ "prim-post-commit",
83
+ "prim-post-rewrite"
84
+ ];
85
+ function binCacheDir() {
86
+ const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
87
+ return join2(base, "prim", "bin");
88
+ }
89
+ function writeAtomic(path, content) {
90
+ const tmp = `${path}.${process.pid}.tmp`;
91
+ writeFileSync(tmp, content, { mode: 384 });
92
+ renameSync(tmp, path);
93
+ }
94
+ function warmBinCache() {
95
+ try {
96
+ if (process.env.PRIM_BIN_CACHE_HIT) {
97
+ return;
98
+ }
99
+ if (process.env.PRIM_BIN_CACHE === "0") {
100
+ return;
101
+ }
102
+ const dir = binCacheDir();
103
+ mkdirSync(dir, { recursive: true, mode: 448 });
104
+ chmodSync(dir, 448);
105
+ writeAtomic(join2(dir, "node"), process.execPath);
106
+ for (const bin of CACHED_BINS) {
107
+ const file = binFile(bin);
108
+ if (file) {
109
+ writeAtomic(join2(dir, bin), file);
110
+ }
111
+ }
112
+ } catch {
113
+ }
114
+ }
115
+
116
+ // src/lib/ansi.ts
117
+ var ANSI_CODES = {
118
+ yellow: "\x1B[33m",
119
+ magenta: "\x1B[35m",
120
+ blue: "\x1B[34m",
121
+ cyan: "\x1B[36m",
122
+ green: "\x1B[32m",
123
+ red: "\x1B[31m",
124
+ // 256-color approximation of the orange trigger marker (xterm 208).
125
+ orange: "\x1B[38;5;208m",
126
+ gray: "\x1B[90m"
127
+ };
128
+ var ANSI_RESET = "\x1B[0m";
129
+ var ANSI_DIM = "\x1B[2m";
130
+ var ANSI_BOLD = "\x1B[1m";
131
+ function supportsColor() {
132
+ if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
133
+ return false;
134
+ }
135
+ return process.stderr.isTTY === true;
136
+ }
137
+ function color(text, c) {
138
+ if (!supportsColor()) {
139
+ return text;
140
+ }
141
+ return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
142
+ }
143
+ function dim(text) {
144
+ if (!supportsColor()) {
145
+ return text;
146
+ }
147
+ return `${ANSI_DIM}${text}${ANSI_RESET}`;
148
+ }
149
+ function bold(text) {
150
+ if (!supportsColor()) {
151
+ return text;
152
+ }
153
+ return `${ANSI_BOLD}${text}${ANSI_RESET}`;
154
+ }
155
+ var AREA_COLORS = {
156
+ auth: "yellow",
157
+ data: "magenta",
158
+ mobile: "blue",
159
+ infra: "cyan",
160
+ ui: "green",
161
+ billing: "orange",
162
+ api: "blue",
163
+ docs: "gray",
164
+ testing: "gray"
165
+ };
166
+ function colorForArea(area) {
167
+ if (!area) {
168
+ return "gray";
169
+ }
170
+ return AREA_COLORS[area] ?? "gray";
171
+ }
172
+ function stripControlChars(text) {
173
+ let out = "";
174
+ for (const ch of text) {
175
+ const code = ch.codePointAt(0) ?? 0;
176
+ if (code > 31 && !(code >= 127 && code <= 159)) {
177
+ out += ch;
178
+ }
179
+ }
180
+ return out;
181
+ }
182
+ var MAX_HEALTH_ERROR_LENGTH = 240;
183
+ function boundedHealthError(value) {
184
+ if (!value) return void 0;
185
+ const clean = stripControlChars(value).replace(/\s+/g, " ").trim();
186
+ if (!clean) return void 0;
187
+ return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
188
+ }
189
+ function stripAnsi(text) {
190
+ return text.replace(/\u001b\[[0-9;]*m/g, "");
191
+ }
192
+
193
+ // src/lib/presence.ts
194
+ var DECISION_ORIGIN = "https://app.getprimitive.ai";
195
+ function validDecisionUrl(value) {
196
+ if (typeof value !== "string" || stripControlChars(value) !== value) {
197
+ return void 0;
198
+ }
199
+ try {
200
+ const url = new URL(value);
201
+ if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
202
+ return void 0;
203
+ }
204
+ return url.href;
205
+ } catch {
206
+ return void 0;
207
+ }
208
+ }
209
+ function decisionLink(label, decisionUrl, plain) {
210
+ const url = validDecisionUrl(decisionUrl);
211
+ if (plain || !url) {
212
+ return label;
213
+ }
214
+ return `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07`;
215
+ }
216
+ function formatLabeled(labels, cap) {
217
+ if (labels === void 0) {
218
+ return "\u2014";
219
+ }
220
+ if (labels.length === 0) {
221
+ return "just you";
222
+ }
223
+ if (labels.length <= cap) {
224
+ return labels.join(", ");
225
+ }
226
+ return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
227
+ }
228
+ function formatTeammates(names, cap) {
229
+ return formatLabeled(names?.map(stripControlChars), cap);
230
+ }
231
+ function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
232
+ return formatLabeled(
233
+ teammates?.map((t) => {
234
+ const name = stripControlChars(t.name);
235
+ const area = stripControlChars(t.area ?? "").trim();
236
+ const label = area ? `${name} - ${area}` : name;
237
+ return decisionLink(label, t.decisionUrl, plainLinks);
238
+ }),
239
+ cap
240
+ );
241
+ }
242
+
243
+ // src/lib/statusline-render.ts
244
+ var STATUSLINE_NAME_CAP = 3;
245
+ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {}) {
246
+ const ingestionSuffix = (status) => status === void 0 ? "" : ` \xB7 Decision ingestion ${status}`;
247
+ const ingestionStatus = snapshot === null || snapshot.healthy === false ? options.includeIngestionWhenUnavailable ? resolveIngestionStatus() : void 0 : resolveIngestionStatus();
248
+ if (!snapshot) {
249
+ return `primitive ${version} (daemon: down${ingestionSuffix(ingestionStatus)})`;
250
+ }
251
+ if (snapshot.healthy === false) {
252
+ if (snapshot.needsReauth) {
253
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
254
+ }
255
+ if (snapshot.ingestion?.healthy === false) {
256
+ const pending = snapshot.ingestion.pendingCount;
257
+ const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
258
+ return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${ingestionSuffix(ingestionStatus)})`;
259
+ }
260
+ if (snapshot.heartbeat?.healthy === false) {
261
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${ingestionSuffix(ingestionStatus)})`;
262
+ }
263
+ return `primitive ${version} (daemon: starting${ingestionSuffix(ingestionStatus)})`;
264
+ }
265
+ if (snapshot.envMismatch) {
266
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
267
+ }
268
+ if (snapshot.presenceStale) {
269
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
270
+ }
271
+ let team;
272
+ if (snapshot.onlineTeammates !== void 0) {
273
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
274
+ } else if (snapshot.onlineNames !== void 0) {
275
+ team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
276
+ } else if (typeof snapshot.onlineCount === "number") {
277
+ team = `team: ${String(snapshot.onlineCount)} online`;
278
+ } else {
279
+ team = "team: \u2014";
280
+ }
281
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
282
+ }
283
+
284
+ export {
285
+ color,
286
+ dim,
287
+ bold,
288
+ colorForArea,
289
+ stripControlChars,
290
+ boundedHealthError,
291
+ stripAnsi,
292
+ binFile,
293
+ packageVersion,
294
+ pinnedHookCommand,
295
+ detachedHookShimCommand,
296
+ commandMatchesBin,
297
+ formatTeammates,
298
+ warmBinCache,
299
+ formatStatusline
300
+ };
@@ -422,10 +422,12 @@ function refreshToken(options = {}) {
422
422
  }
423
423
  var HttpError = class extends Error {
424
424
  status;
425
- constructor(status, message) {
425
+ body;
426
+ constructor(status, message, body) {
426
427
  super(message);
427
428
  this.name = "HttpError";
428
429
  this.status = status;
430
+ this.body = body;
429
431
  }
430
432
  };
431
433
  var _cachedCredential;
@@ -486,7 +488,9 @@ async function request(method, path, body, options) {
486
488
  throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
487
489
  }
488
490
  const errorBody = await response.json().catch(() => null);
489
- throw new HttpError(response.status, errorBody?.error ?? `HTTP ${response.status}`);
491
+ const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
492
+ const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
493
+ throw new HttpError(response.status, message, errorBody);
490
494
  }
491
495
  return response.json();
492
496
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getClient
3
- } from "./chunk-CPX3BUOC.js";
3
+ } from "./chunk-RTLFE6EZ.js";
4
4
 
5
5
  // src/decisions/feedback.ts
6
6
  var FEEDBACK_PROTOCOL_VERSION = 1;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getSiteUrl
3
- } from "./chunk-CPX3BUOC.js";
3
+ } from "./chunk-RTLFE6EZ.js";
4
4
 
5
5
  // src/journal.ts
6
6
  import {
@@ -0,0 +1,26 @@
1
+ // src/ingest-response.ts
2
+ var IngestAcknowledgementError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "IngestAcknowledgementError";
6
+ }
7
+ };
8
+ function requireDurableIngestAcknowledgement(value, expected) {
9
+ if (typeof value !== "object" || value === null) {
10
+ throw new IngestAcknowledgementError("ingest response was not an object");
11
+ }
12
+ const response = value;
13
+ if (response.disposition !== "persisted") {
14
+ throw new IngestAcknowledgementError("ingest response did not confirm persistence");
15
+ }
16
+ if (response.acknowledged !== expected) {
17
+ throw new IngestAcknowledgementError(
18
+ `ingest acknowledged ${String(response.acknowledged)} of ${String(expected)} moves`
19
+ );
20
+ }
21
+ return value;
22
+ }
23
+
24
+ export {
25
+ requireDurableIngestAcknowledgement
26
+ };