@mrciphersmith/keryx 0.3.5 → 0.3.6
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/dist/cli.js +872 -316
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +319 -76
- package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
- package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/django/pack.json +43 -0
- package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
- package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
- package/src/gdskills/bundled/stacks/python/pack.json +1 -1
- package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
- package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
- package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
- package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
- package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
package/dist/cli.js
CHANGED
|
@@ -690,7 +690,7 @@ function deviceCodeMethodLabel(provider) {
|
|
|
690
690
|
return "SuperGrok Subscription";
|
|
691
691
|
if (provider === "github-copilot")
|
|
692
692
|
return "Login with GitHub Copilot";
|
|
693
|
-
if (provider === "openai")
|
|
693
|
+
if (provider === "openai-codex")
|
|
694
694
|
return "ChatGPT Plus/Pro (headless)";
|
|
695
695
|
return "Device authorization";
|
|
696
696
|
}
|
|
@@ -728,8 +728,13 @@ var init_catalog = __esm(() => {
|
|
|
728
728
|
},
|
|
729
729
|
{
|
|
730
730
|
provider: "openai",
|
|
731
|
-
label: "OpenAI",
|
|
732
|
-
methods: ["
|
|
731
|
+
label: "OpenAI API",
|
|
732
|
+
methods: ["api-key"]
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
provider: "openai-codex",
|
|
736
|
+
label: "ChatGPT / Codex subscription",
|
|
737
|
+
methods: ["device-code"]
|
|
733
738
|
},
|
|
734
739
|
{
|
|
735
740
|
provider: "github-copilot",
|
|
@@ -1991,19 +1996,47 @@ var init_model_profile = __esm(() => {
|
|
|
1991
1996
|
STRENGTH_TIERS = new Set(["light", "standard", "deep"]);
|
|
1992
1997
|
});
|
|
1993
1998
|
|
|
1999
|
+
// src/lib/oauth/openai-token.ts
|
|
2000
|
+
function codexTokenMetadata(token) {
|
|
2001
|
+
if (typeof token !== "string" || token.length > 1e5 || token.split(".").length !== 3)
|
|
2002
|
+
return {};
|
|
2003
|
+
try {
|
|
2004
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
|
|
2005
|
+
if (typeof payload !== "object" || payload === null)
|
|
2006
|
+
return {};
|
|
2007
|
+
const claims = payload;
|
|
2008
|
+
const auth = claims["https://api.openai.com/auth"];
|
|
2009
|
+
const accountId = typeof auth === "object" && auth !== null ? auth.chatgpt_account_id : undefined;
|
|
2010
|
+
return {
|
|
2011
|
+
...typeof accountId === "string" && accountId.length > 0 && accountId.trim() === accountId && !/[\r\n]/.test(accountId) ? { accountId } : {},
|
|
2012
|
+
...typeof claims.exp === "number" && Number.isFinite(claims.exp) && claims.exp > 0 ? { expiresAt: claims.exp * 1000 } : {}
|
|
2013
|
+
};
|
|
2014
|
+
} catch {
|
|
2015
|
+
return {};
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1994
2019
|
// src/lib/oauth/grants.ts
|
|
1995
2020
|
function loadOAuthGrant(provider, dir) {
|
|
1996
2021
|
const grants = loadShellConfig(dir).oauthGrants;
|
|
1997
2022
|
if (grants === undefined) {
|
|
1998
2023
|
return;
|
|
1999
2024
|
}
|
|
2000
|
-
const grant = grants[provider];
|
|
2025
|
+
const grant = grants[provider] ?? (provider === "openai-codex" ? grants.openai : undefined);
|
|
2001
2026
|
if (grant === undefined || typeof grant.access !== "string" || grant.access.length === 0) {
|
|
2002
2027
|
return;
|
|
2003
2028
|
}
|
|
2004
2029
|
if (grant.method !== "device-code" && grant.method !== "oauth-pkce-loopback") {
|
|
2005
2030
|
return;
|
|
2006
2031
|
}
|
|
2032
|
+
if (provider === "openai-codex" || provider === "openai") {
|
|
2033
|
+
const metadata = codexTokenMetadata(grant.access);
|
|
2034
|
+
return {
|
|
2035
|
+
...grant,
|
|
2036
|
+
...grant.accountId === undefined && metadata.accountId !== undefined ? { accountId: metadata.accountId } : {},
|
|
2037
|
+
...grant.expires === undefined && metadata.expiresAt !== undefined ? { expires: metadata.expiresAt } : {}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2007
2040
|
return grant;
|
|
2008
2041
|
}
|
|
2009
2042
|
function saveOAuthGrant(provider, grant, dir) {
|
|
@@ -2012,15 +2045,17 @@ function saveOAuthGrant(provider, grant, dir) {
|
|
|
2012
2045
|
}
|
|
2013
2046
|
function deleteOAuthGrant(provider, dir) {
|
|
2014
2047
|
const existing = { ...loadShellConfig(dir).oauthGrants ?? {} };
|
|
2015
|
-
if (existing[provider] === undefined) {
|
|
2048
|
+
if (existing[provider] === undefined && !(provider === "openai-codex" && existing.openai !== undefined)) {
|
|
2016
2049
|
return;
|
|
2017
2050
|
}
|
|
2018
2051
|
delete existing[provider];
|
|
2052
|
+
if (provider === "openai-codex")
|
|
2053
|
+
delete existing.openai;
|
|
2019
2054
|
saveShellConfig({ oauthGrants: existing }, dir);
|
|
2020
2055
|
}
|
|
2021
2056
|
function listOAuthGrantProviders(dir) {
|
|
2022
2057
|
const grants = loadShellConfig(dir).oauthGrants ?? {};
|
|
2023
|
-
return Object.keys(grants).filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
2058
|
+
return [...new Set(Object.keys(grants).map((name) => name === "openai" ? "openai-codex" : name))].filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
2024
2059
|
}
|
|
2025
2060
|
function oauthGrantStatus(provider, dir, now = Date.now) {
|
|
2026
2061
|
const grant = loadOAuthGrant(provider, dir);
|
|
@@ -2087,6 +2122,8 @@ function grantFromTokens(method, tokens, now = Date.now) {
|
|
|
2087
2122
|
access: tokens.accessToken,
|
|
2088
2123
|
...tokens.refreshToken !== undefined ? { refresh: tokens.refreshToken } : {},
|
|
2089
2124
|
...tokens.expiresInSeconds !== undefined ? { expires: now() + tokens.expiresInSeconds * 1000 } : {},
|
|
2125
|
+
...tokens.accountId !== undefined ? { accountId: tokens.accountId } : {},
|
|
2126
|
+
...tokens.expiresInSeconds === undefined && tokens.expiresAt !== undefined ? { expires: tokens.expiresAt } : {},
|
|
2090
2127
|
obtainedAt
|
|
2091
2128
|
};
|
|
2092
2129
|
}
|
|
@@ -2265,6 +2302,315 @@ var init_device_code = __esm(() => {
|
|
|
2265
2302
|
};
|
|
2266
2303
|
});
|
|
2267
2304
|
|
|
2305
|
+
// src/lib/oauth/openai-codex.ts
|
|
2306
|
+
function withSignal(base, signal) {
|
|
2307
|
+
if (signal?.aborted === true)
|
|
2308
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2309
|
+
const timeout = AbortSignal.timeout(30000);
|
|
2310
|
+
return { ...base, signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]) };
|
|
2311
|
+
}
|
|
2312
|
+
async function requestCodexDeviceCode(http) {
|
|
2313
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2316
|
+
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
2317
|
+
}, http.signal));
|
|
2318
|
+
const json = await response.json().catch(() => ({}));
|
|
2319
|
+
if (!response.ok || typeof json !== "object" || json === null) {
|
|
2320
|
+
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
2321
|
+
}
|
|
2322
|
+
const rec = json;
|
|
2323
|
+
rec.user_code ??= rec.usercode;
|
|
2324
|
+
if (typeof rec.device_auth_id !== "string" || rec.device_auth_id.length === 0 || typeof rec.user_code !== "string" || rec.user_code.length === 0 || [...rec.user_code].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
2325
|
+
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
2326
|
+
}
|
|
2327
|
+
const intervalSeconds = Number(rec.interval);
|
|
2328
|
+
return {
|
|
2329
|
+
deviceAuthId: rec.device_auth_id,
|
|
2330
|
+
userCode: rec.user_code,
|
|
2331
|
+
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
2332
|
+
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
async function pollCodexDeviceToken(challenge, http) {
|
|
2336
|
+
const sleep = http.sleep;
|
|
2337
|
+
const now = http.now ?? Date.now;
|
|
2338
|
+
const deadline = now() + DEVICE_LOGIN_TIMEOUT_MS;
|
|
2339
|
+
while (now() < deadline) {
|
|
2340
|
+
if (http.signal?.aborted === true) {
|
|
2341
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2342
|
+
}
|
|
2343
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
2344
|
+
method: "POST",
|
|
2345
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2346
|
+
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
2347
|
+
}, http.signal));
|
|
2348
|
+
if (response.ok) {
|
|
2349
|
+
const data = await response.json();
|
|
2350
|
+
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
2351
|
+
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
2352
|
+
}
|
|
2353
|
+
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
2354
|
+
}
|
|
2355
|
+
if (response.status !== 403 && response.status !== 404) {
|
|
2356
|
+
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
2357
|
+
}
|
|
2358
|
+
await abortableSleep(Math.min(challenge.intervalMs, Math.max(0, deadline - now())), sleep, http.signal);
|
|
2359
|
+
}
|
|
2360
|
+
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
2361
|
+
}
|
|
2362
|
+
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
2363
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
2364
|
+
method: "POST",
|
|
2365
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
2366
|
+
body: new URLSearchParams({
|
|
2367
|
+
grant_type: "authorization_code",
|
|
2368
|
+
code,
|
|
2369
|
+
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
2370
|
+
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
2371
|
+
code_verifier: codeVerifier
|
|
2372
|
+
}).toString()
|
|
2373
|
+
}, http.signal));
|
|
2374
|
+
const json = await response.json().catch(() => ({}));
|
|
2375
|
+
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
2376
|
+
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
2377
|
+
}
|
|
2378
|
+
const rec = json;
|
|
2379
|
+
const access = codexTokenMetadata(rec.access_token);
|
|
2380
|
+
const accountId = codexTokenMetadata(rec.id_token).accountId ?? access.accountId;
|
|
2381
|
+
return {
|
|
2382
|
+
accessToken: rec.access_token,
|
|
2383
|
+
...accountId !== undefined ? { accountId } : {},
|
|
2384
|
+
...access.expiresAt !== undefined ? { expiresAt: access.expiresAt } : {},
|
|
2385
|
+
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
2386
|
+
...typeof rec.expires_in === "number" && Number.isFinite(rec.expires_in) && rec.expires_in > 0 ? { expiresInSeconds: rec.expires_in } : {}
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
async function abortableSleep(ms, sleep, signal) {
|
|
2390
|
+
if (signal?.aborted === true)
|
|
2391
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2392
|
+
await new Promise((resolve, reject) => {
|
|
2393
|
+
let timer;
|
|
2394
|
+
const cleanup = () => {
|
|
2395
|
+
if (timer !== undefined)
|
|
2396
|
+
clearTimeout(timer);
|
|
2397
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2398
|
+
};
|
|
2399
|
+
const onAbort = () => {
|
|
2400
|
+
cleanup();
|
|
2401
|
+
reject(new DeviceCodeError("cancelled", "device authorization was cancelled"));
|
|
2402
|
+
};
|
|
2403
|
+
const done = () => {
|
|
2404
|
+
cleanup();
|
|
2405
|
+
resolve();
|
|
2406
|
+
};
|
|
2407
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2408
|
+
if (sleep === undefined)
|
|
2409
|
+
timer = setTimeout(done, ms);
|
|
2410
|
+
else
|
|
2411
|
+
sleep(ms).then(done, () => {
|
|
2412
|
+
cleanup();
|
|
2413
|
+
reject(new DeviceCodeError("failed", "device authorization wait failed"));
|
|
2414
|
+
});
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", DEVICE_LOGIN_TIMEOUT_MS;
|
|
2418
|
+
var init_openai_codex = __esm(() => {
|
|
2419
|
+
init_device_code();
|
|
2420
|
+
DEVICE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
// src/lib/oauth/openai-subscription.ts
|
|
2424
|
+
import { resolve, join } from "path";
|
|
2425
|
+
function failure(detail) {
|
|
2426
|
+
return new SubscriptionAuthError(`ChatGPT subscription: ${detail}. ${LOGIN}`);
|
|
2427
|
+
}
|
|
2428
|
+
function enrich(grant) {
|
|
2429
|
+
const metadata = codexTokenMetadata(grant.access);
|
|
2430
|
+
return {
|
|
2431
|
+
...grant,
|
|
2432
|
+
...grant.accountId === undefined && metadata.accountId !== undefined ? { accountId: metadata.accountId } : {},
|
|
2433
|
+
...grant.expires === undefined && metadata.expiresAt !== undefined ? { expires: metadata.expiresAt } : {}
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function cancelled(signal) {
|
|
2437
|
+
if (signal?.aborted)
|
|
2438
|
+
throw failure("authorization cancelled");
|
|
2439
|
+
}
|
|
2440
|
+
async function refreshOpenAiCodexGrant(input) {
|
|
2441
|
+
cancelled(input.signal);
|
|
2442
|
+
const loaded = loadOAuthGrant("openai-codex", input.configDir);
|
|
2443
|
+
if (loaded === undefined)
|
|
2444
|
+
throw failure("not signed in");
|
|
2445
|
+
const grant = enrich(loaded);
|
|
2446
|
+
const now = input.now ?? Date.now;
|
|
2447
|
+
if (!input.forceRefresh && grant.expires !== undefined && grant.expires > now() + 120000)
|
|
2448
|
+
return grant;
|
|
2449
|
+
if (!grant.refresh) {
|
|
2450
|
+
if (input.forceRefresh || grant.expires !== undefined && grant.expires <= now())
|
|
2451
|
+
throw failure("login expired and cannot be refreshed");
|
|
2452
|
+
return grant;
|
|
2453
|
+
}
|
|
2454
|
+
const key = resolve(input.configDir ?? keryxConfigDir());
|
|
2455
|
+
let pending = refreshes.get(key);
|
|
2456
|
+
if (pending === undefined) {
|
|
2457
|
+
pending = refreshWithLock(grant, input, key);
|
|
2458
|
+
refreshes.set(key, pending);
|
|
2459
|
+
pending.finally(() => {
|
|
2460
|
+
if (refreshes.get(key) === pending)
|
|
2461
|
+
refreshes.delete(key);
|
|
2462
|
+
}).catch(() => {});
|
|
2463
|
+
}
|
|
2464
|
+
if (input.signal === undefined)
|
|
2465
|
+
return pending;
|
|
2466
|
+
return new Promise((resolveGrant, reject) => {
|
|
2467
|
+
const abort = () => {
|
|
2468
|
+
cleanup();
|
|
2469
|
+
reject(failure("authorization cancelled"));
|
|
2470
|
+
};
|
|
2471
|
+
const cleanup = () => input.signal?.removeEventListener("abort", abort);
|
|
2472
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
2473
|
+
if (input.signal?.aborted) {
|
|
2474
|
+
abort();
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
pending.then((result) => {
|
|
2478
|
+
cleanup();
|
|
2479
|
+
resolveGrant(result);
|
|
2480
|
+
}, (error) => {
|
|
2481
|
+
cleanup();
|
|
2482
|
+
reject(error);
|
|
2483
|
+
});
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
async function refreshWithLock(initial, input, configDir) {
|
|
2487
|
+
try {
|
|
2488
|
+
return await withFileLock(join(configDir, ".openai-codex-refresh.lock"), async () => {
|
|
2489
|
+
const current = loadOAuthGrant("openai-codex", input.configDir);
|
|
2490
|
+
if (current === undefined || current.obtainedAt !== initial.obtainedAt || initial.accountId !== undefined && current.accountId !== initial.accountId) {
|
|
2491
|
+
throw failure("login changed while waiting for token refresh");
|
|
2492
|
+
}
|
|
2493
|
+
if (current.access !== initial.access || current.refresh !== initial.refresh)
|
|
2494
|
+
return enrich(current);
|
|
2495
|
+
return refreshGrant(current, input);
|
|
2496
|
+
}, { timeoutMs: 35000, retryMs: 50, staleMs: 30000, heartbeatMs: 1000 });
|
|
2497
|
+
} catch (error) {
|
|
2498
|
+
if (error instanceof SubscriptionAuthError)
|
|
2499
|
+
throw error;
|
|
2500
|
+
throw failure("could not acquire or release the token refresh lock");
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
async function refreshGrant(grant, input) {
|
|
2504
|
+
const now = input.now ?? Date.now;
|
|
2505
|
+
try {
|
|
2506
|
+
const response = await input.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, {
|
|
2507
|
+
method: "POST",
|
|
2508
|
+
signal: AbortSignal.timeout(30000),
|
|
2509
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2510
|
+
body: JSON.stringify({ grant_type: "refresh_token", client_id: OPENAI_CODEX_CLIENT_ID, refresh_token: grant.refresh })
|
|
2511
|
+
});
|
|
2512
|
+
if (!response.ok)
|
|
2513
|
+
throw failure(`token refresh failed (HTTP ${response.status})`);
|
|
2514
|
+
const json = await response.json();
|
|
2515
|
+
if (typeof json !== "object" || json === null)
|
|
2516
|
+
throw failure("invalid token refresh response");
|
|
2517
|
+
const rec = json;
|
|
2518
|
+
if (typeof rec.access_token !== "string" || rec.access_token.length === 0)
|
|
2519
|
+
throw failure("token refresh returned no access token");
|
|
2520
|
+
const id = codexTokenMetadata(rec.id_token);
|
|
2521
|
+
const access = codexTokenMetadata(rec.access_token);
|
|
2522
|
+
const expires = typeof rec.expires_in === "number" && Number.isFinite(rec.expires_in) && rec.expires_in > 0 ? now() + rec.expires_in * 1000 : access.expiresAt;
|
|
2523
|
+
const accountId = id.accountId ?? access.accountId ?? grant.accountId;
|
|
2524
|
+
if (grant.accountId !== undefined && accountId !== grant.accountId)
|
|
2525
|
+
throw failure("refreshed account does not match saved login");
|
|
2526
|
+
const next = {
|
|
2527
|
+
...grant,
|
|
2528
|
+
access: rec.access_token,
|
|
2529
|
+
...typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? { refresh: rec.refresh_token } : {},
|
|
2530
|
+
...accountId !== undefined ? { accountId } : {},
|
|
2531
|
+
lastRefreshedAt: new Date(now()).toISOString()
|
|
2532
|
+
};
|
|
2533
|
+
if (expires !== undefined)
|
|
2534
|
+
next.expires = expires;
|
|
2535
|
+
else
|
|
2536
|
+
delete next.expires;
|
|
2537
|
+
const current = loadOAuthGrant("openai-codex", input.configDir);
|
|
2538
|
+
if (current?.access !== grant.access || current.refresh !== grant.refresh)
|
|
2539
|
+
throw failure("login changed during token refresh");
|
|
2540
|
+
saveOAuthGrant("openai-codex", next, input.configDir);
|
|
2541
|
+
const persisted = loadOAuthGrant("openai-codex", input.configDir);
|
|
2542
|
+
if (persisted?.access !== next.access || persisted.refresh !== next.refresh)
|
|
2543
|
+
throw failure("could not save refreshed login");
|
|
2544
|
+
return next;
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (error instanceof SubscriptionAuthError)
|
|
2547
|
+
throw error;
|
|
2548
|
+
throw failure("token refresh failed");
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
async function ensureOpenAiCodexGrant(input) {
|
|
2552
|
+
const grant = await refreshOpenAiCodexGrant(input);
|
|
2553
|
+
cancelled(input.signal);
|
|
2554
|
+
if (grant.accountId === undefined || grant.accountId.length === 0 || /[\r\n]/.test(grant.accountId))
|
|
2555
|
+
throw failure("saved login has no account identity");
|
|
2556
|
+
if (grant.expires !== undefined && grant.expires <= (input.now ?? Date.now)())
|
|
2557
|
+
throw failure("login has expired");
|
|
2558
|
+
return { ...grant, accountId: grant.accountId };
|
|
2559
|
+
}
|
|
2560
|
+
var refreshes, LOGIN = "Run `keryx auth login openai-codex`.", SubscriptionAuthError;
|
|
2561
|
+
var init_openai_subscription = __esm(() => {
|
|
2562
|
+
init_config_dir();
|
|
2563
|
+
init_fs();
|
|
2564
|
+
init_grants();
|
|
2565
|
+
init_openai_codex();
|
|
2566
|
+
refreshes = new Map;
|
|
2567
|
+
SubscriptionAuthError = class SubscriptionAuthError extends Error {
|
|
2568
|
+
};
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
// src/commands/subscription-models.ts
|
|
2572
|
+
async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
|
|
2573
|
+
const controller = new AbortController;
|
|
2574
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 8000);
|
|
2575
|
+
let authenticated = false;
|
|
2576
|
+
try {
|
|
2577
|
+
const grant = await ensureOpenAiCodexGrant({ fetch: fetchFn, signal: controller.signal, ...opts.configDir !== undefined ? { configDir: opts.configDir } : {} });
|
|
2578
|
+
authenticated = true;
|
|
2579
|
+
const response = await fetchFn(`${OPENAI_CODEX_PICKER.baseUrl}/models?client_version=${CODEX_CATALOG_CLIENT_VERSION}`, {
|
|
2580
|
+
headers: { Authorization: `Bearer ${grant.access}`, "ChatGPT-Account-ID": grant.accountId, originator: "keryx", Accept: "application/json" },
|
|
2581
|
+
signal: controller.signal,
|
|
2582
|
+
redirect: "error"
|
|
2583
|
+
});
|
|
2584
|
+
if (!response.ok) {
|
|
2585
|
+
return { models: [], source: "fallback", failure: { kind: response.status === 401 || response.status === 403 ? "rejected" : "http", status: response.status } };
|
|
2586
|
+
}
|
|
2587
|
+
const body = await response.json();
|
|
2588
|
+
const entries = typeof body === "object" && body !== null && "models" in body && Array.isArray(body.models) ? body.models : [];
|
|
2589
|
+
const models = [...new Set(entries.flatMap((entry) => {
|
|
2590
|
+
if (typeof entry !== "object" || entry === null || !("slug" in entry) || typeof entry.slug !== "string" || entry.slug.length === 0)
|
|
2591
|
+
return [];
|
|
2592
|
+
if (!("visibility" in entry) || entry.visibility !== "list")
|
|
2593
|
+
return [];
|
|
2594
|
+
return [entry.slug];
|
|
2595
|
+
}))];
|
|
2596
|
+
return models.length > 0 ? { models, source: "live" } : { models: [], source: "fallback", failure: { kind: "empty" } };
|
|
2597
|
+
} catch {
|
|
2598
|
+
return { models: [], source: "fallback", failure: controller.signal.aborted || authenticated ? { kind: "unreachable", detail: controller.signal.aborted ? "model listing timed out" : "subscription model listing failed" } : { kind: "rejected", status: 401, detail: "Run `keryx auth login openai-codex` to connect your ChatGPT subscription." } };
|
|
2599
|
+
} finally {
|
|
2600
|
+
clearTimeout(timer);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
var OPENAI_CODEX_PICKER, CODEX_CATALOG_CLIENT_VERSION = "0.157.0";
|
|
2604
|
+
var init_subscription_models = __esm(() => {
|
|
2605
|
+
init_openai_subscription();
|
|
2606
|
+
OPENAI_CODEX_PICKER = {
|
|
2607
|
+
name: "openai-codex",
|
|
2608
|
+
label: "ChatGPT / Codex",
|
|
2609
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
2610
|
+
models: ["gpt-5.3-codex"]
|
|
2611
|
+
};
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2268
2614
|
// src/lib/oauth/copilot.ts
|
|
2269
2615
|
function copilotApiBaseFromExchange(raw) {
|
|
2270
2616
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
@@ -2357,87 +2703,6 @@ var init_copilot = __esm(() => {
|
|
|
2357
2703
|
init_device_code();
|
|
2358
2704
|
});
|
|
2359
2705
|
|
|
2360
|
-
// src/lib/oauth/openai-codex.ts
|
|
2361
|
-
function withSignal(base, signal) {
|
|
2362
|
-
return signal === undefined ? base : { ...base, signal };
|
|
2363
|
-
}
|
|
2364
|
-
async function requestCodexDeviceCode(http) {
|
|
2365
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
2366
|
-
method: "POST",
|
|
2367
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2368
|
-
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
2369
|
-
}, http.signal));
|
|
2370
|
-
const json = await response.json().catch(() => ({}));
|
|
2371
|
-
if (!response.ok || typeof json !== "object" || json === null) {
|
|
2372
|
-
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
2373
|
-
}
|
|
2374
|
-
const rec = json;
|
|
2375
|
-
if (typeof rec.device_auth_id !== "string" || typeof rec.user_code !== "string") {
|
|
2376
|
-
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
2377
|
-
}
|
|
2378
|
-
const intervalSeconds = Number(rec.interval);
|
|
2379
|
-
return {
|
|
2380
|
-
deviceAuthId: rec.device_auth_id,
|
|
2381
|
-
userCode: rec.user_code,
|
|
2382
|
-
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
2383
|
-
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
async function pollCodexDeviceToken(challenge, http) {
|
|
2387
|
-
const sleep = http.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2388
|
-
const now = http.now ?? Date.now;
|
|
2389
|
-
const deadline = now() + 5 * 60 * 1000;
|
|
2390
|
-
while (now() < deadline) {
|
|
2391
|
-
if (http.signal?.aborted === true) {
|
|
2392
|
-
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2393
|
-
}
|
|
2394
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
2395
|
-
method: "POST",
|
|
2396
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2397
|
-
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
2398
|
-
}, http.signal));
|
|
2399
|
-
if (response.ok) {
|
|
2400
|
-
const data = await response.json();
|
|
2401
|
-
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
2402
|
-
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
2403
|
-
}
|
|
2404
|
-
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
2405
|
-
}
|
|
2406
|
-
if (response.status !== 403 && response.status !== 404) {
|
|
2407
|
-
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
2408
|
-
}
|
|
2409
|
-
await sleep(challenge.intervalMs + POLL_SAFETY_MARGIN_MS2);
|
|
2410
|
-
}
|
|
2411
|
-
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
2412
|
-
}
|
|
2413
|
-
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
2414
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
2415
|
-
method: "POST",
|
|
2416
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
2417
|
-
body: new URLSearchParams({
|
|
2418
|
-
grant_type: "authorization_code",
|
|
2419
|
-
code,
|
|
2420
|
-
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
2421
|
-
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
2422
|
-
code_verifier: codeVerifier
|
|
2423
|
-
}).toString()
|
|
2424
|
-
}, http.signal));
|
|
2425
|
-
const json = await response.json().catch(() => ({}));
|
|
2426
|
-
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
2427
|
-
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
2428
|
-
}
|
|
2429
|
-
const rec = json;
|
|
2430
|
-
return {
|
|
2431
|
-
accessToken: rec.access_token,
|
|
2432
|
-
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
2433
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {}
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", POLL_SAFETY_MARGIN_MS2 = 3000;
|
|
2437
|
-
var init_openai_codex = __esm(() => {
|
|
2438
|
-
init_device_code();
|
|
2439
|
-
});
|
|
2440
|
-
|
|
2441
2706
|
// src/lib/oauth/login.ts
|
|
2442
2707
|
function httpOf(input) {
|
|
2443
2708
|
return {
|
|
@@ -2452,7 +2717,7 @@ function present(challenge, provider) {
|
|
|
2452
2717
|
userCode: challenge.userCode,
|
|
2453
2718
|
verificationUri: challenge.verificationUri,
|
|
2454
2719
|
...challenge.verificationUriComplete !== undefined ? { verificationUriComplete: challenge.verificationUriComplete } : {},
|
|
2455
|
-
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)})
|
|
2720
|
+
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)}).` + (provider === "openai-codex" ? " Enable device code login in ChatGPT Settings > Security if required. Cancel with Ctrl+C." : "")
|
|
2456
2721
|
};
|
|
2457
2722
|
}
|
|
2458
2723
|
async function loginRfc8628(input) {
|
|
@@ -2497,24 +2762,37 @@ async function loginDeviceCode(input) {
|
|
|
2497
2762
|
return { ok: false, error: `${input.provider} does not support device authorization` };
|
|
2498
2763
|
}
|
|
2499
2764
|
try {
|
|
2500
|
-
const tokens = input.provider === "openai" ? await loginCodex(input) : await loginRfc8628(input);
|
|
2765
|
+
const tokens = input.provider === "openai-codex" ? await loginCodex(input) : await loginRfc8628(input);
|
|
2501
2766
|
saveOAuthGrant(input.provider, grantFromTokens("device-code", tokens, input.now), input.dir);
|
|
2767
|
+
if (input.provider === "openai-codex" && loadOAuthGrant(input.provider, input.dir)?.access !== tokens.accessToken) {
|
|
2768
|
+
throw new DeviceCodeError("failed", "Could not save ChatGPT login securely. Check the Keryx config directory permissions.");
|
|
2769
|
+
}
|
|
2502
2770
|
return { ok: true, provider: input.provider };
|
|
2503
2771
|
} catch (err) {
|
|
2504
|
-
const message = err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
2772
|
+
const message = input.signal?.aborted === true ? "Device authorization was cancelled." : input.provider === "openai-codex" && !(err instanceof DeviceCodeError) ? "ChatGPT device authorization failed. Run `keryx auth login openai-codex` to retry." : err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
2505
2773
|
return { ok: false, error: message };
|
|
2506
2774
|
}
|
|
2507
2775
|
}
|
|
2508
2776
|
async function loginCodex(input) {
|
|
2509
|
-
const
|
|
2777
|
+
const timeout = AbortSignal.timeout(15 * 60 * 1000);
|
|
2778
|
+
const http = { ...httpOf(input), signal: input.signal === undefined ? timeout : AbortSignal.any([input.signal, timeout]) };
|
|
2510
2779
|
const challenge = await requestCodexDeviceCode(http);
|
|
2511
2780
|
input.onChallenge(present({
|
|
2512
2781
|
userCode: challenge.userCode,
|
|
2513
2782
|
verificationUri: challenge.verificationUri
|
|
2514
|
-
}, "openai"));
|
|
2515
|
-
|
|
2783
|
+
}, "openai-codex"));
|
|
2784
|
+
const tokens = await pollCodexDeviceToken(challenge, http);
|
|
2785
|
+
if (tokens.accountId === undefined) {
|
|
2786
|
+
throw new DeviceCodeError("failed", "ChatGPT login returned no account identity. Run `keryx auth login openai-codex` to retry.");
|
|
2787
|
+
}
|
|
2788
|
+
return tokens;
|
|
2516
2789
|
}
|
|
2517
2790
|
async function refreshProviderGrant(provider, http, dir) {
|
|
2791
|
+
if (provider === "openai-codex" || provider === "openai") {
|
|
2792
|
+
if (loadOAuthGrant("openai-codex", dir) === undefined)
|
|
2793
|
+
return;
|
|
2794
|
+
return refreshOpenAiCodexGrant({ ...http, ...dir !== undefined ? { configDir: dir } : {} });
|
|
2795
|
+
}
|
|
2518
2796
|
const grant = loadOAuthGrant(provider, dir);
|
|
2519
2797
|
if (grant === undefined || !grantNeedsRefresh(grant, http.now)) {
|
|
2520
2798
|
return grant;
|
|
@@ -2537,9 +2815,6 @@ async function refreshProviderGrant(provider, http, dir) {
|
|
|
2537
2815
|
saveOAuthGrant(provider, next, dir);
|
|
2538
2816
|
return next;
|
|
2539
2817
|
}
|
|
2540
|
-
if (provider === "openai") {
|
|
2541
|
-
return grant;
|
|
2542
|
-
}
|
|
2543
2818
|
const tokens = await refreshAccessToken({
|
|
2544
2819
|
tokenEndpoint: endpoints.tokenEndpoint,
|
|
2545
2820
|
clientId: endpoints.clientId,
|
|
@@ -2566,7 +2841,7 @@ async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANT
|
|
|
2566
2841
|
}
|
|
2567
2842
|
continue;
|
|
2568
2843
|
}
|
|
2569
|
-
if (!grantNeedsRefresh(grant, http.now))
|
|
2844
|
+
if (!grantNeedsRefresh(grant, http.now) && !(provider === "openai-codex" && grant.expires === undefined))
|
|
2570
2845
|
continue;
|
|
2571
2846
|
try {
|
|
2572
2847
|
await refreshProviderGrant(provider, http, dir);
|
|
@@ -2578,7 +2853,7 @@ async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANT
|
|
|
2578
2853
|
return warnings;
|
|
2579
2854
|
}
|
|
2580
2855
|
function logoutProvider(provider, dir) {
|
|
2581
|
-
deleteOAuthGrant(provider, dir);
|
|
2856
|
+
deleteOAuthGrant(provider === "openai" ? "openai-codex" : provider, dir);
|
|
2582
2857
|
}
|
|
2583
2858
|
var SESSION_REFRESHED_GRANTS;
|
|
2584
2859
|
var init_login = __esm(() => {
|
|
@@ -2588,7 +2863,8 @@ var init_login = __esm(() => {
|
|
|
2588
2863
|
init_device_code();
|
|
2589
2864
|
init_grants();
|
|
2590
2865
|
init_openai_codex();
|
|
2591
|
-
|
|
2866
|
+
init_openai_subscription();
|
|
2867
|
+
SESSION_REFRESHED_GRANTS = ["grok", "github-copilot", "openai-codex"];
|
|
2592
2868
|
});
|
|
2593
2869
|
|
|
2594
2870
|
// src/lib/caller-session.ts
|
|
@@ -3635,6 +3911,40 @@ var init_provider_catalog_cache = __esm(() => {
|
|
|
3635
3911
|
CATALOG_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
3636
3912
|
});
|
|
3637
3913
|
|
|
3914
|
+
// src/lib/oauth/open-url.ts
|
|
3915
|
+
import { spawn } from "child_process";
|
|
3916
|
+
function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
3917
|
+
if (url.length === 0) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
if (platform === "darwin") {
|
|
3921
|
+
return { cmd: "open", args: [url] };
|
|
3922
|
+
}
|
|
3923
|
+
if (platform === "win32") {
|
|
3924
|
+
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
3925
|
+
}
|
|
3926
|
+
if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
|
|
3927
|
+
return { cmd: "xdg-open", args: [url] };
|
|
3928
|
+
}
|
|
3929
|
+
if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
|
|
3930
|
+
return { cmd: "xdg-open", args: [url] };
|
|
3931
|
+
}
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
function openVerificationUrl(url, deps = {}) {
|
|
3935
|
+
const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
|
|
3936
|
+
if (plan === undefined) {
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
const spawnFn = deps.spawn ?? spawn;
|
|
3940
|
+
try {
|
|
3941
|
+
const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
|
|
3942
|
+
child.on("error", () => {});
|
|
3943
|
+
child.unref();
|
|
3944
|
+
} catch {}
|
|
3945
|
+
}
|
|
3946
|
+
var init_open_url = () => {};
|
|
3947
|
+
|
|
3638
3948
|
// src/harness/policy/engine.ts
|
|
3639
3949
|
import path8 from "path";
|
|
3640
3950
|
function baseOutcomeFor(risk, defaults) {
|
|
@@ -4151,15 +4461,12 @@ async function detectProviders(deps) {
|
|
|
4151
4461
|
if (typeof anthropicKey === "string" && anthropicKey.length > 0) {
|
|
4152
4462
|
detected.push({ name: "anthropic", models: [...ANTHROPIC_MODELS] });
|
|
4153
4463
|
}
|
|
4154
|
-
|
|
4155
|
-
if (typeof openaiKey === "string" && openaiKey.length > 0) {
|
|
4156
|
-
detected.push({ name: "openai", models: [...OPENAI_MODELS] });
|
|
4157
|
-
}
|
|
4464
|
+
detected.push({ name: OPENAI_CODEX_PICKER.name, label: OPENAI_CODEX_PICKER.label, models: [...OPENAI_CODEX_PICKER.models], note: "ChatGPT subscription \xB7 browser device login" });
|
|
4158
4465
|
const geminiKey = deps.env.GEMINI_API_KEY ?? deps.env.GOOGLE_API_KEY;
|
|
4159
4466
|
if (typeof geminiKey === "string" && geminiKey.length > 0) {
|
|
4160
4467
|
detected.push({ name: "gemini", models: [...GEMINI_MODELS] });
|
|
4161
4468
|
}
|
|
4162
|
-
for (const p of allOpenAiCompatProviders()) {
|
|
4469
|
+
for (const p of allOpenAiCompatProviders(deps.configDir)) {
|
|
4163
4470
|
if (!isProviderPlatformSupported(p, platform)) {
|
|
4164
4471
|
continue;
|
|
4165
4472
|
}
|
|
@@ -4219,6 +4526,31 @@ function parseChoice(line, count) {
|
|
|
4219
4526
|
function toSelection(provider, model) {
|
|
4220
4527
|
return provider.baseUrl === undefined ? { provider: provider.name, model } : { provider: provider.name, model, baseUrl: provider.baseUrl };
|
|
4221
4528
|
}
|
|
4529
|
+
async function loginSubscriptionInReadline(io, deps, fetchFn) {
|
|
4530
|
+
const controller = new AbortController;
|
|
4531
|
+
const cancel = () => controller.abort();
|
|
4532
|
+
const signal = deps.signal === undefined ? controller.signal : AbortSignal.any([controller.signal, deps.signal]);
|
|
4533
|
+
process.on("SIGINT", cancel);
|
|
4534
|
+
try {
|
|
4535
|
+
io.write(`Connect your ChatGPT subscription (Ctrl+C to cancel).
|
|
4536
|
+
`);
|
|
4537
|
+
const result = await loginDeviceCode({
|
|
4538
|
+
provider: "openai-codex",
|
|
4539
|
+
fetch: (url, init) => fetchFn(url, init),
|
|
4540
|
+
signal,
|
|
4541
|
+
...deps.configDir === undefined ? {} : { dir: deps.configDir },
|
|
4542
|
+
onChallenge: (challenge) => {
|
|
4543
|
+
io.write(`${challenge.instructions}
|
|
4544
|
+
`);
|
|
4545
|
+
(deps.openVerificationUrl ?? openVerificationUrl)(challenge.verificationUriComplete ?? challenge.verificationUri);
|
|
4546
|
+
}
|
|
4547
|
+
});
|
|
4548
|
+
if (!result.ok)
|
|
4549
|
+
throw new SubscriptionSelectionError(signal.aborted ? "ChatGPT subscription login cancelled" : result.error);
|
|
4550
|
+
} finally {
|
|
4551
|
+
process.removeListener("SIGINT", cancel);
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4222
4554
|
async function pickProviderModel(io, detected, deps = {}) {
|
|
4223
4555
|
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
4224
4556
|
const env = deps.env ?? process.env;
|
|
@@ -4266,9 +4598,20 @@ async function pickProviderModel(io, detected, deps = {}) {
|
|
|
4266
4598
|
chosenProvider = provider;
|
|
4267
4599
|
break;
|
|
4268
4600
|
}
|
|
4601
|
+
if (chosenProvider.name === "openai-codex" && loadOAuthGrant("openai-codex", deps.configDir) === undefined) {
|
|
4602
|
+
await loginSubscriptionInReadline(io, deps, fetchFn);
|
|
4603
|
+
}
|
|
4269
4604
|
io.write(`Fetching models for ${chosenProvider.name}\u2026
|
|
4270
4605
|
`);
|
|
4271
|
-
const
|
|
4606
|
+
const modelOptions = deps.configDir === undefined ? {} : { configDir: deps.configDir };
|
|
4607
|
+
let resolved = await resolveModelsForPicker(fetchFn, chosenProvider, env, modelOptions);
|
|
4608
|
+
if (chosenProvider.name === "openai-codex" && resolved.failure?.kind === "rejected") {
|
|
4609
|
+
await loginSubscriptionInReadline(io, deps, fetchFn);
|
|
4610
|
+
resolved = await resolveModelsForPicker(fetchFn, chosenProvider, env, modelOptions);
|
|
4611
|
+
}
|
|
4612
|
+
if (chosenProvider.name === "openai-codex" && (resolved.source !== "live" || resolved.models.length === 0)) {
|
|
4613
|
+
throw new SubscriptionSelectionError("ChatGPT subscription models are unavailable. Try again or run `keryx auth login openai-codex`.");
|
|
4614
|
+
}
|
|
4272
4615
|
const models = resolved.models.length > 0 ? resolved.models : chosenProvider.models;
|
|
4273
4616
|
if (resolved.source === "live") {
|
|
4274
4617
|
io.write(` (${models.length} model(s) from API)
|
|
@@ -4303,12 +4646,18 @@ async function pickProviderModel(io, detected, deps = {}) {
|
|
|
4303
4646
|
return toSelection(chosenProvider, model);
|
|
4304
4647
|
}
|
|
4305
4648
|
}
|
|
4306
|
-
var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434", FAKE_MODELS;
|
|
4649
|
+
var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434", FAKE_MODELS, SubscriptionSelectionError;
|
|
4307
4650
|
var init_select = __esm(() => {
|
|
4651
|
+
init_subscription_models();
|
|
4652
|
+
init_grants();
|
|
4653
|
+
init_login();
|
|
4654
|
+
init_open_url();
|
|
4308
4655
|
init_guard();
|
|
4309
4656
|
init_providers();
|
|
4310
4657
|
init_curated_model_lists();
|
|
4311
4658
|
FAKE_MODELS = ["fake-echo"];
|
|
4659
|
+
SubscriptionSelectionError = class SubscriptionSelectionError extends Error {
|
|
4660
|
+
};
|
|
4312
4661
|
});
|
|
4313
4662
|
|
|
4314
4663
|
// src/harness/provider-catalog.ts
|
|
@@ -4326,6 +4675,12 @@ async function buildCatalogEntry(detected, deps, fetchedAt) {
|
|
|
4326
4675
|
...detected.label !== undefined ? { label: detected.label } : {}
|
|
4327
4676
|
};
|
|
4328
4677
|
}
|
|
4678
|
+
if (detected.name === "openai-codex") {
|
|
4679
|
+
if (loadOAuthGrant("openai-codex", deps.dir) === undefined)
|
|
4680
|
+
return;
|
|
4681
|
+
const result = await fetchOpenAiCodexModels(deps.fetch, { ...deps.dir !== undefined ? { configDir: deps.dir } : {}, timeoutMs: deps.timeoutMs ?? CATALOG_FETCH_TIMEOUT_MS });
|
|
4682
|
+
return { name: detected.name, status: classifyModelsStatus(result), models: result.models, fallbackModels: [...detected.models], fetchedAt, label: detected.label ?? "ChatGPT / Codex" };
|
|
4683
|
+
}
|
|
4329
4684
|
const registry = providerByName(detected.name, deps.dir);
|
|
4330
4685
|
if (registry === undefined) {
|
|
4331
4686
|
return {
|
|
@@ -4338,7 +4693,7 @@ async function buildCatalogEntry(detected, deps, fetchedAt) {
|
|
|
4338
4693
|
};
|
|
4339
4694
|
}
|
|
4340
4695
|
const requiresApiKey = registry.requiresApiKey ?? true;
|
|
4341
|
-
const apiKey = providerApiKey(registry, deps.env) ?? registry.apiKey;
|
|
4696
|
+
const apiKey = providerApiKey(registry, envWithSavedApiKeys(deps.env, deps.dir)) ?? registry.apiKey;
|
|
4342
4697
|
if (requiresApiKey && (apiKey === undefined || apiKey.length === 0)) {
|
|
4343
4698
|
return;
|
|
4344
4699
|
}
|
|
@@ -4375,6 +4730,7 @@ async function refreshProviderCatalog(deps) {
|
|
|
4375
4730
|
const detectDeps = {
|
|
4376
4731
|
fetch: deps.fetch,
|
|
4377
4732
|
env: deps.env,
|
|
4733
|
+
...deps.dir !== undefined ? { configDir: deps.dir } : {},
|
|
4378
4734
|
...deps.baseUrl !== undefined ? { baseUrl: deps.baseUrl } : {},
|
|
4379
4735
|
...deps.platform !== undefined ? { platform: deps.platform } : {}
|
|
4380
4736
|
};
|
|
@@ -4446,6 +4802,9 @@ function describeCatalogStatus(status) {
|
|
|
4446
4802
|
}
|
|
4447
4803
|
var CATALOG_FETCH_TIMEOUT_MS = 8000;
|
|
4448
4804
|
var init_provider_catalog = __esm(() => {
|
|
4805
|
+
init_grants();
|
|
4806
|
+
init_shell_config();
|
|
4807
|
+
init_subscription_models();
|
|
4449
4808
|
init_select();
|
|
4450
4809
|
init_providers();
|
|
4451
4810
|
init_provider_catalog_cache();
|
|
@@ -4511,8 +4870,11 @@ function resolveProviderModelParamsByName(name, shellConfig = loadShellConfig(),
|
|
|
4511
4870
|
function providerByName(name, dir) {
|
|
4512
4871
|
return allOpenAiCompatProviders(dir).find((p) => p.name === name);
|
|
4513
4872
|
}
|
|
4873
|
+
function connectionProviderByName(name, dir) {
|
|
4874
|
+
return name === "openai-codex" ? OPENAI_CODEX_PICKER : providerByName(name, dir);
|
|
4875
|
+
}
|
|
4514
4876
|
function customCompatProviders(dir) {
|
|
4515
|
-
const builtinNames = new Set(OPENAI_COMPAT_PROVIDERS.map((p) => p.name));
|
|
4877
|
+
const builtinNames = new Set([...OPENAI_COMPAT_PROVIDERS.map((p) => p.name), "openai-codex"]);
|
|
4516
4878
|
return loadCustomCompatProviders(dir).filter((p) => !builtinNames.has(p.name)).map((p) => ({
|
|
4517
4879
|
name: p.name,
|
|
4518
4880
|
label: p.label ?? p.name,
|
|
@@ -4690,12 +5052,14 @@ async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts)
|
|
|
4690
5052
|
}
|
|
4691
5053
|
}
|
|
4692
5054
|
async function resolveModelsForPicker(fetchFn, provider, env = process.env, opts) {
|
|
4693
|
-
|
|
5055
|
+
if (provider.name === "openai-codex")
|
|
5056
|
+
return fetchOpenAiCodexModels(fetchFn, opts);
|
|
5057
|
+
const compat = providerByName(provider.name, opts?.configDir);
|
|
4694
5058
|
if (compat === undefined) {
|
|
4695
5059
|
return { models: [...provider.models], source: "fallback" };
|
|
4696
5060
|
}
|
|
4697
5061
|
const envKey = provider.envKey ?? compat.envKey;
|
|
4698
|
-
const raw = envKey === undefined ? undefined : env[envKey];
|
|
5062
|
+
const raw = envKey === undefined ? undefined : envWithSavedApiKeys(env, opts?.configDir)[envKey];
|
|
4699
5063
|
const apiKey = typeof raw === "string" && raw.length > 0 ? raw : compat.apiKey;
|
|
4700
5064
|
return fetchOpenAiCompatModelsDetailed(fetchFn, { ...compat, ...provider.baseUrl !== undefined ? { baseUrl: provider.baseUrl } : {} }, apiKey, opts);
|
|
4701
5065
|
}
|
|
@@ -4819,7 +5183,9 @@ function providerApiKey(provider, env = process.env) {
|
|
|
4819
5183
|
return typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
4820
5184
|
}
|
|
4821
5185
|
async function testProviderConnection(provider, fetchFn = globalThis.fetch, env = process.env, profiles) {
|
|
4822
|
-
|
|
5186
|
+
if (provider.name === "openai-codex")
|
|
5187
|
+
return fetchOpenAiCodexModels(fetchFn, { ...profiles?.dir !== undefined ? { configDir: profiles.dir } : {} });
|
|
5188
|
+
const apiKey = providerApiKey(provider, envWithSavedApiKeys(env, profiles?.dir)) ?? provider.apiKey;
|
|
4823
5189
|
return fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, {
|
|
4824
5190
|
timeoutMs: MODELS_FETCH_TIMEOUT_MS,
|
|
4825
5191
|
...profiles !== undefined ? { refreshProfiles: profiles } : {}
|
|
@@ -4837,7 +5203,7 @@ function classifyProviderConnection(name, env = process.env, dir) {
|
|
|
4837
5203
|
if (customCompatProviders(dir).some((p) => p.name === name)) {
|
|
4838
5204
|
return { kind: "custom", sharedWith: [] };
|
|
4839
5205
|
}
|
|
4840
|
-
if (
|
|
5206
|
+
if (name !== "openai" && loadOAuthGrant(name, dir) !== undefined) {
|
|
4841
5207
|
const envKey = oauthEnvKeyFor(name);
|
|
4842
5208
|
const sharedWith = envKey === undefined ? [] : providersSharingEnvKey(envKey, name, allOpenAiCompatProviders(dir));
|
|
4843
5209
|
return { kind: "oauth-grant", ...envKey !== undefined ? { envKey } : {}, sharedWith };
|
|
@@ -4905,7 +5271,7 @@ function configuredProviders(env = process.env, dir) {
|
|
|
4905
5271
|
if (provider.requiresApiKey === false)
|
|
4906
5272
|
return true;
|
|
4907
5273
|
return providerApiKey(provider, env) !== undefined || provider.apiKey !== undefined;
|
|
4908
|
-
}).filter((provider) => isProviderPlatformSupported(provider)).map((provider) => ({ name: provider.name, models: provider.models }));
|
|
5274
|
+
}).filter((provider) => isProviderPlatformSupported(provider)).map((provider) => ({ name: provider.name, models: provider.models })).concat(loadOAuthGrant("openai-codex", dir) === undefined ? [] : [{ name: "openai-codex", models: [...OPENAI_CODEX_PICKER.models] }]);
|
|
4909
5275
|
}
|
|
4910
5276
|
async function providersCommand(args, deps = {}) {
|
|
4911
5277
|
const command = args[0];
|
|
@@ -4945,7 +5311,7 @@ async function runProvidersTest(args, deps) {
|
|
|
4945
5311
|
return;
|
|
4946
5312
|
}
|
|
4947
5313
|
const dir = deps.dir;
|
|
4948
|
-
const provider =
|
|
5314
|
+
const provider = connectionProviderByName(name, dir);
|
|
4949
5315
|
if (provider === undefined) {
|
|
4950
5316
|
console.error(`Unknown provider: ${name}`);
|
|
4951
5317
|
process.exitCode = 1;
|
|
@@ -5017,7 +5383,7 @@ async function runProvidersRemove(args, deps) {
|
|
|
5017
5383
|
const dir = deps.dir;
|
|
5018
5384
|
const env = deps.env ?? process.env;
|
|
5019
5385
|
const json = args.includes("--json");
|
|
5020
|
-
if (
|
|
5386
|
+
if (connectionProviderByName(name, dir) === undefined) {
|
|
5021
5387
|
if (json) {
|
|
5022
5388
|
console.log(JSON.stringify({ provider: name, ok: false, reason: "unknown provider" }, null, 2));
|
|
5023
5389
|
} else {
|
|
@@ -5218,6 +5584,8 @@ var init_providers = __esm(() => {
|
|
|
5218
5584
|
init_provider_config();
|
|
5219
5585
|
init_catalog();
|
|
5220
5586
|
init_model_profile();
|
|
5587
|
+
init_subscription_models();
|
|
5588
|
+
init_curated_model_lists();
|
|
5221
5589
|
init_grants();
|
|
5222
5590
|
init_login();
|
|
5223
5591
|
init_caller_session();
|
|
@@ -5228,6 +5596,7 @@ var init_providers = __esm(() => {
|
|
|
5228
5596
|
MINIMAX_REASONING_PRESET_HOSTS = new Set(["api.minimax.io", "api.minimaxi.com"]);
|
|
5229
5597
|
DEEPSEEK_REASONING_PRESET_HOSTS = new Set(["api.deepseek.com"]);
|
|
5230
5598
|
OPENAI_COMPAT_PROVIDERS = [
|
|
5599
|
+
{ name: "openai", label: "OpenAI API", baseUrl: "https://api.openai.com", envKey: "OPENAI_API_KEY", models: [...OPENAI_MODELS], note: "Platform API key \xB7 separate API billing" },
|
|
5231
5600
|
{
|
|
5232
5601
|
name: "openrouter",
|
|
5233
5602
|
label: "OpenRouter",
|
|
@@ -8796,7 +9165,7 @@ var init_redact = __esm(() => {
|
|
|
8796
9165
|
});
|
|
8797
9166
|
|
|
8798
9167
|
// src/security/output-validation.ts
|
|
8799
|
-
function
|
|
9168
|
+
function failure2(reason) {
|
|
8800
9169
|
return {
|
|
8801
9170
|
ok: false,
|
|
8802
9171
|
text: FORMAT_UNSAFE_OUTPUT_TEXT,
|
|
@@ -9057,12 +9426,12 @@ function validateOutputForTransport(input) {
|
|
|
9057
9426
|
try {
|
|
9058
9427
|
if (input.format === "text") {
|
|
9059
9428
|
if (typeof input.value !== "string") {
|
|
9060
|
-
return
|
|
9429
|
+
return failure2("non-text-value");
|
|
9061
9430
|
}
|
|
9062
9431
|
const safe = redactString(input.value, undefined, input.exemptExfil);
|
|
9063
9432
|
const schemaReason = schemaFailure(safe.value, input.schema);
|
|
9064
9433
|
if (schemaReason !== undefined)
|
|
9065
|
-
return
|
|
9434
|
+
return failure2(schemaReason);
|
|
9066
9435
|
const reasons = uniqueSorted(safe.reasons);
|
|
9067
9436
|
return {
|
|
9068
9437
|
ok: true,
|
|
@@ -9076,10 +9445,10 @@ function validateOutputForTransport(input) {
|
|
|
9076
9445
|
}
|
|
9077
9446
|
const safe = sanitizeJsonValue(input.value, undefined, new WeakSet, input.exemptExfil);
|
|
9078
9447
|
if (!safe.ok)
|
|
9079
|
-
return
|
|
9448
|
+
return failure2(safe.reason);
|
|
9080
9449
|
const schemaReason = schemaFailure(safe.value, input.schema);
|
|
9081
9450
|
if (schemaReason !== undefined)
|
|
9082
|
-
return
|
|
9451
|
+
return failure2(schemaReason);
|
|
9083
9452
|
const reasons = uniqueSorted(safe.reasons);
|
|
9084
9453
|
return {
|
|
9085
9454
|
ok: true,
|
|
@@ -9091,7 +9460,7 @@ function validateOutputForTransport(input) {
|
|
|
9091
9460
|
}
|
|
9092
9461
|
};
|
|
9093
9462
|
} catch {
|
|
9094
|
-
return
|
|
9463
|
+
return failure2("validation.error");
|
|
9095
9464
|
}
|
|
9096
9465
|
}
|
|
9097
9466
|
function skipWhitespace(scanner) {
|
|
@@ -17259,7 +17628,7 @@ var package_default;
|
|
|
17259
17628
|
var init_package = __esm(() => {
|
|
17260
17629
|
package_default = {
|
|
17261
17630
|
name: "@mrciphersmith/keryx",
|
|
17262
|
-
version: "0.3.
|
|
17631
|
+
version: "0.3.6",
|
|
17263
17632
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
17264
17633
|
private: false,
|
|
17265
17634
|
publishConfig: {
|
|
@@ -21823,14 +22192,14 @@ var init_config2 = __esm(() => {
|
|
|
21823
22192
|
// src/sync/provenance.ts
|
|
21824
22193
|
import { mkdir as mkdir9, readFile as readFile20, writeFile as writeFile7 } from "fs/promises";
|
|
21825
22194
|
import path52 from "path";
|
|
21826
|
-
import { spawn } from "child_process";
|
|
22195
|
+
import { spawn as spawn2 } from "child_process";
|
|
21827
22196
|
function provenancePath(cwd, module) {
|
|
21828
22197
|
return path52.join(cwd, ".metaproject", "data", module, ".provenance.json");
|
|
21829
22198
|
}
|
|
21830
22199
|
function gitCmdResult(cwd, args) {
|
|
21831
22200
|
return new Promise((resolve) => {
|
|
21832
22201
|
try {
|
|
21833
|
-
const child =
|
|
22202
|
+
const child = spawn2("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
21834
22203
|
let out = "";
|
|
21835
22204
|
let err = "";
|
|
21836
22205
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -21940,13 +22309,13 @@ var init_provenance = __esm(() => {
|
|
|
21940
22309
|
});
|
|
21941
22310
|
|
|
21942
22311
|
// src/gdgraph/staleness.ts
|
|
21943
|
-
import { spawn as
|
|
22312
|
+
import { spawn as spawn3 } from "child_process";
|
|
21944
22313
|
import { stat as stat8 } from "fs/promises";
|
|
21945
22314
|
import path53 from "path";
|
|
21946
22315
|
function gitStatusPorcelain(cwd) {
|
|
21947
22316
|
return new Promise((resolve) => {
|
|
21948
22317
|
try {
|
|
21949
|
-
const child =
|
|
22318
|
+
const child = spawn3("git", ["status", "--porcelain=v1", "-z"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
21950
22319
|
let out = "";
|
|
21951
22320
|
child.stdout?.on("data", (chunk) => {
|
|
21952
22321
|
out += String(chunk);
|
|
@@ -30016,14 +30385,14 @@ function asNumber5(value) {
|
|
|
30016
30385
|
function clampOpenAiReasoningEffort(effort) {
|
|
30017
30386
|
return effort === "xhigh" || effort === "max" ? "high" : effort;
|
|
30018
30387
|
}
|
|
30019
|
-
function ownedReasoningItems(reasoning) {
|
|
30388
|
+
function ownedReasoningItems(reasoning, providerId) {
|
|
30020
30389
|
const replay = reasoning?.replay;
|
|
30021
30390
|
if (replay === undefined) {
|
|
30022
30391
|
return [];
|
|
30023
30392
|
}
|
|
30024
30393
|
const items = [];
|
|
30025
30394
|
for (const item of replay) {
|
|
30026
|
-
if (item.providerId !==
|
|
30395
|
+
if (item.providerId !== providerId || item.kind !== "reasoning_item") {
|
|
30027
30396
|
continue;
|
|
30028
30397
|
}
|
|
30029
30398
|
if (isPlainObject7(item.data)) {
|
|
@@ -30032,7 +30401,7 @@ function ownedReasoningItems(reasoning) {
|
|
|
30032
30401
|
}
|
|
30033
30402
|
return items;
|
|
30034
30403
|
}
|
|
30035
|
-
function toResponsesInput(messages) {
|
|
30404
|
+
function toResponsesInput(messages, providerId) {
|
|
30036
30405
|
const out = [];
|
|
30037
30406
|
for (const linked of linkToolCalls(messages)) {
|
|
30038
30407
|
const message = linked.message;
|
|
@@ -30048,7 +30417,7 @@ function toResponsesInput(messages) {
|
|
|
30048
30417
|
continue;
|
|
30049
30418
|
}
|
|
30050
30419
|
if (message.role === "assistant") {
|
|
30051
|
-
for (const item of ownedReasoningItems(message.reasoning)) {
|
|
30420
|
+
for (const item of ownedReasoningItems(message.reasoning, providerId)) {
|
|
30052
30421
|
out.push(item);
|
|
30053
30422
|
}
|
|
30054
30423
|
}
|
|
@@ -30136,14 +30505,23 @@ function classifyHttpError3(status, headers, code) {
|
|
|
30136
30505
|
}
|
|
30137
30506
|
return { kind: "unknown", retryable: retryableFor4("unknown", false), message: "" };
|
|
30138
30507
|
}
|
|
30139
|
-
async function raceReadAgainstDeadline4(reader, ms) {
|
|
30508
|
+
async function raceReadAgainstDeadline4(reader, ms, signal) {
|
|
30509
|
+
if (signal?.aborted === true)
|
|
30510
|
+
return READ_CANCELLED;
|
|
30140
30511
|
let timer;
|
|
30141
30512
|
const deadline = new Promise((resolve) => {
|
|
30142
30513
|
timer = setTimeout(() => resolve(READ_TIMED_OUT4), ms);
|
|
30143
30514
|
});
|
|
30515
|
+
let onAbort;
|
|
30516
|
+
const aborted = new Promise((resolve) => {
|
|
30517
|
+
onAbort = () => resolve(READ_CANCELLED);
|
|
30518
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
30519
|
+
});
|
|
30144
30520
|
try {
|
|
30145
|
-
return await Promise.race([reader.read(), deadline]);
|
|
30521
|
+
return await Promise.race([reader.read(), deadline, aborted]);
|
|
30146
30522
|
} finally {
|
|
30523
|
+
if (onAbort !== undefined)
|
|
30524
|
+
signal?.removeEventListener("abort", onAbort);
|
|
30147
30525
|
if (timer !== undefined) {
|
|
30148
30526
|
clearTimeout(timer);
|
|
30149
30527
|
}
|
|
@@ -30165,6 +30543,9 @@ async function* drainAndCheckAbort4(queue, signal, stamp) {
|
|
|
30165
30543
|
|
|
30166
30544
|
class OpenAiProvider {
|
|
30167
30545
|
deps;
|
|
30546
|
+
get providerId() {
|
|
30547
|
+
return this.deps.codex === undefined ? PROVIDER_ID2 : "openai-codex";
|
|
30548
|
+
}
|
|
30168
30549
|
constructor(deps) {
|
|
30169
30550
|
this.deps = deps;
|
|
30170
30551
|
}
|
|
@@ -30182,15 +30563,15 @@ class OpenAiProvider {
|
|
|
30182
30563
|
};
|
|
30183
30564
|
return {
|
|
30184
30565
|
capabilities,
|
|
30185
|
-
descriptor: { providerId:
|
|
30566
|
+
descriptor: { providerId: this.providerId, providerRevision: PROVIDER_REVISION4 }
|
|
30186
30567
|
};
|
|
30187
30568
|
}
|
|
30188
30569
|
descriptorDocument() {
|
|
30189
30570
|
return {
|
|
30190
30571
|
schemaVersion: 1,
|
|
30191
|
-
providerId:
|
|
30572
|
+
providerId: this.providerId,
|
|
30192
30573
|
providerRevision: PROVIDER_REVISION4,
|
|
30193
|
-
models: [{ modelId: DEFAULT_MODEL4.modelId, revision: DEFAULT_MODEL4.revision }],
|
|
30574
|
+
models: [{ modelId: this.deps.codex === undefined ? DEFAULT_MODEL4.modelId : "gpt-5.4", revision: DEFAULT_MODEL4.revision }],
|
|
30194
30575
|
capabilities: {
|
|
30195
30576
|
streaming: true,
|
|
30196
30577
|
tools: true,
|
|
@@ -30215,7 +30596,8 @@ class OpenAiProvider {
|
|
|
30215
30596
|
});
|
|
30216
30597
|
return;
|
|
30217
30598
|
}
|
|
30218
|
-
const
|
|
30599
|
+
const codex = this.deps.codex;
|
|
30600
|
+
const baseUrl = codex === undefined ? grant.baseUrl ?? DEFAULT_BASE_URL4 : "https://chatgpt.com/backend-api/codex";
|
|
30219
30601
|
let host;
|
|
30220
30602
|
try {
|
|
30221
30603
|
host = new URL(baseUrl).hostname;
|
|
@@ -30230,10 +30612,16 @@ class OpenAiProvider {
|
|
|
30230
30612
|
});
|
|
30231
30613
|
return;
|
|
30232
30614
|
}
|
|
30233
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/v1/responses`;
|
|
30615
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${codex === undefined ? "/v1/responses" : "/responses"}`;
|
|
30234
30616
|
const headers = {
|
|
30235
30617
|
authorization: `Bearer ${grant.apiKey}`,
|
|
30236
|
-
"content-type": "application/json"
|
|
30618
|
+
"content-type": "application/json",
|
|
30619
|
+
...codex === undefined ? {} : {
|
|
30620
|
+
"ChatGPT-Account-ID": codex.accountId,
|
|
30621
|
+
originator: "keryx",
|
|
30622
|
+
"User-Agent": "keryx",
|
|
30623
|
+
accept: "text/event-stream"
|
|
30624
|
+
}
|
|
30237
30625
|
};
|
|
30238
30626
|
const requestedReasoningEffort = request.options?.reasoning;
|
|
30239
30627
|
const reasoningRequested = typeof requestedReasoningEffort === "string" && requestedReasoningEffort.length > 0 && requestedReasoningEffort !== "off";
|
|
@@ -30241,10 +30629,10 @@ class OpenAiProvider {
|
|
|
30241
30629
|
const payload = {
|
|
30242
30630
|
model: request.modelId,
|
|
30243
30631
|
instructions: request.systemInstruction,
|
|
30244
|
-
input: toResponsesInput(request.messages),
|
|
30632
|
+
input: toResponsesInput(request.messages, this.providerId),
|
|
30245
30633
|
stream: true,
|
|
30246
|
-
max_output_tokens: request.budget.maxOutputTokens,
|
|
30247
|
-
...request.options?.temperature !== undefined && !reasoningRequested ? { temperature: request.options.temperature } : {},
|
|
30634
|
+
...codex === undefined ? { max_output_tokens: request.budget.maxOutputTokens } : { store: false, tool_choice: "auto", parallel_tool_calls: true, tools: [] },
|
|
30635
|
+
...codex === undefined && request.options?.temperature !== undefined && !reasoningRequested ? { temperature: request.options.temperature } : {},
|
|
30248
30636
|
...reasoningRequested ? {
|
|
30249
30637
|
reasoning: { effort: reasoningEffort, summary: "auto" },
|
|
30250
30638
|
include: ["reasoning.encrypted_content"],
|
|
@@ -30328,7 +30716,7 @@ class OpenAiProvider {
|
|
|
30328
30716
|
const timeoutMs = receivedAnyChunk ? idleTimeoutMs : firstByteTimeoutMs;
|
|
30329
30717
|
let readResult;
|
|
30330
30718
|
try {
|
|
30331
|
-
readResult = await raceReadAgainstDeadline4(reader, timeoutMs);
|
|
30719
|
+
readResult = await raceReadAgainstDeadline4(reader, timeoutMs, opts.signal);
|
|
30332
30720
|
} catch (cause) {
|
|
30333
30721
|
cancelReader();
|
|
30334
30722
|
const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
|
|
@@ -30343,6 +30731,11 @@ class OpenAiProvider {
|
|
|
30343
30731
|
});
|
|
30344
30732
|
return;
|
|
30345
30733
|
}
|
|
30734
|
+
if (readResult === READ_CANCELLED) {
|
|
30735
|
+
cancelReader();
|
|
30736
|
+
yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
|
|
30737
|
+
return;
|
|
30738
|
+
}
|
|
30346
30739
|
if (readResult === READ_TIMED_OUT4) {
|
|
30347
30740
|
cancelReader();
|
|
30348
30741
|
yield errorEvent({
|
|
@@ -30471,7 +30864,7 @@ class OpenAiProvider {
|
|
|
30471
30864
|
}
|
|
30472
30865
|
bodies.push({
|
|
30473
30866
|
kind: "reasoning_replay",
|
|
30474
|
-
replay: { providerId:
|
|
30867
|
+
replay: { providerId: this.providerId, kind: "reasoning_item", data: item }
|
|
30475
30868
|
});
|
|
30476
30869
|
}
|
|
30477
30870
|
break;
|
|
@@ -30591,7 +30984,7 @@ class OpenAiProvider {
|
|
|
30591
30984
|
}
|
|
30592
30985
|
}
|
|
30593
30986
|
}
|
|
30594
|
-
var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_ID2 = "openai", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4, DEFAULT_STREAM_TIMEOUT_MS4 = 120000, READ_TIMED_OUT4;
|
|
30987
|
+
var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_ID2 = "openai", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4, DEFAULT_STREAM_TIMEOUT_MS4 = 120000, READ_TIMED_OUT4, READ_CANCELLED;
|
|
30595
30988
|
var init_openai_provider = __esm(() => {
|
|
30596
30989
|
init_guard();
|
|
30597
30990
|
init_provider_port();
|
|
@@ -30600,11 +30993,94 @@ var init_openai_provider = __esm(() => {
|
|
|
30600
30993
|
revision: "2026-08"
|
|
30601
30994
|
};
|
|
30602
30995
|
READ_TIMED_OUT4 = Symbol("openai-read-timed-out");
|
|
30996
|
+
READ_CANCELLED = Symbol("openai-read-cancelled");
|
|
30997
|
+
});
|
|
30998
|
+
|
|
30999
|
+
// src/harness/provider/openai-codex/openai-codex-provider.ts
|
|
31000
|
+
class OpenAiCodexProvider {
|
|
31001
|
+
deps;
|
|
31002
|
+
constructor(deps) {
|
|
31003
|
+
this.deps = deps;
|
|
31004
|
+
}
|
|
31005
|
+
describe() {
|
|
31006
|
+
return new OpenAiProvider({ fetch: this.deps.fetch, codex: { accountId: "" } }).describe();
|
|
31007
|
+
}
|
|
31008
|
+
async* stream(request, opts) {
|
|
31009
|
+
const failure = (cancelled) => ({
|
|
31010
|
+
kind: "provider_error",
|
|
31011
|
+
sequence: 0,
|
|
31012
|
+
attemptId: opts.attemptId,
|
|
31013
|
+
error: {
|
|
31014
|
+
kind: cancelled ? "cancelled" : "authentication",
|
|
31015
|
+
retryable: false,
|
|
31016
|
+
message: cancelled ? "attempt cancelled" : `ChatGPT subscription authorization is unavailable. ${LOGIN_HINT}`
|
|
31017
|
+
}
|
|
31018
|
+
});
|
|
31019
|
+
if (isCancelled(opts.signal)) {
|
|
31020
|
+
yield failure(true);
|
|
31021
|
+
return;
|
|
31022
|
+
}
|
|
31023
|
+
if (this.deps.authorize === undefined) {
|
|
31024
|
+
yield failure(false);
|
|
31025
|
+
return;
|
|
31026
|
+
}
|
|
31027
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
31028
|
+
let grant;
|
|
31029
|
+
try {
|
|
31030
|
+
grant = await this.deps.authorize(opts.signal, attempt > 0);
|
|
31031
|
+
} catch {
|
|
31032
|
+
yield failure(isCancelled(opts.signal));
|
|
31033
|
+
return;
|
|
31034
|
+
}
|
|
31035
|
+
if (isCancelled(opts.signal)) {
|
|
31036
|
+
yield failure(true);
|
|
31037
|
+
return;
|
|
31038
|
+
}
|
|
31039
|
+
if (grant.access.length === 0 || grant.accountId.length === 0) {
|
|
31040
|
+
yield failure(false);
|
|
31041
|
+
return;
|
|
31042
|
+
}
|
|
31043
|
+
const provider = new OpenAiProvider({
|
|
31044
|
+
fetch: this.deps.fetch,
|
|
31045
|
+
grant: { network: true, apiKey: grant.access },
|
|
31046
|
+
codex: { accountId: grant.accountId }
|
|
31047
|
+
});
|
|
31048
|
+
let emitted = false;
|
|
31049
|
+
let retry = false;
|
|
31050
|
+
for await (const event of provider.stream(request, opts)) {
|
|
31051
|
+
if (event.error?.kind === "authentication") {
|
|
31052
|
+
if (attempt === 0 && !emitted) {
|
|
31053
|
+
retry = true;
|
|
31054
|
+
break;
|
|
31055
|
+
}
|
|
31056
|
+
yield { ...event, error: { ...event.error, message: `ChatGPT subscription authorization was rejected. ${LOGIN_HINT}` } };
|
|
31057
|
+
return;
|
|
31058
|
+
}
|
|
31059
|
+
emitted = true;
|
|
31060
|
+
yield event;
|
|
31061
|
+
}
|
|
31062
|
+
if (!retry)
|
|
31063
|
+
return;
|
|
31064
|
+
}
|
|
31065
|
+
}
|
|
31066
|
+
}
|
|
31067
|
+
var LOGIN_HINT = "Run `keryx auth login openai-codex`.", isCancelled = (signal) => signal?.aborted === true;
|
|
31068
|
+
var init_openai_codex_provider = __esm(() => {
|
|
31069
|
+
init_openai_provider();
|
|
30603
31070
|
});
|
|
30604
31071
|
|
|
30605
31072
|
// src/harness/provider/make-provider.ts
|
|
30606
31073
|
function makeProvider(name, _model, opts) {
|
|
30607
31074
|
const env = opts.credentials ?? opts.env ?? process.env;
|
|
31075
|
+
if (name === "openai-codex") {
|
|
31076
|
+
const authorize = opts.openAiCodexGrant ?? (opts.credentials === undefined ? (signal, forceRefresh) => ensureOpenAiCodexGrant({
|
|
31077
|
+
fetch: (url, init) => opts.fetch(url, init),
|
|
31078
|
+
...opts.configDir === undefined ? {} : { configDir: opts.configDir },
|
|
31079
|
+
...signal === undefined ? {} : { signal },
|
|
31080
|
+
...forceRefresh === undefined ? {} : { forceRefresh }
|
|
31081
|
+
}) : undefined);
|
|
31082
|
+
return new OpenAiCodexProvider({ fetch: opts.fetch, ...authorize === undefined ? {} : { authorize } });
|
|
31083
|
+
}
|
|
30608
31084
|
if (name === "anthropic") {
|
|
30609
31085
|
const apiKey = env.ANTHROPIC_API_KEY;
|
|
30610
31086
|
if (apiKey === undefined || apiKey.length === 0) {
|
|
@@ -30661,12 +31137,14 @@ var OLLAMA_COMPAT_IDENTITY;
|
|
|
30661
31137
|
var init_make_provider = __esm(() => {
|
|
30662
31138
|
init_providers();
|
|
30663
31139
|
init_catalog();
|
|
31140
|
+
init_openai_subscription();
|
|
30664
31141
|
init_anthropic_provider();
|
|
30665
31142
|
init_openai_compat_provider();
|
|
30666
31143
|
init_fake_provider();
|
|
30667
31144
|
init_gemini_provider();
|
|
30668
31145
|
init_ollama_provider();
|
|
30669
31146
|
init_openai_provider();
|
|
31147
|
+
init_openai_codex_provider();
|
|
30670
31148
|
OLLAMA_COMPAT_IDENTITY = {
|
|
30671
31149
|
defaultBaseUrl: "http://localhost:11434",
|
|
30672
31150
|
providerRevision: "ollama-2024-10-22",
|
|
@@ -36841,7 +37319,7 @@ var init_template_structure = __esm(() => {
|
|
|
36841
37319
|
});
|
|
36842
37320
|
|
|
36843
37321
|
// src/wiki/service.ts
|
|
36844
|
-
import { spawn as
|
|
37322
|
+
import { spawn as spawn4 } from "child_process";
|
|
36845
37323
|
import { createHash as createHash22 } from "crypto";
|
|
36846
37324
|
import { mkdir as mkdir22, readFile as readFile55, readdir as readdir15, rm as rm8, writeFile as writeFile21 } from "fs/promises";
|
|
36847
37325
|
import path118 from "path";
|
|
@@ -37836,7 +38314,7 @@ async function changedModules(cwd, since) {
|
|
|
37836
38314
|
}
|
|
37837
38315
|
function gitDiffNames(cwd, base) {
|
|
37838
38316
|
return new Promise((resolve) => {
|
|
37839
|
-
const child =
|
|
38317
|
+
const child = spawn4("git", ["diff", "--name-only", base], { cwd });
|
|
37840
38318
|
let out = "";
|
|
37841
38319
|
child.stdout.on("data", (chunk) => {
|
|
37842
38320
|
out += String(chunk);
|
|
@@ -39623,7 +40101,7 @@ var init_metaproject_state = __esm(() => {
|
|
|
39623
40101
|
|
|
39624
40102
|
// src/ctx/orient.ts
|
|
39625
40103
|
import path124 from "path";
|
|
39626
|
-
import { spawn as
|
|
40104
|
+
import { spawn as spawn5 } from "child_process";
|
|
39627
40105
|
import { readFile as readFile58 } from "fs/promises";
|
|
39628
40106
|
function metaPath(cwd, parts) {
|
|
39629
40107
|
return path124.join(cwd, ".metaproject", ...parts);
|
|
@@ -39701,7 +40179,7 @@ async function metaprojectIndexContext(cwd) {
|
|
|
39701
40179
|
function gitOutput(cwd, args) {
|
|
39702
40180
|
return new Promise((resolve) => {
|
|
39703
40181
|
try {
|
|
39704
|
-
const child =
|
|
40182
|
+
const child = spawn5("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
39705
40183
|
let out = "";
|
|
39706
40184
|
child.stdout?.on("data", (chunk) => {
|
|
39707
40185
|
out += String(chunk);
|
|
@@ -39864,7 +40342,7 @@ var init_orient = __esm(() => {
|
|
|
39864
40342
|
});
|
|
39865
40343
|
|
|
39866
40344
|
// src/commands/gdgraph.ts
|
|
39867
|
-
import { spawn as
|
|
40345
|
+
import { spawn as spawn6 } from "child_process";
|
|
39868
40346
|
import { existsSync as existsSync19 } from "fs";
|
|
39869
40347
|
import path125 from "path";
|
|
39870
40348
|
async function gdgraphCommand(args) {
|
|
@@ -40506,7 +40984,7 @@ function collectSeeds(args) {
|
|
|
40506
40984
|
async function changedFiles() {
|
|
40507
40985
|
return new Promise((resolve) => {
|
|
40508
40986
|
try {
|
|
40509
|
-
const child =
|
|
40987
|
+
const child = spawn6("git", ["diff", "--name-only", "HEAD"], {
|
|
40510
40988
|
cwd: process.cwd(),
|
|
40511
40989
|
stdio: ["ignore", "pipe", "ignore"]
|
|
40512
40990
|
});
|
|
@@ -40536,7 +41014,7 @@ async function delegateToLocalRunner(args) {
|
|
|
40536
41014
|
return { delegated: false };
|
|
40537
41015
|
}
|
|
40538
41016
|
const exitCode = await new Promise((resolve, reject) => {
|
|
40539
|
-
const child =
|
|
41017
|
+
const child = spawn6(process.execPath, [...SAFE_BUN_SPAWN_ARGS, localRunner, ...args], {
|
|
40540
41018
|
cwd: process.cwd(),
|
|
40541
41019
|
stdio: "inherit",
|
|
40542
41020
|
env: {
|
|
@@ -42464,7 +42942,7 @@ var init_profile = __esm(() => {
|
|
|
42464
42942
|
});
|
|
42465
42943
|
|
|
42466
42944
|
// src/harness/hooks/runner.ts
|
|
42467
|
-
import { spawn as
|
|
42945
|
+
import { spawn as spawn7 } from "child_process";
|
|
42468
42946
|
import path135 from "path";
|
|
42469
42947
|
function buildHookEnv(opts) {
|
|
42470
42948
|
const env = {};
|
|
@@ -42503,7 +42981,7 @@ function spawnAndCollect(cmdPath, args, opts) {
|
|
|
42503
42981
|
const isPosix = process.platform !== "win32";
|
|
42504
42982
|
let child;
|
|
42505
42983
|
try {
|
|
42506
|
-
child =
|
|
42984
|
+
child = spawn7(cmdPath, args, {
|
|
42507
42985
|
cwd: opts.cwd,
|
|
42508
42986
|
env: opts.env,
|
|
42509
42987
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -59309,7 +59787,7 @@ var init_wiki_freshness = () => {};
|
|
|
59309
59787
|
|
|
59310
59788
|
// src/harness/tool/builtin/interactive-tools.ts
|
|
59311
59789
|
import { readdir as readdir28 } from "fs/promises";
|
|
59312
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
59790
|
+
import { basename, dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep } from "path";
|
|
59313
59791
|
import { lstatSync as lstatSync4, realpathSync as realpathSync7 } from "fs";
|
|
59314
59792
|
async function readFromLine(file, startLine, cap) {
|
|
59315
59793
|
const reader = file.stream().getReader();
|
|
@@ -59367,7 +59845,7 @@ async function readFromLine(file, startLine, cap) {
|
|
|
59367
59845
|
}
|
|
59368
59846
|
function confineToRoot(root, candidate) {
|
|
59369
59847
|
const rootReal = realpathOr(root);
|
|
59370
|
-
const target =
|
|
59848
|
+
const target = resolve2(rootReal, candidate ?? ".");
|
|
59371
59849
|
const effective = realpathThroughAncestors(target);
|
|
59372
59850
|
if (effective === undefined) {
|
|
59373
59851
|
return null;
|
|
@@ -59385,7 +59863,7 @@ function realpathOr(candidate) {
|
|
|
59385
59863
|
try {
|
|
59386
59864
|
return realpathSync7(candidate);
|
|
59387
59865
|
} catch {
|
|
59388
|
-
return
|
|
59866
|
+
return resolve2(candidate);
|
|
59389
59867
|
}
|
|
59390
59868
|
}
|
|
59391
59869
|
function realpathThroughAncestors(absolute) {
|
|
@@ -59394,7 +59872,7 @@ function realpathThroughAncestors(absolute) {
|
|
|
59394
59872
|
for (;; ) {
|
|
59395
59873
|
try {
|
|
59396
59874
|
const real = realpathSync7(current);
|
|
59397
|
-
return missing.length === 0 ? real :
|
|
59875
|
+
return missing.length === 0 ? real : join2(real, ...missing.reverse());
|
|
59398
59876
|
} catch {
|
|
59399
59877
|
try {
|
|
59400
59878
|
lstatSync4(current);
|
|
@@ -59402,7 +59880,7 @@ function realpathThroughAncestors(absolute) {
|
|
|
59402
59880
|
} catch {}
|
|
59403
59881
|
const parent = dirname(current);
|
|
59404
59882
|
if (parent === current) {
|
|
59405
|
-
return
|
|
59883
|
+
return resolve2(absolute);
|
|
59406
59884
|
}
|
|
59407
59885
|
missing.push(basename(current));
|
|
59408
59886
|
current = parent;
|
|
@@ -65712,7 +66190,7 @@ var init_github = __esm(() => {
|
|
|
65712
66190
|
|
|
65713
66191
|
// src/harness/tool/metaproject-adapter.ts
|
|
65714
66192
|
import { lstat as lstat13, readdir as readdir33, readFile as readFile101, realpath as realpath15, stat as stat19 } from "fs/promises";
|
|
65715
|
-
import { isAbsolute as isAbsolute2, join as
|
|
66193
|
+
import { isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "path";
|
|
65716
66194
|
function isMissingExecutable(cause) {
|
|
65717
66195
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
65718
66196
|
return /Executable not found|\bENOENT\b|not found in \$?PATH/i.test(message);
|
|
@@ -65753,8 +66231,8 @@ function portableMemoryPath(candidate) {
|
|
|
65753
66231
|
return !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../") ? normalized : null;
|
|
65754
66232
|
}
|
|
65755
66233
|
function confineToWiki(cwd, candidate) {
|
|
65756
|
-
const wikiRoot =
|
|
65757
|
-
const target =
|
|
66234
|
+
const wikiRoot = join3(cwd, ".metaproject", "wiki");
|
|
66235
|
+
const target = resolve3(wikiRoot, candidate);
|
|
65758
66236
|
const rel = relative2(wikiRoot, target);
|
|
65759
66237
|
if (rel === "") {
|
|
65760
66238
|
return null;
|
|
@@ -65796,7 +66274,7 @@ async function projectRemovalTrail(cwd, query) {
|
|
|
65796
66274
|
async function detectMemoryStoreUnreadable(cwd) {
|
|
65797
66275
|
const root = memoryRoot(cwd);
|
|
65798
66276
|
for (const { folder } of MEMORY_TYPES) {
|
|
65799
|
-
const dir =
|
|
66277
|
+
const dir = join3(root, folder);
|
|
65800
66278
|
try {
|
|
65801
66279
|
await readdir33(dir);
|
|
65802
66280
|
} catch (error) {
|
|
@@ -65810,8 +66288,8 @@ async function detectMemoryStoreUnreadable(cwd) {
|
|
|
65810
66288
|
return null;
|
|
65811
66289
|
}
|
|
65812
66290
|
function confineToSkills(cwd, candidate) {
|
|
65813
|
-
const skillsRoot =
|
|
65814
|
-
const target =
|
|
66291
|
+
const skillsRoot = join3(cwd, ".metaproject", "skills", "gdskills");
|
|
66292
|
+
const target = resolve3(cwd, candidate);
|
|
65815
66293
|
const rel = relative2(skillsRoot, target);
|
|
65816
66294
|
if (rel === "") {
|
|
65817
66295
|
return null;
|
|
@@ -65825,7 +66303,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
65825
66303
|
const summaries = new Map;
|
|
65826
66304
|
let content;
|
|
65827
66305
|
try {
|
|
65828
|
-
const bytes = await readContainedFile(
|
|
66306
|
+
const bytes = await readContainedFile(join3(cwd, ".metaproject", "skills"), join3(cwd, ".metaproject", "skills", "catalog.md"), { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
65829
66307
|
content = bytes.toString("utf8");
|
|
65830
66308
|
} catch {
|
|
65831
66309
|
return summaries;
|
|
@@ -65846,7 +66324,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
65846
66324
|
return summaries;
|
|
65847
66325
|
}
|
|
65848
66326
|
async function walkSkillCatalog(cwd) {
|
|
65849
|
-
const root =
|
|
66327
|
+
const root = join3(cwd, ".metaproject", "skills", "gdskills");
|
|
65850
66328
|
const entries = [];
|
|
65851
66329
|
const rootInfo = await lstat13(root).catch(() => null);
|
|
65852
66330
|
if (!rootInfo || !rootInfo.isDirectory() || rootInfo.isSymbolicLink())
|
|
@@ -65864,7 +66342,7 @@ async function walkSkillCatalog(cwd) {
|
|
|
65864
66342
|
for (const categoryDir of categoryDirs) {
|
|
65865
66343
|
if (!categoryDir.isDirectory() && !categoryDir.isSymbolicLink())
|
|
65866
66344
|
continue;
|
|
65867
|
-
const categoryPath =
|
|
66345
|
+
const categoryPath = join3(root, categoryDir.name);
|
|
65868
66346
|
const categoryReal = await realpath15(categoryPath).catch(() => null);
|
|
65869
66347
|
const categoryInfo = await stat19(categoryPath).catch(() => null);
|
|
65870
66348
|
if (!categoryReal || !categoryInfo?.isDirectory() || !isPathInside(rootReal, categoryReal))
|
|
@@ -65876,12 +66354,12 @@ async function walkSkillCatalog(cwd) {
|
|
|
65876
66354
|
continue;
|
|
65877
66355
|
}
|
|
65878
66356
|
for (const skillDir of skillDirs) {
|
|
65879
|
-
const skillPath =
|
|
66357
|
+
const skillPath = join3(categoryPath, skillDir.name);
|
|
65880
66358
|
const skillReal = await realpath15(skillPath).catch(() => null);
|
|
65881
66359
|
const skillInfo = await stat19(skillPath).catch(() => null);
|
|
65882
66360
|
if (!skillReal || !skillInfo?.isDirectory() || !isPathInside(rootReal, skillReal))
|
|
65883
66361
|
continue;
|
|
65884
|
-
const skillMdPath =
|
|
66362
|
+
const skillMdPath = join3(skillPath, "SKILL.md");
|
|
65885
66363
|
let content;
|
|
65886
66364
|
try {
|
|
65887
66365
|
const bytes = await readContainedFile(root, skillMdPath, { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
@@ -66147,7 +66625,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66147
66625
|
exists = false;
|
|
66148
66626
|
}
|
|
66149
66627
|
if (!exists) {
|
|
66150
|
-
const relativePath = toPosix(relative2(
|
|
66628
|
+
const relativePath = toPosix(relative2(join3(cwd, ".metaproject", "wiki"), target));
|
|
66151
66629
|
const registry = await readSectionRegistryState(cwd);
|
|
66152
66630
|
if (registry.state === "unreadable") {
|
|
66153
66631
|
return {
|
|
@@ -66188,7 +66666,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66188
66666
|
};
|
|
66189
66667
|
}
|
|
66190
66668
|
try {
|
|
66191
|
-
const content = (await readContainedFile(
|
|
66669
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "wiki"), target, {
|
|
66192
66670
|
maxBytes: 8 * 1024 * 1024,
|
|
66193
66671
|
requireRegularFile: true
|
|
66194
66672
|
})).toString("utf8");
|
|
@@ -66232,7 +66710,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66232
66710
|
}
|
|
66233
66711
|
let hasWikiIndex;
|
|
66234
66712
|
try {
|
|
66235
|
-
await readFile101(
|
|
66713
|
+
await readFile101(join3(cwd, ".metaproject", "wiki", "index.md"), "utf8");
|
|
66236
66714
|
hasWikiIndex = true;
|
|
66237
66715
|
} catch {
|
|
66238
66716
|
hasWikiIndex = false;
|
|
@@ -66507,7 +66985,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66507
66985
|
const byName = catalog.find((entry) => entry.name === input.name);
|
|
66508
66986
|
if (byName !== undefined) {
|
|
66509
66987
|
try {
|
|
66510
|
-
const content = (await readContainedFile(
|
|
66988
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), join3(cwd, byName.path), {
|
|
66511
66989
|
maxBytes: 512 * 1024,
|
|
66512
66990
|
requireRegularFile: true
|
|
66513
66991
|
})).toString("utf8");
|
|
@@ -66520,12 +66998,12 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66520
66998
|
if (confined === null) {
|
|
66521
66999
|
return { name: input.name, path: "", content: "", found: false };
|
|
66522
67000
|
}
|
|
66523
|
-
const byPath = catalog.find((entry) =>
|
|
67001
|
+
const byPath = catalog.find((entry) => join3(cwd, entry.path) === confined);
|
|
66524
67002
|
if (byPath === undefined) {
|
|
66525
67003
|
return { name: input.name, path: "", content: "", found: false };
|
|
66526
67004
|
}
|
|
66527
67005
|
try {
|
|
66528
|
-
const content = (await readContainedFile(
|
|
67006
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), confined, {
|
|
66529
67007
|
maxBytes: 512 * 1024,
|
|
66530
67008
|
requireRegularFile: true
|
|
66531
67009
|
})).toString("utf8");
|
|
@@ -69660,7 +70138,7 @@ var init_narrate = __esm(() => {
|
|
|
69660
70138
|
// src/commands/memory.ts
|
|
69661
70139
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
69662
70140
|
import { readdir as readdir34 } from "fs/promises";
|
|
69663
|
-
import { join as
|
|
70141
|
+
import { join as join4 } from "path";
|
|
69664
70142
|
function getService() {
|
|
69665
70143
|
service3 ??= createMemoryService();
|
|
69666
70144
|
return service3;
|
|
@@ -69767,7 +70245,7 @@ async function runIndex2(args) {
|
|
|
69767
70245
|
async function detectMemoryStoreUnreadable2(cwd) {
|
|
69768
70246
|
const root = memoryRoot(cwd);
|
|
69769
70247
|
for (const { folder } of MEMORY_TYPES) {
|
|
69770
|
-
const dir =
|
|
70248
|
+
const dir = join4(root, folder);
|
|
69771
70249
|
try {
|
|
69772
70250
|
await readdir34(dir);
|
|
69773
70251
|
} catch (error) {
|
|
@@ -90455,6 +90933,31 @@ var JEV_MODEL_1_13 = "jev-1.13";
|
|
|
90455
90933
|
var DEFAULT_JEV_MODEL = JEV_MODEL_1_13;
|
|
90456
90934
|
var JEV_TOKEN_BUDGET = 64000;
|
|
90457
90935
|
var DEFAULT_JEV_TIMEOUT_MS = 30000;
|
|
90936
|
+
var JEV_MAX_ATTEMPTS = 3;
|
|
90937
|
+
var JEV_RETRY_BACKOFF_MS = [500, 1500];
|
|
90938
|
+
var JEV_RETRY_AFTER_CAP_MS = 5000;
|
|
90939
|
+
var JEV_RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
90940
|
+
function defaultJevSleep(ms) {
|
|
90941
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
90942
|
+
}
|
|
90943
|
+
function jevBackoffMs(attemptJustFailed) {
|
|
90944
|
+
return JEV_RETRY_BACKOFF_MS[attemptJustFailed - 1] ?? JEV_RETRY_BACKOFF_MS[JEV_RETRY_BACKOFF_MS.length - 1] ?? 500;
|
|
90945
|
+
}
|
|
90946
|
+
function retryAfterMsFromHeader(headerValue) {
|
|
90947
|
+
if (headerValue === null || headerValue.trim().length === 0)
|
|
90948
|
+
return;
|
|
90949
|
+
const seconds = Number(headerValue);
|
|
90950
|
+
if (Number.isFinite(seconds)) {
|
|
90951
|
+
return seconds > 0 ? Math.min(seconds * 1000, JEV_RETRY_AFTER_CAP_MS) : undefined;
|
|
90952
|
+
}
|
|
90953
|
+
const dateMs = Date.parse(headerValue);
|
|
90954
|
+
if (!Number.isNaN(dateMs)) {
|
|
90955
|
+
const deltaMs = dateMs - Date.now();
|
|
90956
|
+
if (deltaMs > 0)
|
|
90957
|
+
return Math.min(deltaMs, JEV_RETRY_AFTER_CAP_MS);
|
|
90958
|
+
}
|
|
90959
|
+
return;
|
|
90960
|
+
}
|
|
90458
90961
|
|
|
90459
90962
|
class JevCredentialError extends Error {
|
|
90460
90963
|
constructor(message = "OPENROUTER_API_KEY is not set, and no openrouterKey is saved in the keryx shell config: " + "Jev needs an OpenRouter credential and refuses to send an unauthenticated request.") {
|
|
@@ -90478,13 +90981,28 @@ class JevBudgetError extends Error {
|
|
|
90478
90981
|
|
|
90479
90982
|
class JevRequestError extends Error {
|
|
90480
90983
|
status;
|
|
90481
|
-
|
|
90984
|
+
attempts;
|
|
90985
|
+
constructor(status, body, attempts = 1) {
|
|
90482
90986
|
const redactedBody = redactSensitiveText(body);
|
|
90483
|
-
|
|
90987
|
+
const attemptsNote = attempts > 1 ? ` after ${attempts} attempts` : "";
|
|
90988
|
+
super(`Jev request to ${JEV_ENDPOINT} failed: HTTP ${status}${attemptsNote}${redactedBody.length > 0 ? ` \u2014 ${redactedBody.slice(0, 500)}` : ""}`);
|
|
90484
90989
|
this.status = status;
|
|
90990
|
+
this.attempts = attempts;
|
|
90485
90991
|
this.name = "JevRequestError";
|
|
90486
90992
|
}
|
|
90487
90993
|
}
|
|
90994
|
+
|
|
90995
|
+
class JevNetworkError extends Error {
|
|
90996
|
+
attempts;
|
|
90997
|
+
cause;
|
|
90998
|
+
constructor(attempts, cause) {
|
|
90999
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
91000
|
+
super(`Jev request to ${JEV_ENDPOINT} failed after ${attempts} attempt(s): ${causeMessage}`);
|
|
91001
|
+
this.attempts = attempts;
|
|
91002
|
+
this.cause = cause;
|
|
91003
|
+
this.name = "JevNetworkError";
|
|
91004
|
+
}
|
|
91005
|
+
}
|
|
90488
91006
|
function sourceLabel(source) {
|
|
90489
91007
|
if (source === "env")
|
|
90490
91008
|
return "the OPENROUTER_API_KEY environment variable";
|
|
@@ -90591,22 +91109,9 @@ function validatedAnswer(key, question, raw) {
|
|
|
90591
91109
|
}
|
|
90592
91110
|
return { type: "choice", choice };
|
|
90593
91111
|
}
|
|
90594
|
-
async function
|
|
90595
|
-
const keyResolution = resolveJevApiKeyResolution(opts?.env ?? process.env, opts?.dir);
|
|
90596
|
-
const apiKey = keyResolution.key;
|
|
90597
|
-
if (apiKey === undefined || apiKey.length === 0) {
|
|
90598
|
-
throw new JevCredentialError;
|
|
90599
|
-
}
|
|
90600
|
-
preflightBudget(input.state, input.questions);
|
|
90601
|
-
const body = {
|
|
90602
|
-
model: input.model ?? DEFAULT_JEV_MODEL,
|
|
90603
|
-
state: input.state,
|
|
90604
|
-
questions: input.questions
|
|
90605
|
-
};
|
|
90606
|
-
const timeoutMs = opts?.timeoutMs ?? DEFAULT_JEV_TIMEOUT_MS;
|
|
91112
|
+
async function attemptJevRequest(fetchFn, apiKey, body, timeoutMs, external) {
|
|
90607
91113
|
const controller = new AbortController;
|
|
90608
91114
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
90609
|
-
const external = opts?.signal;
|
|
90610
91115
|
let onExternalAbort;
|
|
90611
91116
|
if (external !== undefined) {
|
|
90612
91117
|
if (external.aborted) {
|
|
@@ -90616,9 +91121,6 @@ async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
|
90616
91121
|
external.addEventListener("abort", onExternalAbort);
|
|
90617
91122
|
}
|
|
90618
91123
|
}
|
|
90619
|
-
let text;
|
|
90620
|
-
let status;
|
|
90621
|
-
let ok;
|
|
90622
91124
|
try {
|
|
90623
91125
|
const res = await fetchFn(JEV_ENDPOINT, {
|
|
90624
91126
|
method: "POST",
|
|
@@ -90626,25 +91128,65 @@ async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
|
90626
91128
|
body: JSON.stringify(body),
|
|
90627
91129
|
signal: controller.signal
|
|
90628
91130
|
});
|
|
90629
|
-
|
|
90630
|
-
ok
|
|
90631
|
-
text = await res.text();
|
|
91131
|
+
const text = await res.text();
|
|
91132
|
+
return { kind: "response", status: res.status, ok: res.ok, text, retryAfterHeader: res.headers.get("retry-after") };
|
|
90632
91133
|
} catch (cause) {
|
|
90633
91134
|
if (controller.signal.aborted) {
|
|
90634
|
-
|
|
91135
|
+
return { kind: "timeout" };
|
|
90635
91136
|
}
|
|
90636
|
-
|
|
91137
|
+
return { kind: "network-error", cause };
|
|
90637
91138
|
} finally {
|
|
90638
91139
|
clearTimeout(timer);
|
|
90639
91140
|
if (onExternalAbort !== undefined) {
|
|
90640
91141
|
external?.removeEventListener("abort", onExternalAbort);
|
|
90641
91142
|
}
|
|
90642
91143
|
}
|
|
90643
|
-
|
|
91144
|
+
}
|
|
91145
|
+
async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
91146
|
+
const keyResolution = resolveJevApiKeyResolution(opts?.env ?? process.env, opts?.dir);
|
|
91147
|
+
const apiKey = keyResolution.key;
|
|
91148
|
+
if (apiKey === undefined || apiKey.length === 0) {
|
|
91149
|
+
throw new JevCredentialError;
|
|
91150
|
+
}
|
|
91151
|
+
preflightBudget(input.state, input.questions);
|
|
91152
|
+
const body = {
|
|
91153
|
+
model: input.model ?? DEFAULT_JEV_MODEL,
|
|
91154
|
+
state: input.state,
|
|
91155
|
+
questions: input.questions
|
|
91156
|
+
};
|
|
91157
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_JEV_TIMEOUT_MS;
|
|
91158
|
+
const external = opts?.signal;
|
|
91159
|
+
const sleepFn = opts?.sleepFn ?? defaultJevSleep;
|
|
91160
|
+
let text;
|
|
91161
|
+
let attempt = 0;
|
|
91162
|
+
for (;; ) {
|
|
91163
|
+
attempt += 1;
|
|
91164
|
+
const outcome = await attemptJevRequest(fetchFn, apiKey, body, timeoutMs, external);
|
|
91165
|
+
if (outcome.kind === "timeout") {
|
|
91166
|
+
throw new JevTimeoutError(timeoutMs);
|
|
91167
|
+
}
|
|
91168
|
+
if (outcome.kind === "network-error") {
|
|
91169
|
+
if (attempt >= JEV_MAX_ATTEMPTS) {
|
|
91170
|
+
throw new JevNetworkError(attempt, outcome.cause);
|
|
91171
|
+
}
|
|
91172
|
+
await sleepFn(jevBackoffMs(attempt));
|
|
91173
|
+
continue;
|
|
91174
|
+
}
|
|
91175
|
+
if (outcome.ok) {
|
|
91176
|
+
text = outcome.text;
|
|
91177
|
+
break;
|
|
91178
|
+
}
|
|
91179
|
+
const status = outcome.status;
|
|
91180
|
+
text = outcome.text;
|
|
91181
|
+
if (JEV_RETRYABLE_STATUSES.has(status) && attempt < JEV_MAX_ATTEMPTS) {
|
|
91182
|
+
const retryAfterMs = retryAfterMsFromHeader(outcome.retryAfterHeader);
|
|
91183
|
+
await sleepFn(retryAfterMs ?? jevBackoffMs(attempt));
|
|
91184
|
+
continue;
|
|
91185
|
+
}
|
|
90644
91186
|
if (status === 401) {
|
|
90645
91187
|
throw new JevAuthRejectedError(keyResolution.source, status, text, looksLikeOpenRouterKey(apiKey));
|
|
90646
91188
|
}
|
|
90647
|
-
throw new JevRequestError(status, text);
|
|
91189
|
+
throw new JevRequestError(status, text, attempt);
|
|
90648
91190
|
}
|
|
90649
91191
|
let parsed;
|
|
90650
91192
|
try {
|
|
@@ -92435,7 +92977,7 @@ init_service4();
|
|
|
92435
92977
|
init_learn();
|
|
92436
92978
|
init_service9();
|
|
92437
92979
|
import { mkdir as mkdir51, readdir as readdir47, readFile as readFile133 } from "fs/promises";
|
|
92438
|
-
import path238, { join as
|
|
92980
|
+
import path238, { join as join9 } from "path";
|
|
92439
92981
|
init_review_learning();
|
|
92440
92982
|
|
|
92441
92983
|
// src/review/managed.ts
|
|
@@ -95480,7 +96022,7 @@ directly.
|
|
|
95480
96022
|
// src/commands/review-jev-rules.ts
|
|
95481
96023
|
init_args();
|
|
95482
96024
|
import { readFile as readFile118, readdir as readdir44, stat as stat22 } from "fs/promises";
|
|
95483
|
-
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as
|
|
96025
|
+
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join5, relative as relative3 } from "path";
|
|
95484
96026
|
|
|
95485
96027
|
// src/review/conform-state.ts
|
|
95486
96028
|
init_redact();
|
|
@@ -96647,7 +97189,7 @@ async function walkMatching(root, suffixes) {
|
|
|
96647
97189
|
return;
|
|
96648
97190
|
}
|
|
96649
97191
|
for (const entry of entries) {
|
|
96650
|
-
const full =
|
|
97192
|
+
const full = join5(dir, entry.name);
|
|
96651
97193
|
if (entry.isDirectory()) {
|
|
96652
97194
|
await walk(full);
|
|
96653
97195
|
continue;
|
|
@@ -96672,7 +97214,7 @@ function classify2(file) {
|
|
|
96672
97214
|
}
|
|
96673
97215
|
async function discoverRuleSources(cwd, explicitPaths) {
|
|
96674
97216
|
const discovered = [];
|
|
96675
|
-
for (const root of [
|
|
97217
|
+
for (const root of [join5(cwd, ".metaproject", "rules"), join5(cwd, "rules")]) {
|
|
96676
97218
|
for (const file of await walkMatching(root, [".md", ".mdc"])) {
|
|
96677
97219
|
const text = await readIfExists(file);
|
|
96678
97220
|
if (text === undefined)
|
|
@@ -96681,8 +97223,8 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96681
97223
|
}
|
|
96682
97224
|
}
|
|
96683
97225
|
const skillRoots = [
|
|
96684
|
-
[
|
|
96685
|
-
[
|
|
97226
|
+
[join5(cwd, ".metaproject", "project-skills"), "project-skill"],
|
|
97227
|
+
[join5(cwd, ".metaproject", "skills", "gdskills"), "gdskill"]
|
|
96686
97228
|
];
|
|
96687
97229
|
for (const [root, kind] of skillRoots) {
|
|
96688
97230
|
for (const skillMd of await walkMatching(root, ["SKILL.md"])) {
|
|
@@ -96707,7 +97249,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96707
97249
|
const explicitFiles = [];
|
|
96708
97250
|
const explicitDirSources = [];
|
|
96709
97251
|
for (const raw of explicitPaths) {
|
|
96710
|
-
const resolved = isAbsolute3(raw) ? raw :
|
|
97252
|
+
const resolved = isAbsolute3(raw) ? raw : join5(cwd, raw);
|
|
96711
97253
|
const stats = await stat22(resolved).catch(() => {
|
|
96712
97254
|
return;
|
|
96713
97255
|
});
|
|
@@ -96750,11 +97292,11 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96750
97292
|
return { sources: [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)), excluded: excluded.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
96751
97293
|
}
|
|
96752
97294
|
async function fixturePrPort(dir) {
|
|
96753
|
-
const raw = await readFile118(
|
|
97295
|
+
const raw = await readFile118(join5(dir, "pr.json"), "utf8");
|
|
96754
97296
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
96755
97297
|
}
|
|
96756
97298
|
async function fixtureJevFetch(dir) {
|
|
96757
|
-
const raw = await readFile118(
|
|
97299
|
+
const raw = await readFile118(join5(dir, "jev-responses.json"), "utf8");
|
|
96758
97300
|
const responses = JSON.parse(raw);
|
|
96759
97301
|
let index = 0;
|
|
96760
97302
|
const fn = async () => {
|
|
@@ -97557,7 +98099,7 @@ async function runJevRisk(args) {
|
|
|
97557
98099
|
// src/commands/review-jev-scenarios.ts
|
|
97558
98100
|
init_args();
|
|
97559
98101
|
import { readFile as readFile122, readdir as readdir45 } from "fs/promises";
|
|
97560
|
-
import { join as
|
|
98102
|
+
import { join as join6, relative as relative4 } from "path";
|
|
97561
98103
|
|
|
97562
98104
|
// src/review/jev-scenarios-config.ts
|
|
97563
98105
|
init_review_gate();
|
|
@@ -97900,7 +98442,7 @@ async function walkMarkdown2(root) {
|
|
|
97900
98442
|
return;
|
|
97901
98443
|
}
|
|
97902
98444
|
for (const entry of entries) {
|
|
97903
|
-
const full =
|
|
98445
|
+
const full = join6(dir, entry.name);
|
|
97904
98446
|
if (entry.isDirectory()) {
|
|
97905
98447
|
await walk(full);
|
|
97906
98448
|
continue;
|
|
@@ -97914,19 +98456,19 @@ async function walkMarkdown2(root) {
|
|
|
97914
98456
|
}
|
|
97915
98457
|
async function discoverScenarioSources(cwd) {
|
|
97916
98458
|
const scenarios = [];
|
|
97917
|
-
for (const file of await walkMarkdown2(
|
|
98459
|
+
for (const file of await walkMarkdown2(join6(cwd, ".metaproject", "wiki", "user-scenarios"))) {
|
|
97918
98460
|
const text = await readIfExists2(file);
|
|
97919
98461
|
if (text === undefined)
|
|
97920
98462
|
continue;
|
|
97921
98463
|
scenarios.push(scenarioFromWikiPage(relative4(cwd, file), text));
|
|
97922
98464
|
}
|
|
97923
|
-
for (const file of await walkMarkdown2(
|
|
98465
|
+
for (const file of await walkMarkdown2(join6(cwd, "docs", "requirements"))) {
|
|
97924
98466
|
const text = await readIfExists2(file);
|
|
97925
98467
|
if (text === undefined)
|
|
97926
98468
|
continue;
|
|
97927
98469
|
scenarios.push(...scenariosFromPrd(relative4(cwd, file), text));
|
|
97928
98470
|
}
|
|
97929
|
-
const readmeLikeFiles = [
|
|
98471
|
+
const readmeLikeFiles = [join6(cwd, "README.md"), ...await walkMarkdown2(join6(cwd, "docs", "docs"))];
|
|
97930
98472
|
for (const file of readmeLikeFiles) {
|
|
97931
98473
|
const text = await readIfExists2(file);
|
|
97932
98474
|
if (text === undefined)
|
|
@@ -97936,11 +98478,11 @@ async function discoverScenarioSources(cwd) {
|
|
|
97936
98478
|
return scenarios;
|
|
97937
98479
|
}
|
|
97938
98480
|
async function fixturePrPort3(dir) {
|
|
97939
|
-
const raw = await readFile122(
|
|
98481
|
+
const raw = await readFile122(join6(dir, "pr.json"), "utf8");
|
|
97940
98482
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
97941
98483
|
}
|
|
97942
98484
|
async function fixtureJevFetch3(dir) {
|
|
97943
|
-
const raw = await readFile122(
|
|
98485
|
+
const raw = await readFile122(join6(dir, "jev-responses.json"), "utf8");
|
|
97944
98486
|
const responses = JSON.parse(raw);
|
|
97945
98487
|
let index = 0;
|
|
97946
98488
|
const fn = async () => {
|
|
@@ -98096,7 +98638,7 @@ async function gitDiffForJevScenarios(ref) {
|
|
|
98096
98638
|
// src/commands/review-jev-docs.ts
|
|
98097
98639
|
init_args();
|
|
98098
98640
|
import { readFile as readFile124, readdir as readdir46 } from "fs/promises";
|
|
98099
|
-
import { join as
|
|
98641
|
+
import { join as join7, relative as relative5 } from "path";
|
|
98100
98642
|
|
|
98101
98643
|
// src/review/jev-docs-config.ts
|
|
98102
98644
|
init_review_gate();
|
|
@@ -98576,7 +99118,7 @@ async function walkMatching2(root, suffixes) {
|
|
|
98576
99118
|
return;
|
|
98577
99119
|
}
|
|
98578
99120
|
for (const entry of entries) {
|
|
98579
|
-
const full =
|
|
99121
|
+
const full = join7(dir, entry.name);
|
|
98580
99122
|
if (entry.isDirectory()) {
|
|
98581
99123
|
await walk(full);
|
|
98582
99124
|
continue;
|
|
@@ -98637,7 +99179,7 @@ async function walkAllDocFiles(dir) {
|
|
|
98637
99179
|
return out;
|
|
98638
99180
|
}
|
|
98639
99181
|
for (const entry of entries) {
|
|
98640
|
-
const full =
|
|
99182
|
+
const full = join7(dir, entry.name);
|
|
98641
99183
|
if (entry.isDirectory()) {
|
|
98642
99184
|
if (INCLUDE_WALK_PRUNE_DIRS.has(entry.name))
|
|
98643
99185
|
continue;
|
|
@@ -98651,19 +99193,19 @@ async function walkAllDocFiles(dir) {
|
|
|
98651
99193
|
async function resolveIncludeGlob(cwd, glob) {
|
|
98652
99194
|
const rootDir = includeGlobRootDir(glob);
|
|
98653
99195
|
const regex = includeGlobToRegExp(glob);
|
|
98654
|
-
const candidates = await walkAllDocFiles(
|
|
99196
|
+
const candidates = await walkAllDocFiles(join7(cwd, rootDir));
|
|
98655
99197
|
return candidates.filter((file) => regex.test(relative5(cwd, file)));
|
|
98656
99198
|
}
|
|
98657
99199
|
async function discoverDocFiles(cwd, includeGlobs = []) {
|
|
98658
99200
|
const files = new Set;
|
|
98659
|
-
for (const file of await walkMatching2(
|
|
99201
|
+
for (const file of await walkMatching2(join7(cwd, "docs"), [".md"]))
|
|
98660
99202
|
files.add(file);
|
|
98661
99203
|
for (const name of await matchingFileNames(cwd, /^readme/i)) {
|
|
98662
99204
|
if (/^changelog/i.test(name))
|
|
98663
99205
|
continue;
|
|
98664
|
-
files.add(
|
|
99206
|
+
files.add(join7(cwd, name));
|
|
98665
99207
|
}
|
|
98666
|
-
for (const file of await walkMatching2(
|
|
99208
|
+
for (const file of await walkMatching2(join7(cwd, ".metaproject", "wiki"), [".md"]))
|
|
98667
99209
|
files.add(file);
|
|
98668
99210
|
for (const glob of includeGlobs) {
|
|
98669
99211
|
for (const file of await resolveIncludeGlob(cwd, glob))
|
|
@@ -98691,11 +99233,11 @@ function changedCommandNamesFromRegions(regions) {
|
|
|
98691
99233
|
return names;
|
|
98692
99234
|
}
|
|
98693
99235
|
async function fixturePrPort4(dir) {
|
|
98694
|
-
const raw = await readFile124(
|
|
99236
|
+
const raw = await readFile124(join7(dir, "pr.json"), "utf8");
|
|
98695
99237
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
98696
99238
|
}
|
|
98697
99239
|
async function fixtureJevFetch4(dir) {
|
|
98698
|
-
const raw = await readFile124(
|
|
99240
|
+
const raw = await readFile124(join7(dir, "jev-responses.json"), "utf8");
|
|
98699
99241
|
const responses = JSON.parse(raw);
|
|
98700
99242
|
let index = 0;
|
|
98701
99243
|
const fn = async () => {
|
|
@@ -98869,7 +99411,7 @@ async function runJevDocs(args) {
|
|
|
98869
99411
|
init_args();
|
|
98870
99412
|
init_fs();
|
|
98871
99413
|
import { readFile as readFile126 } from "fs/promises";
|
|
98872
|
-
import { join as
|
|
99414
|
+
import { join as join8 } from "path";
|
|
98873
99415
|
init_pr_comments();
|
|
98874
99416
|
|
|
98875
99417
|
// src/review/jev-comments-config.ts
|
|
@@ -99163,15 +99705,15 @@ function createFixtureJevCommentsFactsPort(facts) {
|
|
|
99163
99705
|
};
|
|
99164
99706
|
}
|
|
99165
99707
|
async function fixturePrPort5(dir) {
|
|
99166
|
-
const raw = await readFile126(
|
|
99708
|
+
const raw = await readFile126(join8(dir, "pr.json"), "utf8");
|
|
99167
99709
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
99168
99710
|
}
|
|
99169
99711
|
async function fixtureFactsPort(dir) {
|
|
99170
|
-
const raw = await readFile126(
|
|
99712
|
+
const raw = await readFile126(join8(dir, "git-facts.json"), "utf8").catch(() => "{}");
|
|
99171
99713
|
return createFixtureJevCommentsFactsPort(JSON.parse(raw));
|
|
99172
99714
|
}
|
|
99173
99715
|
async function fixtureJevFetch5(dir) {
|
|
99174
|
-
const raw = await readFile126(
|
|
99716
|
+
const raw = await readFile126(join8(dir, "jev-responses.json"), "utf8");
|
|
99175
99717
|
const responses = JSON.parse(raw);
|
|
99176
99718
|
let index = 0;
|
|
99177
99719
|
const fn = async () => {
|
|
@@ -99185,7 +99727,7 @@ async function fixtureJevFetch5(dir) {
|
|
|
99185
99727
|
return fn;
|
|
99186
99728
|
}
|
|
99187
99729
|
function advisoryLabelsPath(cwd, repo, number) {
|
|
99188
|
-
return
|
|
99730
|
+
return join8(cwd, ".metaproject", "data", "review-jev-comments", `labels__${repo.replace(/\//g, "__")}__${number}.json`);
|
|
99189
99731
|
}
|
|
99190
99732
|
async function readCommentAdvisoryLabels(cwd, repo, number) {
|
|
99191
99733
|
const file = advisoryLabelsPath(cwd, repo, number);
|
|
@@ -102579,7 +103121,7 @@ async function resolvePort(args) {
|
|
|
102579
103121
|
}
|
|
102580
103122
|
const files = {};
|
|
102581
103123
|
for (const key of ["pull", "pull-comments", "pull-reviews", "issue-comments"]) {
|
|
102582
|
-
const file =
|
|
103124
|
+
const file = join9(fixtures, `${key}.json`);
|
|
102583
103125
|
try {
|
|
102584
103126
|
files[key] = JSON.parse(await readFile133(file, "utf8"));
|
|
102585
103127
|
} catch {
|
|
@@ -102589,13 +103131,13 @@ async function resolvePort(args) {
|
|
|
102589
103131
|
return createFixturePort(files);
|
|
102590
103132
|
}
|
|
102591
103133
|
async function fixtureCiPort(dir) {
|
|
102592
|
-
const runInfo = JSON.parse(await readFile133(
|
|
102593
|
-
const log = await readFile133(
|
|
102594
|
-
const history = await readFile133(
|
|
102595
|
-
const attempts = await readFile133(
|
|
102596
|
-
const changedFiles = await readFile133(
|
|
102597
|
-
const runsByHeadSha = await readFile133(
|
|
102598
|
-
const related = await readFile133(
|
|
103134
|
+
const runInfo = JSON.parse(await readFile133(join9(dir, "ci-run-info.json"), "utf8"));
|
|
103135
|
+
const log = await readFile133(join9(dir, "ci-failed-log.txt"), "utf8").catch(() => "");
|
|
103136
|
+
const history = await readFile133(join9(dir, "ci-history.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103137
|
+
const attempts = await readFile133(join9(dir, "ci-attempts.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103138
|
+
const changedFiles = await readFile133(join9(dir, "ci-changed-files.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103139
|
+
const runsByHeadSha = await readFile133(join9(dir, "ci-runs-by-head-sha.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103140
|
+
const related = await readFile133(join9(dir, "ci-related-runs.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
102599
103141
|
return createFixtureCiPort({
|
|
102600
103142
|
runs: { [runInfo.runId]: runInfo, ...related.runs },
|
|
102601
103143
|
logs: { [runInfo.runId]: log, ...related.logs },
|
|
@@ -102606,7 +103148,7 @@ async function fixtureCiPort(dir) {
|
|
|
102606
103148
|
});
|
|
102607
103149
|
}
|
|
102608
103150
|
async function fixtureJevFetch8(dir) {
|
|
102609
|
-
const body = await readFile133(
|
|
103151
|
+
const body = await readFile133(join9(dir, "jev-response.json"), "utf8");
|
|
102610
103152
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
102611
103153
|
return fn;
|
|
102612
103154
|
}
|
|
@@ -102772,12 +103314,12 @@ async function runCiTriageEval(cwd, args, evalFile) {
|
|
|
102772
103314
|
}
|
|
102773
103315
|
}
|
|
102774
103316
|
async function fixtureConformJevFetch(dir) {
|
|
102775
|
-
const body = await readFile133(
|
|
103317
|
+
const body = await readFile133(join9(dir, "jev-response.json"), "utf8");
|
|
102776
103318
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
102777
103319
|
return fn;
|
|
102778
103320
|
}
|
|
102779
103321
|
async function fixtureConformPrPortFrom(dir) {
|
|
102780
|
-
const pr = JSON.parse(await readFile133(
|
|
103322
|
+
const pr = JSON.parse(await readFile133(join9(dir, "pr.json"), "utf8"));
|
|
102781
103323
|
return createFixtureConformPrPort({ pr });
|
|
102782
103324
|
}
|
|
102783
103325
|
function accumulateJevUsage(acc, usage) {
|
|
@@ -102858,7 +103400,7 @@ async function explainConformVerdicts(cwd, refPath, verdicts, threshold, fixture
|
|
|
102858
103400
|
return {};
|
|
102859
103401
|
if (fixturesDir !== undefined) {
|
|
102860
103402
|
try {
|
|
102861
|
-
return JSON.parse(await readFile133(
|
|
103403
|
+
return JSON.parse(await readFile133(join9(fixturesDir, "explain-response.json"), "utf8"));
|
|
102862
103404
|
} catch {
|
|
102863
103405
|
return {};
|
|
102864
103406
|
}
|
|
@@ -102906,7 +103448,7 @@ function parsePositiveIntFlag(args, flag, fallback) {
|
|
|
102906
103448
|
}
|
|
102907
103449
|
async function readReportFindings(reportDir) {
|
|
102908
103450
|
try {
|
|
102909
|
-
const raw = JSON.parse(await readFile133(
|
|
103451
|
+
const raw = JSON.parse(await readFile133(join9(reportDir, "findings.json"), "utf8"));
|
|
102910
103452
|
return Array.isArray(raw) ? raw : [];
|
|
102911
103453
|
} catch {
|
|
102912
103454
|
return [];
|
|
@@ -102978,7 +103520,7 @@ async function runConform(args) {
|
|
|
102978
103520
|
const fetchFn = fixturesDir === undefined ? globalThis.fetch : await fixtureConformJevFetch(fixturesDir);
|
|
102979
103521
|
const acc = { jevCalls: 0, inputTokens: 0, outputTokens: 0, costUsd: 0, sawUsage: false };
|
|
102980
103522
|
const clauses = await resolveConformClauseTags(cwd, refPath, docText, rawClauses, fetchFn, model, acc);
|
|
102981
|
-
await writeFileAtomic(
|
|
103523
|
+
await writeFileAtomic(join9(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(await readRecentConformDocs(cwd), refPath), null, 2)}
|
|
102982
103524
|
`, { mode: 384 }).catch(() => {});
|
|
102983
103525
|
const notCheckable = clauses.filter((c) => !c.checkable);
|
|
102984
103526
|
const checkable = clauses.filter((c) => c.checkable);
|
|
@@ -103010,7 +103552,7 @@ async function runConform(args) {
|
|
|
103010
103552
|
hunkTotalRegions = hunkResult.totalRegions;
|
|
103011
103553
|
} else if (reportDir !== undefined) {
|
|
103012
103554
|
target = { kind: "report", label: reportDir };
|
|
103013
|
-
const reportMarkdown = await readFile133(
|
|
103555
|
+
const reportMarkdown = await readFile133(join9(reportDir, "report.md"), "utf8").catch(() => "");
|
|
103014
103556
|
const findings = await readReportFindings(reportDir);
|
|
103015
103557
|
const facts = computeReportConformFacts(reportMarkdown, findings);
|
|
103016
103558
|
const redacted = reportRedactedStateText(reportMarkdown);
|
|
@@ -103051,7 +103593,7 @@ async function runConform(args) {
|
|
|
103051
103593
|
}
|
|
103052
103594
|
async function readRecentConformDocs(cwd) {
|
|
103053
103595
|
try {
|
|
103054
|
-
const raw = JSON.parse(await readFile133(
|
|
103596
|
+
const raw = JSON.parse(await readFile133(join9(cwd, CONFORM_RECENTS_PATH), "utf8"));
|
|
103055
103597
|
return Array.isArray(raw) ? raw : [];
|
|
103056
103598
|
} catch {
|
|
103057
103599
|
return [];
|
|
@@ -103297,7 +103839,7 @@ async function runStack(args) {
|
|
|
103297
103839
|
console.log(renderStackScopingMarkdown(detected, decisions));
|
|
103298
103840
|
}
|
|
103299
103841
|
async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
103300
|
-
const reviewRoot =
|
|
103842
|
+
const reviewRoot = join9(cwd, ".metaproject", "skills", "gdskills", "review");
|
|
103301
103843
|
let entries;
|
|
103302
103844
|
try {
|
|
103303
103845
|
entries = await readdir47(reviewRoot, { withFileTypes: true });
|
|
@@ -103309,7 +103851,7 @@ async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
|
103309
103851
|
if (!entry.isDirectory()) {
|
|
103310
103852
|
continue;
|
|
103311
103853
|
}
|
|
103312
|
-
const skillMdPath =
|
|
103854
|
+
const skillMdPath = join9(reviewRoot, entry.name, "SKILL.md");
|
|
103313
103855
|
let content;
|
|
103314
103856
|
try {
|
|
103315
103857
|
content = await readFile133(skillMdPath, "utf8");
|
|
@@ -110424,38 +110966,8 @@ function formatDoctorReport(report) {
|
|
|
110424
110966
|
`);
|
|
110425
110967
|
}
|
|
110426
110968
|
|
|
110427
|
-
// src/
|
|
110428
|
-
|
|
110429
|
-
function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
110430
|
-
if (url.length === 0) {
|
|
110431
|
-
return;
|
|
110432
|
-
}
|
|
110433
|
-
if (platform === "darwin") {
|
|
110434
|
-
return { cmd: "open", args: [url] };
|
|
110435
|
-
}
|
|
110436
|
-
if (platform === "win32") {
|
|
110437
|
-
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
110438
|
-
}
|
|
110439
|
-
if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
|
|
110440
|
-
return { cmd: "xdg-open", args: [url] };
|
|
110441
|
-
}
|
|
110442
|
-
if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
|
|
110443
|
-
return { cmd: "xdg-open", args: [url] };
|
|
110444
|
-
}
|
|
110445
|
-
return;
|
|
110446
|
-
}
|
|
110447
|
-
function openVerificationUrl(url, deps = {}) {
|
|
110448
|
-
const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
|
|
110449
|
-
if (plan === undefined) {
|
|
110450
|
-
return;
|
|
110451
|
-
}
|
|
110452
|
-
const spawnFn = deps.spawn ?? spawn7;
|
|
110453
|
-
try {
|
|
110454
|
-
const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
|
|
110455
|
-
child.on("error", () => {});
|
|
110456
|
-
child.unref();
|
|
110457
|
-
} catch {}
|
|
110458
|
-
}
|
|
110969
|
+
// src/commands/mcp-servers.ts
|
|
110970
|
+
init_open_url();
|
|
110459
110971
|
|
|
110460
110972
|
// src/mcp-servers/oauth-callback.ts
|
|
110461
110973
|
import { randomBytes as randomBytes8, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
@@ -126678,7 +127190,7 @@ init_safe_exec();
|
|
|
126678
127190
|
init_metaproject_operations();
|
|
126679
127191
|
init_interactive_tools();
|
|
126680
127192
|
import { existsSync as existsSync43 } from "fs";
|
|
126681
|
-
import { resolve as
|
|
127193
|
+
import { resolve as resolve4 } from "path";
|
|
126682
127194
|
var MAX_OUTPUT_BYTES3 = 20000;
|
|
126683
127195
|
var RG_UNAVAILABLE_SIGNATURE = /ripgrep \(rg\) is not installed|Executable not found[^\n]*\brg\b|\brg\b[^\n]*\bENOENT\b|\bENOENT\b[^\n]*\brg\b/i;
|
|
126684
127196
|
var SEARCH_CODE_RG_UNAVAILABLE_MESSAGE = "ripgrep (rg) is not installed or not on PATH, and search_code needs it. Install it " + "(`brew install ripgrep` / `apt install ripgrep`), or use read_file and list_dir to " + "inspect files directly instead of retrying search_code.";
|
|
@@ -126689,11 +127201,11 @@ function normalizeSearchResult(result) {
|
|
|
126689
127201
|
return result;
|
|
126690
127202
|
}
|
|
126691
127203
|
var KERYX_ENTRIES = [
|
|
126692
|
-
|
|
126693
|
-
|
|
127204
|
+
resolve4(import.meta.dir, "..", "..", "..", "cli.ts"),
|
|
127205
|
+
resolve4(import.meta.dir, "cli.js")
|
|
126694
127206
|
];
|
|
126695
127207
|
function keryxSelfCommand(main = Bun.main, execPath = process.execPath, exists = existsSync43, entries = KERYX_ENTRIES) {
|
|
126696
|
-
if (entries.includes(
|
|
127208
|
+
if (entries.includes(resolve4(main)) && exists(main)) {
|
|
126697
127209
|
return [execPath, ...SAFE_BUN_SPAWN_ARGS, main];
|
|
126698
127210
|
}
|
|
126699
127211
|
if (isCompiledBinaryEntry(main)) {
|
|
@@ -132825,6 +133337,7 @@ ${lines.join(`
|
|
|
132825
133337
|
}
|
|
132826
133338
|
|
|
132827
133339
|
// src/tui/tui-shell.ts
|
|
133340
|
+
init_subscription_models();
|
|
132828
133341
|
init_agent();
|
|
132829
133342
|
init_single_turn();
|
|
132830
133343
|
|
|
@@ -146485,6 +146998,7 @@ init_patch_risk();
|
|
|
146485
146998
|
init_catalog();
|
|
146486
146999
|
init_grants();
|
|
146487
147000
|
init_login();
|
|
147001
|
+
init_open_url();
|
|
146488
147002
|
init_shell_config();
|
|
146489
147003
|
init_provider_config();
|
|
146490
147004
|
|
|
@@ -151854,14 +152368,22 @@ async function filterConnectedDetectedProviders(detected, options = {}) {
|
|
|
151854
152368
|
const env = options.env ?? process.env;
|
|
151855
152369
|
const connected = [];
|
|
151856
152370
|
for (const prov of detected) {
|
|
151857
|
-
|
|
152371
|
+
if (prov.name === "openai-codex") {
|
|
152372
|
+
if (oauthAccessToken(prov.name, options.configDir) === undefined)
|
|
152373
|
+
continue;
|
|
152374
|
+
const result = await fetchOpenAiCodexModels(fetchFn, { ...options.configDir !== undefined ? { configDir: options.configDir } : {} });
|
|
152375
|
+
if (result.source === "live" && result.models.length > 0)
|
|
152376
|
+
connected.push({ ...prov, models: result.models });
|
|
152377
|
+
continue;
|
|
152378
|
+
}
|
|
152379
|
+
const registry = providerByName(prov.name, options.configDir);
|
|
151858
152380
|
if (registry === undefined) {
|
|
151859
152381
|
connected.push(prov);
|
|
151860
152382
|
continue;
|
|
151861
152383
|
}
|
|
151862
152384
|
const requiresApiKey = registry.requiresApiKey ?? true;
|
|
151863
152385
|
const envKey = prov.envKey ?? registry.envKey;
|
|
151864
|
-
const raw = envKey !== undefined ? env[envKey] : undefined;
|
|
152386
|
+
const raw = envKey !== undefined ? env[envKey] ?? loadShellConfig(options.configDir).apiKeys?.[envKey] : undefined;
|
|
151865
152387
|
if (requiresApiKey && (raw === undefined || raw.length === 0)) {
|
|
151866
152388
|
continue;
|
|
151867
152389
|
}
|
|
@@ -152946,7 +153468,7 @@ function pickAuthMethodStep(otui, target, providerLabel, methods) {
|
|
|
152946
153468
|
});
|
|
152947
153469
|
});
|
|
152948
153470
|
}
|
|
152949
|
-
function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
153471
|
+
function runDeviceLoginInTui(otui, target, provider, dir, fetchFn = globalThis.fetch) {
|
|
152950
153472
|
const r = stepRenderer(target);
|
|
152951
153473
|
return new Promise((resolve) => {
|
|
152952
153474
|
const controller = new AbortController;
|
|
@@ -152969,7 +153491,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152969
153491
|
};
|
|
152970
153492
|
loginDeviceCode({
|
|
152971
153493
|
provider,
|
|
152972
|
-
fetch: (input, init) =>
|
|
153494
|
+
fetch: (input, init) => fetchFn(input, init),
|
|
152973
153495
|
signal: controller.signal,
|
|
152974
153496
|
...dir !== undefined ? { dir } : {},
|
|
152975
153497
|
onChallenge: (challenge) => {
|
|
@@ -152982,7 +153504,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152982
153504
|
}
|
|
152983
153505
|
if (result.ok) {
|
|
152984
153506
|
cleanup();
|
|
152985
|
-
applyOAuthAccessToEnv();
|
|
153507
|
+
applyOAuthAccessToEnv(dir);
|
|
152986
153508
|
resolve(true);
|
|
152987
153509
|
return;
|
|
152988
153510
|
}
|
|
@@ -152997,7 +153519,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152997
153519
|
});
|
|
152998
153520
|
}
|
|
152999
153521
|
async function modelsForPicker(prov, deps = {}) {
|
|
153000
|
-
return await resolveModelsForPicker(deps.fetch ?? globalThis.fetch, prov, deps.env ?? process.env);
|
|
153522
|
+
return await resolveModelsForPicker(deps.fetch ?? globalThis.fetch, prov, deps.env ?? process.env, { ...deps.configDir !== undefined ? { configDir: deps.configDir } : {} });
|
|
153001
153523
|
}
|
|
153002
153524
|
function endpointMayBeAtFault(failure) {
|
|
153003
153525
|
return failure?.kind === "unreachable" || failure?.kind === "http";
|
|
@@ -153141,7 +153663,7 @@ function pickConnectedProviderStep(otui, target, detected, opts) {
|
|
|
153141
153663
|
}
|
|
153142
153664
|
async function runTest(name) {
|
|
153143
153665
|
const block = rowBlocks.find((b) => b.name === name);
|
|
153144
|
-
const provider =
|
|
153666
|
+
const provider = connectionProviderByName(name, opts.configDir);
|
|
153145
153667
|
if (block === undefined)
|
|
153146
153668
|
return;
|
|
153147
153669
|
if (provider === undefined) {
|
|
@@ -153305,7 +153827,8 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153305
153827
|
}
|
|
153306
153828
|
const candidatesPromise = options.onlyConnected ? filterConnectedDetectedProviders(detected, {
|
|
153307
153829
|
...options.fetch !== undefined ? { fetch: options.fetch } : {},
|
|
153308
|
-
...options.env !== undefined ? { env: options.env } : {}
|
|
153830
|
+
...options.env !== undefined ? { env: options.env } : {},
|
|
153831
|
+
...options.configDir !== undefined ? { configDir: options.configDir } : {}
|
|
153309
153832
|
}) : Promise.resolve(detected);
|
|
153310
153833
|
(async () => {
|
|
153311
153834
|
const candidates = await candidatesPromise;
|
|
@@ -153349,7 +153872,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153349
153872
|
}
|
|
153350
153873
|
continue;
|
|
153351
153874
|
}
|
|
153352
|
-
const lockDiscoveredHost =
|
|
153875
|
+
const lockDiscoveredHost = ["github-copilot", "openai", "openai-codex"].includes(prov.name);
|
|
153353
153876
|
let selectedBaseUrl = options.onlyConnected || prov.baseUrl === undefined || lockDiscoveredHost ? prov.baseUrl : await promptBaseUrlStep(otui, rOrChrome, prov.label ?? prov.name, prov.baseUrl);
|
|
153354
153877
|
if (!options.onlyConnected && !lockDiscoveredHost && prov.baseUrl !== undefined && selectedBaseUrl === undefined) {
|
|
153355
153878
|
continue;
|
|
@@ -153361,12 +153884,12 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153361
153884
|
const envKey = prov.envKey;
|
|
153362
153885
|
const label = prov.label ?? prov.name;
|
|
153363
153886
|
const collectCredential = async (notice) => {
|
|
153364
|
-
if (options.onlyConnected || envKey === undefined) {
|
|
153887
|
+
if (options.onlyConnected || envKey === undefined && !catalogAllows(prov.name, "device-code")) {
|
|
153365
153888
|
return "ok";
|
|
153366
153889
|
}
|
|
153367
153890
|
if (notice === undefined) {
|
|
153368
|
-
const existingKey = (options.env ?? process.env)[envKey];
|
|
153369
|
-
const hasOauth = oauthAccessToken(prov.name, options.configDir) !== undefined;
|
|
153891
|
+
const existingKey = envKey === undefined ? undefined : (options.env ?? process.env)[envKey] ?? loadShellConfig(options.configDir).apiKeys?.[envKey];
|
|
153892
|
+
const hasOauth = prov.name !== "openai" && oauthAccessToken(prov.name, options.configDir) !== undefined;
|
|
153370
153893
|
if (existingKey !== undefined && existingKey.length > 0 || hasOauth) {
|
|
153371
153894
|
return "ok";
|
|
153372
153895
|
}
|
|
@@ -153384,7 +153907,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153384
153907
|
}
|
|
153385
153908
|
}
|
|
153386
153909
|
if (method === "device-code") {
|
|
153387
|
-
if (!await runDeviceLoginInTui(otui, rOrChrome, prov.name, options.configDir)) {
|
|
153910
|
+
if (!await runDeviceLoginInTui(otui, rOrChrome, prov.name, options.configDir, options.fetch)) {
|
|
153388
153911
|
return "back";
|
|
153389
153912
|
}
|
|
153390
153913
|
const saved = loadShellConfig(options.configDir).baseUrls?.[prov.name];
|
|
@@ -153394,8 +153917,10 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153394
153917
|
}
|
|
153395
153918
|
return "ok";
|
|
153396
153919
|
}
|
|
153920
|
+
if (envKey === undefined)
|
|
153921
|
+
return "back";
|
|
153397
153922
|
const kr = await promptApiKeyStep(otui, rOrChrome, {
|
|
153398
|
-
label,
|
|
153923
|
+
label: prov.name === "openai" ? "OpenAI" : label,
|
|
153399
153924
|
envKey,
|
|
153400
153925
|
...notice === undefined ? {} : { notice }
|
|
153401
153926
|
});
|
|
@@ -153414,7 +153939,8 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153414
153939
|
}
|
|
153415
153940
|
const modelDeps = {
|
|
153416
153941
|
...options.fetch === undefined ? {} : { fetch: options.fetch },
|
|
153417
|
-
...options.env === undefined ? {} : { env: options.env }
|
|
153942
|
+
...options.env === undefined ? {} : { env: options.env },
|
|
153943
|
+
...options.configDir === undefined ? {} : { configDir: options.configDir }
|
|
153418
153944
|
};
|
|
153419
153945
|
let models = await modelsForPicker(selectedProvider, modelDeps);
|
|
153420
153946
|
if (models.failure?.kind === "rejected") {
|
|
@@ -153423,7 +153949,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153423
153949
|
}
|
|
153424
153950
|
models = await modelsForPicker(selectedProvider, modelDeps);
|
|
153425
153951
|
}
|
|
153426
|
-
if (!options.onlyConnected && selectedProvider.baseUrl !== undefined && endpointMayBeAtFault(models.failure)) {
|
|
153952
|
+
if (!options.onlyConnected && !lockDiscoveredHost && selectedProvider.baseUrl !== undefined && endpointMayBeAtFault(models.failure)) {
|
|
153427
153953
|
const corrected = await promptBaseUrlStep(otui, rOrChrome, label, selectedProvider.baseUrl, modelsFailureLine(label, models.failure));
|
|
153428
153954
|
if (corrected === undefined) {
|
|
153429
153955
|
continue;
|
|
@@ -156548,7 +157074,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
156548
157074
|
io.onSystem?.(`Reasoning effort: ${wanted}
|
|
156549
157075
|
`);
|
|
156550
157076
|
const compatProvider = providerByName(currentSel.provider);
|
|
156551
|
-
if (compatProvider !== undefined && compatProvider.reasoning === undefined) {
|
|
157077
|
+
if (currentSel.provider !== "openai" && compatProvider !== undefined && compatProvider.reasoning === undefined) {
|
|
156552
157078
|
io.onSystem?.(`Note: ${currentSel.provider} is OpenAI-compatible with no "reasoning" entry \u2014 its reasoning ` + `is configured per-provider in llm-providers.json (reasoning.requestParams); this setting has no effect for it.
|
|
156553
157079
|
`);
|
|
156554
157080
|
}
|
|
@@ -156611,7 +157137,8 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
156611
157137
|
`);
|
|
156612
157138
|
}
|
|
156613
157139
|
if (name === currentSel.provider) {
|
|
156614
|
-
io.onSystem?.(`\u25C7 ${name} disconnected \u2014
|
|
157140
|
+
io.onSystem?.(name === "openai-codex" ? `\u25C7 ${name} disconnected \u2014 it stays selected, but the next turn requires a new login. Use /provider to reconnect or /connect to switch.
|
|
157141
|
+
` : `\u25C7 ${name} disconnected \u2014 this session keeps its already-loaded credential until you /connect another provider or restart.
|
|
156615
157142
|
`);
|
|
156616
157143
|
}
|
|
156617
157144
|
}
|
|
@@ -157954,6 +158481,17 @@ Starting a new session.
|
|
|
157954
158481
|
modelParams = picked.modelParams ?? {};
|
|
157955
158482
|
provider = makeActive();
|
|
157956
158483
|
};
|
|
158484
|
+
const selectAndApply = async (options) => {
|
|
158485
|
+
if (deps.selectProviderModel === undefined)
|
|
158486
|
+
return;
|
|
158487
|
+
try {
|
|
158488
|
+
applySelection(await deps.selectProviderModel(io, options));
|
|
158489
|
+
} catch (cause) {
|
|
158490
|
+
system(`${cause instanceof Error ? cause.message : "Provider selection failed"}
|
|
158491
|
+
Kept the current session.
|
|
158492
|
+
`);
|
|
158493
|
+
}
|
|
158494
|
+
};
|
|
157957
158495
|
for await (const line of io.lines) {
|
|
157958
158496
|
io.onSafeBoundary?.();
|
|
157959
158497
|
if (line.startsWith("/")) {
|
|
@@ -158091,12 +158629,19 @@ Kept the current session.
|
|
|
158091
158629
|
`);
|
|
158092
158630
|
continue;
|
|
158093
158631
|
}
|
|
158094
|
-
|
|
158095
|
-
applySelection(picked);
|
|
158632
|
+
await selectAndApply({ onlyProvider: providerName });
|
|
158096
158633
|
continue;
|
|
158097
158634
|
}
|
|
158098
158635
|
if (command === "/provider") {
|
|
158099
158636
|
if (argument.length > 0) {
|
|
158637
|
+
if (argument === "openai-codex") {
|
|
158638
|
+
if (deps.selectProviderModel === undefined) {
|
|
158639
|
+
system("Interactive subscription selection is not available. Run `keryx auth login openai-codex` and select an authorized model.\n");
|
|
158640
|
+
} else {
|
|
158641
|
+
await selectAndApply({ onlyProvider: "openai-codex" });
|
|
158642
|
+
}
|
|
158643
|
+
continue;
|
|
158644
|
+
}
|
|
158100
158645
|
providerName = argument;
|
|
158101
158646
|
provider = makeActive();
|
|
158102
158647
|
continue;
|
|
@@ -158106,8 +158651,7 @@ Kept the current session.
|
|
|
158106
158651
|
`);
|
|
158107
158652
|
continue;
|
|
158108
158653
|
}
|
|
158109
|
-
|
|
158110
|
-
applySelection(picked);
|
|
158654
|
+
await selectAndApply({});
|
|
158111
158655
|
continue;
|
|
158112
158656
|
}
|
|
158113
158657
|
if (command === "/connect") {
|
|
@@ -158115,8 +158659,7 @@ Kept the current session.
|
|
|
158115
158659
|
system(CONNECT_GUIDANCE);
|
|
158116
158660
|
continue;
|
|
158117
158661
|
}
|
|
158118
|
-
|
|
158119
|
-
applySelection(picked);
|
|
158662
|
+
await selectAndApply({ onlyConnected: true });
|
|
158120
158663
|
continue;
|
|
158121
158664
|
}
|
|
158122
158665
|
const wrongMode = describeUnavailableCommand(command, "chat");
|
|
@@ -158224,6 +158767,8 @@ function realMakeProvider(write) {
|
|
|
158224
158767
|
};
|
|
158225
158768
|
}
|
|
158226
158769
|
function oauthCredentialsFor(name) {
|
|
158770
|
+
if (name === "openai" || name === "openai-codex")
|
|
158771
|
+
return {};
|
|
158227
158772
|
const definition = providerByName(name);
|
|
158228
158773
|
const envKey = definition?.envKey;
|
|
158229
158774
|
if (envKey === undefined)
|
|
@@ -169511,6 +170056,7 @@ async function routingCommand(args, deps = {}) {
|
|
|
169511
170056
|
init_catalog();
|
|
169512
170057
|
init_grants();
|
|
169513
170058
|
init_login();
|
|
170059
|
+
init_open_url();
|
|
169514
170060
|
function printAuthHelp() {
|
|
169515
170061
|
console.log(`Usage:
|
|
169516
170062
|
keryx auth list [--json]
|
|
@@ -169518,7 +170064,7 @@ function printAuthHelp() {
|
|
|
169518
170064
|
keryx auth logout <provider>
|
|
169519
170065
|
keryx auth status <provider> [--json]
|
|
169520
170066
|
|
|
169521
|
-
Providers with a sanctioned subscription login: grok (SuperGrok), openai (ChatGPT Plus/Pro), github-copilot.
|
|
170067
|
+
Providers with a sanctioned subscription login: grok (SuperGrok), openai-codex (ChatGPT Plus/Pro), github-copilot.
|
|
169522
170068
|
Claude Pro/Max, Gemini Google-account login, and DeepSeek OAuth are not offered.
|
|
169523
170069
|
`);
|
|
169524
170070
|
}
|
|
@@ -169574,7 +170120,9 @@ function runAuthList(args) {
|
|
|
169574
170120
|
}
|
|
169575
170121
|
}
|
|
169576
170122
|
async function runAuthLogin(args) {
|
|
169577
|
-
const provider = args[0];
|
|
170123
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170124
|
+
if (args[0] === "openai")
|
|
170125
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169578
170126
|
if (provider === undefined || provider.length === 0) {
|
|
169579
170127
|
console.error("Usage: keryx auth login <provider>");
|
|
169580
170128
|
process.exitCode = 1;
|
|
@@ -169586,14 +170134,18 @@ async function runAuthLogin(args) {
|
|
|
169586
170134
|
process.exitCode = 1;
|
|
169587
170135
|
return;
|
|
169588
170136
|
}
|
|
170137
|
+
const controller = new AbortController;
|
|
170138
|
+
const cancel = () => controller.abort();
|
|
170139
|
+
process.on("SIGINT", cancel);
|
|
169589
170140
|
const result = await loginDeviceCode({
|
|
169590
170141
|
provider,
|
|
170142
|
+
signal: controller.signal,
|
|
169591
170143
|
fetch: (input, init) => globalThis.fetch(input, init),
|
|
169592
170144
|
onChallenge: (challenge) => {
|
|
169593
170145
|
console.log(challenge.instructions);
|
|
169594
170146
|
openVerificationUrl(challenge.verificationUriComplete ?? challenge.verificationUri);
|
|
169595
170147
|
}
|
|
169596
|
-
});
|
|
170148
|
+
}).finally(() => process.removeListener("SIGINT", cancel));
|
|
169597
170149
|
if (!result.ok) {
|
|
169598
170150
|
console.error(result.error);
|
|
169599
170151
|
process.exitCode = 1;
|
|
@@ -169603,7 +170155,9 @@ async function runAuthLogin(args) {
|
|
|
169603
170155
|
console.log(`Authorized ${provider} via ${deviceCodeMethodLabel(provider)}.`);
|
|
169604
170156
|
}
|
|
169605
170157
|
function runAuthLogout(args) {
|
|
169606
|
-
const provider = args[0];
|
|
170158
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170159
|
+
if (args[0] === "openai")
|
|
170160
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169607
170161
|
if (provider === undefined || provider.length === 0) {
|
|
169608
170162
|
console.error("Usage: keryx auth logout <provider>");
|
|
169609
170163
|
process.exitCode = 1;
|
|
@@ -169613,7 +170167,9 @@ function runAuthLogout(args) {
|
|
|
169613
170167
|
console.log(`Logged out ${provider}.`);
|
|
169614
170168
|
}
|
|
169615
170169
|
function runAuthStatus(args) {
|
|
169616
|
-
const provider = args[0];
|
|
170170
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170171
|
+
if (args[0] === "openai")
|
|
170172
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169617
170173
|
if (provider === undefined || provider.length === 0) {
|
|
169618
170174
|
console.error("Usage: keryx auth status <provider>");
|
|
169619
170175
|
process.exitCode = 1;
|