@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.81

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-3EEFL6NJ.js +529 -0
  4. package/dist/chunk-4CZTNXIS.js +505 -0
  5. package/dist/chunk-4GLVJLEQ.js +55 -0
  6. package/dist/chunk-BQUJ4AFJ.js +771 -0
  7. package/dist/chunk-CUE2LKLR.js +155 -0
  8. package/dist/chunk-EZXEWFDG.js +210 -0
  9. package/dist/chunk-FI3LIRCB.js +262 -0
  10. package/dist/chunk-HWSS4BV5.js +1400 -0
  11. package/dist/chunk-LEMX3IOG.js +79 -0
  12. package/dist/chunk-LMRYZL3D.js +209 -0
  13. package/dist/chunk-OUXH3UEP.js +247 -0
  14. package/dist/chunk-PMRDE2DA.js +310 -0
  15. package/dist/chunk-RO3F6JYV.js +126 -0
  16. package/dist/chunk-TJTVHDJ5.js +321 -0
  17. package/dist/chunk-WB7AUZ4C.js +52 -0
  18. package/dist/chunk-XGADQPCI.js +26 -0
  19. package/dist/chunk-YWO7O6QG.js +327 -0
  20. package/dist/chunk-Z7YPFWAZ.js +917 -0
  21. package/dist/chunk-ZOSNEO5N.js +77 -0
  22. package/dist/daemon/server.js +15329 -0
  23. package/dist/hook-runtime/daemon/server.js +15329 -0
  24. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  25. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  26. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  27. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  28. package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
  29. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  30. package/dist/hook-runtime/hooks/session-end.js +495 -0
  31. package/dist/hook-runtime/hooks/session-start.js +11313 -0
  32. package/dist/hook-runtime/index.js +44696 -0
  33. package/dist/hook-runtime/statusline-main.js +765 -0
  34. package/dist/hooks/post-commit.js +179 -0
  35. package/dist/hooks/post-rewrite.js +197 -0
  36. package/dist/hooks/post-tool-use.js +250 -0
  37. package/dist/hooks/pre-commit.js +21 -74
  38. package/dist/hooks/pre-tool-use.js +348 -0
  39. package/dist/hooks/prim-hook.js +195 -0
  40. package/dist/hooks/session-end.js +70 -0
  41. package/dist/hooks/session-start.js +299 -0
  42. package/dist/index.js +20981 -519
  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,917 @@
1
+ import {
2
+ CREDENTIAL_FAMILY_PATH,
3
+ CREDENTIAL_LOCK_PATH,
4
+ CREDENTIAL_METADATA_PATH,
5
+ CREDENTIAL_MIGRATION_PATH,
6
+ CREDENTIAL_MIGRATION_STATE,
7
+ CREDENTIAL_MIGRATION_VERSION,
8
+ REFRESH_TOKEN_PATH,
9
+ TERMINAL_REFRESH_PATH,
10
+ TOKEN_EXPIRES_PATH,
11
+ TOKEN_FILE_PATH,
12
+ WORKOS_CONNECT_RESPONSE_MAX_BYTES,
13
+ jwtExpiresAt,
14
+ parseWorkosConnectTokens,
15
+ readBoundedJson,
16
+ readCredentialMigrationState,
17
+ readStoredCredentialMetadata,
18
+ resolveAuthCredential,
19
+ workosConnectTokenEndpoint
20
+ } from "./chunk-3EEFL6NJ.js";
21
+
22
+ // src/lib/atomic-file.ts
23
+ import { randomUUID } from "crypto";
24
+ import {
25
+ constants,
26
+ closeSync,
27
+ fchmodSync,
28
+ fsyncSync,
29
+ mkdirSync,
30
+ openSync,
31
+ renameSync,
32
+ rmSync,
33
+ writeFileSync
34
+ } from "fs";
35
+ import { dirname } from "path";
36
+ var DEFAULT_FILE_MODE = 438;
37
+ function syncDirectory(path) {
38
+ const fd = openSync(path, constants.O_RDONLY);
39
+ try {
40
+ fsyncSync(fd);
41
+ } finally {
42
+ closeSync(fd);
43
+ }
44
+ }
45
+ function syncFile(path) {
46
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
47
+ try {
48
+ fsyncSync(fd);
49
+ } finally {
50
+ closeSync(fd);
51
+ }
52
+ }
53
+ function syncCreatedParentChain(firstCreatedParent, parent) {
54
+ const createdDirectories = [];
55
+ let current = parent;
56
+ while (current !== firstCreatedParent) {
57
+ createdDirectories.push(current);
58
+ const next = dirname(current);
59
+ if (next === current) {
60
+ throw new Error("recursive mkdir returned a path outside the target parent chain");
61
+ }
62
+ current = next;
63
+ }
64
+ createdDirectories.push(firstCreatedParent);
65
+ syncDirectory(dirname(firstCreatedParent));
66
+ for (const directory of createdDirectories.reverse().slice(0, -1)) {
67
+ syncDirectory(directory);
68
+ }
69
+ }
70
+ function ensureDurableDirectory(path, mode) {
71
+ const firstCreatedParent = mkdirSync(path, { recursive: true, mode });
72
+ if (firstCreatedParent) {
73
+ syncCreatedParentChain(firstCreatedParent, path);
74
+ }
75
+ }
76
+ function atomicWriteFile(target, content, options = {}) {
77
+ const parent = dirname(target);
78
+ if (options.ensureParent) {
79
+ ensureDurableDirectory(parent);
80
+ }
81
+ const temporaryPath = `${target}.${randomUUID()}.tmp`;
82
+ let temporaryCreated = false;
83
+ try {
84
+ const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
85
+ const fd = openSync(temporaryPath, flags, options.mode ?? DEFAULT_FILE_MODE);
86
+ temporaryCreated = true;
87
+ try {
88
+ if (options.mode !== void 0) fchmodSync(fd, options.mode);
89
+ writeFileSync(fd, content);
90
+ fsyncSync(fd);
91
+ } finally {
92
+ closeSync(fd);
93
+ }
94
+ options.validate?.(temporaryPath);
95
+ renameSync(temporaryPath, target);
96
+ syncDirectory(parent);
97
+ } catch (error) {
98
+ if (temporaryCreated) {
99
+ try {
100
+ rmSync(temporaryPath, { force: true });
101
+ } catch {
102
+ }
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ // src/lib/process-liveness.ts
109
+ function processIsAlive(pid, probe = process.kill) {
110
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
111
+ try {
112
+ probe(pid, 0);
113
+ return true;
114
+ } catch (error) {
115
+ return error.code === "EPERM";
116
+ }
117
+ }
118
+
119
+ // src/lib/file-lock.ts
120
+ import { randomBytes } from "crypto";
121
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
122
+ import { dirname as dirname2, join } from "path";
123
+ var DEFAULT_TIMEOUT_MS = 3e4;
124
+ var DEFAULT_POLL_MS = 50;
125
+ var DEFAULT_INIT_GRACE_MS = 2e3;
126
+ var DIRECTORY_MODE = 448;
127
+ var FILE_MODE = 384;
128
+ function ownerPath(lockDir) {
129
+ return join(lockDir, "owner.json");
130
+ }
131
+ function readOwner(lockDir) {
132
+ try {
133
+ const owner = JSON.parse(readFileSync(ownerPath(lockDir), "utf8"));
134
+ if (!Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.nonce !== "string" || typeof owner.createdAt !== "number") {
135
+ return null;
136
+ }
137
+ return owner;
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+ function sameOwner(a, b) {
143
+ return a?.pid === b.pid && a.nonce === b.nonce && a.createdAt === b.createdAt;
144
+ }
145
+ function tryTakeLock(lockDir, now) {
146
+ try {
147
+ mkdirSync2(lockDir, { mode: DIRECTORY_MODE });
148
+ } catch (error) {
149
+ if (error.code === "EEXIST") return null;
150
+ throw error;
151
+ }
152
+ const owner = {
153
+ pid: process.pid,
154
+ nonce: randomBytes(16).toString("hex"),
155
+ createdAt: now
156
+ };
157
+ try {
158
+ writeFileSync2(ownerPath(lockDir), `${JSON.stringify(owner)}
159
+ `, {
160
+ encoding: "utf8",
161
+ mode: FILE_MODE,
162
+ flag: "wx"
163
+ });
164
+ return owner;
165
+ } catch (error) {
166
+ rmSync2(lockDir, { recursive: true, force: true });
167
+ throw error;
168
+ }
169
+ }
170
+ function recoverStaleLock(lockDir, now, alive, initGraceMs) {
171
+ const owner = readOwner(lockDir);
172
+ if (owner) {
173
+ if (alive(owner.pid)) return false;
174
+ if (!sameOwner(readOwner(lockDir), owner)) return false;
175
+ rmSync2(lockDir, { recursive: true, force: true });
176
+ return true;
177
+ }
178
+ try {
179
+ const before = statSync(lockDir).mtimeMs;
180
+ if (now - before < initGraceMs) return false;
181
+ if (readOwner(lockDir)) return false;
182
+ if (statSync(lockDir).mtimeMs !== before) return false;
183
+ rmSync2(lockDir, { recursive: true, force: true });
184
+ return true;
185
+ } catch {
186
+ return true;
187
+ }
188
+ }
189
+ function abortError(signal) {
190
+ if (signal.reason instanceof Error) return signal.reason;
191
+ const error = new Error("The operation was aborted");
192
+ error.name = "AbortError";
193
+ return error;
194
+ }
195
+ function throwIfAborted(signal) {
196
+ if (signal?.aborted) throw abortError(signal);
197
+ }
198
+ async function defaultSleep(ms, signal) {
199
+ if (!signal) {
200
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
201
+ return;
202
+ }
203
+ throwIfAborted(signal);
204
+ await new Promise((resolve2, reject) => {
205
+ const timer = setTimeout(() => {
206
+ signal.removeEventListener("abort", onAbort);
207
+ resolve2();
208
+ }, ms);
209
+ const onAbort = () => {
210
+ clearTimeout(timer);
211
+ reject(abortError(signal));
212
+ };
213
+ signal.addEventListener("abort", onAbort, { once: true });
214
+ });
215
+ }
216
+ var syncSleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
217
+ function defaultSleepSync(ms) {
218
+ Atomics.wait(syncSleeper, 0, 0, ms);
219
+ }
220
+ function withFileLockSync(lockDir, operation, options = {}) {
221
+ mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
222
+ const nowMs = options.nowMs ?? Date.now;
223
+ const alive = options.processAlive ?? processIsAlive;
224
+ const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
225
+ let owner = null;
226
+ while (!owner) {
227
+ const now = nowMs();
228
+ owner = tryTakeLock(lockDir, now);
229
+ if (owner) break;
230
+ recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
231
+ if (now >= deadline) {
232
+ throw new Error(`timed out waiting for file lock ${lockDir}`);
233
+ }
234
+ (options.sleep ?? defaultSleepSync)(options.pollMs ?? DEFAULT_POLL_MS);
235
+ }
236
+ try {
237
+ return operation();
238
+ } finally {
239
+ if (sameOwner(readOwner(lockDir), owner)) {
240
+ rmSync2(lockDir, { recursive: true, force: true });
241
+ }
242
+ }
243
+ }
244
+ async function withFileLock(lockDir, operation, options = {}) {
245
+ mkdirSync2(dirname2(lockDir), { recursive: true, mode: DIRECTORY_MODE });
246
+ const nowMs = options.nowMs ?? Date.now;
247
+ const alive = options.processAlive ?? processIsAlive;
248
+ const deadline = nowMs() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
249
+ let owner = null;
250
+ while (!owner) {
251
+ throwIfAborted(options.signal);
252
+ const now = nowMs();
253
+ owner = tryTakeLock(lockDir, now);
254
+ if (owner) break;
255
+ recoverStaleLock(lockDir, now, alive, options.initGraceMs ?? DEFAULT_INIT_GRACE_MS);
256
+ if (now >= deadline) {
257
+ throw new Error(`timed out waiting for file lock ${lockDir}`);
258
+ }
259
+ if (options.sleep) {
260
+ await options.sleep(options.pollMs ?? DEFAULT_POLL_MS);
261
+ throwIfAborted(options.signal);
262
+ } else {
263
+ await defaultSleep(options.pollMs ?? DEFAULT_POLL_MS, options.signal);
264
+ }
265
+ }
266
+ try {
267
+ throwIfAborted(options.signal);
268
+ return await operation();
269
+ } finally {
270
+ if (sameOwner(readOwner(lockDir), owner)) {
271
+ rmSync2(lockDir, { recursive: true, force: true });
272
+ }
273
+ }
274
+ }
275
+
276
+ // src/lib/terminal-safe.ts
277
+ function replaceIsolatedSurrogates(value) {
278
+ let out = "";
279
+ for (let index = 0; index < value.length; index += 1) {
280
+ const unit = value.charCodeAt(index);
281
+ if (unit >= 55296 && unit <= 56319) {
282
+ const next = value.charCodeAt(index + 1);
283
+ if (next >= 56320 && next <= 57343) {
284
+ out += value[index] + value[index + 1];
285
+ index += 1;
286
+ } else {
287
+ out += "\uFFFD";
288
+ }
289
+ } else if (unit >= 56320 && unit <= 57343) {
290
+ out += "\uFFFD";
291
+ } else {
292
+ out += value[index];
293
+ }
294
+ }
295
+ return out;
296
+ }
297
+ function isUnsafeTerminalCodePoint(point) {
298
+ const c0OrC1 = point <= 31 || point >= 127 && point <= 159;
299
+ const bidiOrZeroWidth = point === 173 || // soft hyphen
300
+ point === 1564 || // Arabic letter mark
301
+ point === 6158 || // Mongolian vowel separator
302
+ point >= 8203 && point <= 8207 || // zero-width + directional marks
303
+ point >= 8234 && point <= 8238 || // bidi embeddings/overrides
304
+ point >= 8288 && point <= 8303 || // word joiner + bidi isolates/controls
305
+ point >= 65529 && point <= 65531 || // interlinear annotation controls
306
+ point === 65279 || // zero-width no-break space / BOM
307
+ point >= 917504 && point <= 917631;
308
+ return c0OrC1 || bidiOrZeroWidth;
309
+ }
310
+ function terminalSafeText(value) {
311
+ return Array.from(replaceIsolatedSurrogates(value)).filter((character) => !isUnsafeTerminalCodePoint(character.codePointAt(0) ?? 0)).join("");
312
+ }
313
+ function terminalSafeLine(value) {
314
+ return terminalSafeText(value.replace(/\s+/gu, " ")).replace(/\s+/gu, " ").trim();
315
+ }
316
+ function isTerminalSafeText(value) {
317
+ return terminalSafeText(value) === value;
318
+ }
319
+ var stripControlChars = terminalSafeText;
320
+
321
+ // src/client.ts
322
+ import { createHash } from "crypto";
323
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, rmSync as rmSync3 } from "fs";
324
+ import { dirname as dirname3 } from "path";
325
+ var CONFIG_DIR_MODE = 448;
326
+ var CREDENTIAL_FILE_MODE = 384;
327
+ var REFRESH_THRESHOLD_MS = 6e4;
328
+ var DEFAULT_API_URL = "https://api.getprimitive.ai";
329
+ var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
330
+ function readTrimmed(path) {
331
+ try {
332
+ const value = readFileSync2(path, "utf8").trim();
333
+ return value || void 0;
334
+ } catch {
335
+ return void 0;
336
+ }
337
+ }
338
+ function getSiteUrl() {
339
+ return getSiteUrlForEnvironment(process.env.PRIM_API_URL);
340
+ }
341
+ function getSiteUrlForEnvironment(primApiUrl) {
342
+ return primApiUrl?.trim() || DEFAULT_API_URL;
343
+ }
344
+ function expiresAtFor(token, expiresIn) {
345
+ return expiresIn === void 0 ? jwtExpiresAt(token) : Date.now() + expiresIn * 1e3;
346
+ }
347
+ function ensureConfigDirectory() {
348
+ const directory = dirname3(TOKEN_FILE_PATH);
349
+ mkdirSync3(directory, { recursive: true, mode: CONFIG_DIR_MODE });
350
+ chmodSync(directory, CONFIG_DIR_MODE);
351
+ }
352
+ function atomicWrite(path, content) {
353
+ ensureConfigDirectory();
354
+ atomicWriteFile(path, content, { mode: CREDENTIAL_FILE_MODE });
355
+ }
356
+ function removeCredentialFile(path) {
357
+ if (!existsSync2(path)) return false;
358
+ rmSync3(path, { force: true });
359
+ return true;
360
+ }
361
+ function getTokenExpiresAt() {
362
+ const stored = readTrimmed(TOKEN_EXPIRES_PATH);
363
+ if (stored === void 0) return void 0;
364
+ const value = Number(stored);
365
+ return Number.isNaN(value) ? void 0 : value;
366
+ }
367
+ function isTokenExpiringSoon(credential) {
368
+ if (credential.source !== "token_file") return false;
369
+ const expiresAt = getTokenExpiresAt();
370
+ return expiresAt !== void 0 && Date.now() >= expiresAt - REFRESH_THRESHOLD_MS;
371
+ }
372
+ function refreshFingerprint(refreshToken2) {
373
+ return createHash("sha256").update(refreshToken2).digest("hex");
374
+ }
375
+ function legacyBrokerCredentialMetadata(accessToken, refreshToken2) {
376
+ return {
377
+ version: 1,
378
+ family: "legacy_broker",
379
+ accessTokenHash: refreshFingerprint(accessToken),
380
+ refreshTokenHash: refreshFingerprint(refreshToken2)
381
+ };
382
+ }
383
+ function credentialFamilyMetadata(accessToken, refreshToken2, metadata) {
384
+ const hashes = {
385
+ accessTokenHash: refreshFingerprint(accessToken),
386
+ refreshTokenHash: refreshFingerprint(refreshToken2)
387
+ };
388
+ return metadata === void 0 ? legacyBrokerCredentialMetadata(accessToken, refreshToken2) : { ...metadata, ...hashes };
389
+ }
390
+ function writeCredentialMigrationSentinel() {
391
+ atomicWrite(
392
+ CREDENTIAL_MIGRATION_PATH,
393
+ `${JSON.stringify({
394
+ version: CREDENTIAL_MIGRATION_VERSION,
395
+ state: CREDENTIAL_MIGRATION_STATE
396
+ })}
397
+ `
398
+ );
399
+ }
400
+ function bindLegacyBrokerGeneration(accessToken, refreshToken2, metadata) {
401
+ if (metadata.state !== "legacy_broker" || !accessToken || !refreshToken2) {
402
+ return { state: "invalid" };
403
+ }
404
+ const familyMetadata = legacyBrokerCredentialMetadata(accessToken, refreshToken2);
405
+ if (metadata.metadata !== void 0 && (metadata.metadata.accessTokenHash !== familyMetadata.accessTokenHash || metadata.metadata.refreshTokenHash !== familyMetadata.refreshTokenHash)) {
406
+ return { state: "invalid" };
407
+ }
408
+ if (metadata.metadata === void 0) {
409
+ atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
410
+ `);
411
+ }
412
+ const migrationState = readCredentialMigrationState();
413
+ if (migrationState === "invalid") return { state: "invalid" };
414
+ if (migrationState === "absent") writeCredentialMigrationSentinel();
415
+ return { state: "legacy_broker", metadata: familyMetadata };
416
+ }
417
+ function preserveMarkerlessLegacyGeneration() {
418
+ const metadata = readStoredCredentialMetadata();
419
+ if (metadata.state !== "legacy_broker" || metadata.metadata !== void 0) return;
420
+ const accessToken = readTrimmed(TOKEN_FILE_PATH);
421
+ const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
422
+ if (!(accessToken && refreshToken2)) return;
423
+ const familyMetadata = legacyBrokerCredentialMetadata(accessToken, refreshToken2);
424
+ atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
425
+ `);
426
+ }
427
+ function terminalFingerprint() {
428
+ return readTrimmed(TERMINAL_REFRESH_PATH);
429
+ }
430
+ function writeTerminalFingerprint(refreshToken2) {
431
+ atomicWrite(TERMINAL_REFRESH_PATH, `${refreshFingerprint(refreshToken2)}
432
+ `);
433
+ }
434
+ function clearTerminalFingerprint() {
435
+ removeCredentialFile(TERMINAL_REFRESH_PATH);
436
+ }
437
+ function isSessionEnded() {
438
+ if (resolveAuthCredential()?.source !== "token_file") return false;
439
+ const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
440
+ const ended = terminalFingerprint();
441
+ return Boolean(refreshToken2 && ended && refreshFingerprint(refreshToken2) === ended);
442
+ }
443
+ function withCredentialLock(operation, options = {}) {
444
+ return withFileLock(CREDENTIAL_LOCK_PATH, operation, options);
445
+ }
446
+ function commitCredentialsUnlocked(credentials) {
447
+ const accessToken = credentials.accessToken.trim();
448
+ const refreshToken2 = credentials.refreshToken.trim();
449
+ if (!accessToken || !refreshToken2) {
450
+ throw new Error("OAuth credentials require both access and refresh tokens");
451
+ }
452
+ const familyMetadata = credentialFamilyMetadata(accessToken, refreshToken2, credentials.metadata);
453
+ if (credentials.metadata !== void 0) {
454
+ preserveMarkerlessLegacyGeneration();
455
+ writeCredentialMigrationSentinel();
456
+ atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
457
+ `);
458
+ atomicWrite(CREDENTIAL_METADATA_PATH, `${JSON.stringify(familyMetadata)}
459
+ `);
460
+ atomicWrite(REFRESH_TOKEN_PATH, `${refreshToken2}
461
+ `);
462
+ } else {
463
+ atomicWrite(REFRESH_TOKEN_PATH, `${refreshToken2}
464
+ `);
465
+ atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
466
+ `);
467
+ removeCredentialFile(CREDENTIAL_METADATA_PATH);
468
+ }
469
+ const expiresAt = expiresAtFor(accessToken, credentials.expiresIn);
470
+ if (expiresAt === void 0) removeCredentialFile(TOKEN_EXPIRES_PATH);
471
+ else atomicWrite(TOKEN_EXPIRES_PATH, `${expiresAt}
472
+ `);
473
+ atomicWrite(TOKEN_FILE_PATH, `${accessToken}
474
+ `);
475
+ clearTerminalFingerprint();
476
+ _cachedCredential = { token: accessToken, source: "token_file" };
477
+ }
478
+ async function commitCredentials(credentials, options = {}) {
479
+ await withCredentialLock(() => commitCredentialsUnlocked(credentials), options);
480
+ }
481
+ async function setStoredToken(token, options = {}) {
482
+ const value = token.trim();
483
+ if (!value) throw new Error("Token cannot be empty");
484
+ await withCredentialLock(() => {
485
+ removeCredentialFile(REFRESH_TOKEN_PATH);
486
+ removeCredentialFile(TOKEN_EXPIRES_PATH);
487
+ removeCredentialFile(CREDENTIAL_FAMILY_PATH);
488
+ removeCredentialFile(CREDENTIAL_METADATA_PATH);
489
+ clearTerminalFingerprint();
490
+ removeCredentialFile(CREDENTIAL_MIGRATION_PATH);
491
+ atomicWrite(TOKEN_FILE_PATH, `${value}
492
+ `);
493
+ _cachedCredential = { token: value, source: "token_file" };
494
+ }, options);
495
+ }
496
+ async function clearStoredCredentials(options = {}) {
497
+ const { beforeClear, ...lockOptions } = options;
498
+ return withCredentialLock(async () => {
499
+ const accessToken = readTrimmed(TOKEN_FILE_PATH);
500
+ const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
501
+ const metadata = readStoredCredentialMetadata();
502
+ let protectedMetadata = metadata;
503
+ if (metadata.state === "legacy_broker") {
504
+ try {
505
+ protectedMetadata = bindLegacyBrokerGeneration(accessToken, refreshToken2, metadata);
506
+ } catch {
507
+ protectedMetadata = { state: "invalid" };
508
+ }
509
+ }
510
+ let callbackError;
511
+ try {
512
+ await beforeClear?.(refreshToken2, protectedMetadata);
513
+ } catch (error) {
514
+ callbackError = error;
515
+ }
516
+ let removed = false;
517
+ for (const path of [
518
+ REFRESH_TOKEN_PATH,
519
+ TOKEN_EXPIRES_PATH,
520
+ CREDENTIAL_FAMILY_PATH,
521
+ CREDENTIAL_METADATA_PATH,
522
+ TERMINAL_REFRESH_PATH,
523
+ TOKEN_FILE_PATH,
524
+ CREDENTIAL_MIGRATION_PATH
525
+ ]) {
526
+ removed = removeCredentialFile(path) || removed;
527
+ }
528
+ _cachedCredential = void 0;
529
+ if (callbackError) throw callbackError;
530
+ return removed;
531
+ }, lockOptions);
532
+ }
533
+ function isTerminalRefreshResponse(response, detail) {
534
+ if (detail.includes("invalid_grant") || detail.includes("Session has already ended")) return true;
535
+ try {
536
+ const error = JSON.parse(detail).error;
537
+ return error === "invalid_grant" || response.status === 401 && error === "Invalid or expired refresh token";
538
+ } catch {
539
+ return response.status === 401 && detail.includes("Invalid or expired refresh token");
540
+ }
541
+ }
542
+ function workosConnectRefreshErrorCode(value) {
543
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
544
+ return value.error === "invalid_grant" ? "invalid_grant" : "";
545
+ }
546
+ function refreshDiagnostic(response, detail, quiet, provider) {
547
+ if (quiet) return;
548
+ const statusText = terminalSafeLine(response.statusText).slice(0, 120);
549
+ const safeDetail = terminalSafeLine(detail).slice(0, 200);
550
+ process.stderr.write(
551
+ `[prim] token refresh rejected by ${provider}: ${response.status}${statusText ? ` ${statusText}` : ""}${safeDetail ? ` \u2014 ${safeDetail}` : ""}
552
+ `
553
+ );
554
+ }
555
+ function metadataMatchesCredentialGeneration(expected, accessToken, refreshToken2) {
556
+ const current = readStoredCredentialMetadata();
557
+ if (expected.state === "legacy_broker") {
558
+ if (current.state !== "legacy_broker") return false;
559
+ if (!expected.metadata) return current.metadata === void 0;
560
+ return current.metadata !== void 0 && current.metadata.accessTokenHash === refreshFingerprint(accessToken) && current.metadata.accessTokenHash === expected.metadata.accessTokenHash && current.metadata.refreshTokenHash === refreshFingerprint(refreshToken2) && current.metadata.refreshTokenHash === expected.metadata.refreshTokenHash;
561
+ }
562
+ if (expected.state !== "workos_connect" || current.state !== "workos_connect") return false;
563
+ return current.metadata.issuer === expected.metadata.issuer && current.metadata.clientId === expected.metadata.clientId && current.metadata.accessTokenHash === refreshFingerprint(accessToken) && current.metadata.accessTokenHash === expected.metadata.accessTokenHash && current.metadata.refreshTokenHash === refreshFingerprint(refreshToken2) && current.metadata.refreshTokenHash === expected.metadata.refreshTokenHash;
564
+ }
565
+ function uncoordinatedLegacyWinnerAccessToken(expected, currentGeneration) {
566
+ if (!expected || readCredentialMigrationState() !== "valid") return void 0;
567
+ const stored = readStoredCredentialMetadata();
568
+ if (stored.state !== "legacy_broker" || stored.metadata === void 0 || stored.metadata.accessTokenHash !== expected.accessTokenHash || stored.metadata.refreshTokenHash !== expected.refreshTokenHash) {
569
+ return void 0;
570
+ }
571
+ const replacementRefreshToken = readTrimmed(REFRESH_TOKEN_PATH);
572
+ if (!replacementRefreshToken || replacementRefreshToken === currentGeneration) return void 0;
573
+ return readTrimmed(TOKEN_FILE_PATH);
574
+ }
575
+ function winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration) {
576
+ if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) return void 0;
577
+ const winner = resolveAuthCredential();
578
+ return (winner?.source === "token_file" ? winner.token : void 0) ?? uncoordinatedLegacyWinnerAccessToken(expectedLegacyGeneration, currentGeneration);
579
+ }
580
+ async function performTokenRefresh(options = {}) {
581
+ const selected = resolveAuthCredential();
582
+ if (selected?.source !== "token_file") return void 0;
583
+ const startingGeneration = readTrimmed(REFRESH_TOKEN_PATH);
584
+ if (!startingGeneration || isSessionEnded()) return void 0;
585
+ const startingAccessToken = selected.token;
586
+ return withCredentialLock(
587
+ async () => {
588
+ const currentCredential = resolveAuthCredential();
589
+ if (currentCredential?.source !== "token_file") return void 0;
590
+ const currentGeneration = readTrimmed(REFRESH_TOKEN_PATH);
591
+ if (!currentGeneration) return void 0;
592
+ if (currentGeneration !== startingGeneration) {
593
+ return currentCredential.token;
594
+ }
595
+ if (currentCredential.token !== startingAccessToken) {
596
+ return currentCredential.token;
597
+ }
598
+ if (isSessionEnded()) return void 0;
599
+ let metadata = readStoredCredentialMetadata();
600
+ const markerlessLegacyAtStart = metadata.state === "legacy_broker" && metadata.metadata === void 0;
601
+ if (metadata.state === "invalid" || metadata.state === "workos_connect" && (metadata.metadata.accessTokenHash !== refreshFingerprint(currentCredential.token) || metadata.metadata.refreshTokenHash !== refreshFingerprint(currentGeneration))) {
602
+ return void 0;
603
+ }
604
+ if (metadata.state === "legacy_broker") {
605
+ try {
606
+ metadata = bindLegacyBrokerGeneration(
607
+ currentCredential.token,
608
+ currentGeneration,
609
+ metadata
610
+ );
611
+ } catch {
612
+ return void 0;
613
+ }
614
+ if (metadata.state !== "legacy_broker") return void 0;
615
+ }
616
+ const expectedLegacyGeneration = markerlessLegacyAtStart && metadata.state === "legacy_broker" ? metadata.metadata : void 0;
617
+ const connectContext = metadata.state === "workos_connect" ? {
618
+ version: 1,
619
+ family: "workos_connect",
620
+ issuer: metadata.metadata.issuer,
621
+ clientId: metadata.metadata.clientId
622
+ } : void 0;
623
+ const response = await fetch(
624
+ connectContext ? workosConnectTokenEndpoint(connectContext.issuer) : `${getSiteUrl()}/mcp/broker/refresh`,
625
+ connectContext ? {
626
+ method: "POST",
627
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
628
+ body: new URLSearchParams({
629
+ grant_type: "refresh_token",
630
+ refresh_token: currentGeneration,
631
+ client_id: connectContext.clientId
632
+ }),
633
+ signal: options.signal
634
+ } : {
635
+ method: "POST",
636
+ headers: { "Content-Type": "application/json" },
637
+ body: JSON.stringify({ refresh_token: currentGeneration }),
638
+ signal: options.signal
639
+ }
640
+ );
641
+ let directResponse;
642
+ if (connectContext) {
643
+ try {
644
+ directResponse = await readBoundedJson(response, WORKOS_CONNECT_RESPONSE_MAX_BYTES);
645
+ } catch {
646
+ if (response.ok && readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(
647
+ metadata,
648
+ currentCredential.token,
649
+ currentGeneration
650
+ )) {
651
+ writeTerminalFingerprint(currentGeneration);
652
+ }
653
+ return void 0;
654
+ }
655
+ }
656
+ if (!response.ok) {
657
+ let detail = "";
658
+ if (connectContext) {
659
+ detail = workosConnectRefreshErrorCode(directResponse);
660
+ } else {
661
+ detail = (await response.text().catch(() => "")).slice(0, 200);
662
+ }
663
+ if (isTerminalRefreshResponse(response, detail) && readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(metadata, currentCredential.token, currentGeneration)) {
664
+ writeTerminalFingerprint(currentGeneration);
665
+ }
666
+ refreshDiagnostic(
667
+ response,
668
+ detail,
669
+ options.quiet,
670
+ connectContext ? "WorkOS Connect" : "broker"
671
+ );
672
+ return winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration);
673
+ }
674
+ let accessToken;
675
+ let replacementRefreshToken;
676
+ let expiresIn;
677
+ if (connectContext) {
678
+ try {
679
+ const tokens = parseWorkosConnectTokens(directResponse, {
680
+ fallbackRefreshToken: currentGeneration
681
+ });
682
+ accessToken = tokens.accessToken;
683
+ replacementRefreshToken = tokens.refreshToken;
684
+ expiresIn = tokens.expiresIn;
685
+ } catch {
686
+ if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(
687
+ metadata,
688
+ currentCredential.token,
689
+ currentGeneration
690
+ )) {
691
+ writeTerminalFingerprint(currentGeneration);
692
+ }
693
+ return void 0;
694
+ }
695
+ } else {
696
+ let data;
697
+ try {
698
+ data = await response.json();
699
+ } catch {
700
+ if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) {
701
+ writeTerminalFingerprint(currentGeneration);
702
+ }
703
+ return void 0;
704
+ }
705
+ const record = typeof data === "object" && data !== null && !Array.isArray(data) ? data : void 0;
706
+ accessToken = typeof record?.access_token === "string" ? record.access_token.trim() : "";
707
+ replacementRefreshToken = typeof record?.refresh_token === "string" ? record.refresh_token.trim() : "";
708
+ expiresIn = typeof record?.expires_in === "number" && Number.isFinite(record.expires_in) ? record.expires_in : void 0;
709
+ if (!(accessToken && replacementRefreshToken)) {
710
+ if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) {
711
+ writeTerminalFingerprint(currentGeneration);
712
+ }
713
+ return void 0;
714
+ }
715
+ }
716
+ const latestCredential = resolveAuthCredential();
717
+ if (readTrimmed(REFRESH_TOKEN_PATH) !== currentGeneration || latestCredential?.source !== "token_file" || latestCredential.token !== currentCredential.token || !metadataMatchesCredentialGeneration(metadata, currentCredential.token, currentGeneration)) {
718
+ return latestCredential?.source === "token_file" ? latestCredential.token : winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration);
719
+ }
720
+ commitCredentialsUnlocked({
721
+ accessToken,
722
+ refreshToken: replacementRefreshToken,
723
+ expiresIn,
724
+ metadata: connectContext
725
+ });
726
+ return accessToken;
727
+ },
728
+ { signal: options.signal }
729
+ );
730
+ }
731
+ var _refreshInFlight;
732
+ function refreshToken(options = {}) {
733
+ if (_refreshInFlight) return _refreshInFlight;
734
+ const attempt = performTokenRefresh(options).then((token) => {
735
+ if (token) _cachedCredential = { token, source: "token_file" };
736
+ return token;
737
+ }).finally(() => {
738
+ if (_refreshInFlight === attempt) _refreshInFlight = void 0;
739
+ });
740
+ _refreshInFlight = attempt;
741
+ return attempt;
742
+ }
743
+ var HttpError = class extends Error {
744
+ status;
745
+ body;
746
+ constructor(status, message, body) {
747
+ super(message);
748
+ this.name = "HttpError";
749
+ this.status = status;
750
+ this.body = body;
751
+ }
752
+ };
753
+ var CliManagementResponseError = class extends Error {
754
+ constructor() {
755
+ super("Invalid API-key management response");
756
+ this.name = "CliManagementResponseError";
757
+ }
758
+ };
759
+ var CLI_MANAGEMENT_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
760
+ async function managementResponseValue(response) {
761
+ if (response.status === 401) {
762
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
763
+ }
764
+ let value;
765
+ try {
766
+ value = await readBoundedJson(response, CLI_MANAGEMENT_RESPONSE_MAX_BYTES);
767
+ } catch {
768
+ if (response.ok) throw new CliManagementResponseError();
769
+ throw new HttpError(response.status, `HTTP ${response.status}`, null);
770
+ }
771
+ if (!response.ok) {
772
+ throw new HttpError(response.status, `HTTP ${response.status}`, value);
773
+ }
774
+ return value;
775
+ }
776
+ var _cachedCredential;
777
+ function selectedCredential() {
778
+ const resolved = resolveAuthCredential();
779
+ if (!_cachedCredential || resolved?.token !== _cachedCredential.token || resolved?.source !== _cachedCredential.source) {
780
+ _cachedCredential = resolved;
781
+ }
782
+ return _cachedCredential;
783
+ }
784
+ function fetchWithToken(method, path, body, options, token, siteUrl) {
785
+ return fetch(`${siteUrl}${path}`, {
786
+ method,
787
+ headers: {
788
+ Authorization: `Bearer ${token}`,
789
+ "Content-Type": "application/json"
790
+ },
791
+ body: body === void 0 ? void 0 : JSON.stringify(body),
792
+ signal: options?.signal
793
+ });
794
+ }
795
+ async function responseValue(response) {
796
+ if (!response.ok) {
797
+ if (response.status === 401) {
798
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
799
+ }
800
+ const errorBody = await response.json().catch(() => null);
801
+ const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
802
+ const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
803
+ throw new HttpError(response.status, message, errorBody);
804
+ }
805
+ return response.json();
806
+ }
807
+ async function request(method, path, body, options) {
808
+ const siteUrl = getSiteUrl();
809
+ let credential = selectedCredential();
810
+ let refreshAttempted = false;
811
+ if (credential && isTokenExpiringSoon(credential)) {
812
+ refreshAttempted = true;
813
+ const token = await refreshToken({
814
+ signal: options?.signal,
815
+ quiet: options?.quietRefresh
816
+ });
817
+ if (token) credential = { token, source: "token_file" };
818
+ }
819
+ if (!credential) {
820
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
821
+ }
822
+ if (isTokenExpiringSoon(credential) && isSessionEnded()) {
823
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
824
+ }
825
+ const tokenUsed = credential.token;
826
+ let response = await fetchWithToken(method, path, body, options, tokenUsed, siteUrl);
827
+ if (response.status === 401) {
828
+ const latest = resolveAuthCredential();
829
+ let retryToken = latest?.token !== tokenUsed ? latest?.token : void 0;
830
+ if (!retryToken && latest?.source === "token_file" && !refreshAttempted) {
831
+ refreshAttempted = true;
832
+ retryToken = await refreshToken({ signal: options?.signal, quiet: options?.quietRefresh });
833
+ }
834
+ if (retryToken) {
835
+ _cachedCredential = {
836
+ token: retryToken,
837
+ source: latest?.source === "token_file" ? "token_file" : latest?.source ?? "token_file"
838
+ };
839
+ response = await fetchWithToken(method, path, body, options, retryToken, siteUrl);
840
+ }
841
+ }
842
+ return responseValue(response);
843
+ }
844
+ function getClient() {
845
+ return {
846
+ get: (path, options) => request("GET", path, void 0, options),
847
+ post: (path, body, options) => request("POST", path, body, options)
848
+ };
849
+ }
850
+ async function getPinnedClient(options = {}) {
851
+ const siteUrl = getSiteUrl();
852
+ let credential = selectedCredential();
853
+ if (credential && isTokenExpiringSoon(credential)) {
854
+ const token2 = await refreshToken({
855
+ signal: options.signal,
856
+ quiet: options.quietRefresh
857
+ });
858
+ if (token2) credential = { token: token2, source: "token_file" };
859
+ }
860
+ if (!credential || isTokenExpiringSoon(credential) && isSessionEnded()) {
861
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
862
+ }
863
+ const token = credential.token;
864
+ const pinnedRequest = async (method, path, body, requestOptions) => responseValue(await fetchWithToken(method, path, body, requestOptions, token, siteUrl));
865
+ return {
866
+ get: (path, requestOptions) => pinnedRequest("GET", path, void 0, requestOptions),
867
+ post: (path, body, requestOptions) => pinnedRequest("POST", path, body, requestOptions)
868
+ };
869
+ }
870
+ async function getPinnedManagementClient(options = {}) {
871
+ const siteUrl = getSiteUrl();
872
+ let credential = selectedCredential();
873
+ if (credential && isTokenExpiringSoon(credential)) {
874
+ const token2 = await refreshToken({
875
+ signal: options.signal,
876
+ quiet: options.quietRefresh
877
+ });
878
+ if (token2) credential = { token: token2, source: "token_file" };
879
+ }
880
+ if (!credential || isTokenExpiringSoon(credential) && isSessionEnded()) {
881
+ throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
882
+ }
883
+ const token = credential.token;
884
+ const pinnedRequest = async (method, path, body, requestOptions) => managementResponseValue(
885
+ await fetchWithToken(method, path, body, requestOptions, token, siteUrl)
886
+ );
887
+ return {
888
+ delete: (path, body, requestOptions) => pinnedRequest("DELETE", path, body, requestOptions),
889
+ get: (path, requestOptions) => pinnedRequest("GET", path, void 0, requestOptions),
890
+ post: (path, body, requestOptions) => pinnedRequest("POST", path, body, requestOptions)
891
+ };
892
+ }
893
+
894
+ export {
895
+ syncDirectory,
896
+ syncFile,
897
+ atomicWriteFile,
898
+ processIsAlive,
899
+ withFileLockSync,
900
+ withFileLock,
901
+ terminalSafeText,
902
+ terminalSafeLine,
903
+ isTerminalSafeText,
904
+ stripControlChars,
905
+ getSiteUrl,
906
+ getTokenExpiresAt,
907
+ isSessionEnded,
908
+ commitCredentials,
909
+ setStoredToken,
910
+ clearStoredCredentials,
911
+ refreshToken,
912
+ HttpError,
913
+ CliManagementResponseError,
914
+ getClient,
915
+ getPinnedClient,
916
+ getPinnedManagementClient
917
+ };