@mrciphersmith/keryx 0.3.12 → 0.3.13
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 +296 -290
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -316,6 +316,26 @@ var init_safe_exec = __esm(() => {
|
|
|
316
316
|
FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT", "SIGWINCH"];
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
+
// src/lib/oauth/openai-token.ts
|
|
320
|
+
function codexTokenMetadata(token) {
|
|
321
|
+
if (typeof token !== "string" || token.length > 1e5 || token.split(".").length !== 3)
|
|
322
|
+
return {};
|
|
323
|
+
try {
|
|
324
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
|
|
325
|
+
if (typeof payload !== "object" || payload === null)
|
|
326
|
+
return {};
|
|
327
|
+
const claims = payload;
|
|
328
|
+
const auth = claims["https://api.openai.com/auth"];
|
|
329
|
+
const accountId = typeof auth === "object" && auth !== null ? auth.chatgpt_account_id : undefined;
|
|
330
|
+
return {
|
|
331
|
+
...typeof accountId === "string" && accountId.length > 0 && accountId.trim() === accountId && !/[\r\n]/.test(accountId) ? { accountId } : {},
|
|
332
|
+
...typeof claims.exp === "number" && Number.isFinite(claims.exp) && claims.exp > 0 ? { expiresAt: claims.exp * 1000 } : {}
|
|
333
|
+
};
|
|
334
|
+
} catch {
|
|
335
|
+
return {};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
319
339
|
// src/lib/config-dir.ts
|
|
320
340
|
import {
|
|
321
341
|
appendFileSync,
|
|
@@ -465,9 +485,258 @@ var init_config_dir = __esm(() => {
|
|
|
465
485
|
MAX_TURN_FILE_BYTES = MAX_CONTENT_FILE_BYTES;
|
|
466
486
|
});
|
|
467
487
|
|
|
468
|
-
// src/lib/
|
|
488
|
+
// src/lib/shell-config.ts
|
|
469
489
|
import { existsSync } from "fs";
|
|
470
490
|
import path3 from "path";
|
|
491
|
+
function noteSavedCredentialEnv(keys) {
|
|
492
|
+
for (const key of keys)
|
|
493
|
+
savedCredentialKeys.add(key);
|
|
494
|
+
}
|
|
495
|
+
function savedCredentialEnvKeys() {
|
|
496
|
+
return savedCredentialKeys;
|
|
497
|
+
}
|
|
498
|
+
function shellConfigPath(dir) {
|
|
499
|
+
return path3.join(keryxConfigDir(dir), "auth.json");
|
|
500
|
+
}
|
|
501
|
+
function loadShellConfig(dir) {
|
|
502
|
+
try {
|
|
503
|
+
const file = shellConfigPath(dir);
|
|
504
|
+
if (!existsSync(file)) {
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
507
|
+
const read = readConfigFile(file);
|
|
508
|
+
if (!read.ok) {
|
|
509
|
+
return {};
|
|
510
|
+
}
|
|
511
|
+
const raw = JSON.parse(read.text);
|
|
512
|
+
return raw !== null && typeof raw === "object" ? raw : {};
|
|
513
|
+
} catch {
|
|
514
|
+
return {};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function saveShellConfig(patch, dir) {
|
|
518
|
+
try {
|
|
519
|
+
ensureKeryxConfigDir(dir);
|
|
520
|
+
const next = { ...loadShellConfig(dir), ...patch };
|
|
521
|
+
writeOwnerOnlyFileAtomic(shellConfigPath(dir), `${JSON.stringify(next, null, 2)}
|
|
522
|
+
`);
|
|
523
|
+
} catch {}
|
|
524
|
+
}
|
|
525
|
+
function saveApiKey(envKey, value, dir) {
|
|
526
|
+
const existing = loadShellConfig(dir).apiKeys ?? {};
|
|
527
|
+
saveShellConfig({ apiKeys: { ...existing, [envKey]: value } }, dir);
|
|
528
|
+
}
|
|
529
|
+
function saveProviderBaseUrl(provider, baseUrl, dir) {
|
|
530
|
+
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
531
|
+
saveShellConfig({ baseUrls: { ...existing, [provider]: baseUrl } }, dir);
|
|
532
|
+
}
|
|
533
|
+
function removeApiKey(envKey, dir) {
|
|
534
|
+
const existing = loadShellConfig(dir).apiKeys ?? {};
|
|
535
|
+
if (!(envKey in existing))
|
|
536
|
+
return;
|
|
537
|
+
const { [envKey]: _removed, ...rest } = existing;
|
|
538
|
+
saveShellConfig({ apiKeys: rest }, dir);
|
|
539
|
+
}
|
|
540
|
+
function removeProviderBaseUrl(provider, dir) {
|
|
541
|
+
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
542
|
+
if (!(provider in existing))
|
|
543
|
+
return;
|
|
544
|
+
const { [provider]: _removed, ...rest } = existing;
|
|
545
|
+
saveShellConfig({ baseUrls: rest }, dir);
|
|
546
|
+
}
|
|
547
|
+
function removeProviderModelParams(provider, dir) {
|
|
548
|
+
const existing = loadShellConfig(dir).modelParams ?? {};
|
|
549
|
+
if (!(provider in existing))
|
|
550
|
+
return;
|
|
551
|
+
const { [provider]: _removed, ...rest } = existing;
|
|
552
|
+
saveShellConfig({ modelParams: rest }, dir);
|
|
553
|
+
}
|
|
554
|
+
function envWithSavedApiKeys(env = process.env, dir) {
|
|
555
|
+
const merged = { ...env };
|
|
556
|
+
try {
|
|
557
|
+
const cfg = loadShellConfig(dir);
|
|
558
|
+
const keys = { ...cfg.apiKeys ?? {} };
|
|
559
|
+
if (typeof cfg.openrouterKey === "string" && cfg.openrouterKey.length > 0 && keys.OPENROUTER_API_KEY === undefined) {
|
|
560
|
+
keys.OPENROUTER_API_KEY = cfg.openrouterKey;
|
|
561
|
+
}
|
|
562
|
+
for (const [envKey, value] of Object.entries(keys)) {
|
|
563
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const current = merged[envKey];
|
|
567
|
+
if (current === undefined || current.length === 0) {
|
|
568
|
+
merged[envKey] = value;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const grants = cfg.oauthGrants ?? {};
|
|
572
|
+
const grokAccess = grants.grok?.access;
|
|
573
|
+
if (typeof grokAccess === "string" && grokAccess.length > 0 && (merged.XAI_API_KEY === undefined || merged.XAI_API_KEY.length === 0)) {
|
|
574
|
+
merged.XAI_API_KEY = grokAccess;
|
|
575
|
+
}
|
|
576
|
+
const copilotAccess = grants["github-copilot"]?.access;
|
|
577
|
+
if (typeof copilotAccess === "string" && copilotAccess.length > 0 && (merged.GITHUB_COPILOT_TOKEN === undefined || merged.GITHUB_COPILOT_TOKEN.length === 0)) {
|
|
578
|
+
merged.GITHUB_COPILOT_TOKEN = copilotAccess;
|
|
579
|
+
}
|
|
580
|
+
} catch {}
|
|
581
|
+
return merged;
|
|
582
|
+
}
|
|
583
|
+
function declaredCredentialEnvKeys(dir) {
|
|
584
|
+
try {
|
|
585
|
+
return new Set(Object.keys(envWithSavedApiKeys({}, dir)));
|
|
586
|
+
} catch {
|
|
587
|
+
return new Set;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function applySavedApiKeys(dir) {
|
|
591
|
+
const applied = [];
|
|
592
|
+
try {
|
|
593
|
+
const before = new Set(Object.entries(process.env).filter(([, v]) => typeof v === "string" && v.length > 0).map(([k]) => k));
|
|
594
|
+
const merged = envWithSavedApiKeys(process.env, dir);
|
|
595
|
+
for (const [envKey, value] of Object.entries(merged)) {
|
|
596
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const current = process.env[envKey];
|
|
600
|
+
if (current === undefined || current.length === 0) {
|
|
601
|
+
process.env[envKey] = value;
|
|
602
|
+
if (!before.has(envKey)) {
|
|
603
|
+
applied.push(envKey);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} catch {}
|
|
608
|
+
noteSavedCredentialEnv(applied);
|
|
609
|
+
return applied;
|
|
610
|
+
}
|
|
611
|
+
var savedCredentialKeys;
|
|
612
|
+
var init_shell_config = __esm(() => {
|
|
613
|
+
init_config_dir();
|
|
614
|
+
savedCredentialKeys = new Set;
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// src/lib/oauth/grants.ts
|
|
618
|
+
function loadOAuthGrant(provider, dir) {
|
|
619
|
+
const grants = loadShellConfig(dir).oauthGrants;
|
|
620
|
+
if (grants === undefined) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const grant = grants[provider] ?? (provider === "openai-codex" ? grants.openai : undefined);
|
|
624
|
+
if (grant === undefined || typeof grant.access !== "string" || grant.access.length === 0) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (grant.method !== "device-code" && grant.method !== "oauth-pkce-loopback") {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (provider === "openai-codex" || provider === "openai") {
|
|
631
|
+
const metadata = codexTokenMetadata(grant.access);
|
|
632
|
+
return {
|
|
633
|
+
...grant,
|
|
634
|
+
...grant.accountId === undefined && metadata.accountId !== undefined ? { accountId: metadata.accountId } : {},
|
|
635
|
+
...grant.expires === undefined && metadata.expiresAt !== undefined ? { expires: metadata.expiresAt } : {}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return grant;
|
|
639
|
+
}
|
|
640
|
+
function saveOAuthGrant(provider, grant, dir) {
|
|
641
|
+
const existing = loadShellConfig(dir).oauthGrants ?? {};
|
|
642
|
+
saveShellConfig({ oauthGrants: { ...existing, [provider]: grant } }, dir);
|
|
643
|
+
}
|
|
644
|
+
function deleteOAuthGrant(provider, dir) {
|
|
645
|
+
const existing = { ...loadShellConfig(dir).oauthGrants ?? {} };
|
|
646
|
+
if (existing[provider] === undefined && !(provider === "openai-codex" && existing.openai !== undefined)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
delete existing[provider];
|
|
650
|
+
if (provider === "openai-codex")
|
|
651
|
+
delete existing.openai;
|
|
652
|
+
saveShellConfig({ oauthGrants: existing }, dir);
|
|
653
|
+
}
|
|
654
|
+
function listOAuthGrantProviders(dir) {
|
|
655
|
+
const grants = loadShellConfig(dir).oauthGrants ?? {};
|
|
656
|
+
return [...new Set(Object.keys(grants).map((name) => name === "openai" ? "openai-codex" : name))].filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
657
|
+
}
|
|
658
|
+
function oauthGrantStatus(provider, dir, now = Date.now) {
|
|
659
|
+
const grant = loadOAuthGrant(provider, dir);
|
|
660
|
+
if (grant === undefined) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const expired = grant.expires !== undefined && grant.expires - now() <= 0;
|
|
664
|
+
return {
|
|
665
|
+
provider,
|
|
666
|
+
method: grant.method,
|
|
667
|
+
state: expired ? "expired" : "active",
|
|
668
|
+
...grant.expires !== undefined ? { expiresAt: new Date(grant.expires).toISOString() } : {},
|
|
669
|
+
refreshable: typeof grant.refresh === "string" && grant.refresh.length > 0
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function oauthAccessToken(provider, dir) {
|
|
673
|
+
const grant = loadOAuthGrant(provider, dir);
|
|
674
|
+
return grant?.access;
|
|
675
|
+
}
|
|
676
|
+
function grantNeedsRefresh(grant, now = Date.now) {
|
|
677
|
+
if (grant.refresh === undefined || grant.refresh.length === 0) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (grant.expires === undefined) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
return grant.expires - now() <= ACCESS_TOKEN_REFRESH_SKEW_MS;
|
|
684
|
+
}
|
|
685
|
+
function oauthEnvKeyFor(provider) {
|
|
686
|
+
return OAUTH_ENV_KEYS.find((entry) => entry.provider === provider)?.envKey;
|
|
687
|
+
}
|
|
688
|
+
function envWithOAuthAccess(env = process.env, dir) {
|
|
689
|
+
const merged = { ...env };
|
|
690
|
+
for (const { provider, envKey } of OAUTH_ENV_KEYS) {
|
|
691
|
+
const current = merged[envKey];
|
|
692
|
+
if (typeof current === "string" && current.length > 0) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const access = oauthAccessToken(provider, dir);
|
|
696
|
+
if (access !== undefined) {
|
|
697
|
+
merged[envKey] = access;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return merged;
|
|
701
|
+
}
|
|
702
|
+
function applyOAuthAccessToEnv(dir) {
|
|
703
|
+
const applied = [];
|
|
704
|
+
const merged = envWithOAuthAccess(process.env, dir);
|
|
705
|
+
for (const { envKey } of OAUTH_ENV_KEYS) {
|
|
706
|
+
const value = merged[envKey];
|
|
707
|
+
const current = process.env[envKey];
|
|
708
|
+
if (typeof value === "string" && value.length > 0 && (current === undefined || current.length === 0)) {
|
|
709
|
+
process.env[envKey] = value;
|
|
710
|
+
applied.push(envKey);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
noteSavedCredentialEnv(applied);
|
|
714
|
+
return applied;
|
|
715
|
+
}
|
|
716
|
+
function grantFromTokens(method, tokens, now = Date.now) {
|
|
717
|
+
const obtainedAt = new Date(now()).toISOString();
|
|
718
|
+
return {
|
|
719
|
+
method,
|
|
720
|
+
access: tokens.accessToken,
|
|
721
|
+
...tokens.refreshToken !== undefined ? { refresh: tokens.refreshToken } : {},
|
|
722
|
+
...tokens.expiresInSeconds !== undefined ? { expires: now() + tokens.expiresInSeconds * 1000 } : {},
|
|
723
|
+
...tokens.accountId !== undefined ? { accountId: tokens.accountId } : {},
|
|
724
|
+
...tokens.expiresInSeconds === undefined && tokens.expiresAt !== undefined ? { expires: tokens.expiresAt } : {},
|
|
725
|
+
obtainedAt
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
var ACCESS_TOKEN_REFRESH_SKEW_MS = 120000, OAUTH_ENV_KEYS;
|
|
729
|
+
var init_grants = __esm(() => {
|
|
730
|
+
init_shell_config();
|
|
731
|
+
OAUTH_ENV_KEYS = [
|
|
732
|
+
{ provider: "grok", envKey: "XAI_API_KEY" },
|
|
733
|
+
{ provider: "github-copilot", envKey: "GITHUB_COPILOT_TOKEN" }
|
|
734
|
+
];
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// src/lib/provider-config.ts
|
|
738
|
+
import { existsSync as existsSync2 } from "fs";
|
|
739
|
+
import path4 from "path";
|
|
471
740
|
function isValidReasoningConfig(value) {
|
|
472
741
|
if (value === undefined)
|
|
473
742
|
return true;
|
|
@@ -486,11 +755,11 @@ function isValidReasoningConfig(value) {
|
|
|
486
755
|
return true;
|
|
487
756
|
}
|
|
488
757
|
function llmProvidersConfigPath(dir) {
|
|
489
|
-
return
|
|
758
|
+
return path4.join(keryxConfigDir(dir), "llm-providers.json");
|
|
490
759
|
}
|
|
491
760
|
function readJson(file) {
|
|
492
761
|
try {
|
|
493
|
-
if (!
|
|
762
|
+
if (!existsSync2(file))
|
|
494
763
|
return;
|
|
495
764
|
const read = readConfigFile(file);
|
|
496
765
|
return read.ok ? JSON.parse(read.text) : undefined;
|
|
@@ -779,7 +1048,7 @@ import {
|
|
|
779
1048
|
} from "fs";
|
|
780
1049
|
import { access, chmod, mkdir, readFile, rename, rm, stat, utimes, writeFile } from "fs/promises";
|
|
781
1050
|
import { hostname } from "os";
|
|
782
|
-
import
|
|
1051
|
+
import path5 from "path";
|
|
783
1052
|
async function pathExists(filePath) {
|
|
784
1053
|
try {
|
|
785
1054
|
await access(filePath);
|
|
@@ -795,13 +1064,13 @@ function toPosix(value) {
|
|
|
795
1064
|
return value.replace(/\\/g, "/");
|
|
796
1065
|
}
|
|
797
1066
|
function isPathInside(root, candidate) {
|
|
798
|
-
const relative =
|
|
799
|
-
return relative === "" || relative.length > 0 && !relative.startsWith("..") && !
|
|
1067
|
+
const relative = path5.relative(path5.resolve(root), path5.resolve(candidate));
|
|
1068
|
+
return relative === "" || relative.length > 0 && !relative.startsWith("..") && !path5.isAbsolute(relative);
|
|
800
1069
|
}
|
|
801
1070
|
async function writeFileAtomic(filePath, content, options = {}) {
|
|
802
|
-
const dir =
|
|
1071
|
+
const dir = path5.dirname(filePath);
|
|
803
1072
|
await mkdir(dir, { recursive: true });
|
|
804
|
-
const tmp =
|
|
1073
|
+
const tmp = path5.join(dir, `.${path5.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID2()}.tmp`);
|
|
805
1074
|
try {
|
|
806
1075
|
if (options.mode === undefined) {
|
|
807
1076
|
await writeFile(tmp, content, "utf8");
|
|
@@ -823,8 +1092,8 @@ async function withFileLock(lockPath, fn, options = {}) {
|
|
|
823
1092
|
const heartbeatMs = options.heartbeatMs ?? Math.max(100, Math.floor(staleMs / 3));
|
|
824
1093
|
const startedAt = Date.now();
|
|
825
1094
|
const owner = { pid: process.pid, token: randomUUID2() };
|
|
826
|
-
const ownerPath =
|
|
827
|
-
await mkdir(
|
|
1095
|
+
const ownerPath = path5.join(lockPath, "owner.json");
|
|
1096
|
+
await mkdir(path5.dirname(lockPath), { recursive: true });
|
|
828
1097
|
while (true) {
|
|
829
1098
|
try {
|
|
830
1099
|
await mkdir(lockPath);
|
|
@@ -869,7 +1138,7 @@ async function isLockHeld(lockPath, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
|
869
1138
|
const stats = await stat(lockPath);
|
|
870
1139
|
if (Date.now() - stats.mtimeMs <= staleMs)
|
|
871
1140
|
return true;
|
|
872
|
-
const owner = await readLockOwner(
|
|
1141
|
+
const owner = await readLockOwner(path5.join(lockPath, "owner.json"));
|
|
873
1142
|
return owner !== undefined && processIsAlive(owner.pid);
|
|
874
1143
|
} catch {
|
|
875
1144
|
return false;
|
|
@@ -882,7 +1151,7 @@ async function isSameEmptyOrOwnerlessDir(dir, ino) {
|
|
|
882
1151
|
try {
|
|
883
1152
|
if ((await stat(dir)).ino !== ino)
|
|
884
1153
|
return false;
|
|
885
|
-
return await readLockOwner(
|
|
1154
|
+
return await readLockOwner(path5.join(dir, "owner.json")) === undefined;
|
|
886
1155
|
} catch {
|
|
887
1156
|
return false;
|
|
888
1157
|
}
|
|
@@ -892,7 +1161,7 @@ async function removeStaleLock(lockPath, staleMs, raceHook) {
|
|
|
892
1161
|
const stats = await stat(lockPath);
|
|
893
1162
|
if (Date.now() - stats.mtimeMs <= staleMs)
|
|
894
1163
|
return;
|
|
895
|
-
const owner = await readLockOwner(
|
|
1164
|
+
const owner = await readLockOwner(path5.join(lockPath, "owner.json"));
|
|
896
1165
|
if (owner && processIsAlive(owner.pid))
|
|
897
1166
|
return;
|
|
898
1167
|
await raceHook?.("stale-judged");
|
|
@@ -900,7 +1169,7 @@ async function removeStaleLock(lockPath, staleMs, raceHook) {
|
|
|
900
1169
|
await rename(lockPath, stalePath);
|
|
901
1170
|
await raceHook?.("moved-aside");
|
|
902
1171
|
const movedIno = (await stat(stalePath)).ino;
|
|
903
|
-
const moved = await readLockOwner(
|
|
1172
|
+
const moved = await readLockOwner(path5.join(stalePath, "owner.json"));
|
|
904
1173
|
if (movedIno !== stats.ino || moved?.token !== owner?.token) {
|
|
905
1174
|
await rename(stalePath, lockPath).catch(() => {});
|
|
906
1175
|
await raceHook?.("restored");
|
|
@@ -933,7 +1202,7 @@ function delay(ms) {
|
|
|
933
1202
|
}
|
|
934
1203
|
function readLeaseOwnerSync(lockPath) {
|
|
935
1204
|
try {
|
|
936
|
-
const value = JSON.parse(readFileSync2(
|
|
1205
|
+
const value = JSON.parse(readFileSync2(path5.join(lockPath, LEASE_OWNER_FILE), "utf8"));
|
|
937
1206
|
if (value === null || typeof value !== "object")
|
|
938
1207
|
return;
|
|
939
1208
|
const record = value;
|
|
@@ -975,7 +1244,7 @@ function reclaimStaleLeaseSync(lockPath, owner, opts) {
|
|
|
975
1244
|
}
|
|
976
1245
|
function acquireOrReclaim(lockPath, owner, opts, takeStale) {
|
|
977
1246
|
const now = opts.now ?? Date.now;
|
|
978
|
-
mkdirSync2(
|
|
1247
|
+
mkdirSync2(path5.dirname(lockPath), { recursive: true, mode: 448 });
|
|
979
1248
|
for (let attempt = 0;attempt < MAX_LEASE_ATTEMPTS; attempt += 1) {
|
|
980
1249
|
const createdIno = tryCreateLease(lockPath, owner, opts.raceHook);
|
|
981
1250
|
if (createdIno !== undefined) {
|
|
@@ -1013,7 +1282,7 @@ function tryCreateLease(lockPath, owner, raceHook) {
|
|
|
1013
1282
|
throw error;
|
|
1014
1283
|
}
|
|
1015
1284
|
raceHook?.("create");
|
|
1016
|
-
const ownerPath =
|
|
1285
|
+
const ownerPath = path5.join(lockPath, LEASE_OWNER_FILE);
|
|
1017
1286
|
try {
|
|
1018
1287
|
chmodSync2(lockPath, 448);
|
|
1019
1288
|
writeFileSync2(ownerPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
@@ -1063,7 +1332,7 @@ function removeLeaseDirIfUnchanged(lockPath, expectedToken) {
|
|
|
1063
1332
|
function createLeaseHandle(lockPath, initial, createdIno, now, raceHook) {
|
|
1064
1333
|
let current = { ...initial };
|
|
1065
1334
|
let released = false;
|
|
1066
|
-
const ownerPath =
|
|
1335
|
+
const ownerPath = path5.join(lockPath, LEASE_OWNER_FILE);
|
|
1067
1336
|
const ownership = () => {
|
|
1068
1337
|
let ino;
|
|
1069
1338
|
try {
|
|
@@ -1089,7 +1358,7 @@ function createLeaseHandle(lockPath, initial, createdIno, now, raceHook) {
|
|
|
1089
1358
|
return false;
|
|
1090
1359
|
raceHook?.("refresh");
|
|
1091
1360
|
const next = { ...current, ...patch, token: initial.token, heartbeatAt: new Date(now()).toISOString() };
|
|
1092
|
-
const tmp =
|
|
1361
|
+
const tmp = path5.join(lockPath, `.${LEASE_OWNER_FILE}.${randomUUID2()}.tmp`);
|
|
1093
1362
|
try {
|
|
1094
1363
|
writeFileSync2(tmp, JSON.stringify(next), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1095
1364
|
if (!holds()) {
|
|
@@ -1124,135 +1393,6 @@ function createLeaseHandle(lockPath, initial, createdIno, now, raceHook) {
|
|
|
1124
1393
|
var DEFAULT_LOCK_STALE_MS = 30000, LEASE_OWNER_FILE = "owner.json", MAX_LEASE_ATTEMPTS = 3;
|
|
1125
1394
|
var init_fs = () => {};
|
|
1126
1395
|
|
|
1127
|
-
// src/lib/shell-config.ts
|
|
1128
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1129
|
-
import path5 from "path";
|
|
1130
|
-
function noteSavedCredentialEnv(keys) {
|
|
1131
|
-
for (const key of keys)
|
|
1132
|
-
savedCredentialKeys.add(key);
|
|
1133
|
-
}
|
|
1134
|
-
function savedCredentialEnvKeys() {
|
|
1135
|
-
return savedCredentialKeys;
|
|
1136
|
-
}
|
|
1137
|
-
function shellConfigPath(dir) {
|
|
1138
|
-
return path5.join(keryxConfigDir(dir), "auth.json");
|
|
1139
|
-
}
|
|
1140
|
-
function loadShellConfig(dir) {
|
|
1141
|
-
try {
|
|
1142
|
-
const file = shellConfigPath(dir);
|
|
1143
|
-
if (!existsSync2(file)) {
|
|
1144
|
-
return {};
|
|
1145
|
-
}
|
|
1146
|
-
const read = readConfigFile(file);
|
|
1147
|
-
if (!read.ok) {
|
|
1148
|
-
return {};
|
|
1149
|
-
}
|
|
1150
|
-
const raw = JSON.parse(read.text);
|
|
1151
|
-
return raw !== null && typeof raw === "object" ? raw : {};
|
|
1152
|
-
} catch {
|
|
1153
|
-
return {};
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
function saveShellConfig(patch, dir) {
|
|
1157
|
-
try {
|
|
1158
|
-
ensureKeryxConfigDir(dir);
|
|
1159
|
-
const next = { ...loadShellConfig(dir), ...patch };
|
|
1160
|
-
writeOwnerOnlyFileAtomic(shellConfigPath(dir), `${JSON.stringify(next, null, 2)}
|
|
1161
|
-
`);
|
|
1162
|
-
} catch {}
|
|
1163
|
-
}
|
|
1164
|
-
function saveApiKey(envKey, value, dir) {
|
|
1165
|
-
const existing = loadShellConfig(dir).apiKeys ?? {};
|
|
1166
|
-
saveShellConfig({ apiKeys: { ...existing, [envKey]: value } }, dir);
|
|
1167
|
-
}
|
|
1168
|
-
function saveProviderBaseUrl(provider, baseUrl, dir) {
|
|
1169
|
-
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
1170
|
-
saveShellConfig({ baseUrls: { ...existing, [provider]: baseUrl } }, dir);
|
|
1171
|
-
}
|
|
1172
|
-
function removeApiKey(envKey, dir) {
|
|
1173
|
-
const existing = loadShellConfig(dir).apiKeys ?? {};
|
|
1174
|
-
if (!(envKey in existing))
|
|
1175
|
-
return;
|
|
1176
|
-
const { [envKey]: _removed, ...rest } = existing;
|
|
1177
|
-
saveShellConfig({ apiKeys: rest }, dir);
|
|
1178
|
-
}
|
|
1179
|
-
function removeProviderBaseUrl(provider, dir) {
|
|
1180
|
-
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
1181
|
-
if (!(provider in existing))
|
|
1182
|
-
return;
|
|
1183
|
-
const { [provider]: _removed, ...rest } = existing;
|
|
1184
|
-
saveShellConfig({ baseUrls: rest }, dir);
|
|
1185
|
-
}
|
|
1186
|
-
function removeProviderModelParams(provider, dir) {
|
|
1187
|
-
const existing = loadShellConfig(dir).modelParams ?? {};
|
|
1188
|
-
if (!(provider in existing))
|
|
1189
|
-
return;
|
|
1190
|
-
const { [provider]: _removed, ...rest } = existing;
|
|
1191
|
-
saveShellConfig({ modelParams: rest }, dir);
|
|
1192
|
-
}
|
|
1193
|
-
function envWithSavedApiKeys(env = process.env, dir) {
|
|
1194
|
-
const merged = { ...env };
|
|
1195
|
-
try {
|
|
1196
|
-
const cfg = loadShellConfig(dir);
|
|
1197
|
-
const keys = { ...cfg.apiKeys ?? {} };
|
|
1198
|
-
if (typeof cfg.openrouterKey === "string" && cfg.openrouterKey.length > 0 && keys.OPENROUTER_API_KEY === undefined) {
|
|
1199
|
-
keys.OPENROUTER_API_KEY = cfg.openrouterKey;
|
|
1200
|
-
}
|
|
1201
|
-
for (const [envKey, value] of Object.entries(keys)) {
|
|
1202
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1203
|
-
continue;
|
|
1204
|
-
}
|
|
1205
|
-
const current = merged[envKey];
|
|
1206
|
-
if (current === undefined || current.length === 0) {
|
|
1207
|
-
merged[envKey] = value;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
const grants = cfg.oauthGrants ?? {};
|
|
1211
|
-
const grokAccess = grants.grok?.access;
|
|
1212
|
-
if (typeof grokAccess === "string" && grokAccess.length > 0 && (merged.XAI_API_KEY === undefined || merged.XAI_API_KEY.length === 0)) {
|
|
1213
|
-
merged.XAI_API_KEY = grokAccess;
|
|
1214
|
-
}
|
|
1215
|
-
const copilotAccess = grants["github-copilot"]?.access;
|
|
1216
|
-
if (typeof copilotAccess === "string" && copilotAccess.length > 0 && (merged.GITHUB_COPILOT_TOKEN === undefined || merged.GITHUB_COPILOT_TOKEN.length === 0)) {
|
|
1217
|
-
merged.GITHUB_COPILOT_TOKEN = copilotAccess;
|
|
1218
|
-
}
|
|
1219
|
-
} catch {}
|
|
1220
|
-
return merged;
|
|
1221
|
-
}
|
|
1222
|
-
function declaredCredentialEnvKeys(dir) {
|
|
1223
|
-
try {
|
|
1224
|
-
return new Set(Object.keys(envWithSavedApiKeys({}, dir)));
|
|
1225
|
-
} catch {
|
|
1226
|
-
return new Set;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
function applySavedApiKeys(dir) {
|
|
1230
|
-
const applied = [];
|
|
1231
|
-
try {
|
|
1232
|
-
const before = new Set(Object.entries(process.env).filter(([, v]) => typeof v === "string" && v.length > 0).map(([k]) => k));
|
|
1233
|
-
const merged = envWithSavedApiKeys(process.env, dir);
|
|
1234
|
-
for (const [envKey, value] of Object.entries(merged)) {
|
|
1235
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
const current = process.env[envKey];
|
|
1239
|
-
if (current === undefined || current.length === 0) {
|
|
1240
|
-
process.env[envKey] = value;
|
|
1241
|
-
if (!before.has(envKey)) {
|
|
1242
|
-
applied.push(envKey);
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
} catch {}
|
|
1247
|
-
noteSavedCredentialEnv(applied);
|
|
1248
|
-
return applied;
|
|
1249
|
-
}
|
|
1250
|
-
var savedCredentialKeys;
|
|
1251
|
-
var init_shell_config = __esm(() => {
|
|
1252
|
-
init_config_dir();
|
|
1253
|
-
savedCredentialKeys = new Set;
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
1396
|
// src/gdskills/model-tier.ts
|
|
1257
1397
|
function isModelTier(value) {
|
|
1258
1398
|
return typeof value === "string" && MODEL_TIERS.includes(value);
|
|
@@ -1996,146 +2136,6 @@ var init_model_profile = __esm(() => {
|
|
|
1996
2136
|
STRENGTH_TIERS = new Set(["light", "standard", "deep"]);
|
|
1997
2137
|
});
|
|
1998
2138
|
|
|
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
|
-
|
|
2019
|
-
// src/lib/oauth/grants.ts
|
|
2020
|
-
function loadOAuthGrant(provider, dir) {
|
|
2021
|
-
const grants = loadShellConfig(dir).oauthGrants;
|
|
2022
|
-
if (grants === undefined) {
|
|
2023
|
-
return;
|
|
2024
|
-
}
|
|
2025
|
-
const grant = grants[provider] ?? (provider === "openai-codex" ? grants.openai : undefined);
|
|
2026
|
-
if (grant === undefined || typeof grant.access !== "string" || grant.access.length === 0) {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
if (grant.method !== "device-code" && grant.method !== "oauth-pkce-loopback") {
|
|
2030
|
-
return;
|
|
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
|
-
}
|
|
2040
|
-
return grant;
|
|
2041
|
-
}
|
|
2042
|
-
function saveOAuthGrant(provider, grant, dir) {
|
|
2043
|
-
const existing = loadShellConfig(dir).oauthGrants ?? {};
|
|
2044
|
-
saveShellConfig({ oauthGrants: { ...existing, [provider]: grant } }, dir);
|
|
2045
|
-
}
|
|
2046
|
-
function deleteOAuthGrant(provider, dir) {
|
|
2047
|
-
const existing = { ...loadShellConfig(dir).oauthGrants ?? {} };
|
|
2048
|
-
if (existing[provider] === undefined && !(provider === "openai-codex" && existing.openai !== undefined)) {
|
|
2049
|
-
return;
|
|
2050
|
-
}
|
|
2051
|
-
delete existing[provider];
|
|
2052
|
-
if (provider === "openai-codex")
|
|
2053
|
-
delete existing.openai;
|
|
2054
|
-
saveShellConfig({ oauthGrants: existing }, dir);
|
|
2055
|
-
}
|
|
2056
|
-
function listOAuthGrantProviders(dir) {
|
|
2057
|
-
const grants = loadShellConfig(dir).oauthGrants ?? {};
|
|
2058
|
-
return [...new Set(Object.keys(grants).map((name) => name === "openai" ? "openai-codex" : name))].filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
2059
|
-
}
|
|
2060
|
-
function oauthGrantStatus(provider, dir, now = Date.now) {
|
|
2061
|
-
const grant = loadOAuthGrant(provider, dir);
|
|
2062
|
-
if (grant === undefined) {
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
const expired = grant.expires !== undefined && grant.expires - now() <= 0;
|
|
2066
|
-
return {
|
|
2067
|
-
provider,
|
|
2068
|
-
method: grant.method,
|
|
2069
|
-
state: expired ? "expired" : "active",
|
|
2070
|
-
...grant.expires !== undefined ? { expiresAt: new Date(grant.expires).toISOString() } : {},
|
|
2071
|
-
refreshable: typeof grant.refresh === "string" && grant.refresh.length > 0
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
function oauthAccessToken(provider, dir) {
|
|
2075
|
-
const grant = loadOAuthGrant(provider, dir);
|
|
2076
|
-
return grant?.access;
|
|
2077
|
-
}
|
|
2078
|
-
function grantNeedsRefresh(grant, now = Date.now) {
|
|
2079
|
-
if (grant.refresh === undefined || grant.refresh.length === 0) {
|
|
2080
|
-
return false;
|
|
2081
|
-
}
|
|
2082
|
-
if (grant.expires === undefined) {
|
|
2083
|
-
return false;
|
|
2084
|
-
}
|
|
2085
|
-
return grant.expires - now() <= ACCESS_TOKEN_REFRESH_SKEW_MS;
|
|
2086
|
-
}
|
|
2087
|
-
function oauthEnvKeyFor(provider) {
|
|
2088
|
-
return OAUTH_ENV_KEYS.find((entry) => entry.provider === provider)?.envKey;
|
|
2089
|
-
}
|
|
2090
|
-
function envWithOAuthAccess(env = process.env, dir) {
|
|
2091
|
-
const merged = { ...env };
|
|
2092
|
-
for (const { provider, envKey } of OAUTH_ENV_KEYS) {
|
|
2093
|
-
const current = merged[envKey];
|
|
2094
|
-
if (typeof current === "string" && current.length > 0) {
|
|
2095
|
-
continue;
|
|
2096
|
-
}
|
|
2097
|
-
const access = oauthAccessToken(provider, dir);
|
|
2098
|
-
if (access !== undefined) {
|
|
2099
|
-
merged[envKey] = access;
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
return merged;
|
|
2103
|
-
}
|
|
2104
|
-
function applyOAuthAccessToEnv(dir) {
|
|
2105
|
-
const applied = [];
|
|
2106
|
-
const merged = envWithOAuthAccess(process.env, dir);
|
|
2107
|
-
for (const { envKey } of OAUTH_ENV_KEYS) {
|
|
2108
|
-
const value = merged[envKey];
|
|
2109
|
-
const current = process.env[envKey];
|
|
2110
|
-
if (typeof value === "string" && value.length > 0 && (current === undefined || current.length === 0)) {
|
|
2111
|
-
process.env[envKey] = value;
|
|
2112
|
-
applied.push(envKey);
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
noteSavedCredentialEnv(applied);
|
|
2116
|
-
return applied;
|
|
2117
|
-
}
|
|
2118
|
-
function grantFromTokens(method, tokens, now = Date.now) {
|
|
2119
|
-
const obtainedAt = new Date(now()).toISOString();
|
|
2120
|
-
return {
|
|
2121
|
-
method,
|
|
2122
|
-
access: tokens.accessToken,
|
|
2123
|
-
...tokens.refreshToken !== undefined ? { refresh: tokens.refreshToken } : {},
|
|
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 } : {},
|
|
2127
|
-
obtainedAt
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
var ACCESS_TOKEN_REFRESH_SKEW_MS = 120000, OAUTH_ENV_KEYS;
|
|
2131
|
-
var init_grants = __esm(() => {
|
|
2132
|
-
init_shell_config();
|
|
2133
|
-
OAUTH_ENV_KEYS = [
|
|
2134
|
-
{ provider: "grok", envKey: "XAI_API_KEY" },
|
|
2135
|
-
{ provider: "github-copilot", envKey: "GITHUB_COPILOT_TOKEN" }
|
|
2136
|
-
];
|
|
2137
|
-
});
|
|
2138
|
-
|
|
2139
2139
|
// src/lib/oauth/device-code.ts
|
|
2140
2140
|
function requestInit(base, signal) {
|
|
2141
2141
|
return signal === undefined ? base : { ...base, signal };
|
|
@@ -17628,7 +17628,7 @@ var package_default;
|
|
|
17628
17628
|
var init_package = __esm(() => {
|
|
17629
17629
|
package_default = {
|
|
17630
17630
|
name: "@mrciphersmith/keryx",
|
|
17631
|
-
version: "0.3.
|
|
17631
|
+
version: "0.3.13",
|
|
17632
17632
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
17633
17633
|
private: false,
|
|
17634
17634
|
publishConfig: {
|
|
@@ -31215,7 +31215,10 @@ function defaultModelFor(provider) {
|
|
|
31215
31215
|
const compat = providerByName(provider);
|
|
31216
31216
|
return compat?.models[0] ?? "unknown";
|
|
31217
31217
|
}
|
|
31218
|
-
function hasCredential(provider, env) {
|
|
31218
|
+
function hasCredential(provider, env, configDir) {
|
|
31219
|
+
if (provider === "openai-codex") {
|
|
31220
|
+
return loadOAuthGrant(provider, configDir) !== undefined;
|
|
31221
|
+
}
|
|
31219
31222
|
if (provider === "ollama") {
|
|
31220
31223
|
return true;
|
|
31221
31224
|
}
|
|
@@ -31338,6 +31341,7 @@ async function runModelTurn(input) {
|
|
|
31338
31341
|
}
|
|
31339
31342
|
var DEFAULT_PROVIDER = "anthropic", DEFAULT_MODELS;
|
|
31340
31343
|
var init_single_turn = __esm(() => {
|
|
31344
|
+
init_grants();
|
|
31341
31345
|
init_make_provider();
|
|
31342
31346
|
init_providers();
|
|
31343
31347
|
init_shell_config();
|
|
@@ -83593,6 +83597,8 @@ async function runSections(args) {
|
|
|
83593
83597
|
}
|
|
83594
83598
|
}
|
|
83595
83599
|
async function runEnrich(args) {
|
|
83600
|
+
await Promise.resolve().then(() => init_grants());
|
|
83601
|
+
applyOAuthAccessToEnv();
|
|
83596
83602
|
await Promise.resolve().then(() => init_enrich2());
|
|
83597
83603
|
const prompt = optionValue(args, "--prompt");
|
|
83598
83604
|
const provider = optionValue(args, "--provider");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|