@primitive.ai/prim 0.1.0-alpha.9 → 0.1.0-alpha.90
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-2JRX7ZR4.js +247 -0
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4GLVJLEQ.js +55 -0
- package/dist/chunk-6IWNH776.js +77 -0
- package/dist/chunk-C7T7B45R.js +1400 -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-LEMX3IOG.js +79 -0
- package/dist/chunk-LMRYZL3D.js +209 -0
- package/dist/chunk-MAZWKJGL.js +18548 -0
- package/dist/chunk-PMRDE2DA.js +310 -0
- package/dist/chunk-PPQYQEIR.js +815 -0
- package/dist/chunk-R26A7TWY.js +36 -0
- package/dist/chunk-RO3F6JYV.js +126 -0
- package/dist/chunk-UZPBB7V4.js +505 -0
- package/dist/chunk-WB7AUZ4C.js +52 -0
- package/dist/chunk-YWO7O6QG.js +327 -0
- package/dist/chunk-Z7YPFWAZ.js +917 -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 +25846 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +25284 -0
- package/dist/hook-runtime/hooks/prim-hook.js +25870 -0
- package/dist/hook-runtime/hooks/session-end.js +495 -0
- package/dist/hook-runtime/hooks/session-start.js +28180 -0
- package/dist/hook-runtime/index.js +50516 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +188 -0
- package/dist/hooks/post-rewrite.js +201 -0
- package/dist/hooks/post-tool-use.js +263 -0
- package/dist/hooks/pre-commit.js +16 -101
- package/dist/hooks/pre-tool-use.js +367 -0
- package/dist/hooks/prim-hook.js +207 -0
- package/dist/hooks/session-end.js +70 -0
- package/dist/hooks/session-start.js +289 -0
- package/dist/index.js +9749 -425
- package/dist/statusline-main.js +765 -0
- package/package.json +35 -13
- package/dist/chunk-3APLWTLB.js +0 -165
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
// src/lib/paths.ts
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { isAbsolute, join, normalize } from "path";
|
|
30
|
+
var UNSAFE_CONFIG_PATH_CHARACTERS = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
31
|
+
function absolutePath(value, variable) {
|
|
32
|
+
if (value !== void 0 && UNSAFE_CONFIG_PATH_CHARACTERS.test(value)) {
|
|
33
|
+
throw new Error(`${variable} contains unsafe characters`);
|
|
34
|
+
}
|
|
35
|
+
if (!value || value.trim() !== value || !isAbsolute(value)) return;
|
|
36
|
+
const normalized = normalize(value);
|
|
37
|
+
return normalized === value ? value : void 0;
|
|
38
|
+
}
|
|
39
|
+
function resolvePrimConfigDirectory(options = {}) {
|
|
40
|
+
const env = options.env ?? process.env;
|
|
41
|
+
const explicit = absolutePath(env.PRIM_CONFIG_DIR, "PRIM_CONFIG_DIR");
|
|
42
|
+
if (explicit) return { path: explicit, source: "explicit" };
|
|
43
|
+
const xdgConfigHome = absolutePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME");
|
|
44
|
+
if (xdgConfigHome) return { path: join(xdgConfigHome, "prim"), source: "xdg" };
|
|
45
|
+
const home = absolutePath(options.homeDir ?? homedir(), "HOME");
|
|
46
|
+
if (!home) {
|
|
47
|
+
throw new Error("cannot resolve Primitive config directory: HOME is not an absolute path");
|
|
48
|
+
}
|
|
49
|
+
return { path: join(home, ".config", "prim"), source: "default" };
|
|
50
|
+
}
|
|
51
|
+
function primConfigDirectory(options = {}) {
|
|
52
|
+
return resolvePrimConfigDirectory(options).path;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/lib/workos-connect.ts
|
|
56
|
+
var DISCOVERY_PATH = "/api/cli/auth/connect/config";
|
|
57
|
+
var DEVICE_AUTHORIZATION_PATH = "/oauth2/device_authorization";
|
|
58
|
+
var TOKEN_PATH = "/oauth2/token";
|
|
59
|
+
var DISCOVERY_MAX_BYTES = 16 * 1024;
|
|
60
|
+
var WORKOS_CONNECT_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
61
|
+
var MAX_DEVICE_LIFETIME_SECONDS = 60 * 60;
|
|
62
|
+
var MAX_TOKEN_LIFETIME_SECONDS = 24 * 60 * 60;
|
|
63
|
+
var MAX_INTERVAL_SECONDS = 60;
|
|
64
|
+
var POLL_REQUEST_TIMEOUT_MS = 3e4;
|
|
65
|
+
var SLOW_DOWN_SECONDS = 5;
|
|
66
|
+
var MAX_URL_LENGTH = 2048;
|
|
67
|
+
var MAX_DEVICE_CODE_LENGTH = 4096;
|
|
68
|
+
var MAX_USER_CODE_LENGTH = 128;
|
|
69
|
+
var MAX_CLIENT_ID_LENGTH = 256;
|
|
70
|
+
var CONNECT_CLIENT_ID_PATTERN = /^client_[A-Za-z0-9_-]+$/u;
|
|
71
|
+
var WORKOS_CONNECT_DEFAULT_SCOPES = ["openid", "profile", "email", "offline_access"];
|
|
72
|
+
var WorkosConnectProtocolError = class extends Error {
|
|
73
|
+
code;
|
|
74
|
+
constructor(code, message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = "WorkosConnectProtocolError";
|
|
77
|
+
this.code = code;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function recordFrom(value, label) {
|
|
81
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
82
|
+
throw new WorkosConnectProtocolError("invalid_response", `${label} must be an object`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
function exactString(value, field, maxLength) {
|
|
87
|
+
const containsControl = typeof value === "string" && [...value].some((character) => {
|
|
88
|
+
const code = character.charCodeAt(0);
|
|
89
|
+
return code <= 31 || code === 127;
|
|
90
|
+
});
|
|
91
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength || value.trim() !== value || containsControl) {
|
|
92
|
+
throw new WorkosConnectProtocolError("invalid_response", `${field} is invalid`);
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function safeInteger(value, field, minimum, maximum) {
|
|
97
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
98
|
+
throw new WorkosConnectProtocolError("invalid_response", `${field} is invalid`);
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function canonicalIssuer(value) {
|
|
103
|
+
const issuer = exactString(value, "issuer", MAX_URL_LENGTH);
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = new URL(issuer);
|
|
107
|
+
} catch {
|
|
108
|
+
throw new WorkosConnectProtocolError("invalid_response", "issuer must be an HTTPS origin");
|
|
109
|
+
}
|
|
110
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash || parsed.origin !== issuer) {
|
|
111
|
+
throw new WorkosConnectProtocolError("invalid_response", "issuer must be an HTTPS origin");
|
|
112
|
+
}
|
|
113
|
+
return issuer;
|
|
114
|
+
}
|
|
115
|
+
function sameIssuerUrl(value, field, issuer) {
|
|
116
|
+
const raw = exactString(value, field, MAX_URL_LENGTH);
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = new URL(raw);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new WorkosConnectProtocolError("invalid_response", `${field} must be an HTTPS URL`);
|
|
122
|
+
}
|
|
123
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.origin !== issuer) {
|
|
124
|
+
throw new WorkosConnectProtocolError("invalid_response", `${field} is not trusted`);
|
|
125
|
+
}
|
|
126
|
+
return parsed.toString();
|
|
127
|
+
}
|
|
128
|
+
function parseWorkosConnectConfiguration(value) {
|
|
129
|
+
const record = recordFrom(value, "Connect discovery");
|
|
130
|
+
const expectedKeys = ["protocol_version", "issuer", "client_id", "default_scopes"];
|
|
131
|
+
if (record.protocol_version !== 1 || Object.keys(record).length !== expectedKeys.length || expectedKeys.some((key) => !Object.hasOwn(record, key)) || !Array.isArray(record.default_scopes) || record.default_scopes.length !== WORKOS_CONNECT_DEFAULT_SCOPES.length || record.default_scopes.some((scope, index) => scope !== WORKOS_CONNECT_DEFAULT_SCOPES[index])) {
|
|
132
|
+
throw new WorkosConnectProtocolError("invalid_response", "Connect discovery is invalid");
|
|
133
|
+
}
|
|
134
|
+
const issuer = canonicalIssuer(record.issuer);
|
|
135
|
+
const clientId = exactString(record.client_id, "client_id", MAX_CLIENT_ID_LENGTH);
|
|
136
|
+
if (!CONNECT_CLIENT_ID_PATTERN.test(clientId)) {
|
|
137
|
+
throw new WorkosConnectProtocolError("invalid_response", "Connect discovery is invalid");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
issuer,
|
|
141
|
+
clientId,
|
|
142
|
+
scopes: [...WORKOS_CONNECT_DEFAULT_SCOPES]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function parseWorkosConnectCredentialMetadata(value) {
|
|
146
|
+
const record = recordFrom(value, "credential metadata");
|
|
147
|
+
if (record.version !== 1 || record.family !== "workos_connect") {
|
|
148
|
+
throw new WorkosConnectProtocolError("invalid_metadata", "credential metadata is invalid");
|
|
149
|
+
}
|
|
150
|
+
const issuer = canonicalIssuer(record.issuer);
|
|
151
|
+
const clientId = exactString(record.clientId, "clientId", MAX_CLIENT_ID_LENGTH);
|
|
152
|
+
const accessTokenHash = exactString(record.accessTokenHash, "accessTokenHash", 64);
|
|
153
|
+
const refreshTokenHash = exactString(record.refreshTokenHash, "refreshTokenHash", 64);
|
|
154
|
+
if (!CONNECT_CLIENT_ID_PATTERN.test(clientId)) {
|
|
155
|
+
throw new WorkosConnectProtocolError("invalid_metadata", "credential clientId is invalid");
|
|
156
|
+
}
|
|
157
|
+
if (!/^[a-f0-9]{64}$/u.test(accessTokenHash)) {
|
|
158
|
+
throw new WorkosConnectProtocolError(
|
|
159
|
+
"invalid_metadata",
|
|
160
|
+
"credential accessTokenHash is invalid"
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (!/^[a-f0-9]{64}$/u.test(refreshTokenHash)) {
|
|
164
|
+
throw new WorkosConnectProtocolError(
|
|
165
|
+
"invalid_metadata",
|
|
166
|
+
"credential refreshTokenHash is invalid"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
version: 1,
|
|
171
|
+
family: "workos_connect",
|
|
172
|
+
issuer,
|
|
173
|
+
clientId,
|
|
174
|
+
accessTokenHash,
|
|
175
|
+
refreshTokenHash
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function readBoundedJson(response, maxBytes) {
|
|
179
|
+
const contentLength = response.headers.get("content-length");
|
|
180
|
+
if (contentLength !== null) {
|
|
181
|
+
if (!/^\d+$/u.test(contentLength) || Number(contentLength) > maxBytes) {
|
|
182
|
+
throw new WorkosConnectProtocolError("invalid_response", "response body is too large");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!response.body) {
|
|
186
|
+
throw new WorkosConnectProtocolError("invalid_response", "response body is missing");
|
|
187
|
+
}
|
|
188
|
+
const reader = response.body.getReader();
|
|
189
|
+
const chunks = [];
|
|
190
|
+
let total = 0;
|
|
191
|
+
try {
|
|
192
|
+
while (true) {
|
|
193
|
+
const result = await reader.read();
|
|
194
|
+
if (result.done) break;
|
|
195
|
+
total += result.value.byteLength;
|
|
196
|
+
if (total > maxBytes) {
|
|
197
|
+
await reader.cancel();
|
|
198
|
+
throw new WorkosConnectProtocolError("invalid_response", "response body is too large");
|
|
199
|
+
}
|
|
200
|
+
chunks.push(result.value);
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
reader.releaseLock();
|
|
204
|
+
}
|
|
205
|
+
const bytes = new Uint8Array(total);
|
|
206
|
+
let offset = 0;
|
|
207
|
+
for (const chunk of chunks) {
|
|
208
|
+
bytes.set(chunk, offset);
|
|
209
|
+
offset += chunk.byteLength;
|
|
210
|
+
}
|
|
211
|
+
let text;
|
|
212
|
+
try {
|
|
213
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
214
|
+
return JSON.parse(text);
|
|
215
|
+
} catch {
|
|
216
|
+
throw new WorkosConnectProtocolError("invalid_response", "response body is not valid JSON");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function discoverWorkosConnect(siteUrl, fetchImpl = fetch, signal = AbortSignal.timeout(1e4)) {
|
|
220
|
+
const response = await fetchImpl(`${siteUrl}${DISCOVERY_PATH}`, { signal });
|
|
221
|
+
if (response.status === 404) return { state: "legacy_server" };
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new WorkosConnectProtocolError(
|
|
224
|
+
"discovery_unavailable",
|
|
225
|
+
`Connect discovery returned HTTP ${response.status}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
state: "available",
|
|
230
|
+
configuration: parseWorkosConnectConfiguration(
|
|
231
|
+
await readBoundedJson(response, DISCOVERY_MAX_BYTES)
|
|
232
|
+
)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function workosConnectTokenEndpoint(issuer) {
|
|
236
|
+
return `${issuer}${TOKEN_PATH}`;
|
|
237
|
+
}
|
|
238
|
+
async function requestWorkosDeviceAuthorization(configuration, fetchImpl = fetch, signal = AbortSignal.timeout(1e4)) {
|
|
239
|
+
const body = new URLSearchParams({
|
|
240
|
+
client_id: configuration.clientId,
|
|
241
|
+
scope: configuration.scopes.join(" ")
|
|
242
|
+
});
|
|
243
|
+
const response = await fetchImpl(`${configuration.issuer}${DEVICE_AUTHORIZATION_PATH}`, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
246
|
+
body,
|
|
247
|
+
signal
|
|
248
|
+
});
|
|
249
|
+
const value = await readBoundedJson(response, WORKOS_CONNECT_RESPONSE_MAX_BYTES);
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
const error = recordFrom(value, "device authorization error").error;
|
|
252
|
+
throw new WorkosConnectProtocolError(
|
|
253
|
+
typeof error === "string" ? error : "device_authorization_failed",
|
|
254
|
+
`Device authorization returned HTTP ${response.status}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const record = recordFrom(value, "device authorization");
|
|
258
|
+
const verificationUri = sameIssuerUrl(
|
|
259
|
+
record.verification_uri,
|
|
260
|
+
"verification_uri",
|
|
261
|
+
configuration.issuer
|
|
262
|
+
);
|
|
263
|
+
return {
|
|
264
|
+
deviceCode: exactString(record.device_code, "device_code", MAX_DEVICE_CODE_LENGTH),
|
|
265
|
+
userCode: exactString(record.user_code, "user_code", MAX_USER_CODE_LENGTH),
|
|
266
|
+
verificationUri,
|
|
267
|
+
verificationUriComplete: record.verification_uri_complete === void 0 ? void 0 : sameIssuerUrl(
|
|
268
|
+
record.verification_uri_complete,
|
|
269
|
+
"verification_uri_complete",
|
|
270
|
+
configuration.issuer
|
|
271
|
+
),
|
|
272
|
+
expiresIn: safeInteger(record.expires_in, "expires_in", 1, MAX_DEVICE_LIFETIME_SECONDS),
|
|
273
|
+
interval: safeInteger(record.interval ?? 5, "interval", 1, MAX_INTERVAL_SECONDS)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function parseWorkosConnectTokens(value, options = {}) {
|
|
277
|
+
const record = recordFrom(value, "token response");
|
|
278
|
+
const accessToken = exactString(
|
|
279
|
+
record.access_token,
|
|
280
|
+
"access_token",
|
|
281
|
+
WORKOS_CONNECT_RESPONSE_MAX_BYTES
|
|
282
|
+
);
|
|
283
|
+
const refreshToken = record.refresh_token === void 0 && options.fallbackRefreshToken !== void 0 ? options.fallbackRefreshToken : exactString(record.refresh_token, "refresh_token", WORKOS_CONNECT_RESPONSE_MAX_BYTES);
|
|
284
|
+
return {
|
|
285
|
+
accessToken,
|
|
286
|
+
refreshToken,
|
|
287
|
+
expiresIn: record.expires_in === void 0 ? void 0 : safeInteger(record.expires_in, "expires_in", 1, MAX_TOKEN_LIFETIME_SECONDS)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function providerError(value) {
|
|
291
|
+
const error = recordFrom(value, "token error").error;
|
|
292
|
+
return exactString(error, "error", MAX_USER_CODE_LENGTH);
|
|
293
|
+
}
|
|
294
|
+
async function defaultSleep(milliseconds, signal) {
|
|
295
|
+
await new Promise((resolve, reject) => {
|
|
296
|
+
if (signal.aborted) {
|
|
297
|
+
reject(signal.reason);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const abort = () => {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
reject(signal.reason);
|
|
303
|
+
};
|
|
304
|
+
const timer = setTimeout(() => {
|
|
305
|
+
signal.removeEventListener("abort", abort);
|
|
306
|
+
resolve();
|
|
307
|
+
}, milliseconds);
|
|
308
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
async function pollWorkosDeviceAuthorization(configuration, authorization, options = {}) {
|
|
312
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
313
|
+
const now = options.now ?? Date.now;
|
|
314
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
315
|
+
const signal = options.signal ?? AbortSignal.timeout(authorization.expiresIn * 1e3);
|
|
316
|
+
const deadline = now() + authorization.expiresIn * 1e3;
|
|
317
|
+
let intervalSeconds = authorization.interval;
|
|
318
|
+
while (now() < deadline) {
|
|
319
|
+
await sleep(Math.min(intervalSeconds * 1e3, Math.max(0, deadline - now())), signal);
|
|
320
|
+
if (now() >= deadline) break;
|
|
321
|
+
let response;
|
|
322
|
+
try {
|
|
323
|
+
response = await fetchImpl(workosConnectTokenEndpoint(configuration.issuer), {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
326
|
+
body: new URLSearchParams({
|
|
327
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
328
|
+
device_code: authorization.deviceCode,
|
|
329
|
+
client_id: configuration.clientId
|
|
330
|
+
}),
|
|
331
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS)])
|
|
332
|
+
});
|
|
333
|
+
} catch (error2) {
|
|
334
|
+
if (signal.aborted) {
|
|
335
|
+
throw new WorkosConnectProtocolError("expired_token", "Device authorization expired");
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const value = await readBoundedJson(response, WORKOS_CONNECT_RESPONSE_MAX_BYTES);
|
|
340
|
+
if (response.ok) return parseWorkosConnectTokens(value);
|
|
341
|
+
const error = providerError(value);
|
|
342
|
+
if (error === "authorization_pending") continue;
|
|
343
|
+
if (error === "slow_down") {
|
|
344
|
+
intervalSeconds = Math.min(MAX_INTERVAL_SECONDS, intervalSeconds + SLOW_DOWN_SECONDS);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (error === "access_denied" || error === "expired_token") {
|
|
348
|
+
throw new WorkosConnectProtocolError(error, `Device authorization failed: ${error}`);
|
|
349
|
+
}
|
|
350
|
+
throw new WorkosConnectProtocolError(error, "Token endpoint rejected device authorization");
|
|
351
|
+
}
|
|
352
|
+
throw new WorkosConnectProtocolError("expired_token", "Device authorization expired");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/lib/credentials.ts
|
|
356
|
+
import { createHash } from "crypto";
|
|
357
|
+
import { readFileSync } from "fs";
|
|
358
|
+
import { join as join2 } from "path";
|
|
359
|
+
var CONFIG_DIRECTORY = primConfigDirectory();
|
|
360
|
+
var TOKEN_FILE_PATH = join2(CONFIG_DIRECTORY, "token");
|
|
361
|
+
var REFRESH_TOKEN_PATH = join2(CONFIG_DIRECTORY, "refresh_token");
|
|
362
|
+
var TOKEN_EXPIRES_PATH = join2(CONFIG_DIRECTORY, "token_expires_at");
|
|
363
|
+
var TERMINAL_REFRESH_PATH = join2(CONFIG_DIRECTORY, "refresh_terminal");
|
|
364
|
+
var CREDENTIAL_METADATA_PATH = join2(CONFIG_DIRECTORY, "credential_metadata.json");
|
|
365
|
+
var CREDENTIAL_FAMILY_PATH = join2(CONFIG_DIRECTORY, "credential_family.json");
|
|
366
|
+
var CREDENTIAL_MIGRATION_PATH = join2(CONFIG_DIRECTORY, "credential_migration.json");
|
|
367
|
+
var CREDENTIAL_LOCK_PATH = join2(CONFIG_DIRECTORY, "credentials.lock");
|
|
368
|
+
var MAX_CREDENTIAL_METADATA_BYTES = 4096;
|
|
369
|
+
var CREDENTIAL_MIGRATION_VERSION = 1;
|
|
370
|
+
var CREDENTIAL_MIGRATION_STATE = "family_bound";
|
|
371
|
+
function readTrimmed(path) {
|
|
372
|
+
try {
|
|
373
|
+
const value = readFileSync(path, "utf8").trim();
|
|
374
|
+
return value || void 0;
|
|
375
|
+
} catch {
|
|
376
|
+
return void 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function resolveAuthCredential(options = {}) {
|
|
380
|
+
const envToken = (options.env ?? process.env).PRIM_TOKEN?.trim();
|
|
381
|
+
if (envToken) return { token: envToken, source: "environment" };
|
|
382
|
+
const stored = readTrimmed(options.tokenFilePath ?? TOKEN_FILE_PATH);
|
|
383
|
+
if (!stored) return void 0;
|
|
384
|
+
const metadata = readStoredCredentialMetadata(
|
|
385
|
+
options.metadataPath ?? CREDENTIAL_METADATA_PATH,
|
|
386
|
+
options.familyPath ?? CREDENTIAL_FAMILY_PATH,
|
|
387
|
+
options.migrationPath ?? CREDENTIAL_MIGRATION_PATH
|
|
388
|
+
);
|
|
389
|
+
if (metadata.state === "legacy_broker") {
|
|
390
|
+
if (metadata.metadata) {
|
|
391
|
+
const refreshToken2 = readTrimmed(options.refreshTokenPath ?? REFRESH_TOKEN_PATH);
|
|
392
|
+
if (!refreshToken2 || metadata.metadata.accessTokenHash !== credentialFingerprint(stored) || metadata.metadata.refreshTokenHash !== credentialFingerprint(refreshToken2)) {
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { token: stored, source: "token_file" };
|
|
397
|
+
}
|
|
398
|
+
if (metadata.state === "invalid") return void 0;
|
|
399
|
+
const refreshToken = readTrimmed(options.refreshTokenPath ?? REFRESH_TOKEN_PATH);
|
|
400
|
+
if (!refreshToken || metadata.metadata.accessTokenHash !== credentialFingerprint(stored) || metadata.metadata.refreshTokenHash !== credentialFingerprint(refreshToken)) {
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
return { token: stored, source: "token_file" };
|
|
404
|
+
}
|
|
405
|
+
function credentialFingerprint(value) {
|
|
406
|
+
return createHash("sha256").update(value).digest("hex");
|
|
407
|
+
}
|
|
408
|
+
function parseLegacyBrokerCredentialMetadata(value) {
|
|
409
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
410
|
+
throw new Error("credential family is invalid");
|
|
411
|
+
}
|
|
412
|
+
const record = value;
|
|
413
|
+
if (record.version !== 1 || record.family !== "legacy_broker" || typeof record.accessTokenHash !== "string" || typeof record.refreshTokenHash !== "string" || !/^[a-f0-9]{64}$/u.test(record.accessTokenHash) || !/^[a-f0-9]{64}$/u.test(record.refreshTokenHash)) {
|
|
414
|
+
throw new Error("credential family is invalid");
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
version: 1,
|
|
418
|
+
family: "legacy_broker",
|
|
419
|
+
accessTokenHash: record.accessTokenHash,
|
|
420
|
+
refreshTokenHash: record.refreshTokenHash
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function readBoundedCredentialJson(path) {
|
|
424
|
+
let raw;
|
|
425
|
+
try {
|
|
426
|
+
raw = readFileSync(path, "utf8");
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return error.code === "ENOENT" ? void 0 : null;
|
|
429
|
+
}
|
|
430
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_CREDENTIAL_METADATA_BYTES) return null;
|
|
431
|
+
try {
|
|
432
|
+
return JSON.parse(raw);
|
|
433
|
+
} catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function readCredentialFamily(path) {
|
|
438
|
+
const parsed = readBoundedCredentialJson(path);
|
|
439
|
+
if (parsed === void 0) return void 0;
|
|
440
|
+
if (parsed === null) return { state: "invalid" };
|
|
441
|
+
try {
|
|
442
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && parsed.family === "legacy_broker") {
|
|
443
|
+
return { state: "legacy_broker", metadata: parseLegacyBrokerCredentialMetadata(parsed) };
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
state: "workos_connect",
|
|
447
|
+
metadata: parseWorkosConnectCredentialMetadata(parsed)
|
|
448
|
+
};
|
|
449
|
+
} catch {
|
|
450
|
+
return { state: "invalid" };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function isCredentialMigrationSentinel(value) {
|
|
454
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
455
|
+
const record = value;
|
|
456
|
+
const keys = Object.keys(record).sort();
|
|
457
|
+
return keys.length === 2 && keys[0] === "state" && keys[1] === "version" && record.version === CREDENTIAL_MIGRATION_VERSION && record.state === CREDENTIAL_MIGRATION_STATE;
|
|
458
|
+
}
|
|
459
|
+
function readCredentialMigrationState(path = CREDENTIAL_MIGRATION_PATH) {
|
|
460
|
+
const parsed = readBoundedCredentialJson(path);
|
|
461
|
+
if (parsed === void 0) return "absent";
|
|
462
|
+
return parsed !== null && isCredentialMigrationSentinel(parsed) ? "valid" : "invalid";
|
|
463
|
+
}
|
|
464
|
+
function readStoredCredentialMetadata(metadataPath = CREDENTIAL_METADATA_PATH, familyPath = CREDENTIAL_FAMILY_PATH, migrationPath = CREDENTIAL_MIGRATION_PATH) {
|
|
465
|
+
const family = readCredentialFamily(familyPath);
|
|
466
|
+
if (family) return family;
|
|
467
|
+
const parsed = readBoundedCredentialJson(metadataPath);
|
|
468
|
+
if (parsed === void 0) {
|
|
469
|
+
return readCredentialMigrationState(migrationPath) === "absent" ? { state: "legacy_broker" } : { state: "invalid" };
|
|
470
|
+
}
|
|
471
|
+
if (parsed === null) return { state: "invalid" };
|
|
472
|
+
try {
|
|
473
|
+
return {
|
|
474
|
+
state: "workos_connect",
|
|
475
|
+
metadata: parseWorkosConnectCredentialMetadata(parsed)
|
|
476
|
+
};
|
|
477
|
+
} catch {
|
|
478
|
+
return { state: "invalid" };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function decodeJwtPayload(token) {
|
|
482
|
+
const parts = token.split(".");
|
|
483
|
+
if (parts.length !== 3) return void 0;
|
|
484
|
+
try {
|
|
485
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
486
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return void 0;
|
|
487
|
+
return payload;
|
|
488
|
+
} catch {
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function jwtExpiresAt(token) {
|
|
493
|
+
const exp = decodeJwtPayload(token)?.exp;
|
|
494
|
+
return typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : void 0;
|
|
495
|
+
}
|
|
496
|
+
function jwtOrganizationId(token) {
|
|
497
|
+
const orgId = decodeJwtPayload(token)?.org_id;
|
|
498
|
+
return typeof orgId === "string" && orgId.length > 0 ? orgId : void 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export {
|
|
502
|
+
__commonJS,
|
|
503
|
+
__toESM,
|
|
504
|
+
primConfigDirectory,
|
|
505
|
+
WORKOS_CONNECT_RESPONSE_MAX_BYTES,
|
|
506
|
+
WorkosConnectProtocolError,
|
|
507
|
+
readBoundedJson,
|
|
508
|
+
discoverWorkosConnect,
|
|
509
|
+
workosConnectTokenEndpoint,
|
|
510
|
+
requestWorkosDeviceAuthorization,
|
|
511
|
+
parseWorkosConnectTokens,
|
|
512
|
+
pollWorkosDeviceAuthorization,
|
|
513
|
+
TOKEN_FILE_PATH,
|
|
514
|
+
REFRESH_TOKEN_PATH,
|
|
515
|
+
TOKEN_EXPIRES_PATH,
|
|
516
|
+
TERMINAL_REFRESH_PATH,
|
|
517
|
+
CREDENTIAL_METADATA_PATH,
|
|
518
|
+
CREDENTIAL_FAMILY_PATH,
|
|
519
|
+
CREDENTIAL_MIGRATION_PATH,
|
|
520
|
+
CREDENTIAL_LOCK_PATH,
|
|
521
|
+
CREDENTIAL_MIGRATION_VERSION,
|
|
522
|
+
CREDENTIAL_MIGRATION_STATE,
|
|
523
|
+
resolveAuthCredential,
|
|
524
|
+
readCredentialMigrationState,
|
|
525
|
+
readStoredCredentialMetadata,
|
|
526
|
+
decodeJwtPayload,
|
|
527
|
+
jwtExpiresAt,
|
|
528
|
+
jwtOrganizationId
|
|
529
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/hooks/decision-feedback-core.ts
|
|
2
|
+
function buildHookOutput(options) {
|
|
3
|
+
const output = {};
|
|
4
|
+
if (options.systemMessage) output.systemMessage = options.systemMessage;
|
|
5
|
+
const fields = {};
|
|
6
|
+
if (options.additionalContext) fields.additionalContext = options.additionalContext;
|
|
7
|
+
if (options.reloadSkills) fields.reloadSkills = true;
|
|
8
|
+
if (Object.keys(fields).length > 0) {
|
|
9
|
+
output.hookSpecificOutput = { hookEventName: "SessionStart", ...fields };
|
|
10
|
+
}
|
|
11
|
+
return output;
|
|
12
|
+
}
|
|
13
|
+
function writeHookOutput(output, stream = process.stdout) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const finish = (ok, retainErrorListener = false) => {
|
|
17
|
+
if (settled) return;
|
|
18
|
+
settled = true;
|
|
19
|
+
if (!retainErrorListener) stream.off("error", onError);
|
|
20
|
+
resolve(ok);
|
|
21
|
+
};
|
|
22
|
+
const onError = () => {
|
|
23
|
+
if (settled) {
|
|
24
|
+
stream.off("error", onError);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
finish(false);
|
|
28
|
+
};
|
|
29
|
+
stream.once("error", onError);
|
|
30
|
+
try {
|
|
31
|
+
stream.write(
|
|
32
|
+
`${JSON.stringify(output)}
|
|
33
|
+
`,
|
|
34
|
+
(error) => error ? finish(false, true) : finish(true)
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
finish(false);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function handoffHookOutput(output, acknowledge, stream = process.stdout) {
|
|
42
|
+
const handedOff = await writeHookOutput(output, stream);
|
|
43
|
+
if (handedOff && acknowledge) {
|
|
44
|
+
try {
|
|
45
|
+
await acknowledge();
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return handedOff;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
buildHookOutput,
|
|
54
|
+
handoffHookOutput
|
|
55
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeShellTargets,
|
|
3
|
+
extractFileTargets
|
|
4
|
+
} from "./chunk-PPQYQEIR.js";
|
|
5
|
+
import {
|
|
6
|
+
canonicalRepositoryPath
|
|
7
|
+
} from "./chunk-MAZWKJGL.js";
|
|
8
|
+
|
|
9
|
+
// src/hooks/file-refs.ts
|
|
10
|
+
var MAX_PREFLIGHT_FILE_TARGETS = 25;
|
|
11
|
+
function primitiveMetadataForResolution(resolution) {
|
|
12
|
+
const shouldAttach = resolution.fileRefs.length > 0 || resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation !== void 0;
|
|
13
|
+
if (!shouldAttach) return;
|
|
14
|
+
return {
|
|
15
|
+
fileRefs: [...resolution.fileRefs],
|
|
16
|
+
...resolution.rejected.length > 0 || resolution.targetsIncomplete ? { fileRefsIncomplete: true } : {},
|
|
17
|
+
...resolution.targetsTruncated ? { fileRefsTruncated: true } : {},
|
|
18
|
+
...resolution.shellMutation !== void 0 ? { shellMutation: resolution.shellMutation } : {}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function preserveHookFileMetadata(payload, resolution) {
|
|
22
|
+
const primitive = primitiveMetadataForResolution(resolution);
|
|
23
|
+
if (!primitive || !payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
24
|
+
return payload;
|
|
25
|
+
}
|
|
26
|
+
return { ...payload, primitive };
|
|
27
|
+
}
|
|
28
|
+
function resolveHookFileRefs(args) {
|
|
29
|
+
const command = (args.agent === "claude_code" || args.agent === "codex") && args.toolName === "Bash" ? typeof args.toolInput === "string" ? args.toolInput : args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.command === "string" ? args.toolInput.command : void 0 : void 0;
|
|
30
|
+
const shell = command === void 0 ? void 0 : analyzeShellTargets(command);
|
|
31
|
+
const shellMutation = shell?.mutation === "none" ? "none" : shell?.coverage === "complete" ? "resolved" : shell ? "unresolved" : void 0;
|
|
32
|
+
const readPath = args.agent === "claude_code" && args.toolName === "Read" && args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.file_path === "string" ? [args.toolInput.file_path] : void 0;
|
|
33
|
+
const nativeTargets = shell ? null : readPath ? { paths: readPath, complete: true } : extractFileTargets(args.toolName, args.toolInput, args.agent);
|
|
34
|
+
const rawPaths = shell ? shellMutation === "unresolved" ? [] : shell.paths : nativeTargets?.paths ?? [];
|
|
35
|
+
const uniqueRawPaths = Array.from(new Set(rawPaths));
|
|
36
|
+
const targetsTruncated = uniqueRawPaths.length > MAX_PREFLIGHT_FILE_TARGETS;
|
|
37
|
+
const fileRefs = /* @__PURE__ */ new Set();
|
|
38
|
+
const rejected = [];
|
|
39
|
+
for (const path of uniqueRawPaths.slice(0, MAX_PREFLIGHT_FILE_TARGETS)) {
|
|
40
|
+
const canonical = canonicalRepositoryPath(path, args.repository, args.cwd);
|
|
41
|
+
if (canonical.ok) fileRefs.add(canonical.file);
|
|
42
|
+
else rejected.push({ path, reason: canonical.reason });
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
fileRefs: [...fileRefs],
|
|
46
|
+
rejected,
|
|
47
|
+
targetsIncomplete: nativeTargets?.complete === false,
|
|
48
|
+
targetsTruncated,
|
|
49
|
+
...shellMutation ? { shellMutation: targetsTruncated ? "unresolved" : shellMutation } : {}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function enrichHookPayloadWithFileRefs(args) {
|
|
53
|
+
const toolName = typeof args.parsed.tool_name === "string" ? args.parsed.tool_name : "";
|
|
54
|
+
const resolution = resolveHookFileRefs({
|
|
55
|
+
toolName,
|
|
56
|
+
toolInput: args.parsed.tool_input,
|
|
57
|
+
agent: args.agent,
|
|
58
|
+
cwd: args.cwd,
|
|
59
|
+
repository: args.repository
|
|
60
|
+
});
|
|
61
|
+
const primitive = primitiveMetadataForResolution(resolution);
|
|
62
|
+
if (!primitive) return { parsed: args.parsed, resolution };
|
|
63
|
+
return {
|
|
64
|
+
parsed: {
|
|
65
|
+
...args.parsed,
|
|
66
|
+
// An explicit empty list is authoritative when every exposed target was
|
|
67
|
+
// rejected. It prevents backend fallback to unsafe raw tool input.
|
|
68
|
+
primitive
|
|
69
|
+
},
|
|
70
|
+
resolution
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
preserveHookFileMetadata,
|
|
76
|
+
enrichHookPayloadWithFileRefs
|
|
77
|
+
};
|