@primitive.ai/prim 0.1.0-alpha.9 → 0.1.0-alpha.91
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 +303 -37
- package/SKILL.md +269 -0
- package/dist/chunk-4GLVJLEQ.js +55 -0
- package/dist/chunk-5AFRMOPX.js +209 -0
- package/dist/chunk-5GYQ4J3D.js +126 -0
- package/dist/chunk-67VNLLBN.js +789 -0
- package/dist/chunk-BTNNEH2W.js +79 -0
- package/dist/chunk-EWZMDD44.js +1474 -0
- package/dist/chunk-EZXEWFDG.js +210 -0
- package/dist/chunk-FBAYCS3X.js +327 -0
- package/dist/chunk-FKXFUUWJ.js +36 -0
- package/dist/chunk-FOLKHXLJ.js +1529 -0
- package/dist/chunk-H4UNYFUC.js +196 -0
- package/dist/chunk-HU4PW6I6.js +264 -0
- package/dist/chunk-KFXF2V5G.js +155 -0
- package/dist/chunk-LFFLHSQO.js +78 -0
- package/dist/chunk-M6RIJVRE.js +18358 -0
- package/dist/chunk-NAHHFSLH.js +258 -0
- package/dist/chunk-OAAP2MY6.js +262 -0
- package/dist/chunk-PCVGBE5B.js +659 -0
- package/dist/chunk-VW3FDYRT.js +517 -0
- package/dist/chunk-WX52U7Z2.js +112 -0
- package/dist/chunk-ZB4F75SZ.js +843 -0
- package/dist/daemon/server.js +21633 -0
- package/dist/hook-runtime/daemon/server.js +21633 -0
- package/dist/hook-runtime/hooks/post-commit.js +19258 -0
- package/dist/hook-runtime/hooks/post-rewrite.js +19273 -0
- package/dist/hook-runtime/hooks/post-tool-use.js +26365 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +25832 -0
- package/dist/hook-runtime/hooks/prim-hook.js +26386 -0
- package/dist/hook-runtime/hooks/session-end.js +1078 -0
- package/dist/hook-runtime/hooks/session-start.js +28779 -0
- package/dist/hook-runtime/index.js +50987 -0
- package/dist/hook-runtime/statusline-main.js +809 -0
- package/dist/hooks/post-commit.js +190 -0
- package/dist/hooks/post-rewrite.js +203 -0
- package/dist/hooks/post-tool-use.js +288 -0
- package/dist/hooks/pre-commit.js +16 -101
- package/dist/hooks/pre-tool-use.js +369 -0
- package/dist/hooks/prim-hook.js +229 -0
- package/dist/hooks/session-end.js +59 -0
- package/dist/hooks/session-start.js +316 -0
- package/dist/index.js +8875 -429
- package/dist/statusline-main.js +809 -0
- package/package.json +35 -13
- package/dist/chunk-3APLWTLB.js +0 -165
|
@@ -0,0 +1,659 @@
|
|
|
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
|
+
atomicWriteFile,
|
|
14
|
+
jwtExpiresAt,
|
|
15
|
+
parseWorkosConnectTokens,
|
|
16
|
+
readBoundedJson,
|
|
17
|
+
readCredentialMigrationState,
|
|
18
|
+
readStoredCredentialMetadata,
|
|
19
|
+
resolveAuthCredential,
|
|
20
|
+
withFileLock,
|
|
21
|
+
workosConnectTokenEndpoint
|
|
22
|
+
} from "./chunk-67VNLLBN.js";
|
|
23
|
+
|
|
24
|
+
// src/lib/terminal-safe.ts
|
|
25
|
+
function replaceIsolatedSurrogates(value) {
|
|
26
|
+
let out = "";
|
|
27
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
28
|
+
const unit = value.charCodeAt(index);
|
|
29
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
30
|
+
const next = value.charCodeAt(index + 1);
|
|
31
|
+
if (next >= 56320 && next <= 57343) {
|
|
32
|
+
out += value[index] + value[index + 1];
|
|
33
|
+
index += 1;
|
|
34
|
+
} else {
|
|
35
|
+
out += "\uFFFD";
|
|
36
|
+
}
|
|
37
|
+
} else if (unit >= 56320 && unit <= 57343) {
|
|
38
|
+
out += "\uFFFD";
|
|
39
|
+
} else {
|
|
40
|
+
out += value[index];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function isUnsafeTerminalCodePoint(point) {
|
|
46
|
+
const c0OrC1 = point <= 31 || point >= 127 && point <= 159;
|
|
47
|
+
const bidiOrZeroWidth = point === 173 || // soft hyphen
|
|
48
|
+
point === 1564 || // Arabic letter mark
|
|
49
|
+
point === 6158 || // Mongolian vowel separator
|
|
50
|
+
point >= 8203 && point <= 8207 || // zero-width + directional marks
|
|
51
|
+
point >= 8234 && point <= 8238 || // bidi embeddings/overrides
|
|
52
|
+
point >= 8288 && point <= 8303 || // word joiner + bidi isolates/controls
|
|
53
|
+
point >= 65529 && point <= 65531 || // interlinear annotation controls
|
|
54
|
+
point === 65279 || // zero-width no-break space / BOM
|
|
55
|
+
point >= 917504 && point <= 917631;
|
|
56
|
+
return c0OrC1 || bidiOrZeroWidth;
|
|
57
|
+
}
|
|
58
|
+
function terminalSafeText(value) {
|
|
59
|
+
return Array.from(replaceIsolatedSurrogates(value)).filter((character) => !isUnsafeTerminalCodePoint(character.codePointAt(0) ?? 0)).join("");
|
|
60
|
+
}
|
|
61
|
+
function terminalSafeLine(value) {
|
|
62
|
+
return terminalSafeText(value.replace(/\s+/gu, " ")).replace(/\s+/gu, " ").trim();
|
|
63
|
+
}
|
|
64
|
+
function isTerminalSafeText(value) {
|
|
65
|
+
return terminalSafeText(value) === value;
|
|
66
|
+
}
|
|
67
|
+
var stripControlChars = terminalSafeText;
|
|
68
|
+
|
|
69
|
+
// src/client.ts
|
|
70
|
+
import { createHash } from "crypto";
|
|
71
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
|
|
72
|
+
import { dirname } from "path";
|
|
73
|
+
var CONFIG_DIR_MODE = 448;
|
|
74
|
+
var CREDENTIAL_FILE_MODE = 384;
|
|
75
|
+
var REFRESH_THRESHOLD_MS = 6e4;
|
|
76
|
+
var DEFAULT_API_URL = "https://api.getprimitive.ai";
|
|
77
|
+
var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
|
|
78
|
+
function readTrimmed(path) {
|
|
79
|
+
try {
|
|
80
|
+
const value = readFileSync(path, "utf8").trim();
|
|
81
|
+
return value || void 0;
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function getSiteUrl() {
|
|
87
|
+
return getSiteUrlForEnvironment(process.env.PRIM_API_URL);
|
|
88
|
+
}
|
|
89
|
+
function getSiteUrlForEnvironment(primApiUrl) {
|
|
90
|
+
return primApiUrl?.trim() || DEFAULT_API_URL;
|
|
91
|
+
}
|
|
92
|
+
function expiresAtFor(token, expiresIn) {
|
|
93
|
+
return expiresIn === void 0 ? jwtExpiresAt(token) : Date.now() + expiresIn * 1e3;
|
|
94
|
+
}
|
|
95
|
+
function ensureConfigDirectory() {
|
|
96
|
+
const directory = dirname(TOKEN_FILE_PATH);
|
|
97
|
+
mkdirSync(directory, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
98
|
+
chmodSync(directory, CONFIG_DIR_MODE);
|
|
99
|
+
}
|
|
100
|
+
function atomicWrite(path, content) {
|
|
101
|
+
ensureConfigDirectory();
|
|
102
|
+
atomicWriteFile(path, content, { mode: CREDENTIAL_FILE_MODE });
|
|
103
|
+
}
|
|
104
|
+
function removeCredentialFile(path) {
|
|
105
|
+
if (!existsSync(path)) return false;
|
|
106
|
+
rmSync(path, { force: true });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
function getTokenExpiresAt() {
|
|
110
|
+
const stored = readTrimmed(TOKEN_EXPIRES_PATH);
|
|
111
|
+
if (stored === void 0) return void 0;
|
|
112
|
+
const value = Number(stored);
|
|
113
|
+
return Number.isNaN(value) ? void 0 : value;
|
|
114
|
+
}
|
|
115
|
+
function isTokenExpiringSoon(credential) {
|
|
116
|
+
if (credential.source !== "token_file") return false;
|
|
117
|
+
const expiresAt = getTokenExpiresAt();
|
|
118
|
+
return expiresAt !== void 0 && Date.now() >= expiresAt - REFRESH_THRESHOLD_MS;
|
|
119
|
+
}
|
|
120
|
+
function refreshFingerprint(refreshToken2) {
|
|
121
|
+
return createHash("sha256").update(refreshToken2).digest("hex");
|
|
122
|
+
}
|
|
123
|
+
function legacyBrokerCredentialMetadata(accessToken, refreshToken2) {
|
|
124
|
+
return {
|
|
125
|
+
version: 1,
|
|
126
|
+
family: "legacy_broker",
|
|
127
|
+
accessTokenHash: refreshFingerprint(accessToken),
|
|
128
|
+
refreshTokenHash: refreshFingerprint(refreshToken2)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function credentialFamilyMetadata(accessToken, refreshToken2, metadata) {
|
|
132
|
+
const hashes = {
|
|
133
|
+
accessTokenHash: refreshFingerprint(accessToken),
|
|
134
|
+
refreshTokenHash: refreshFingerprint(refreshToken2)
|
|
135
|
+
};
|
|
136
|
+
return metadata === void 0 ? legacyBrokerCredentialMetadata(accessToken, refreshToken2) : { ...metadata, ...hashes };
|
|
137
|
+
}
|
|
138
|
+
function writeCredentialMigrationSentinel() {
|
|
139
|
+
atomicWrite(
|
|
140
|
+
CREDENTIAL_MIGRATION_PATH,
|
|
141
|
+
`${JSON.stringify({
|
|
142
|
+
version: CREDENTIAL_MIGRATION_VERSION,
|
|
143
|
+
state: CREDENTIAL_MIGRATION_STATE
|
|
144
|
+
})}
|
|
145
|
+
`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function bindLegacyBrokerGeneration(accessToken, refreshToken2, metadata) {
|
|
149
|
+
if (metadata.state !== "legacy_broker" || !accessToken || !refreshToken2) {
|
|
150
|
+
return { state: "invalid" };
|
|
151
|
+
}
|
|
152
|
+
const familyMetadata = legacyBrokerCredentialMetadata(accessToken, refreshToken2);
|
|
153
|
+
if (metadata.metadata !== void 0 && (metadata.metadata.accessTokenHash !== familyMetadata.accessTokenHash || metadata.metadata.refreshTokenHash !== familyMetadata.refreshTokenHash)) {
|
|
154
|
+
return { state: "invalid" };
|
|
155
|
+
}
|
|
156
|
+
if (metadata.metadata === void 0) {
|
|
157
|
+
atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
|
|
158
|
+
`);
|
|
159
|
+
}
|
|
160
|
+
const migrationState = readCredentialMigrationState();
|
|
161
|
+
if (migrationState === "invalid") return { state: "invalid" };
|
|
162
|
+
if (migrationState === "absent") writeCredentialMigrationSentinel();
|
|
163
|
+
return { state: "legacy_broker", metadata: familyMetadata };
|
|
164
|
+
}
|
|
165
|
+
function preserveMarkerlessLegacyGeneration() {
|
|
166
|
+
const metadata = readStoredCredentialMetadata();
|
|
167
|
+
if (metadata.state !== "legacy_broker" || metadata.metadata !== void 0) return;
|
|
168
|
+
const accessToken = readTrimmed(TOKEN_FILE_PATH);
|
|
169
|
+
const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
|
|
170
|
+
if (!(accessToken && refreshToken2)) return;
|
|
171
|
+
const familyMetadata = legacyBrokerCredentialMetadata(accessToken, refreshToken2);
|
|
172
|
+
atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
|
|
173
|
+
`);
|
|
174
|
+
}
|
|
175
|
+
function terminalFingerprint() {
|
|
176
|
+
return readTrimmed(TERMINAL_REFRESH_PATH);
|
|
177
|
+
}
|
|
178
|
+
function writeTerminalFingerprint(refreshToken2) {
|
|
179
|
+
atomicWrite(TERMINAL_REFRESH_PATH, `${refreshFingerprint(refreshToken2)}
|
|
180
|
+
`);
|
|
181
|
+
}
|
|
182
|
+
function clearTerminalFingerprint() {
|
|
183
|
+
removeCredentialFile(TERMINAL_REFRESH_PATH);
|
|
184
|
+
}
|
|
185
|
+
function isSessionEnded() {
|
|
186
|
+
if (resolveAuthCredential()?.source !== "token_file") return false;
|
|
187
|
+
const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
|
|
188
|
+
const ended = terminalFingerprint();
|
|
189
|
+
return Boolean(refreshToken2 && ended && refreshFingerprint(refreshToken2) === ended);
|
|
190
|
+
}
|
|
191
|
+
function withCredentialLock(operation, options = {}) {
|
|
192
|
+
return withFileLock(CREDENTIAL_LOCK_PATH, operation, options);
|
|
193
|
+
}
|
|
194
|
+
function commitCredentialsUnlocked(credentials) {
|
|
195
|
+
const accessToken = credentials.accessToken.trim();
|
|
196
|
+
const refreshToken2 = credentials.refreshToken.trim();
|
|
197
|
+
if (!accessToken || !refreshToken2) {
|
|
198
|
+
throw new Error("OAuth credentials require both access and refresh tokens");
|
|
199
|
+
}
|
|
200
|
+
const familyMetadata = credentialFamilyMetadata(accessToken, refreshToken2, credentials.metadata);
|
|
201
|
+
if (credentials.metadata !== void 0) {
|
|
202
|
+
preserveMarkerlessLegacyGeneration();
|
|
203
|
+
writeCredentialMigrationSentinel();
|
|
204
|
+
atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
|
|
205
|
+
`);
|
|
206
|
+
atomicWrite(CREDENTIAL_METADATA_PATH, `${JSON.stringify(familyMetadata)}
|
|
207
|
+
`);
|
|
208
|
+
atomicWrite(REFRESH_TOKEN_PATH, `${refreshToken2}
|
|
209
|
+
`);
|
|
210
|
+
} else {
|
|
211
|
+
atomicWrite(REFRESH_TOKEN_PATH, `${refreshToken2}
|
|
212
|
+
`);
|
|
213
|
+
atomicWrite(CREDENTIAL_FAMILY_PATH, `${JSON.stringify(familyMetadata)}
|
|
214
|
+
`);
|
|
215
|
+
removeCredentialFile(CREDENTIAL_METADATA_PATH);
|
|
216
|
+
}
|
|
217
|
+
const expiresAt = expiresAtFor(accessToken, credentials.expiresIn);
|
|
218
|
+
if (expiresAt === void 0) removeCredentialFile(TOKEN_EXPIRES_PATH);
|
|
219
|
+
else atomicWrite(TOKEN_EXPIRES_PATH, `${expiresAt}
|
|
220
|
+
`);
|
|
221
|
+
atomicWrite(TOKEN_FILE_PATH, `${accessToken}
|
|
222
|
+
`);
|
|
223
|
+
clearTerminalFingerprint();
|
|
224
|
+
_cachedCredential = { token: accessToken, source: "token_file" };
|
|
225
|
+
}
|
|
226
|
+
async function commitCredentials(credentials, options = {}) {
|
|
227
|
+
await withCredentialLock(() => commitCredentialsUnlocked(credentials), options);
|
|
228
|
+
}
|
|
229
|
+
async function setStoredToken(token, options = {}) {
|
|
230
|
+
const value = token.trim();
|
|
231
|
+
if (!value) throw new Error("Token cannot be empty");
|
|
232
|
+
await withCredentialLock(() => {
|
|
233
|
+
removeCredentialFile(REFRESH_TOKEN_PATH);
|
|
234
|
+
removeCredentialFile(TOKEN_EXPIRES_PATH);
|
|
235
|
+
removeCredentialFile(CREDENTIAL_FAMILY_PATH);
|
|
236
|
+
removeCredentialFile(CREDENTIAL_METADATA_PATH);
|
|
237
|
+
clearTerminalFingerprint();
|
|
238
|
+
removeCredentialFile(CREDENTIAL_MIGRATION_PATH);
|
|
239
|
+
atomicWrite(TOKEN_FILE_PATH, `${value}
|
|
240
|
+
`);
|
|
241
|
+
_cachedCredential = { token: value, source: "token_file" };
|
|
242
|
+
}, options);
|
|
243
|
+
}
|
|
244
|
+
async function clearStoredCredentials(options = {}) {
|
|
245
|
+
const { beforeClear, ...lockOptions } = options;
|
|
246
|
+
return withCredentialLock(async () => {
|
|
247
|
+
const accessToken = readTrimmed(TOKEN_FILE_PATH);
|
|
248
|
+
const refreshToken2 = readTrimmed(REFRESH_TOKEN_PATH);
|
|
249
|
+
const metadata = readStoredCredentialMetadata();
|
|
250
|
+
let protectedMetadata = metadata;
|
|
251
|
+
if (metadata.state === "legacy_broker") {
|
|
252
|
+
try {
|
|
253
|
+
protectedMetadata = bindLegacyBrokerGeneration(accessToken, refreshToken2, metadata);
|
|
254
|
+
} catch {
|
|
255
|
+
protectedMetadata = { state: "invalid" };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let callbackError;
|
|
259
|
+
try {
|
|
260
|
+
await beforeClear?.(refreshToken2, protectedMetadata);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
callbackError = error;
|
|
263
|
+
}
|
|
264
|
+
let removed = false;
|
|
265
|
+
for (const path of [
|
|
266
|
+
REFRESH_TOKEN_PATH,
|
|
267
|
+
TOKEN_EXPIRES_PATH,
|
|
268
|
+
CREDENTIAL_FAMILY_PATH,
|
|
269
|
+
CREDENTIAL_METADATA_PATH,
|
|
270
|
+
TERMINAL_REFRESH_PATH,
|
|
271
|
+
TOKEN_FILE_PATH,
|
|
272
|
+
CREDENTIAL_MIGRATION_PATH
|
|
273
|
+
]) {
|
|
274
|
+
removed = removeCredentialFile(path) || removed;
|
|
275
|
+
}
|
|
276
|
+
_cachedCredential = void 0;
|
|
277
|
+
if (callbackError) throw callbackError;
|
|
278
|
+
return removed;
|
|
279
|
+
}, lockOptions);
|
|
280
|
+
}
|
|
281
|
+
function isTerminalRefreshResponse(response, detail) {
|
|
282
|
+
if (detail.includes("invalid_grant") || detail.includes("Session has already ended")) return true;
|
|
283
|
+
try {
|
|
284
|
+
const error = JSON.parse(detail).error;
|
|
285
|
+
return error === "invalid_grant" || response.status === 401 && error === "Invalid or expired refresh token";
|
|
286
|
+
} catch {
|
|
287
|
+
return response.status === 401 && detail.includes("Invalid or expired refresh token");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function workosConnectRefreshErrorCode(value) {
|
|
291
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
|
292
|
+
return value.error === "invalid_grant" ? "invalid_grant" : "";
|
|
293
|
+
}
|
|
294
|
+
function refreshDiagnostic(response, detail, quiet, provider) {
|
|
295
|
+
if (quiet) return;
|
|
296
|
+
const statusText = terminalSafeLine(response.statusText).slice(0, 120);
|
|
297
|
+
const safeDetail = terminalSafeLine(detail).slice(0, 200);
|
|
298
|
+
process.stderr.write(
|
|
299
|
+
`[prim] token refresh rejected by ${provider}: ${response.status}${statusText ? ` ${statusText}` : ""}${safeDetail ? ` \u2014 ${safeDetail}` : ""}
|
|
300
|
+
`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function metadataMatchesCredentialGeneration(expected, accessToken, refreshToken2) {
|
|
304
|
+
const current = readStoredCredentialMetadata();
|
|
305
|
+
if (expected.state === "legacy_broker") {
|
|
306
|
+
if (current.state !== "legacy_broker") return false;
|
|
307
|
+
if (!expected.metadata) return current.metadata === void 0;
|
|
308
|
+
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;
|
|
309
|
+
}
|
|
310
|
+
if (expected.state !== "workos_connect" || current.state !== "workos_connect") return false;
|
|
311
|
+
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;
|
|
312
|
+
}
|
|
313
|
+
function uncoordinatedLegacyWinnerAccessToken(expected, currentGeneration) {
|
|
314
|
+
if (!expected || readCredentialMigrationState() !== "valid") return void 0;
|
|
315
|
+
const stored = readStoredCredentialMetadata();
|
|
316
|
+
if (stored.state !== "legacy_broker" || stored.metadata === void 0 || stored.metadata.accessTokenHash !== expected.accessTokenHash || stored.metadata.refreshTokenHash !== expected.refreshTokenHash) {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
const replacementRefreshToken = readTrimmed(REFRESH_TOKEN_PATH);
|
|
320
|
+
if (!replacementRefreshToken || replacementRefreshToken === currentGeneration) return void 0;
|
|
321
|
+
return readTrimmed(TOKEN_FILE_PATH);
|
|
322
|
+
}
|
|
323
|
+
function winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration) {
|
|
324
|
+
if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) return void 0;
|
|
325
|
+
const winner = resolveAuthCredential();
|
|
326
|
+
return (winner?.source === "token_file" ? winner.token : void 0) ?? uncoordinatedLegacyWinnerAccessToken(expectedLegacyGeneration, currentGeneration);
|
|
327
|
+
}
|
|
328
|
+
async function performTokenRefresh(options = {}) {
|
|
329
|
+
const selected = resolveAuthCredential();
|
|
330
|
+
if (selected?.source !== "token_file") return void 0;
|
|
331
|
+
const startingGeneration = readTrimmed(REFRESH_TOKEN_PATH);
|
|
332
|
+
if (!startingGeneration || isSessionEnded()) return void 0;
|
|
333
|
+
const startingAccessToken = selected.token;
|
|
334
|
+
return withCredentialLock(
|
|
335
|
+
async () => {
|
|
336
|
+
const currentCredential = resolveAuthCredential();
|
|
337
|
+
if (currentCredential?.source !== "token_file") return void 0;
|
|
338
|
+
const currentGeneration = readTrimmed(REFRESH_TOKEN_PATH);
|
|
339
|
+
if (!currentGeneration) return void 0;
|
|
340
|
+
if (currentGeneration !== startingGeneration) {
|
|
341
|
+
return currentCredential.token;
|
|
342
|
+
}
|
|
343
|
+
if (currentCredential.token !== startingAccessToken) {
|
|
344
|
+
return currentCredential.token;
|
|
345
|
+
}
|
|
346
|
+
if (isSessionEnded()) return void 0;
|
|
347
|
+
let metadata = readStoredCredentialMetadata();
|
|
348
|
+
const markerlessLegacyAtStart = metadata.state === "legacy_broker" && metadata.metadata === void 0;
|
|
349
|
+
if (metadata.state === "invalid" || metadata.state === "workos_connect" && (metadata.metadata.accessTokenHash !== refreshFingerprint(currentCredential.token) || metadata.metadata.refreshTokenHash !== refreshFingerprint(currentGeneration))) {
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
if (metadata.state === "legacy_broker") {
|
|
353
|
+
try {
|
|
354
|
+
metadata = bindLegacyBrokerGeneration(
|
|
355
|
+
currentCredential.token,
|
|
356
|
+
currentGeneration,
|
|
357
|
+
metadata
|
|
358
|
+
);
|
|
359
|
+
} catch {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
if (metadata.state !== "legacy_broker") return void 0;
|
|
363
|
+
}
|
|
364
|
+
const expectedLegacyGeneration = markerlessLegacyAtStart && metadata.state === "legacy_broker" ? metadata.metadata : void 0;
|
|
365
|
+
const connectContext = metadata.state === "workos_connect" ? {
|
|
366
|
+
version: 1,
|
|
367
|
+
family: "workos_connect",
|
|
368
|
+
issuer: metadata.metadata.issuer,
|
|
369
|
+
clientId: metadata.metadata.clientId
|
|
370
|
+
} : void 0;
|
|
371
|
+
const response = await fetch(
|
|
372
|
+
connectContext ? workosConnectTokenEndpoint(connectContext.issuer) : `${getSiteUrl()}/mcp/broker/refresh`,
|
|
373
|
+
connectContext ? {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
376
|
+
body: new URLSearchParams({
|
|
377
|
+
grant_type: "refresh_token",
|
|
378
|
+
refresh_token: currentGeneration,
|
|
379
|
+
client_id: connectContext.clientId
|
|
380
|
+
}),
|
|
381
|
+
signal: options.signal
|
|
382
|
+
} : {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: { "Content-Type": "application/json" },
|
|
385
|
+
body: JSON.stringify({ refresh_token: currentGeneration }),
|
|
386
|
+
signal: options.signal
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
let directResponse;
|
|
390
|
+
if (connectContext) {
|
|
391
|
+
try {
|
|
392
|
+
directResponse = await readBoundedJson(response, WORKOS_CONNECT_RESPONSE_MAX_BYTES);
|
|
393
|
+
} catch {
|
|
394
|
+
if (response.ok && readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(
|
|
395
|
+
metadata,
|
|
396
|
+
currentCredential.token,
|
|
397
|
+
currentGeneration
|
|
398
|
+
)) {
|
|
399
|
+
writeTerminalFingerprint(currentGeneration);
|
|
400
|
+
}
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
let detail = "";
|
|
406
|
+
if (connectContext) {
|
|
407
|
+
detail = workosConnectRefreshErrorCode(directResponse);
|
|
408
|
+
} else {
|
|
409
|
+
detail = (await response.text().catch(() => "")).slice(0, 200);
|
|
410
|
+
}
|
|
411
|
+
if (isTerminalRefreshResponse(response, detail) && readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(metadata, currentCredential.token, currentGeneration)) {
|
|
412
|
+
writeTerminalFingerprint(currentGeneration);
|
|
413
|
+
}
|
|
414
|
+
refreshDiagnostic(
|
|
415
|
+
response,
|
|
416
|
+
detail,
|
|
417
|
+
options.quiet,
|
|
418
|
+
connectContext ? "WorkOS Connect" : "broker"
|
|
419
|
+
);
|
|
420
|
+
return winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration);
|
|
421
|
+
}
|
|
422
|
+
let accessToken;
|
|
423
|
+
let replacementRefreshToken;
|
|
424
|
+
let expiresIn;
|
|
425
|
+
if (connectContext) {
|
|
426
|
+
try {
|
|
427
|
+
const tokens = parseWorkosConnectTokens(directResponse, {
|
|
428
|
+
fallbackRefreshToken: currentGeneration
|
|
429
|
+
});
|
|
430
|
+
accessToken = tokens.accessToken;
|
|
431
|
+
replacementRefreshToken = tokens.refreshToken;
|
|
432
|
+
expiresIn = tokens.expiresIn;
|
|
433
|
+
} catch {
|
|
434
|
+
if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration && metadataMatchesCredentialGeneration(
|
|
435
|
+
metadata,
|
|
436
|
+
currentCredential.token,
|
|
437
|
+
currentGeneration
|
|
438
|
+
)) {
|
|
439
|
+
writeTerminalFingerprint(currentGeneration);
|
|
440
|
+
}
|
|
441
|
+
return void 0;
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
let data;
|
|
445
|
+
try {
|
|
446
|
+
data = await response.json();
|
|
447
|
+
} catch {
|
|
448
|
+
if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) {
|
|
449
|
+
writeTerminalFingerprint(currentGeneration);
|
|
450
|
+
}
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
const record = typeof data === "object" && data !== null && !Array.isArray(data) ? data : void 0;
|
|
454
|
+
accessToken = typeof record?.access_token === "string" ? record.access_token.trim() : "";
|
|
455
|
+
replacementRefreshToken = typeof record?.refresh_token === "string" ? record.refresh_token.trim() : "";
|
|
456
|
+
expiresIn = typeof record?.expires_in === "number" && Number.isFinite(record.expires_in) ? record.expires_in : void 0;
|
|
457
|
+
if (!(accessToken && replacementRefreshToken)) {
|
|
458
|
+
if (readTrimmed(REFRESH_TOKEN_PATH) === currentGeneration) {
|
|
459
|
+
writeTerminalFingerprint(currentGeneration);
|
|
460
|
+
}
|
|
461
|
+
return void 0;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const latestCredential = resolveAuthCredential();
|
|
465
|
+
if (readTrimmed(REFRESH_TOKEN_PATH) !== currentGeneration || latestCredential?.source !== "token_file" || latestCredential.token !== currentCredential.token || !metadataMatchesCredentialGeneration(metadata, currentCredential.token, currentGeneration)) {
|
|
466
|
+
return latestCredential?.source === "token_file" ? latestCredential.token : winnerAfterRefreshRace(currentGeneration, expectedLegacyGeneration);
|
|
467
|
+
}
|
|
468
|
+
commitCredentialsUnlocked({
|
|
469
|
+
accessToken,
|
|
470
|
+
refreshToken: replacementRefreshToken,
|
|
471
|
+
expiresIn,
|
|
472
|
+
metadata: connectContext
|
|
473
|
+
});
|
|
474
|
+
return accessToken;
|
|
475
|
+
},
|
|
476
|
+
{ signal: options.signal }
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
var _refreshInFlight;
|
|
480
|
+
function refreshToken(options = {}) {
|
|
481
|
+
if (_refreshInFlight) return _refreshInFlight;
|
|
482
|
+
const attempt = performTokenRefresh(options).then((token) => {
|
|
483
|
+
if (token) _cachedCredential = { token, source: "token_file" };
|
|
484
|
+
return token;
|
|
485
|
+
}).finally(() => {
|
|
486
|
+
if (_refreshInFlight === attempt) _refreshInFlight = void 0;
|
|
487
|
+
});
|
|
488
|
+
_refreshInFlight = attempt;
|
|
489
|
+
return attempt;
|
|
490
|
+
}
|
|
491
|
+
var HttpError = class extends Error {
|
|
492
|
+
status;
|
|
493
|
+
body;
|
|
494
|
+
constructor(status, message, body) {
|
|
495
|
+
super(message);
|
|
496
|
+
this.name = "HttpError";
|
|
497
|
+
this.status = status;
|
|
498
|
+
this.body = body;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var CliManagementResponseError = class extends Error {
|
|
502
|
+
constructor() {
|
|
503
|
+
super("Invalid API-key management response");
|
|
504
|
+
this.name = "CliManagementResponseError";
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
var CLI_MANAGEMENT_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
|
|
508
|
+
async function managementResponseValue(response) {
|
|
509
|
+
if (response.status === 401) {
|
|
510
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
511
|
+
}
|
|
512
|
+
let value;
|
|
513
|
+
try {
|
|
514
|
+
value = await readBoundedJson(response, CLI_MANAGEMENT_RESPONSE_MAX_BYTES);
|
|
515
|
+
} catch {
|
|
516
|
+
if (response.ok) throw new CliManagementResponseError();
|
|
517
|
+
throw new HttpError(response.status, `HTTP ${response.status}`, null);
|
|
518
|
+
}
|
|
519
|
+
if (!response.ok) {
|
|
520
|
+
throw new HttpError(response.status, `HTTP ${response.status}`, value);
|
|
521
|
+
}
|
|
522
|
+
return value;
|
|
523
|
+
}
|
|
524
|
+
var _cachedCredential;
|
|
525
|
+
function selectedCredential() {
|
|
526
|
+
const resolved = resolveAuthCredential();
|
|
527
|
+
if (!_cachedCredential || resolved?.token !== _cachedCredential.token || resolved?.source !== _cachedCredential.source) {
|
|
528
|
+
_cachedCredential = resolved;
|
|
529
|
+
}
|
|
530
|
+
return _cachedCredential;
|
|
531
|
+
}
|
|
532
|
+
function fetchWithToken(method, path, body, options, token, siteUrl) {
|
|
533
|
+
return fetch(`${siteUrl}${path}`, {
|
|
534
|
+
method,
|
|
535
|
+
headers: {
|
|
536
|
+
Authorization: `Bearer ${token}`,
|
|
537
|
+
"Content-Type": "application/json"
|
|
538
|
+
},
|
|
539
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
540
|
+
signal: options?.signal
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async function responseValue(response) {
|
|
544
|
+
if (!response.ok) {
|
|
545
|
+
if (response.status === 401) {
|
|
546
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
547
|
+
}
|
|
548
|
+
const errorBody = await response.json().catch(() => null);
|
|
549
|
+
const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
|
|
550
|
+
const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
|
|
551
|
+
throw new HttpError(response.status, message, errorBody);
|
|
552
|
+
}
|
|
553
|
+
return response.json();
|
|
554
|
+
}
|
|
555
|
+
async function request(method, path, body, options) {
|
|
556
|
+
const siteUrl = getSiteUrl();
|
|
557
|
+
let credential = selectedCredential();
|
|
558
|
+
let refreshAttempted = false;
|
|
559
|
+
if (credential && isTokenExpiringSoon(credential)) {
|
|
560
|
+
refreshAttempted = true;
|
|
561
|
+
const token = await refreshToken({
|
|
562
|
+
signal: options?.signal,
|
|
563
|
+
quiet: options?.quietRefresh
|
|
564
|
+
});
|
|
565
|
+
if (token) credential = { token, source: "token_file" };
|
|
566
|
+
}
|
|
567
|
+
if (!credential) {
|
|
568
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
569
|
+
}
|
|
570
|
+
if (isTokenExpiringSoon(credential) && isSessionEnded()) {
|
|
571
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
572
|
+
}
|
|
573
|
+
const tokenUsed = credential.token;
|
|
574
|
+
let response = await fetchWithToken(method, path, body, options, tokenUsed, siteUrl);
|
|
575
|
+
if (response.status === 401) {
|
|
576
|
+
const latest = resolveAuthCredential();
|
|
577
|
+
let retryToken = latest?.token !== tokenUsed ? latest?.token : void 0;
|
|
578
|
+
if (!retryToken && latest?.source === "token_file" && !refreshAttempted) {
|
|
579
|
+
refreshAttempted = true;
|
|
580
|
+
retryToken = await refreshToken({ signal: options?.signal, quiet: options?.quietRefresh });
|
|
581
|
+
}
|
|
582
|
+
if (retryToken) {
|
|
583
|
+
_cachedCredential = {
|
|
584
|
+
token: retryToken,
|
|
585
|
+
source: latest?.source === "token_file" ? "token_file" : latest?.source ?? "token_file"
|
|
586
|
+
};
|
|
587
|
+
response = await fetchWithToken(method, path, body, options, retryToken, siteUrl);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return responseValue(response);
|
|
591
|
+
}
|
|
592
|
+
function getClient() {
|
|
593
|
+
return {
|
|
594
|
+
get: (path, options) => request("GET", path, void 0, options),
|
|
595
|
+
post: (path, body, options) => request("POST", path, body, options)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
async function getPinnedClient(options = {}) {
|
|
599
|
+
const siteUrl = getSiteUrl();
|
|
600
|
+
let credential = selectedCredential();
|
|
601
|
+
if (credential && isTokenExpiringSoon(credential)) {
|
|
602
|
+
const token2 = await refreshToken({
|
|
603
|
+
signal: options.signal,
|
|
604
|
+
quiet: options.quietRefresh
|
|
605
|
+
});
|
|
606
|
+
if (token2) credential = { token: token2, source: "token_file" };
|
|
607
|
+
}
|
|
608
|
+
if (!credential || isTokenExpiringSoon(credential) && isSessionEnded()) {
|
|
609
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
610
|
+
}
|
|
611
|
+
const token = credential.token;
|
|
612
|
+
const pinnedRequest = async (method, path, body, requestOptions) => responseValue(await fetchWithToken(method, path, body, requestOptions, token, siteUrl));
|
|
613
|
+
return {
|
|
614
|
+
get: (path, requestOptions) => pinnedRequest("GET", path, void 0, requestOptions),
|
|
615
|
+
post: (path, body, requestOptions) => pinnedRequest("POST", path, body, requestOptions)
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async function getPinnedManagementClient(options = {}) {
|
|
619
|
+
const siteUrl = getSiteUrl();
|
|
620
|
+
let credential = selectedCredential();
|
|
621
|
+
if (credential && isTokenExpiringSoon(credential)) {
|
|
622
|
+
const token2 = await refreshToken({
|
|
623
|
+
signal: options.signal,
|
|
624
|
+
quiet: options.quietRefresh
|
|
625
|
+
});
|
|
626
|
+
if (token2) credential = { token: token2, source: "token_file" };
|
|
627
|
+
}
|
|
628
|
+
if (!credential || isTokenExpiringSoon(credential) && isSessionEnded()) {
|
|
629
|
+
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
630
|
+
}
|
|
631
|
+
const token = credential.token;
|
|
632
|
+
const pinnedRequest = async (method, path, body, requestOptions) => managementResponseValue(
|
|
633
|
+
await fetchWithToken(method, path, body, requestOptions, token, siteUrl)
|
|
634
|
+
);
|
|
635
|
+
return {
|
|
636
|
+
delete: (path, body, requestOptions) => pinnedRequest("DELETE", path, body, requestOptions),
|
|
637
|
+
get: (path, requestOptions) => pinnedRequest("GET", path, void 0, requestOptions),
|
|
638
|
+
post: (path, body, requestOptions) => pinnedRequest("POST", path, body, requestOptions)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export {
|
|
643
|
+
terminalSafeText,
|
|
644
|
+
terminalSafeLine,
|
|
645
|
+
isTerminalSafeText,
|
|
646
|
+
stripControlChars,
|
|
647
|
+
getSiteUrl,
|
|
648
|
+
getTokenExpiresAt,
|
|
649
|
+
isSessionEnded,
|
|
650
|
+
commitCredentials,
|
|
651
|
+
setStoredToken,
|
|
652
|
+
clearStoredCredentials,
|
|
653
|
+
refreshToken,
|
|
654
|
+
HttpError,
|
|
655
|
+
CliManagementResponseError,
|
|
656
|
+
getClient,
|
|
657
|
+
getPinnedClient,
|
|
658
|
+
getPinnedManagementClient
|
|
659
|
+
};
|