@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.
- package/README.md +295 -37
- package/SKILL.md +269 -0
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4CZTNXIS.js +505 -0
- package/dist/chunk-4GLVJLEQ.js +55 -0
- package/dist/chunk-BQUJ4AFJ.js +771 -0
- package/dist/chunk-CUE2LKLR.js +155 -0
- package/dist/chunk-EZXEWFDG.js +210 -0
- package/dist/chunk-FI3LIRCB.js +262 -0
- package/dist/chunk-HWSS4BV5.js +1400 -0
- package/dist/chunk-LEMX3IOG.js +79 -0
- package/dist/chunk-LMRYZL3D.js +209 -0
- package/dist/chunk-OUXH3UEP.js +247 -0
- package/dist/chunk-PMRDE2DA.js +310 -0
- package/dist/chunk-RO3F6JYV.js +126 -0
- package/dist/chunk-TJTVHDJ5.js +321 -0
- package/dist/chunk-WB7AUZ4C.js +52 -0
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/chunk-YWO7O6QG.js +327 -0
- package/dist/chunk-Z7YPFWAZ.js +917 -0
- package/dist/chunk-ZOSNEO5N.js +77 -0
- package/dist/daemon/server.js +15329 -0
- package/dist/hook-runtime/daemon/server.js +15329 -0
- package/dist/hook-runtime/hooks/post-commit.js +1128 -0
- package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
- package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
- package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
- package/dist/hook-runtime/hooks/session-end.js +495 -0
- package/dist/hook-runtime/hooks/session-start.js +11313 -0
- package/dist/hook-runtime/index.js +44696 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +179 -0
- package/dist/hooks/post-rewrite.js +197 -0
- package/dist/hooks/post-tool-use.js +250 -0
- package/dist/hooks/pre-commit.js +21 -74
- package/dist/hooks/pre-tool-use.js +348 -0
- package/dist/hooks/prim-hook.js +195 -0
- package/dist/hooks/session-end.js +70 -0
- package/dist/hooks/session-start.js +299 -0
- package/dist/index.js +20981 -519
- package/dist/statusline-main.js +765 -0
- package/package.json +35 -13
- 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
|
+
};
|