@mrciphersmith/keryx 0.2.159 → 0.2.161
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 +1916 -1367
- package/dist/core.js +33 -20
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -320,7 +320,20 @@ function saveCustomCompatProvider(provider, dir) {
|
|
|
320
320
|
return acc;
|
|
321
321
|
}, {});
|
|
322
322
|
current[provider.name] = provider;
|
|
323
|
-
|
|
323
|
+
writeOwnerOnlyFileAtomic(llmProvidersConfigPath(dir), `${JSON.stringify({ schemaVersion: 1, providers: current }, null, 2)}
|
|
324
|
+
`);
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
function removeCustomCompatProvider(name, dir) {
|
|
328
|
+
try {
|
|
329
|
+
const current = loadCustomCompatProviders(dir).reduce((acc, p) => {
|
|
330
|
+
acc[p.name] = p;
|
|
331
|
+
return acc;
|
|
332
|
+
}, {});
|
|
333
|
+
if (!(name in current))
|
|
334
|
+
return;
|
|
335
|
+
delete current[name];
|
|
336
|
+
writeOwnerOnlyFileAtomic(llmProvidersConfigPath(dir), `${JSON.stringify({ schemaVersion: 1, providers: current }, null, 2)}
|
|
324
337
|
`);
|
|
325
338
|
} catch {}
|
|
326
339
|
}
|
|
@@ -467,7 +480,7 @@ function saveShellConfig(patch, dir) {
|
|
|
467
480
|
try {
|
|
468
481
|
ensureKeryxConfigDir(dir);
|
|
469
482
|
const next = { ...loadShellConfig(dir), ...patch };
|
|
470
|
-
|
|
483
|
+
writeOwnerOnlyFileAtomic(shellConfigPath(dir), `${JSON.stringify(next, null, 2)}
|
|
471
484
|
`);
|
|
472
485
|
} catch {}
|
|
473
486
|
}
|
|
@@ -479,6 +492,27 @@ function saveProviderBaseUrl(provider, baseUrl, dir) {
|
|
|
479
492
|
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
480
493
|
saveShellConfig({ baseUrls: { ...existing, [provider]: baseUrl } }, dir);
|
|
481
494
|
}
|
|
495
|
+
function removeApiKey(envKey, dir) {
|
|
496
|
+
const existing = loadShellConfig(dir).apiKeys ?? {};
|
|
497
|
+
if (!(envKey in existing))
|
|
498
|
+
return;
|
|
499
|
+
const { [envKey]: _removed, ...rest } = existing;
|
|
500
|
+
saveShellConfig({ apiKeys: rest }, dir);
|
|
501
|
+
}
|
|
502
|
+
function removeProviderBaseUrl(provider, dir) {
|
|
503
|
+
const existing = loadShellConfig(dir).baseUrls ?? {};
|
|
504
|
+
if (!(provider in existing))
|
|
505
|
+
return;
|
|
506
|
+
const { [provider]: _removed, ...rest } = existing;
|
|
507
|
+
saveShellConfig({ baseUrls: rest }, dir);
|
|
508
|
+
}
|
|
509
|
+
function removeProviderModelParams(provider, dir) {
|
|
510
|
+
const existing = loadShellConfig(dir).modelParams ?? {};
|
|
511
|
+
if (!(provider in existing))
|
|
512
|
+
return;
|
|
513
|
+
const { [provider]: _removed, ...rest } = existing;
|
|
514
|
+
saveShellConfig({ modelParams: rest }, dir);
|
|
515
|
+
}
|
|
482
516
|
function envWithSavedApiKeys(env = process.env, dir) {
|
|
483
517
|
const merged = { ...env };
|
|
484
518
|
try {
|
|
@@ -600,6 +634,9 @@ function grantNeedsRefresh(grant, now = Date.now) {
|
|
|
600
634
|
}
|
|
601
635
|
return grant.expires - now() <= ACCESS_TOKEN_REFRESH_SKEW_MS;
|
|
602
636
|
}
|
|
637
|
+
function oauthEnvKeyFor(provider) {
|
|
638
|
+
return OAUTH_ENV_KEYS.find((entry) => entry.provider === provider)?.envKey;
|
|
639
|
+
}
|
|
603
640
|
function envWithOAuthAccess(env = process.env, dir) {
|
|
604
641
|
const merged = { ...env };
|
|
605
642
|
for (const { provider, envKey } of OAUTH_ENV_KEYS) {
|
|
@@ -647,6 +684,498 @@ var init_grants = __esm(() => {
|
|
|
647
684
|
];
|
|
648
685
|
});
|
|
649
686
|
|
|
687
|
+
// src/lib/oauth/device-code.ts
|
|
688
|
+
function requestInit(base, signal) {
|
|
689
|
+
return signal === undefined ? base : { ...base, signal };
|
|
690
|
+
}
|
|
691
|
+
function formHeaders(extra) {
|
|
692
|
+
return {
|
|
693
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
694
|
+
Accept: "application/json",
|
|
695
|
+
...extra
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function positiveSecondsToMs(value, defaultMs) {
|
|
699
|
+
const seconds = Number(value);
|
|
700
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : defaultMs;
|
|
701
|
+
}
|
|
702
|
+
async function defaultSleep(ms) {
|
|
703
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
704
|
+
}
|
|
705
|
+
function aborted(signal) {
|
|
706
|
+
return signal?.aborted === true;
|
|
707
|
+
}
|
|
708
|
+
function readJsonError(body) {
|
|
709
|
+
if (typeof body !== "object" || body === null) {
|
|
710
|
+
return {};
|
|
711
|
+
}
|
|
712
|
+
const rec = body;
|
|
713
|
+
return {
|
|
714
|
+
...typeof rec.error === "string" ? { error: rec.error } : {},
|
|
715
|
+
...typeof rec.error_description === "string" ? { error_description: rec.error_description } : {}
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async function requestDeviceCode(request, http) {
|
|
719
|
+
if (aborted(http.signal)) {
|
|
720
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
721
|
+
}
|
|
722
|
+
const body = new URLSearchParams({
|
|
723
|
+
client_id: request.clientId,
|
|
724
|
+
scope: request.scope,
|
|
725
|
+
...request.extraForm
|
|
726
|
+
});
|
|
727
|
+
const response = await http.fetch(request.deviceAuthorizationEndpoint, requestInit({
|
|
728
|
+
method: "POST",
|
|
729
|
+
headers: formHeaders(request.headers),
|
|
730
|
+
body: body.toString()
|
|
731
|
+
}, http.signal));
|
|
732
|
+
const json = await response.json().catch(() => ({}));
|
|
733
|
+
if (!response.ok) {
|
|
734
|
+
const detail = readJsonError(json).error_description ?? `HTTP ${response.status}`;
|
|
735
|
+
throw new DeviceCodeError("failed", `device code request failed: ${detail}`);
|
|
736
|
+
}
|
|
737
|
+
if (typeof json !== "object" || json === null) {
|
|
738
|
+
throw new DeviceCodeError("failed", "device code response is not an object");
|
|
739
|
+
}
|
|
740
|
+
const rec = json;
|
|
741
|
+
const deviceCode = typeof rec.device_code === "string" ? rec.device_code : "";
|
|
742
|
+
const userCode = typeof rec.user_code === "string" ? rec.user_code : "";
|
|
743
|
+
const verificationUri = typeof rec.verification_uri === "string" ? rec.verification_uri : "";
|
|
744
|
+
if (deviceCode.length === 0 || userCode.length === 0 || verificationUri.length === 0) {
|
|
745
|
+
throw new DeviceCodeError("failed", "device code response is missing device_code / user_code / verification_uri");
|
|
746
|
+
}
|
|
747
|
+
if (![...userCode].every((c) => /[A-Za-z0-9-]/.test(c))) {
|
|
748
|
+
throw new DeviceCodeError("failed", "device code response has an invalid user_code");
|
|
749
|
+
}
|
|
750
|
+
const complete = typeof rec.verification_uri_complete === "string" && rec.verification_uri_complete.length > 0 ? rec.verification_uri_complete : undefined;
|
|
751
|
+
return {
|
|
752
|
+
deviceCode,
|
|
753
|
+
userCode,
|
|
754
|
+
verificationUri,
|
|
755
|
+
...complete !== undefined ? { verificationUriComplete: complete } : {},
|
|
756
|
+
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
757
|
+
...typeof rec.interval === "number" ? { intervalSeconds: rec.interval } : {}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async function pollDeviceCodeToken(challenge, request, http) {
|
|
761
|
+
const sleep = http.sleep ?? defaultSleep;
|
|
762
|
+
const now = http.now ?? Date.now;
|
|
763
|
+
const expiresInMs = positiveSecondsToMs(challenge.expiresInSeconds, DEFAULT_EXPIRES_MS);
|
|
764
|
+
const deadline = now() + expiresInMs;
|
|
765
|
+
let intervalMs = Math.max(positiveSecondsToMs(challenge.intervalSeconds, DEFAULT_INTERVAL_MS), MIN_INTERVAL_MS);
|
|
766
|
+
while (now() < deadline) {
|
|
767
|
+
if (aborted(http.signal)) {
|
|
768
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
769
|
+
}
|
|
770
|
+
const response = await http.fetch(request.tokenEndpoint, requestInit({
|
|
771
|
+
method: "POST",
|
|
772
|
+
headers: formHeaders(request.headers),
|
|
773
|
+
body: new URLSearchParams({
|
|
774
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
775
|
+
client_id: request.clientId,
|
|
776
|
+
device_code: challenge.deviceCode,
|
|
777
|
+
...request.extraForm
|
|
778
|
+
}).toString()
|
|
779
|
+
}, http.signal));
|
|
780
|
+
const json = await response.json().catch(() => ({}));
|
|
781
|
+
if (typeof json === "object" && json !== null && typeof json.access_token === "string") {
|
|
782
|
+
const rec = json;
|
|
783
|
+
return {
|
|
784
|
+
accessToken: rec.access_token,
|
|
785
|
+
...typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? { refreshToken: rec.refresh_token } : {},
|
|
786
|
+
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
787
|
+
...typeof rec.scope === "string" ? { scope: rec.scope } : {},
|
|
788
|
+
...typeof rec.id_token === "string" ? { idToken: rec.id_token } : {}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const { error, error_description } = readJsonError(json);
|
|
792
|
+
const remaining = Math.max(0, deadline - now());
|
|
793
|
+
if (error === "authorization_pending") {
|
|
794
|
+
await sleep(Math.min(intervalMs + POLL_SAFETY_MARGIN_MS, remaining));
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
if (error === "slow_down") {
|
|
798
|
+
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
799
|
+
await sleep(Math.min(intervalMs + POLL_SAFETY_MARGIN_MS, remaining));
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (error === "access_denied" || error === "authorization_denied") {
|
|
803
|
+
throw new DeviceCodeError("access_denied", "device authorization was denied");
|
|
804
|
+
}
|
|
805
|
+
if (error === "expired_token") {
|
|
806
|
+
throw new DeviceCodeError("expired_token", "device code expired");
|
|
807
|
+
}
|
|
808
|
+
if (!response.ok) {
|
|
809
|
+
throw new DeviceCodeError("failed", `device token exchange failed (${response.status})${error_description ? `: ${error_description}` : ""}`);
|
|
810
|
+
}
|
|
811
|
+
throw new DeviceCodeError("failed", `device token exchange failed${error_description ? `: ${error_description}` : ""}`);
|
|
812
|
+
}
|
|
813
|
+
throw new DeviceCodeError("timeout", "device authorization timed out");
|
|
814
|
+
}
|
|
815
|
+
async function refreshAccessToken(request, http) {
|
|
816
|
+
const response = await http.fetch(request.tokenEndpoint, requestInit({
|
|
817
|
+
method: "POST",
|
|
818
|
+
headers: formHeaders(request.headers),
|
|
819
|
+
body: new URLSearchParams({
|
|
820
|
+
grant_type: "refresh_token",
|
|
821
|
+
refresh_token: request.refreshToken,
|
|
822
|
+
client_id: request.clientId,
|
|
823
|
+
...request.extraForm
|
|
824
|
+
}).toString()
|
|
825
|
+
}, http.signal));
|
|
826
|
+
const json = await response.json().catch(() => ({}));
|
|
827
|
+
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
828
|
+
const detail = readJsonError(json).error_description ?? `HTTP ${response.status}`;
|
|
829
|
+
throw new DeviceCodeError("failed", `token refresh failed: ${detail}`);
|
|
830
|
+
}
|
|
831
|
+
const rec = json;
|
|
832
|
+
return {
|
|
833
|
+
accessToken: rec.access_token,
|
|
834
|
+
refreshToken: typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? rec.refresh_token : request.refreshToken,
|
|
835
|
+
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
836
|
+
...typeof rec.scope === "string" ? { scope: rec.scope } : {},
|
|
837
|
+
...typeof rec.id_token === "string" ? { idToken: rec.id_token } : {}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", DEFAULT_INTERVAL_MS = 5000, MIN_INTERVAL_MS = 1000, SLOW_DOWN_INCREMENT_MS = 5000, DEFAULT_EXPIRES_MS, POLL_SAFETY_MARGIN_MS = 3000, DeviceCodeError;
|
|
841
|
+
var init_device_code = __esm(() => {
|
|
842
|
+
DEFAULT_EXPIRES_MS = 5 * 60 * 1000;
|
|
843
|
+
DeviceCodeError = class DeviceCodeError extends Error {
|
|
844
|
+
kind;
|
|
845
|
+
constructor(kind, message) {
|
|
846
|
+
super(message);
|
|
847
|
+
this.name = "DeviceCodeError";
|
|
848
|
+
this.kind = kind;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// src/lib/oauth/copilot.ts
|
|
854
|
+
function copilotApiBaseFromExchange(raw) {
|
|
855
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
const url = new URL(raw);
|
|
860
|
+
if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const host = url.hostname;
|
|
864
|
+
if (host !== "api.githubcopilot.com" && !host.endsWith(".githubcopilot.com")) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
return `https://${host}${url.port.length > 0 ? `:${url.port}` : ""}`;
|
|
868
|
+
} catch {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function expiresInSecondsFrom(rec, now) {
|
|
873
|
+
if (typeof rec.expires_at === "string") {
|
|
874
|
+
const expires = Date.parse(rec.expires_at);
|
|
875
|
+
if (Number.isFinite(expires)) {
|
|
876
|
+
return Math.max(1, Math.floor((expires - now) / 1000));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (typeof rec.expires_at === "number" && Number.isFinite(rec.expires_at)) {
|
|
880
|
+
const expiresMs = rec.expires_at < 1000000000000 ? rec.expires_at * 1000 : rec.expires_at;
|
|
881
|
+
return Math.max(1, Math.floor((expiresMs - now) / 1000));
|
|
882
|
+
}
|
|
883
|
+
if (typeof rec.refresh_in === "number" && Number.isFinite(rec.refresh_in) && rec.refresh_in > 0) {
|
|
884
|
+
return Math.floor(rec.refresh_in);
|
|
885
|
+
}
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
function describeExchangeFailure(status, json, body) {
|
|
889
|
+
if (typeof json === "object" && json !== null) {
|
|
890
|
+
const rec = json;
|
|
891
|
+
if (rec.error_details?.copilot_access_denied === true) {
|
|
892
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): Copilot access denied \u2014 check the GitHub Copilot subscription on this account`;
|
|
893
|
+
}
|
|
894
|
+
if (typeof rec.message === "string" && rec.message.length > 0) {
|
|
895
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): ${rec.message}`;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (/zscaler|blocked because it does not comply/i.test(body)) {
|
|
899
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): request was blocked by a gateway or proxy`;
|
|
900
|
+
}
|
|
901
|
+
return `GitHub Copilot token exchange failed (HTTP ${status})`;
|
|
902
|
+
}
|
|
903
|
+
async function exchangeGithubTokenForCopilot(githubToken, http) {
|
|
904
|
+
const init = {
|
|
905
|
+
method: "GET",
|
|
906
|
+
headers: {
|
|
907
|
+
...GITHUB_COPILOT_REQUEST_HEADERS,
|
|
908
|
+
Authorization: `token ${githubToken}`
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
if (http.signal !== undefined) {
|
|
912
|
+
init.signal = http.signal;
|
|
913
|
+
}
|
|
914
|
+
const response = await http.fetch(COPILOT_TOKEN_URL, init);
|
|
915
|
+
const body = await response.text().catch(() => "");
|
|
916
|
+
let json = {};
|
|
917
|
+
if (body.length > 0) {
|
|
918
|
+
try {
|
|
919
|
+
json = JSON.parse(body);
|
|
920
|
+
} catch {
|
|
921
|
+
json = {};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (!response.ok || typeof json !== "object" || json === null) {
|
|
925
|
+
throw new DeviceCodeError("failed", describeExchangeFailure(response.status, json, body));
|
|
926
|
+
}
|
|
927
|
+
const rec = json;
|
|
928
|
+
if (typeof rec.token !== "string" || rec.token.length === 0) {
|
|
929
|
+
throw new DeviceCodeError("failed", "GitHub Copilot token exchange returned no token");
|
|
930
|
+
}
|
|
931
|
+
const expiresInSeconds = expiresInSecondsFrom(rec, Date.now());
|
|
932
|
+
const apiBaseUrl = copilotApiBaseFromExchange(rec.endpoints?.api);
|
|
933
|
+
return {
|
|
934
|
+
accessToken: rec.token,
|
|
935
|
+
...expiresInSeconds !== undefined ? { expiresInSeconds } : {},
|
|
936
|
+
...apiBaseUrl !== undefined ? { apiBaseUrl } : {}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
940
|
+
var init_copilot = __esm(() => {
|
|
941
|
+
init_catalog();
|
|
942
|
+
init_device_code();
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// src/lib/oauth/openai-codex.ts
|
|
946
|
+
function withSignal(base, signal) {
|
|
947
|
+
return signal === undefined ? base : { ...base, signal };
|
|
948
|
+
}
|
|
949
|
+
async function requestCodexDeviceCode(http) {
|
|
950
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
951
|
+
method: "POST",
|
|
952
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
953
|
+
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
954
|
+
}, http.signal));
|
|
955
|
+
const json = await response.json().catch(() => ({}));
|
|
956
|
+
if (!response.ok || typeof json !== "object" || json === null) {
|
|
957
|
+
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
958
|
+
}
|
|
959
|
+
const rec = json;
|
|
960
|
+
if (typeof rec.device_auth_id !== "string" || typeof rec.user_code !== "string") {
|
|
961
|
+
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
962
|
+
}
|
|
963
|
+
const intervalSeconds = Number(rec.interval);
|
|
964
|
+
return {
|
|
965
|
+
deviceAuthId: rec.device_auth_id,
|
|
966
|
+
userCode: rec.user_code,
|
|
967
|
+
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
968
|
+
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
async function pollCodexDeviceToken(challenge, http) {
|
|
972
|
+
const sleep = http.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
973
|
+
const now = http.now ?? Date.now;
|
|
974
|
+
const deadline = now() + 5 * 60 * 1000;
|
|
975
|
+
while (now() < deadline) {
|
|
976
|
+
if (http.signal?.aborted === true) {
|
|
977
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
978
|
+
}
|
|
979
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
980
|
+
method: "POST",
|
|
981
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
982
|
+
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
983
|
+
}, http.signal));
|
|
984
|
+
if (response.ok) {
|
|
985
|
+
const data = await response.json();
|
|
986
|
+
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
987
|
+
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
988
|
+
}
|
|
989
|
+
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
990
|
+
}
|
|
991
|
+
if (response.status !== 403 && response.status !== 404) {
|
|
992
|
+
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
993
|
+
}
|
|
994
|
+
await sleep(challenge.intervalMs + POLL_SAFETY_MARGIN_MS2);
|
|
995
|
+
}
|
|
996
|
+
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
997
|
+
}
|
|
998
|
+
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
999
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
1000
|
+
method: "POST",
|
|
1001
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
1002
|
+
body: new URLSearchParams({
|
|
1003
|
+
grant_type: "authorization_code",
|
|
1004
|
+
code,
|
|
1005
|
+
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
1006
|
+
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
1007
|
+
code_verifier: codeVerifier
|
|
1008
|
+
}).toString()
|
|
1009
|
+
}, http.signal));
|
|
1010
|
+
const json = await response.json().catch(() => ({}));
|
|
1011
|
+
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
1012
|
+
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
1013
|
+
}
|
|
1014
|
+
const rec = json;
|
|
1015
|
+
return {
|
|
1016
|
+
accessToken: rec.access_token,
|
|
1017
|
+
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
1018
|
+
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {}
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", POLL_SAFETY_MARGIN_MS2 = 3000;
|
|
1022
|
+
var init_openai_codex = __esm(() => {
|
|
1023
|
+
init_device_code();
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// src/lib/oauth/login.ts
|
|
1027
|
+
function httpOf(input) {
|
|
1028
|
+
return {
|
|
1029
|
+
fetch: input.fetch,
|
|
1030
|
+
...input.sleep !== undefined ? { sleep: input.sleep } : {},
|
|
1031
|
+
...input.now !== undefined ? { now: input.now } : {},
|
|
1032
|
+
...input.signal !== undefined ? { signal: input.signal } : {}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function present(challenge, provider) {
|
|
1036
|
+
return {
|
|
1037
|
+
userCode: challenge.userCode,
|
|
1038
|
+
verificationUri: challenge.verificationUri,
|
|
1039
|
+
...challenge.verificationUriComplete !== undefined ? { verificationUriComplete: challenge.verificationUriComplete } : {},
|
|
1040
|
+
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)})`
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
async function loginRfc8628(input) {
|
|
1044
|
+
const endpoints = input.provider === "github-copilot" ? GITHUB_COPILOT_DEVICE : XAI_GROK_DEVICE;
|
|
1045
|
+
const http = httpOf(input);
|
|
1046
|
+
const challenge = await requestDeviceCode({
|
|
1047
|
+
deviceAuthorizationEndpoint: endpoints.deviceAuthorizationEndpoint,
|
|
1048
|
+
clientId: endpoints.clientId,
|
|
1049
|
+
scope: endpoints.scope,
|
|
1050
|
+
...endpoints.extraForm !== undefined ? { extraForm: endpoints.extraForm } : {},
|
|
1051
|
+
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
1052
|
+
}, http);
|
|
1053
|
+
input.onChallenge(present(challenge, input.provider));
|
|
1054
|
+
const tokens = await pollDeviceCodeToken(challenge, {
|
|
1055
|
+
tokenEndpoint: endpoints.tokenEndpoint,
|
|
1056
|
+
clientId: endpoints.clientId,
|
|
1057
|
+
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
1058
|
+
}, http);
|
|
1059
|
+
if (endpoints.afterToken === "github-copilot") {
|
|
1060
|
+
const copilot = await exchangeGithubTokenForCopilot(tokens.accessToken, http);
|
|
1061
|
+
if (copilot.apiBaseUrl !== undefined) {
|
|
1062
|
+
saveProviderBaseUrl("github-copilot", copilot.apiBaseUrl, input.dir);
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
accessToken: copilot.accessToken,
|
|
1066
|
+
refreshToken: tokens.accessToken,
|
|
1067
|
+
...copilot.expiresInSeconds !== undefined ? { expiresInSeconds: copilot.expiresInSeconds } : {}
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
return tokens;
|
|
1071
|
+
}
|
|
1072
|
+
async function loginDeviceCode(input) {
|
|
1073
|
+
const entry = authCatalogEntry(input.provider);
|
|
1074
|
+
if (entry === undefined) {
|
|
1075
|
+
return { ok: false, error: `unknown provider: ${input.provider}` };
|
|
1076
|
+
}
|
|
1077
|
+
if (!entry.methods.includes("device-code")) {
|
|
1078
|
+
const refusal = catalogRefusal(input.provider);
|
|
1079
|
+
if (refusal !== undefined) {
|
|
1080
|
+
return { ok: false, error: refusal, refused: true };
|
|
1081
|
+
}
|
|
1082
|
+
return { ok: false, error: `${input.provider} does not support device authorization` };
|
|
1083
|
+
}
|
|
1084
|
+
try {
|
|
1085
|
+
const tokens = input.provider === "openai" ? await loginCodex(input) : await loginRfc8628(input);
|
|
1086
|
+
saveOAuthGrant(input.provider, grantFromTokens("device-code", tokens, input.now), input.dir);
|
|
1087
|
+
return { ok: true, provider: input.provider };
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
const message = err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
1090
|
+
return { ok: false, error: message };
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
async function loginCodex(input) {
|
|
1094
|
+
const http = httpOf(input);
|
|
1095
|
+
const challenge = await requestCodexDeviceCode(http);
|
|
1096
|
+
input.onChallenge(present({
|
|
1097
|
+
userCode: challenge.userCode,
|
|
1098
|
+
verificationUri: challenge.verificationUri
|
|
1099
|
+
}, "openai"));
|
|
1100
|
+
return pollCodexDeviceToken(challenge, http);
|
|
1101
|
+
}
|
|
1102
|
+
async function refreshProviderGrant(provider, http, dir) {
|
|
1103
|
+
const grant = loadOAuthGrant(provider, dir);
|
|
1104
|
+
if (grant === undefined || !grantNeedsRefresh(grant, http.now)) {
|
|
1105
|
+
return grant;
|
|
1106
|
+
}
|
|
1107
|
+
if (grant.refresh === undefined) {
|
|
1108
|
+
return grant;
|
|
1109
|
+
}
|
|
1110
|
+
const endpoints = provider === "github-copilot" ? GITHUB_COPILOT_DEVICE : XAI_GROK_DEVICE;
|
|
1111
|
+
if (provider === "github-copilot") {
|
|
1112
|
+
const copilot = await exchangeGithubTokenForCopilot(grant.refresh, http);
|
|
1113
|
+
if (copilot.apiBaseUrl !== undefined) {
|
|
1114
|
+
saveProviderBaseUrl("github-copilot", copilot.apiBaseUrl, dir);
|
|
1115
|
+
}
|
|
1116
|
+
const next = grantFromTokens("device-code", {
|
|
1117
|
+
accessToken: copilot.accessToken,
|
|
1118
|
+
refreshToken: grant.refresh,
|
|
1119
|
+
...copilot.expiresInSeconds !== undefined ? { expiresInSeconds: copilot.expiresInSeconds } : {}
|
|
1120
|
+
}, http.now);
|
|
1121
|
+
next.lastRefreshedAt = new Date((http.now ?? Date.now)()).toISOString();
|
|
1122
|
+
saveOAuthGrant(provider, next, dir);
|
|
1123
|
+
return next;
|
|
1124
|
+
}
|
|
1125
|
+
if (provider === "openai") {
|
|
1126
|
+
return grant;
|
|
1127
|
+
}
|
|
1128
|
+
const tokens = await refreshAccessToken({
|
|
1129
|
+
tokenEndpoint: endpoints.tokenEndpoint,
|
|
1130
|
+
clientId: endpoints.clientId,
|
|
1131
|
+
refreshToken: grant.refresh,
|
|
1132
|
+
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
1133
|
+
}, http);
|
|
1134
|
+
const next = grantFromTokens("device-code", tokens, http.now);
|
|
1135
|
+
next.lastRefreshedAt = new Date((http.now ?? Date.now)()).toISOString();
|
|
1136
|
+
saveOAuthGrant(provider, next, dir);
|
|
1137
|
+
return next;
|
|
1138
|
+
}
|
|
1139
|
+
async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANTS) {
|
|
1140
|
+
const warnings = [];
|
|
1141
|
+
const now = http.now ?? Date.now;
|
|
1142
|
+
for (const provider of providers) {
|
|
1143
|
+
if (!SESSION_REFRESHED_GRANTS.includes(provider))
|
|
1144
|
+
continue;
|
|
1145
|
+
const grant = loadOAuthGrant(provider, dir);
|
|
1146
|
+
if (grant === undefined)
|
|
1147
|
+
continue;
|
|
1148
|
+
if (grant.refresh === undefined || grant.refresh.length === 0) {
|
|
1149
|
+
if (grant.expires !== undefined && grant.expires <= now()) {
|
|
1150
|
+
warnings.push(`${provider}: the saved login has expired and holds no refresh token. Run \`keryx auth login ${provider}\`.`);
|
|
1151
|
+
}
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (!grantNeedsRefresh(grant, http.now))
|
|
1155
|
+
continue;
|
|
1156
|
+
try {
|
|
1157
|
+
await refreshProviderGrant(provider, http, dir);
|
|
1158
|
+
} catch (cause) {
|
|
1159
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
1160
|
+
warnings.push(`${provider}: the saved login is expiring or expired and could not be refreshed (${reason}). ` + `Run \`keryx auth login ${provider}\`.`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return warnings;
|
|
1164
|
+
}
|
|
1165
|
+
function logoutProvider(provider, dir) {
|
|
1166
|
+
deleteOAuthGrant(provider, dir);
|
|
1167
|
+
}
|
|
1168
|
+
var SESSION_REFRESHED_GRANTS;
|
|
1169
|
+
var init_login = __esm(() => {
|
|
1170
|
+
init_shell_config();
|
|
1171
|
+
init_catalog();
|
|
1172
|
+
init_copilot();
|
|
1173
|
+
init_device_code();
|
|
1174
|
+
init_grants();
|
|
1175
|
+
init_openai_codex();
|
|
1176
|
+
SESSION_REFRESHED_GRANTS = ["grok", "github-copilot"];
|
|
1177
|
+
});
|
|
1178
|
+
|
|
650
1179
|
// src/lib/caller-session.ts
|
|
651
1180
|
function resolveCallerSession(input = {}) {
|
|
652
1181
|
const pair = (provider, model, source) => ({
|
|
@@ -717,6 +1246,827 @@ function shortFlag(flag) {
|
|
|
717
1246
|
}
|
|
718
1247
|
var init_args = () => {};
|
|
719
1248
|
|
|
1249
|
+
// src/lib/md-blocks.ts
|
|
1250
|
+
function stripTrailingCr(line) {
|
|
1251
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1252
|
+
}
|
|
1253
|
+
function splitLines(text) {
|
|
1254
|
+
return text.split(`
|
|
1255
|
+
`).map(stripTrailingCr);
|
|
1256
|
+
}
|
|
1257
|
+
function markdownTableCells(line) {
|
|
1258
|
+
const source = line.trim();
|
|
1259
|
+
if (!source.includes("|")) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const cells = [];
|
|
1263
|
+
let cell = "";
|
|
1264
|
+
let inCode = false;
|
|
1265
|
+
for (let index = 0;index < source.length; index += 1) {
|
|
1266
|
+
const char = source[index] ?? "";
|
|
1267
|
+
if (char === "\\" && source[index + 1] === "|") {
|
|
1268
|
+
cell += "|";
|
|
1269
|
+
index += 1;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (char === "`") {
|
|
1273
|
+
inCode = !inCode;
|
|
1274
|
+
cell += char;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
if (char === "|" && !inCode) {
|
|
1278
|
+
cells.push(cell.trim());
|
|
1279
|
+
cell = "";
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
cell += char;
|
|
1283
|
+
}
|
|
1284
|
+
cells.push(cell.trim());
|
|
1285
|
+
if (source.startsWith("|")) {
|
|
1286
|
+
cells.shift();
|
|
1287
|
+
}
|
|
1288
|
+
if (source.endsWith("|")) {
|
|
1289
|
+
cells.pop();
|
|
1290
|
+
}
|
|
1291
|
+
return cells.length >= 2 ? cells : undefined;
|
|
1292
|
+
}
|
|
1293
|
+
function tableAlignments(line, columns) {
|
|
1294
|
+
const cells = markdownTableCells(line);
|
|
1295
|
+
if (cells === undefined || cells.length !== columns) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const alignments = [];
|
|
1299
|
+
for (const cell of cells) {
|
|
1300
|
+
const marker = cell.trim();
|
|
1301
|
+
if (!/^:?-{3,}:?$/.test(marker)) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
alignments.push(marker.startsWith(":") && marker.endsWith(":") ? "center" : marker.endsWith(":") ? "right" : "left");
|
|
1305
|
+
}
|
|
1306
|
+
return alignments;
|
|
1307
|
+
}
|
|
1308
|
+
function parseMarkdownTables(text) {
|
|
1309
|
+
if (text.length === 0) {
|
|
1310
|
+
return [];
|
|
1311
|
+
}
|
|
1312
|
+
const lines = splitLines(text);
|
|
1313
|
+
const parts = [];
|
|
1314
|
+
let proseStart = 0;
|
|
1315
|
+
const flushProse = (end) => {
|
|
1316
|
+
if (end <= proseStart) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const prose = lines.slice(proseStart, end).join(`
|
|
1320
|
+
`);
|
|
1321
|
+
if (prose.length > 0) {
|
|
1322
|
+
parts.push({ kind: "prose", text: prose });
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
for (let index = 0;index + 1 < lines.length; index += 1) {
|
|
1326
|
+
const headers = markdownTableCells(lines[index] ?? "");
|
|
1327
|
+
if (headers === undefined) {
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
const alignments = tableAlignments(lines[index + 1] ?? "", headers.length);
|
|
1331
|
+
if (alignments === undefined) {
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
flushProse(index);
|
|
1335
|
+
const rows = [];
|
|
1336
|
+
let cursor = index + 2;
|
|
1337
|
+
while (cursor < lines.length) {
|
|
1338
|
+
const row = markdownTableCells(lines[cursor] ?? "");
|
|
1339
|
+
if (row === undefined || row.length !== headers.length) {
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
rows.push(row);
|
|
1343
|
+
cursor += 1;
|
|
1344
|
+
}
|
|
1345
|
+
parts.push({ kind: "table", table: { headers, alignments, rows } });
|
|
1346
|
+
proseStart = cursor;
|
|
1347
|
+
index = cursor - 1;
|
|
1348
|
+
}
|
|
1349
|
+
flushProse(lines.length);
|
|
1350
|
+
return parts.length > 0 ? parts : [{ kind: "prose", text }];
|
|
1351
|
+
}
|
|
1352
|
+
function fenceInfo(line) {
|
|
1353
|
+
const match = FENCE_LINE.exec(line);
|
|
1354
|
+
if (match === null) {
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
return { marker: match[1] ?? "", lang: (match[2] ?? "").trim().split(/\s+/)[0] ?? "" };
|
|
1358
|
+
}
|
|
1359
|
+
function segmentMarkdown(md) {
|
|
1360
|
+
const segments = [];
|
|
1361
|
+
let text = [];
|
|
1362
|
+
let code;
|
|
1363
|
+
let lang = "";
|
|
1364
|
+
let marker = "";
|
|
1365
|
+
const flushText = () => {
|
|
1366
|
+
const joined = text.join(`
|
|
1367
|
+
`);
|
|
1368
|
+
text = [];
|
|
1369
|
+
if (joined.length > 0) {
|
|
1370
|
+
segments.push({ kind: "text", text: joined });
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
for (const line of splitLines(md)) {
|
|
1374
|
+
const fence = fenceInfo(line);
|
|
1375
|
+
if (code === undefined) {
|
|
1376
|
+
if (fence === undefined) {
|
|
1377
|
+
text.push(line);
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
flushText();
|
|
1381
|
+
marker = fence.marker;
|
|
1382
|
+
lang = fence.lang;
|
|
1383
|
+
code = [];
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (fence?.marker === marker) {
|
|
1387
|
+
segments.push({ kind: "code", lang, body: code.join(`
|
|
1388
|
+
`) });
|
|
1389
|
+
code = undefined;
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
code.push(line);
|
|
1393
|
+
}
|
|
1394
|
+
if (code === undefined) {
|
|
1395
|
+
flushText();
|
|
1396
|
+
} else {
|
|
1397
|
+
segments.push({ kind: "code", lang, body: code.join(`
|
|
1398
|
+
`) });
|
|
1399
|
+
}
|
|
1400
|
+
return segments;
|
|
1401
|
+
}
|
|
1402
|
+
function classifyDiffLine(line) {
|
|
1403
|
+
if (line.startsWith("@@")) {
|
|
1404
|
+
return "hunk";
|
|
1405
|
+
}
|
|
1406
|
+
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
1407
|
+
return "meta";
|
|
1408
|
+
}
|
|
1409
|
+
if (line.startsWith("+")) {
|
|
1410
|
+
return "add";
|
|
1411
|
+
}
|
|
1412
|
+
if (line.startsWith("-")) {
|
|
1413
|
+
return "del";
|
|
1414
|
+
}
|
|
1415
|
+
return "context";
|
|
1416
|
+
}
|
|
1417
|
+
function looksLikeUnifiedDiff(text) {
|
|
1418
|
+
const lines = splitLines(text);
|
|
1419
|
+
for (const [index, line] of lines.entries()) {
|
|
1420
|
+
if (HUNK_HEADER.test(line)) {
|
|
1421
|
+
return true;
|
|
1422
|
+
}
|
|
1423
|
+
if (line.startsWith("--- ") && (lines[index + 1]?.startsWith("+++ ") ?? false)) {
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
function codeCommentPrefix(normalizedLang) {
|
|
1430
|
+
switch (normalizedLang) {
|
|
1431
|
+
case "javascript":
|
|
1432
|
+
case "typescript":
|
|
1433
|
+
case "go":
|
|
1434
|
+
case "rust":
|
|
1435
|
+
case "java":
|
|
1436
|
+
case "c":
|
|
1437
|
+
case "cpp":
|
|
1438
|
+
case "csharp":
|
|
1439
|
+
case "swift":
|
|
1440
|
+
case "kotlin":
|
|
1441
|
+
case "scala":
|
|
1442
|
+
case "php":
|
|
1443
|
+
return "//";
|
|
1444
|
+
case "python":
|
|
1445
|
+
case "bash":
|
|
1446
|
+
case "ruby":
|
|
1447
|
+
case "yaml":
|
|
1448
|
+
case "toml":
|
|
1449
|
+
case "r":
|
|
1450
|
+
case "perl":
|
|
1451
|
+
return "#";
|
|
1452
|
+
case "sql":
|
|
1453
|
+
case "lua":
|
|
1454
|
+
case "haskell":
|
|
1455
|
+
return "--";
|
|
1456
|
+
default:
|
|
1457
|
+
return "";
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function normalizeCodeLang(lang) {
|
|
1461
|
+
const key = lang.trim().toLowerCase();
|
|
1462
|
+
return CODE_LANG_ALIASES[key] ?? key;
|
|
1463
|
+
}
|
|
1464
|
+
function tokenizeCodeWords(text, keywords) {
|
|
1465
|
+
const tokens = [];
|
|
1466
|
+
let last = 0;
|
|
1467
|
+
CODE_WORD_OR_NUMBER.lastIndex = 0;
|
|
1468
|
+
let m;
|
|
1469
|
+
while ((m = CODE_WORD_OR_NUMBER.exec(text)) !== null) {
|
|
1470
|
+
if (m.index > last) {
|
|
1471
|
+
tokens.push({ kind: "plain", text: text.slice(last, m.index) });
|
|
1472
|
+
}
|
|
1473
|
+
const word = m[0];
|
|
1474
|
+
if (/^[0-9]/.test(word)) {
|
|
1475
|
+
tokens.push({ kind: "number", text: word });
|
|
1476
|
+
} else if (keywords.has(word)) {
|
|
1477
|
+
tokens.push({ kind: "keyword", text: word });
|
|
1478
|
+
} else {
|
|
1479
|
+
tokens.push({ kind: "plain", text: word });
|
|
1480
|
+
}
|
|
1481
|
+
last = m.index + word.length;
|
|
1482
|
+
}
|
|
1483
|
+
if (last < text.length) {
|
|
1484
|
+
tokens.push({ kind: "plain", text: text.slice(last) });
|
|
1485
|
+
}
|
|
1486
|
+
return tokens;
|
|
1487
|
+
}
|
|
1488
|
+
function tokenizeCodeLine(line, lang) {
|
|
1489
|
+
const normalized = normalizeCodeLang(lang);
|
|
1490
|
+
const keywords = CODE_LANG_KEYWORDS[normalized] ?? GENERIC_KEYWORDS;
|
|
1491
|
+
const commentPrefix = codeCommentPrefix(normalized);
|
|
1492
|
+
const tokens = [];
|
|
1493
|
+
let plainBuf = "";
|
|
1494
|
+
const flushPlain = () => {
|
|
1495
|
+
if (plainBuf.length > 0) {
|
|
1496
|
+
tokens.push(...tokenizeCodeWords(plainBuf, keywords));
|
|
1497
|
+
plainBuf = "";
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
let i = 0;
|
|
1501
|
+
while (i < line.length) {
|
|
1502
|
+
if (commentPrefix.length > 0 && line.startsWith(commentPrefix, i)) {
|
|
1503
|
+
flushPlain();
|
|
1504
|
+
tokens.push({ kind: "comment", text: line.slice(i) });
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
const ch = line[i];
|
|
1508
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1509
|
+
flushPlain();
|
|
1510
|
+
let j = i + 1;
|
|
1511
|
+
while (j < line.length && line[j] !== ch) {
|
|
1512
|
+
j += line[j] === "\\" ? 2 : 1;
|
|
1513
|
+
}
|
|
1514
|
+
j = Math.min(j + 1, line.length);
|
|
1515
|
+
tokens.push({ kind: "string", text: line.slice(i, j) });
|
|
1516
|
+
i = j;
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
plainBuf += ch;
|
|
1520
|
+
i += 1;
|
|
1521
|
+
}
|
|
1522
|
+
flushPlain();
|
|
1523
|
+
return tokens;
|
|
1524
|
+
}
|
|
1525
|
+
function payloadKind(lang, _lineCount) {
|
|
1526
|
+
const normalized = lang.toLowerCase();
|
|
1527
|
+
if (MARKDOWN_LANGS.has(normalized)) {
|
|
1528
|
+
return "markdown";
|
|
1529
|
+
}
|
|
1530
|
+
if (DIFF_LANGS.has(normalized)) {
|
|
1531
|
+
return "diff";
|
|
1532
|
+
}
|
|
1533
|
+
return "code";
|
|
1534
|
+
}
|
|
1535
|
+
function blockLabel({ kind, lineCount, collapsed, hint }) {
|
|
1536
|
+
const marker = collapsed ? "\u25B8" : "\u25BE";
|
|
1537
|
+
const unit = lineCount === 1 ? "line" : "lines";
|
|
1538
|
+
const suffix = hint !== undefined && hint.length > 0 ? ` \xB7 ${hint}` : "";
|
|
1539
|
+
return `${marker} ${kind} (${lineCount} ${unit})${suffix}`;
|
|
1540
|
+
}
|
|
1541
|
+
function summarizeSubmittedLine(line) {
|
|
1542
|
+
const normalized = line.replace(/\r\n/g, `
|
|
1543
|
+
`).replace(/\r/g, `
|
|
1544
|
+
`);
|
|
1545
|
+
const nonEmpty = normalized.split(`
|
|
1546
|
+
`).filter((linePart) => linePart.length > 0);
|
|
1547
|
+
if (nonEmpty.length <= 1) {
|
|
1548
|
+
return line;
|
|
1549
|
+
}
|
|
1550
|
+
const [first, ...rest] = nonEmpty;
|
|
1551
|
+
return `${first} [+ ${rest.length} pasted line${rest.length === 1 ? "" : "s"}]`;
|
|
1552
|
+
}
|
|
1553
|
+
function isZeroWidth(cp) {
|
|
1554
|
+
return cp >= 768 && cp <= 879 || cp >= 8203 && cp <= 8207 || cp >= 65024 && cp <= 65039 || cp >= 8288 && cp <= 8292;
|
|
1555
|
+
}
|
|
1556
|
+
function codePointWidth(cp) {
|
|
1557
|
+
for (const [lo, hi] of WIDE_RANGES) {
|
|
1558
|
+
if (cp >= lo && cp <= hi) {
|
|
1559
|
+
return 2;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return 1;
|
|
1563
|
+
}
|
|
1564
|
+
function visualWidth(text) {
|
|
1565
|
+
let width = 0;
|
|
1566
|
+
for (const ch of text) {
|
|
1567
|
+
const codePoint = ch.codePointAt(0) ?? 0;
|
|
1568
|
+
if (isZeroWidth(codePoint)) {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
width += codePointWidth(codePoint);
|
|
1572
|
+
}
|
|
1573
|
+
return width;
|
|
1574
|
+
}
|
|
1575
|
+
function hugWidth(text, chrome) {
|
|
1576
|
+
const lines = splitLines(text.replace(/\n+$/, ""));
|
|
1577
|
+
let widest = 0;
|
|
1578
|
+
for (const line of lines) {
|
|
1579
|
+
widest = Math.max(widest, visualWidth(line));
|
|
1580
|
+
}
|
|
1581
|
+
return widest + chrome;
|
|
1582
|
+
}
|
|
1583
|
+
var FENCE_LINE, HUNK_HEADER, MARKDOWN_LANGS, DIFF_LANGS, CODE_LANG_ALIASES, GENERIC_KEYWORDS, CODE_LANG_KEYWORDS, CODE_WORD_OR_NUMBER, WIDE_RANGES;
|
|
1584
|
+
var init_md_blocks = __esm(() => {
|
|
1585
|
+
FENCE_LINE = /^[ \t]{0,3}(```|~~~)(.*)\r?$/;
|
|
1586
|
+
HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
1587
|
+
MARKDOWN_LANGS = new Set(["md", "markdown", "prompt"]);
|
|
1588
|
+
DIFF_LANGS = new Set(["diff", "patch"]);
|
|
1589
|
+
CODE_LANG_ALIASES = {
|
|
1590
|
+
js: "javascript",
|
|
1591
|
+
mjs: "javascript",
|
|
1592
|
+
cjs: "javascript",
|
|
1593
|
+
jsx: "javascript",
|
|
1594
|
+
ts: "typescript",
|
|
1595
|
+
tsx: "typescript",
|
|
1596
|
+
py: "python",
|
|
1597
|
+
py3: "python",
|
|
1598
|
+
sh: "bash",
|
|
1599
|
+
shell: "bash",
|
|
1600
|
+
zsh: "bash",
|
|
1601
|
+
console: "bash",
|
|
1602
|
+
yml: "yaml",
|
|
1603
|
+
rb: "ruby",
|
|
1604
|
+
rs: "rust",
|
|
1605
|
+
kt: "kotlin",
|
|
1606
|
+
cs: "csharp",
|
|
1607
|
+
"c++": "cpp",
|
|
1608
|
+
cc: "cpp",
|
|
1609
|
+
cxx: "cpp",
|
|
1610
|
+
hpp: "cpp"
|
|
1611
|
+
};
|
|
1612
|
+
GENERIC_KEYWORDS = new Set([
|
|
1613
|
+
"if",
|
|
1614
|
+
"else",
|
|
1615
|
+
"for",
|
|
1616
|
+
"while",
|
|
1617
|
+
"return",
|
|
1618
|
+
"function",
|
|
1619
|
+
"class",
|
|
1620
|
+
"const",
|
|
1621
|
+
"let",
|
|
1622
|
+
"var",
|
|
1623
|
+
"import",
|
|
1624
|
+
"export",
|
|
1625
|
+
"from",
|
|
1626
|
+
"true",
|
|
1627
|
+
"false",
|
|
1628
|
+
"null",
|
|
1629
|
+
"new",
|
|
1630
|
+
"this",
|
|
1631
|
+
"try",
|
|
1632
|
+
"catch",
|
|
1633
|
+
"throw",
|
|
1634
|
+
"switch",
|
|
1635
|
+
"case",
|
|
1636
|
+
"break",
|
|
1637
|
+
"continue",
|
|
1638
|
+
"default",
|
|
1639
|
+
"static",
|
|
1640
|
+
"public",
|
|
1641
|
+
"private"
|
|
1642
|
+
]);
|
|
1643
|
+
CODE_LANG_KEYWORDS = {
|
|
1644
|
+
javascript: new Set([
|
|
1645
|
+
...GENERIC_KEYWORDS,
|
|
1646
|
+
"async",
|
|
1647
|
+
"await",
|
|
1648
|
+
"yield",
|
|
1649
|
+
"typeof",
|
|
1650
|
+
"instanceof",
|
|
1651
|
+
"in",
|
|
1652
|
+
"of",
|
|
1653
|
+
"void",
|
|
1654
|
+
"undefined",
|
|
1655
|
+
"super",
|
|
1656
|
+
"extends",
|
|
1657
|
+
"finally",
|
|
1658
|
+
"do",
|
|
1659
|
+
"delete"
|
|
1660
|
+
]),
|
|
1661
|
+
typescript: new Set([
|
|
1662
|
+
...GENERIC_KEYWORDS,
|
|
1663
|
+
"async",
|
|
1664
|
+
"await",
|
|
1665
|
+
"yield",
|
|
1666
|
+
"typeof",
|
|
1667
|
+
"instanceof",
|
|
1668
|
+
"in",
|
|
1669
|
+
"of",
|
|
1670
|
+
"void",
|
|
1671
|
+
"undefined",
|
|
1672
|
+
"super",
|
|
1673
|
+
"extends",
|
|
1674
|
+
"implements",
|
|
1675
|
+
"interface",
|
|
1676
|
+
"type",
|
|
1677
|
+
"enum",
|
|
1678
|
+
"namespace",
|
|
1679
|
+
"declare",
|
|
1680
|
+
"readonly",
|
|
1681
|
+
"abstract",
|
|
1682
|
+
"as",
|
|
1683
|
+
"satisfies",
|
|
1684
|
+
"finally",
|
|
1685
|
+
"do",
|
|
1686
|
+
"delete"
|
|
1687
|
+
]),
|
|
1688
|
+
python: new Set([
|
|
1689
|
+
"def",
|
|
1690
|
+
"return",
|
|
1691
|
+
"if",
|
|
1692
|
+
"elif",
|
|
1693
|
+
"else",
|
|
1694
|
+
"for",
|
|
1695
|
+
"while",
|
|
1696
|
+
"break",
|
|
1697
|
+
"continue",
|
|
1698
|
+
"class",
|
|
1699
|
+
"import",
|
|
1700
|
+
"from",
|
|
1701
|
+
"as",
|
|
1702
|
+
"try",
|
|
1703
|
+
"except",
|
|
1704
|
+
"finally",
|
|
1705
|
+
"raise",
|
|
1706
|
+
"with",
|
|
1707
|
+
"lambda",
|
|
1708
|
+
"yield",
|
|
1709
|
+
"pass",
|
|
1710
|
+
"None",
|
|
1711
|
+
"True",
|
|
1712
|
+
"False",
|
|
1713
|
+
"and",
|
|
1714
|
+
"or",
|
|
1715
|
+
"not",
|
|
1716
|
+
"in",
|
|
1717
|
+
"is",
|
|
1718
|
+
"global",
|
|
1719
|
+
"nonlocal",
|
|
1720
|
+
"assert",
|
|
1721
|
+
"async",
|
|
1722
|
+
"await",
|
|
1723
|
+
"del",
|
|
1724
|
+
"self"
|
|
1725
|
+
]),
|
|
1726
|
+
bash: new Set([
|
|
1727
|
+
"if",
|
|
1728
|
+
"then",
|
|
1729
|
+
"else",
|
|
1730
|
+
"elif",
|
|
1731
|
+
"fi",
|
|
1732
|
+
"for",
|
|
1733
|
+
"while",
|
|
1734
|
+
"do",
|
|
1735
|
+
"done",
|
|
1736
|
+
"case",
|
|
1737
|
+
"esac",
|
|
1738
|
+
"function",
|
|
1739
|
+
"return",
|
|
1740
|
+
"local",
|
|
1741
|
+
"export",
|
|
1742
|
+
"exit",
|
|
1743
|
+
"break",
|
|
1744
|
+
"continue",
|
|
1745
|
+
"in",
|
|
1746
|
+
"select",
|
|
1747
|
+
"until"
|
|
1748
|
+
]),
|
|
1749
|
+
go: new Set([
|
|
1750
|
+
"func",
|
|
1751
|
+
"return",
|
|
1752
|
+
"if",
|
|
1753
|
+
"else",
|
|
1754
|
+
"for",
|
|
1755
|
+
"range",
|
|
1756
|
+
"switch",
|
|
1757
|
+
"case",
|
|
1758
|
+
"default",
|
|
1759
|
+
"break",
|
|
1760
|
+
"continue",
|
|
1761
|
+
"package",
|
|
1762
|
+
"import",
|
|
1763
|
+
"var",
|
|
1764
|
+
"const",
|
|
1765
|
+
"type",
|
|
1766
|
+
"struct",
|
|
1767
|
+
"interface",
|
|
1768
|
+
"map",
|
|
1769
|
+
"chan",
|
|
1770
|
+
"go",
|
|
1771
|
+
"defer",
|
|
1772
|
+
"select",
|
|
1773
|
+
"fallthrough",
|
|
1774
|
+
"nil",
|
|
1775
|
+
"true",
|
|
1776
|
+
"false"
|
|
1777
|
+
]),
|
|
1778
|
+
rust: new Set([
|
|
1779
|
+
"fn",
|
|
1780
|
+
"let",
|
|
1781
|
+
"mut",
|
|
1782
|
+
"return",
|
|
1783
|
+
"if",
|
|
1784
|
+
"else",
|
|
1785
|
+
"for",
|
|
1786
|
+
"while",
|
|
1787
|
+
"loop",
|
|
1788
|
+
"match",
|
|
1789
|
+
"struct",
|
|
1790
|
+
"enum",
|
|
1791
|
+
"impl",
|
|
1792
|
+
"trait",
|
|
1793
|
+
"pub",
|
|
1794
|
+
"use",
|
|
1795
|
+
"mod",
|
|
1796
|
+
"crate",
|
|
1797
|
+
"self",
|
|
1798
|
+
"Self",
|
|
1799
|
+
"super",
|
|
1800
|
+
"const",
|
|
1801
|
+
"static",
|
|
1802
|
+
"async",
|
|
1803
|
+
"await",
|
|
1804
|
+
"move",
|
|
1805
|
+
"ref",
|
|
1806
|
+
"dyn",
|
|
1807
|
+
"where",
|
|
1808
|
+
"unsafe",
|
|
1809
|
+
"true",
|
|
1810
|
+
"false",
|
|
1811
|
+
"None",
|
|
1812
|
+
"Some",
|
|
1813
|
+
"Ok",
|
|
1814
|
+
"Err"
|
|
1815
|
+
]),
|
|
1816
|
+
json: new Set(["true", "false", "null"])
|
|
1817
|
+
};
|
|
1818
|
+
CODE_WORD_OR_NUMBER = /[A-Za-z_$][A-Za-z0-9_$]*|\d+(?:\.\d+)?/g;
|
|
1819
|
+
WIDE_RANGES = [
|
|
1820
|
+
[4352, 4447],
|
|
1821
|
+
[11904, 12350],
|
|
1822
|
+
[12353, 13311],
|
|
1823
|
+
[13312, 19903],
|
|
1824
|
+
[19968, 40959],
|
|
1825
|
+
[40960, 42191],
|
|
1826
|
+
[44032, 55203],
|
|
1827
|
+
[63744, 64255],
|
|
1828
|
+
[65072, 65135],
|
|
1829
|
+
[65280, 65376],
|
|
1830
|
+
[65504, 65510],
|
|
1831
|
+
[127744, 128591],
|
|
1832
|
+
[129280, 129535],
|
|
1833
|
+
[131072, 262141]
|
|
1834
|
+
];
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
// src/lib/ui.ts
|
|
1838
|
+
import { stdout } from "process";
|
|
1839
|
+
function colorEnabled() {
|
|
1840
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
if (process.env.FORCE_COLOR !== undefined) {
|
|
1844
|
+
return true;
|
|
1845
|
+
}
|
|
1846
|
+
return Boolean(stdout.isTTY);
|
|
1847
|
+
}
|
|
1848
|
+
function wrap(open, close) {
|
|
1849
|
+
return (text) => colorEnabled() ? `\x1B[${open}m${text}\x1B[${close}m` : text;
|
|
1850
|
+
}
|
|
1851
|
+
function banner(title, subtitle) {
|
|
1852
|
+
const rule = style.cyan("\u2501".repeat(RULE_WIDTH));
|
|
1853
|
+
console.log(rule);
|
|
1854
|
+
console.log(` ${style.bold(title)}`);
|
|
1855
|
+
if (subtitle) {
|
|
1856
|
+
console.log(` ${style.dim(subtitle)}`);
|
|
1857
|
+
}
|
|
1858
|
+
console.log(rule);
|
|
1859
|
+
}
|
|
1860
|
+
function heading(title, emit = console.log) {
|
|
1861
|
+
emit(`
|
|
1862
|
+
${style.bold(style.cyan(title))}`);
|
|
1863
|
+
}
|
|
1864
|
+
function statusLine(label, enabled, detail) {
|
|
1865
|
+
const marker = enabled ? style.green(symbols.ok) : style.gray(symbols.off);
|
|
1866
|
+
const name = enabled ? label : style.gray(label);
|
|
1867
|
+
const suffix = detail ? style.dim(` (${detail})`) : "";
|
|
1868
|
+
console.log(` ${marker} ${name}${suffix}`);
|
|
1869
|
+
}
|
|
1870
|
+
function nextSteps(steps) {
|
|
1871
|
+
if (steps.length === 0) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
heading("Next steps");
|
|
1875
|
+
for (const step of steps) {
|
|
1876
|
+
console.log(` ${style.cyan(symbols.arrow)} ${step}`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function note(message) {
|
|
1880
|
+
console.log(` ${style.dim(message)}`);
|
|
1881
|
+
}
|
|
1882
|
+
function renderInline(text) {
|
|
1883
|
+
const withCode = text.replace(/`([^`]+)`/g, (_match, code) => style.gray(code));
|
|
1884
|
+
return withCode.replace(/\*\*([^*]+)\*\*/g, (_match, bold) => style.bold(bold));
|
|
1885
|
+
}
|
|
1886
|
+
function renderTextLine(line) {
|
|
1887
|
+
const heading = /^#{1,6}\s+(.*)$/.exec(line);
|
|
1888
|
+
if (heading !== null) {
|
|
1889
|
+
return style.bold(style.cyan(heading[1] ?? ""));
|
|
1890
|
+
}
|
|
1891
|
+
const bullet = /^(\s*)[-*]\s+(.*)$/.exec(line);
|
|
1892
|
+
if (bullet !== null) {
|
|
1893
|
+
return `${bullet[1] ?? ""}${style.cyan(symbols.bullet)} ${renderInline(bullet[2] ?? "")}`;
|
|
1894
|
+
}
|
|
1895
|
+
return renderInline(line);
|
|
1896
|
+
}
|
|
1897
|
+
function renderDiff(text) {
|
|
1898
|
+
if (!colorEnabled()) {
|
|
1899
|
+
return text;
|
|
1900
|
+
}
|
|
1901
|
+
return splitLines(text).map((line) => {
|
|
1902
|
+
switch (classifyDiffLine(line)) {
|
|
1903
|
+
case "add":
|
|
1904
|
+
return style.green(line);
|
|
1905
|
+
case "del":
|
|
1906
|
+
return style.red(line);
|
|
1907
|
+
case "hunk":
|
|
1908
|
+
return style.cyan(line);
|
|
1909
|
+
case "meta":
|
|
1910
|
+
return style.dim(line);
|
|
1911
|
+
default:
|
|
1912
|
+
return line;
|
|
1913
|
+
}
|
|
1914
|
+
}).join(`
|
|
1915
|
+
`);
|
|
1916
|
+
}
|
|
1917
|
+
function renderCodeSegment(lang, body) {
|
|
1918
|
+
const lines = body.split(`
|
|
1919
|
+
`);
|
|
1920
|
+
const out = [];
|
|
1921
|
+
if (lang.length > 0) {
|
|
1922
|
+
out.push(style.dim(lang));
|
|
1923
|
+
}
|
|
1924
|
+
if (payloadKind(lang, lines.length) === "diff" || looksLikeUnifiedDiff(body)) {
|
|
1925
|
+
out.push(renderDiff(body));
|
|
1926
|
+
return out;
|
|
1927
|
+
}
|
|
1928
|
+
for (const line of lines) {
|
|
1929
|
+
out.push(style.gray(line));
|
|
1930
|
+
}
|
|
1931
|
+
return out;
|
|
1932
|
+
}
|
|
1933
|
+
function renderMarkdown(md) {
|
|
1934
|
+
if (!colorEnabled()) {
|
|
1935
|
+
return md;
|
|
1936
|
+
}
|
|
1937
|
+
const out = [];
|
|
1938
|
+
for (const segment of segmentMarkdown(md)) {
|
|
1939
|
+
if (segment.kind === "code") {
|
|
1940
|
+
out.push(...renderCodeSegment(segment.lang, segment.body));
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
for (const line of segment.text.split(`
|
|
1944
|
+
`)) {
|
|
1945
|
+
out.push(renderTextLine(line));
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return out.join(`
|
|
1949
|
+
`);
|
|
1950
|
+
}
|
|
1951
|
+
function collapseToolOutput(text, maxWidth = 100) {
|
|
1952
|
+
const trimmed = text.replace(/\r?\n+$/, "");
|
|
1953
|
+
const lines = trimmed.length === 0 ? [] : splitLines(trimmed);
|
|
1954
|
+
const firstNonEmpty = lines.find((line) => line.trim().length > 0) ?? "";
|
|
1955
|
+
const summary = firstNonEmpty.length > maxWidth ? `${firstNonEmpty.slice(0, maxWidth)}\u2026` : firstNonEmpty;
|
|
1956
|
+
const lineCount = lines.length;
|
|
1957
|
+
return { summary, lineCount, hidden: Math.max(0, lineCount - 1) };
|
|
1958
|
+
}
|
|
1959
|
+
function indentBlock(text, pad) {
|
|
1960
|
+
return text.split(`
|
|
1961
|
+
`).map((line) => line.length > 0 ? pad + line : line).join(`
|
|
1962
|
+
`);
|
|
1963
|
+
}
|
|
1964
|
+
function summarizeToolArgs(input, max = 80) {
|
|
1965
|
+
const clip = (s) => s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
1966
|
+
const raw = input.trim();
|
|
1967
|
+
if (raw.length === 0) {
|
|
1968
|
+
return "";
|
|
1969
|
+
}
|
|
1970
|
+
let parsed;
|
|
1971
|
+
try {
|
|
1972
|
+
parsed = JSON.parse(raw);
|
|
1973
|
+
} catch {
|
|
1974
|
+
return clip(raw);
|
|
1975
|
+
}
|
|
1976
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1977
|
+
return clip(raw);
|
|
1978
|
+
}
|
|
1979
|
+
const parts = [];
|
|
1980
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1981
|
+
let shown;
|
|
1982
|
+
if (value === null) {
|
|
1983
|
+
shown = "null";
|
|
1984
|
+
} else if (Array.isArray(value)) {
|
|
1985
|
+
shown = "[\u2026]";
|
|
1986
|
+
} else if (typeof value === "object") {
|
|
1987
|
+
shown = "{\u2026}";
|
|
1988
|
+
} else {
|
|
1989
|
+
shown = String(value);
|
|
1990
|
+
}
|
|
1991
|
+
parts.push(`${key}=${shown}`);
|
|
1992
|
+
}
|
|
1993
|
+
return clip(parts.join(", "));
|
|
1994
|
+
}
|
|
1995
|
+
function helpTitle(command, summary) {
|
|
1996
|
+
console.log(`${style.bold(command)} ${style.dim(`\u2014 ${summary}`)}`);
|
|
1997
|
+
}
|
|
1998
|
+
function helpUsage(lines) {
|
|
1999
|
+
heading("Usage");
|
|
2000
|
+
for (const line of lines) {
|
|
2001
|
+
console.log(` ${style.cyan(line)}`);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
function helpOptions(options) {
|
|
2005
|
+
heading("Options");
|
|
2006
|
+
const width = Math.max(...options.map((option) => option.flag.length));
|
|
2007
|
+
for (const option of options) {
|
|
2008
|
+
console.log(` ${style.cyan(option.flag.padEnd(width))} ${style.dim(option.desc)}`);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
var style, symbols, RULE_WIDTH = 52;
|
|
2012
|
+
var init_ui = __esm(() => {
|
|
2013
|
+
init_md_blocks();
|
|
2014
|
+
style = {
|
|
2015
|
+
bold: wrap(1, 22),
|
|
2016
|
+
dim: wrap(2, 22),
|
|
2017
|
+
red: wrap(31, 39),
|
|
2018
|
+
green: wrap(32, 39),
|
|
2019
|
+
yellow: wrap(33, 39),
|
|
2020
|
+
cyan: wrap(36, 39),
|
|
2021
|
+
gray: wrap(90, 39)
|
|
2022
|
+
};
|
|
2023
|
+
symbols = {
|
|
2024
|
+
ok: "\u2713",
|
|
2025
|
+
cross: "\u2717",
|
|
2026
|
+
off: "\xB7",
|
|
2027
|
+
arrow: "\u2192",
|
|
2028
|
+
bullet: "\u2022"
|
|
2029
|
+
};
|
|
2030
|
+
});
|
|
2031
|
+
|
|
2032
|
+
// src/lib/prompt.ts
|
|
2033
|
+
import { stdin as input, stdout as output } from "process";
|
|
2034
|
+
import readline from "readline/promises";
|
|
2035
|
+
async function confirm(question, defaultValue = false) {
|
|
2036
|
+
if (!input.isTTY) {
|
|
2037
|
+
return defaultValue;
|
|
2038
|
+
}
|
|
2039
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
2040
|
+
const rl = readline.createInterface({ input, output });
|
|
2041
|
+
try {
|
|
2042
|
+
const answer = await rl.question(`${style.cyan(symbols.arrow)} ${question} ${style.dim(`(${suffix})`)} `);
|
|
2043
|
+
const normalized = answer.trim().toLowerCase();
|
|
2044
|
+
if (!normalized) {
|
|
2045
|
+
return defaultValue;
|
|
2046
|
+
}
|
|
2047
|
+
return normalized === "y" || normalized === "yes";
|
|
2048
|
+
} finally {
|
|
2049
|
+
rl.close();
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
async function choice(question, choices, defaultValue) {
|
|
2053
|
+
if (!input.isTTY) {
|
|
2054
|
+
return defaultValue;
|
|
2055
|
+
}
|
|
2056
|
+
const rl = readline.createInterface({ input, output });
|
|
2057
|
+
try {
|
|
2058
|
+
const answer = await rl.question(`${style.cyan(symbols.arrow)} ${question} ${style.dim(`(${choices.join("/")}; default: ${defaultValue})`)} `);
|
|
2059
|
+
const normalized = answer.trim().toLowerCase();
|
|
2060
|
+
const match = choices.find((item) => item.toLowerCase() === normalized);
|
|
2061
|
+
return match ?? defaultValue;
|
|
2062
|
+
} finally {
|
|
2063
|
+
rl.close();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
var init_prompt = __esm(() => {
|
|
2067
|
+
init_ui();
|
|
2068
|
+
});
|
|
2069
|
+
|
|
720
2070
|
// src/commands/providers.ts
|
|
721
2071
|
function isProviderPlatformSupported(provider, platform = process.platform) {
|
|
722
2072
|
if (provider.platforms === undefined || provider.platforms.length === 0) {
|
|
@@ -987,6 +2337,82 @@ function providerApiKey(provider, env = process.env) {
|
|
|
987
2337
|
const raw = env[envKey];
|
|
988
2338
|
return typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
989
2339
|
}
|
|
2340
|
+
async function testProviderConnection(provider, fetchFn = globalThis.fetch, env = process.env) {
|
|
2341
|
+
const apiKey = providerApiKey(provider, env) ?? provider.apiKey;
|
|
2342
|
+
return fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, { timeoutMs: MODELS_FETCH_TIMEOUT_MS });
|
|
2343
|
+
}
|
|
2344
|
+
function providersSharingEnvKey(envKey, excludeName, providers) {
|
|
2345
|
+
return providers.filter((p) => p.envKey === envKey && p.name !== excludeName).map((p) => p.name).sort();
|
|
2346
|
+
}
|
|
2347
|
+
function sharedCredentialWarning(sharedWith, envKey) {
|
|
2348
|
+
if (sharedWith.length === 0 || envKey === undefined)
|
|
2349
|
+
return;
|
|
2350
|
+
return `this also disconnects ${sharedWith.join(", ")} (same ${envKey})`;
|
|
2351
|
+
}
|
|
2352
|
+
function classifyProviderConnection(name, env = process.env, dir) {
|
|
2353
|
+
if (customCompatProviders(dir).some((p) => p.name === name)) {
|
|
2354
|
+
return { kind: "custom", sharedWith: [] };
|
|
2355
|
+
}
|
|
2356
|
+
if (loadShellConfig(dir).oauthGrants?.[name] !== undefined) {
|
|
2357
|
+
const envKey = oauthEnvKeyFor(name);
|
|
2358
|
+
const sharedWith = envKey === undefined ? [] : providersSharingEnvKey(envKey, name, allOpenAiCompatProviders(dir));
|
|
2359
|
+
return { kind: "oauth-grant", ...envKey !== undefined ? { envKey } : {}, sharedWith };
|
|
2360
|
+
}
|
|
2361
|
+
const registry = providerByName(name, dir);
|
|
2362
|
+
const envKey = registry?.envKey;
|
|
2363
|
+
if (envKey === undefined) {
|
|
2364
|
+
return { kind: "no-credential", sharedWith: [] };
|
|
2365
|
+
}
|
|
2366
|
+
const sharedWith = providersSharingEnvKey(envKey, name, allOpenAiCompatProviders(dir));
|
|
2367
|
+
if (loadShellConfig(dir).apiKeys?.[envKey] !== undefined) {
|
|
2368
|
+
return { kind: "saved-api-key", envKey, sharedWith };
|
|
2369
|
+
}
|
|
2370
|
+
const raw = env[envKey];
|
|
2371
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
2372
|
+
return { kind: "env-var-only", envKey, sharedWith };
|
|
2373
|
+
}
|
|
2374
|
+
return { kind: "no-credential", sharedWith: [] };
|
|
2375
|
+
}
|
|
2376
|
+
function disconnectProvider(name, env = process.env, dir) {
|
|
2377
|
+
const classification = classifyProviderConnection(name, env, dir);
|
|
2378
|
+
switch (classification.kind) {
|
|
2379
|
+
case "custom":
|
|
2380
|
+
removeCustomCompatProvider(name, dir);
|
|
2381
|
+
removeProviderBaseUrl(name, dir);
|
|
2382
|
+
removeProviderModelParams(name, dir);
|
|
2383
|
+
return { ok: true, kind: "custom", sharedWith: classification.sharedWith };
|
|
2384
|
+
case "oauth-grant": {
|
|
2385
|
+
logoutProvider(name, dir);
|
|
2386
|
+
const envKey = classification.envKey;
|
|
2387
|
+
if (envKey !== undefined && savedCredentialEnvKeys().has(envKey)) {
|
|
2388
|
+
delete process.env[envKey];
|
|
2389
|
+
}
|
|
2390
|
+
return { ok: true, kind: "oauth-grant", sharedWith: classification.sharedWith };
|
|
2391
|
+
}
|
|
2392
|
+
case "saved-api-key": {
|
|
2393
|
+
const envKey = classification.envKey;
|
|
2394
|
+
removeApiKey(envKey, dir);
|
|
2395
|
+
if (savedCredentialEnvKeys().has(envKey)) {
|
|
2396
|
+
delete process.env[envKey];
|
|
2397
|
+
}
|
|
2398
|
+
return { ok: true, kind: "saved-api-key", sharedWith: classification.sharedWith };
|
|
2399
|
+
}
|
|
2400
|
+
case "env-var-only":
|
|
2401
|
+
return {
|
|
2402
|
+
ok: false,
|
|
2403
|
+
kind: "env-var-only",
|
|
2404
|
+
reason: `set via ${classification.envKey} in your environment \u2014 unset ${classification.envKey} in your shell to disconnect`,
|
|
2405
|
+
sharedWith: classification.sharedWith
|
|
2406
|
+
};
|
|
2407
|
+
case "no-credential":
|
|
2408
|
+
return {
|
|
2409
|
+
ok: true,
|
|
2410
|
+
kind: "no-credential",
|
|
2411
|
+
reason: "no saved credential for this provider \u2014 nothing to remove",
|
|
2412
|
+
sharedWith: classification.sharedWith
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
990
2416
|
function configuredProviders(env = process.env, dir) {
|
|
991
2417
|
const customNames = new Set(customCompatProviders(dir).map((provider) => provider.name));
|
|
992
2418
|
return allOpenAiCompatProviders(dir).filter((provider) => {
|
|
@@ -997,7 +2423,7 @@ function configuredProviders(env = process.env, dir) {
|
|
|
997
2423
|
return providerApiKey(provider, env) !== undefined || provider.apiKey !== undefined;
|
|
998
2424
|
}).filter((provider) => isProviderPlatformSupported(provider)).map((provider) => ({ name: provider.name, models: provider.models }));
|
|
999
2425
|
}
|
|
1000
|
-
function providersCommand(args) {
|
|
2426
|
+
async function providersCommand(args, deps = {}) {
|
|
1001
2427
|
const command = args[0];
|
|
1002
2428
|
if (!command || command === "--help" || command === "-h") {
|
|
1003
2429
|
printProvidersHelp();
|
|
@@ -1011,10 +2437,99 @@ function providersCommand(args) {
|
|
|
1011
2437
|
runCrossFamily(args.slice(1));
|
|
1012
2438
|
return;
|
|
1013
2439
|
}
|
|
2440
|
+
if (command === "test") {
|
|
2441
|
+
await runProvidersTest(args.slice(1), deps);
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
if (command === "remove") {
|
|
2445
|
+
await runProvidersRemove(args.slice(1), deps);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
1014
2448
|
console.error(`Unknown providers command: ${command}`);
|
|
1015
2449
|
printProvidersHelp();
|
|
1016
2450
|
process.exitCode = 1;
|
|
1017
2451
|
}
|
|
2452
|
+
async function runProvidersTest(args, deps) {
|
|
2453
|
+
const name = args[0];
|
|
2454
|
+
if (name === undefined || name === "--help" || name === "-h") {
|
|
2455
|
+
console.error("Usage: keryx providers test <name> [--json]");
|
|
2456
|
+
process.exitCode = 1;
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const dir = deps.dir;
|
|
2460
|
+
const provider = providerByName(name, dir);
|
|
2461
|
+
if (provider === undefined) {
|
|
2462
|
+
console.error(`Unknown provider: ${name}`);
|
|
2463
|
+
process.exitCode = 1;
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const env = deps.env ?? envWithOAuthAccess(envWithSavedApiKeys(process.env, dir));
|
|
2467
|
+
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
2468
|
+
const result = await testProviderConnection(provider, fetchFn, env);
|
|
2469
|
+
const label = provider.label ?? provider.name;
|
|
2470
|
+
if (args.includes("--json")) {
|
|
2471
|
+
console.log(JSON.stringify({ provider: name, ok: result.source === "live", models: result.models.length, failure: result.failure ?? null }, null, 2));
|
|
2472
|
+
if (result.source !== "live")
|
|
2473
|
+
process.exitCode = 1;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (result.source === "live") {
|
|
2477
|
+
console.log(`${label}: ok \u2014 ${result.models.length} model(s)`);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
console.log(modelsFailureLine(label, result.failure ?? { kind: "empty" }));
|
|
2481
|
+
process.exitCode = 1;
|
|
2482
|
+
}
|
|
2483
|
+
async function runProvidersRemove(args, deps) {
|
|
2484
|
+
const name = args[0];
|
|
2485
|
+
if (name === undefined || name === "--help" || name === "-h") {
|
|
2486
|
+
console.error("Usage: keryx providers remove <name> [--yes] [--json]");
|
|
2487
|
+
process.exitCode = 1;
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
const dir = deps.dir;
|
|
2491
|
+
const env = deps.env ?? process.env;
|
|
2492
|
+
const json = args.includes("--json");
|
|
2493
|
+
if (providerByName(name, dir) === undefined) {
|
|
2494
|
+
if (json) {
|
|
2495
|
+
console.log(JSON.stringify({ provider: name, ok: false, reason: "unknown provider" }, null, 2));
|
|
2496
|
+
} else {
|
|
2497
|
+
console.error(`Unknown provider: ${name}`);
|
|
2498
|
+
}
|
|
2499
|
+
process.exitCode = 1;
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
const classification = classifyProviderConnection(name, env, dir);
|
|
2503
|
+
const warning = sharedCredentialWarning(classification.sharedWith, classification.envKey);
|
|
2504
|
+
const yes = args.includes("--yes");
|
|
2505
|
+
const confirmFn = deps.confirm ?? confirm;
|
|
2506
|
+
const question = warning === undefined ? `Disconnect provider "${name}" and remove its saved credential?` : `Disconnect provider "${name}" and remove its saved credential? Note: ${warning}.`;
|
|
2507
|
+
const confirmed = yes || await confirmFn(question, false);
|
|
2508
|
+
if (!confirmed) {
|
|
2509
|
+
if (json) {
|
|
2510
|
+
console.log(JSON.stringify({ provider: name, ok: false, reason: "not confirmed", sharedWith: classification.sharedWith }, null, 2));
|
|
2511
|
+
} else {
|
|
2512
|
+
console.log("keryx providers remove: not confirmed \u2014 nothing was changed.");
|
|
2513
|
+
}
|
|
2514
|
+
if (!yes && !process.stdin.isTTY && deps.confirm === undefined)
|
|
2515
|
+
process.exitCode = 1;
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const result = disconnectProvider(name, env, dir);
|
|
2519
|
+
if (json) {
|
|
2520
|
+
console.log(JSON.stringify({ provider: name, ok: result.ok, kind: result.kind, sharedWith: result.sharedWith, reason: result.reason ?? null }, null, 2));
|
|
2521
|
+
if (!result.ok)
|
|
2522
|
+
process.exitCode = 1;
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (!result.ok) {
|
|
2526
|
+
console.log(`keryx providers remove: "${name}" not removed \u2014 ${result.reason}`);
|
|
2527
|
+
process.exitCode = 1;
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
const sharedNote = sharedCredentialWarning(result.sharedWith, classification.envKey);
|
|
2531
|
+
console.log(`keryx providers remove: "${name}" disconnected (${result.kind}).${sharedNote !== undefined ? ` Note: ${sharedNote}.` : ""}${result.reason !== undefined ? ` ${result.reason}` : ""}`);
|
|
2532
|
+
}
|
|
1018
2533
|
function runProvidersList(args) {
|
|
1019
2534
|
const env = envWithOAuthAccess(envWithSavedApiKeys());
|
|
1020
2535
|
const configured = configuredProviders(env).map((provider) => ({
|
|
@@ -1098,17 +2613,38 @@ function printProvidersHelp() {
|
|
|
1098
2613
|
Usage:
|
|
1099
2614
|
keryx providers list [--json]
|
|
1100
2615
|
keryx providers cross-family [--opt-in] [--session-provider <id>] [--session-model <id>] [--from-shell-config] [--json]
|
|
2616
|
+
keryx providers test <name> [--json]
|
|
2617
|
+
keryx providers remove <name> [--yes] [--json]
|
|
1101
2618
|
|
|
1102
2619
|
Commands:
|
|
1103
2620
|
list Providers this operator has configured, and the family of each
|
|
1104
2621
|
cross-family Whether review can run on a different model family than authored
|
|
1105
2622
|
the change, and the record the round should carry
|
|
2623
|
+
test Run this provider's live model-list probe and report ok/count
|
|
2624
|
+
or the failure reason. Makes ONE network call \u2014 unlike list/
|
|
2625
|
+
cross-family, not network-free
|
|
2626
|
+
remove Disconnect a provider: remove its saved API key, OAuth grant,
|
|
2627
|
+
or custom-provider entry. Errors on an unknown name (exit 1)
|
|
2628
|
+
before asking anything. Asks for confirmation on a terminal;
|
|
2629
|
+
refuses without one unless --yes. A provider whose only
|
|
2630
|
+
credential is an environment variable you exported yourself
|
|
2631
|
+
cannot be removed \u2014 the command names the variable to unset.
|
|
2632
|
+
Some built-ins share one env var (e.g. zai/zai-coding both
|
|
2633
|
+
read ZAI_API_KEY): removing either ALSO disconnects the
|
|
2634
|
+
other, and both the confirmation prompt and the result name
|
|
2635
|
+
every provider this affects \u2014 --json lists them under
|
|
2636
|
+
sharedWith
|
|
1106
2637
|
|
|
1107
2638
|
cross-family is OPT-IN: without --opt-in it reports what would happen and
|
|
1108
2639
|
chooses single-family review. Dispatching to another provider spends tokens and
|
|
1109
2640
|
sends the change to a second vendor, which is a decision rather than an
|
|
1110
2641
|
optimisation. With no second family configured it reports single-family review
|
|
1111
2642
|
with a stated reason and exits 0 \u2014 that is a normal configuration, not an error.
|
|
2643
|
+
|
|
2644
|
+
Disconnecting a provider removes only keryx's LOCAL copy of its credential. It
|
|
2645
|
+
does not revoke anything at the vendor: an OAuth grant is deleted from
|
|
2646
|
+
auth.json only (no revoke call), and a saved API key simply stops being read \u2014
|
|
2647
|
+
the key itself is still valid until you revoke it yourself with the vendor.
|
|
1112
2648
|
`);
|
|
1113
2649
|
}
|
|
1114
2650
|
var DEFAULT_MODELS_PATH = "/v1/models", MINIMAX_REASONING_PRESET_HOSTS, DEEPSEEK_REASONING_PRESET_HOSTS, OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4, MODELS_ERROR_BODY_LIMIT = 4096, BALANCE_FETCH_TIMEOUT_MS = 8000;
|
|
@@ -1116,9 +2652,11 @@ var init_providers = __esm(() => {
|
|
|
1116
2652
|
init_provider_config();
|
|
1117
2653
|
init_catalog();
|
|
1118
2654
|
init_grants();
|
|
2655
|
+
init_login();
|
|
1119
2656
|
init_caller_session();
|
|
1120
2657
|
init_shell_config();
|
|
1121
2658
|
init_args();
|
|
2659
|
+
init_prompt();
|
|
1122
2660
|
MINIMAX_REASONING_PRESET_HOSTS = new Set(["api.minimax.io", "api.minimaxi.com"]);
|
|
1123
2661
|
DEEPSEEK_REASONING_PRESET_HOSTS = new Set(["api.deepseek.com"]);
|
|
1124
2662
|
OPENAI_COMPAT_PROVIDERS = [
|
|
@@ -45281,498 +46819,6 @@ var init_debug_log = __esm(() => {
|
|
|
45281
46819
|
init_config_dir();
|
|
45282
46820
|
});
|
|
45283
46821
|
|
|
45284
|
-
// src/lib/oauth/device-code.ts
|
|
45285
|
-
function requestInit(base, signal) {
|
|
45286
|
-
return signal === undefined ? base : { ...base, signal };
|
|
45287
|
-
}
|
|
45288
|
-
function formHeaders(extra) {
|
|
45289
|
-
return {
|
|
45290
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
45291
|
-
Accept: "application/json",
|
|
45292
|
-
...extra
|
|
45293
|
-
};
|
|
45294
|
-
}
|
|
45295
|
-
function positiveSecondsToMs(value, defaultMs) {
|
|
45296
|
-
const seconds = Number(value);
|
|
45297
|
-
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : defaultMs;
|
|
45298
|
-
}
|
|
45299
|
-
async function defaultSleep(ms) {
|
|
45300
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
45301
|
-
}
|
|
45302
|
-
function aborted(signal) {
|
|
45303
|
-
return signal?.aborted === true;
|
|
45304
|
-
}
|
|
45305
|
-
function readJsonError(body) {
|
|
45306
|
-
if (typeof body !== "object" || body === null) {
|
|
45307
|
-
return {};
|
|
45308
|
-
}
|
|
45309
|
-
const rec = body;
|
|
45310
|
-
return {
|
|
45311
|
-
...typeof rec.error === "string" ? { error: rec.error } : {},
|
|
45312
|
-
...typeof rec.error_description === "string" ? { error_description: rec.error_description } : {}
|
|
45313
|
-
};
|
|
45314
|
-
}
|
|
45315
|
-
async function requestDeviceCode(request, http) {
|
|
45316
|
-
if (aborted(http.signal)) {
|
|
45317
|
-
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
45318
|
-
}
|
|
45319
|
-
const body = new URLSearchParams({
|
|
45320
|
-
client_id: request.clientId,
|
|
45321
|
-
scope: request.scope,
|
|
45322
|
-
...request.extraForm
|
|
45323
|
-
});
|
|
45324
|
-
const response = await http.fetch(request.deviceAuthorizationEndpoint, requestInit({
|
|
45325
|
-
method: "POST",
|
|
45326
|
-
headers: formHeaders(request.headers),
|
|
45327
|
-
body: body.toString()
|
|
45328
|
-
}, http.signal));
|
|
45329
|
-
const json = await response.json().catch(() => ({}));
|
|
45330
|
-
if (!response.ok) {
|
|
45331
|
-
const detail = readJsonError(json).error_description ?? `HTTP ${response.status}`;
|
|
45332
|
-
throw new DeviceCodeError("failed", `device code request failed: ${detail}`);
|
|
45333
|
-
}
|
|
45334
|
-
if (typeof json !== "object" || json === null) {
|
|
45335
|
-
throw new DeviceCodeError("failed", "device code response is not an object");
|
|
45336
|
-
}
|
|
45337
|
-
const rec = json;
|
|
45338
|
-
const deviceCode = typeof rec.device_code === "string" ? rec.device_code : "";
|
|
45339
|
-
const userCode = typeof rec.user_code === "string" ? rec.user_code : "";
|
|
45340
|
-
const verificationUri = typeof rec.verification_uri === "string" ? rec.verification_uri : "";
|
|
45341
|
-
if (deviceCode.length === 0 || userCode.length === 0 || verificationUri.length === 0) {
|
|
45342
|
-
throw new DeviceCodeError("failed", "device code response is missing device_code / user_code / verification_uri");
|
|
45343
|
-
}
|
|
45344
|
-
if (![...userCode].every((c) => /[A-Za-z0-9-]/.test(c))) {
|
|
45345
|
-
throw new DeviceCodeError("failed", "device code response has an invalid user_code");
|
|
45346
|
-
}
|
|
45347
|
-
const complete = typeof rec.verification_uri_complete === "string" && rec.verification_uri_complete.length > 0 ? rec.verification_uri_complete : undefined;
|
|
45348
|
-
return {
|
|
45349
|
-
deviceCode,
|
|
45350
|
-
userCode,
|
|
45351
|
-
verificationUri,
|
|
45352
|
-
...complete !== undefined ? { verificationUriComplete: complete } : {},
|
|
45353
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
45354
|
-
...typeof rec.interval === "number" ? { intervalSeconds: rec.interval } : {}
|
|
45355
|
-
};
|
|
45356
|
-
}
|
|
45357
|
-
async function pollDeviceCodeToken(challenge, request, http) {
|
|
45358
|
-
const sleep = http.sleep ?? defaultSleep;
|
|
45359
|
-
const now = http.now ?? Date.now;
|
|
45360
|
-
const expiresInMs = positiveSecondsToMs(challenge.expiresInSeconds, DEFAULT_EXPIRES_MS);
|
|
45361
|
-
const deadline = now() + expiresInMs;
|
|
45362
|
-
let intervalMs = Math.max(positiveSecondsToMs(challenge.intervalSeconds, DEFAULT_INTERVAL_MS), MIN_INTERVAL_MS);
|
|
45363
|
-
while (now() < deadline) {
|
|
45364
|
-
if (aborted(http.signal)) {
|
|
45365
|
-
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
45366
|
-
}
|
|
45367
|
-
const response = await http.fetch(request.tokenEndpoint, requestInit({
|
|
45368
|
-
method: "POST",
|
|
45369
|
-
headers: formHeaders(request.headers),
|
|
45370
|
-
body: new URLSearchParams({
|
|
45371
|
-
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
45372
|
-
client_id: request.clientId,
|
|
45373
|
-
device_code: challenge.deviceCode,
|
|
45374
|
-
...request.extraForm
|
|
45375
|
-
}).toString()
|
|
45376
|
-
}, http.signal));
|
|
45377
|
-
const json = await response.json().catch(() => ({}));
|
|
45378
|
-
if (typeof json === "object" && json !== null && typeof json.access_token === "string") {
|
|
45379
|
-
const rec = json;
|
|
45380
|
-
return {
|
|
45381
|
-
accessToken: rec.access_token,
|
|
45382
|
-
...typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? { refreshToken: rec.refresh_token } : {},
|
|
45383
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
45384
|
-
...typeof rec.scope === "string" ? { scope: rec.scope } : {},
|
|
45385
|
-
...typeof rec.id_token === "string" ? { idToken: rec.id_token } : {}
|
|
45386
|
-
};
|
|
45387
|
-
}
|
|
45388
|
-
const { error, error_description } = readJsonError(json);
|
|
45389
|
-
const remaining = Math.max(0, deadline - now());
|
|
45390
|
-
if (error === "authorization_pending") {
|
|
45391
|
-
await sleep(Math.min(intervalMs + POLL_SAFETY_MARGIN_MS, remaining));
|
|
45392
|
-
continue;
|
|
45393
|
-
}
|
|
45394
|
-
if (error === "slow_down") {
|
|
45395
|
-
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
45396
|
-
await sleep(Math.min(intervalMs + POLL_SAFETY_MARGIN_MS, remaining));
|
|
45397
|
-
continue;
|
|
45398
|
-
}
|
|
45399
|
-
if (error === "access_denied" || error === "authorization_denied") {
|
|
45400
|
-
throw new DeviceCodeError("access_denied", "device authorization was denied");
|
|
45401
|
-
}
|
|
45402
|
-
if (error === "expired_token") {
|
|
45403
|
-
throw new DeviceCodeError("expired_token", "device code expired");
|
|
45404
|
-
}
|
|
45405
|
-
if (!response.ok) {
|
|
45406
|
-
throw new DeviceCodeError("failed", `device token exchange failed (${response.status})${error_description ? `: ${error_description}` : ""}`);
|
|
45407
|
-
}
|
|
45408
|
-
throw new DeviceCodeError("failed", `device token exchange failed${error_description ? `: ${error_description}` : ""}`);
|
|
45409
|
-
}
|
|
45410
|
-
throw new DeviceCodeError("timeout", "device authorization timed out");
|
|
45411
|
-
}
|
|
45412
|
-
async function refreshAccessToken(request, http) {
|
|
45413
|
-
const response = await http.fetch(request.tokenEndpoint, requestInit({
|
|
45414
|
-
method: "POST",
|
|
45415
|
-
headers: formHeaders(request.headers),
|
|
45416
|
-
body: new URLSearchParams({
|
|
45417
|
-
grant_type: "refresh_token",
|
|
45418
|
-
refresh_token: request.refreshToken,
|
|
45419
|
-
client_id: request.clientId,
|
|
45420
|
-
...request.extraForm
|
|
45421
|
-
}).toString()
|
|
45422
|
-
}, http.signal));
|
|
45423
|
-
const json = await response.json().catch(() => ({}));
|
|
45424
|
-
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
45425
|
-
const detail = readJsonError(json).error_description ?? `HTTP ${response.status}`;
|
|
45426
|
-
throw new DeviceCodeError("failed", `token refresh failed: ${detail}`);
|
|
45427
|
-
}
|
|
45428
|
-
const rec = json;
|
|
45429
|
-
return {
|
|
45430
|
-
accessToken: rec.access_token,
|
|
45431
|
-
refreshToken: typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? rec.refresh_token : request.refreshToken,
|
|
45432
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {},
|
|
45433
|
-
...typeof rec.scope === "string" ? { scope: rec.scope } : {},
|
|
45434
|
-
...typeof rec.id_token === "string" ? { idToken: rec.id_token } : {}
|
|
45435
|
-
};
|
|
45436
|
-
}
|
|
45437
|
-
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", DEFAULT_INTERVAL_MS = 5000, MIN_INTERVAL_MS = 1000, SLOW_DOWN_INCREMENT_MS = 5000, DEFAULT_EXPIRES_MS, POLL_SAFETY_MARGIN_MS = 3000, DeviceCodeError;
|
|
45438
|
-
var init_device_code = __esm(() => {
|
|
45439
|
-
DEFAULT_EXPIRES_MS = 5 * 60 * 1000;
|
|
45440
|
-
DeviceCodeError = class DeviceCodeError extends Error {
|
|
45441
|
-
kind;
|
|
45442
|
-
constructor(kind, message) {
|
|
45443
|
-
super(message);
|
|
45444
|
-
this.name = "DeviceCodeError";
|
|
45445
|
-
this.kind = kind;
|
|
45446
|
-
}
|
|
45447
|
-
};
|
|
45448
|
-
});
|
|
45449
|
-
|
|
45450
|
-
// src/lib/oauth/copilot.ts
|
|
45451
|
-
function copilotApiBaseFromExchange(raw) {
|
|
45452
|
-
if (typeof raw !== "string" || raw.length === 0) {
|
|
45453
|
-
return;
|
|
45454
|
-
}
|
|
45455
|
-
try {
|
|
45456
|
-
const url = new URL(raw);
|
|
45457
|
-
if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0) {
|
|
45458
|
-
return;
|
|
45459
|
-
}
|
|
45460
|
-
const host = url.hostname;
|
|
45461
|
-
if (host !== "api.githubcopilot.com" && !host.endsWith(".githubcopilot.com")) {
|
|
45462
|
-
return;
|
|
45463
|
-
}
|
|
45464
|
-
return `https://${host}${url.port.length > 0 ? `:${url.port}` : ""}`;
|
|
45465
|
-
} catch {
|
|
45466
|
-
return;
|
|
45467
|
-
}
|
|
45468
|
-
}
|
|
45469
|
-
function expiresInSecondsFrom(rec, now) {
|
|
45470
|
-
if (typeof rec.expires_at === "string") {
|
|
45471
|
-
const expires = Date.parse(rec.expires_at);
|
|
45472
|
-
if (Number.isFinite(expires)) {
|
|
45473
|
-
return Math.max(1, Math.floor((expires - now) / 1000));
|
|
45474
|
-
}
|
|
45475
|
-
}
|
|
45476
|
-
if (typeof rec.expires_at === "number" && Number.isFinite(rec.expires_at)) {
|
|
45477
|
-
const expiresMs = rec.expires_at < 1000000000000 ? rec.expires_at * 1000 : rec.expires_at;
|
|
45478
|
-
return Math.max(1, Math.floor((expiresMs - now) / 1000));
|
|
45479
|
-
}
|
|
45480
|
-
if (typeof rec.refresh_in === "number" && Number.isFinite(rec.refresh_in) && rec.refresh_in > 0) {
|
|
45481
|
-
return Math.floor(rec.refresh_in);
|
|
45482
|
-
}
|
|
45483
|
-
return;
|
|
45484
|
-
}
|
|
45485
|
-
function describeExchangeFailure(status, json, body) {
|
|
45486
|
-
if (typeof json === "object" && json !== null) {
|
|
45487
|
-
const rec = json;
|
|
45488
|
-
if (rec.error_details?.copilot_access_denied === true) {
|
|
45489
|
-
return `GitHub Copilot token exchange failed (HTTP ${status}): Copilot access denied \u2014 check the GitHub Copilot subscription on this account`;
|
|
45490
|
-
}
|
|
45491
|
-
if (typeof rec.message === "string" && rec.message.length > 0) {
|
|
45492
|
-
return `GitHub Copilot token exchange failed (HTTP ${status}): ${rec.message}`;
|
|
45493
|
-
}
|
|
45494
|
-
}
|
|
45495
|
-
if (/zscaler|blocked because it does not comply/i.test(body)) {
|
|
45496
|
-
return `GitHub Copilot token exchange failed (HTTP ${status}): request was blocked by a gateway or proxy`;
|
|
45497
|
-
}
|
|
45498
|
-
return `GitHub Copilot token exchange failed (HTTP ${status})`;
|
|
45499
|
-
}
|
|
45500
|
-
async function exchangeGithubTokenForCopilot(githubToken, http) {
|
|
45501
|
-
const init = {
|
|
45502
|
-
method: "GET",
|
|
45503
|
-
headers: {
|
|
45504
|
-
...GITHUB_COPILOT_REQUEST_HEADERS,
|
|
45505
|
-
Authorization: `token ${githubToken}`
|
|
45506
|
-
}
|
|
45507
|
-
};
|
|
45508
|
-
if (http.signal !== undefined) {
|
|
45509
|
-
init.signal = http.signal;
|
|
45510
|
-
}
|
|
45511
|
-
const response = await http.fetch(COPILOT_TOKEN_URL, init);
|
|
45512
|
-
const body = await response.text().catch(() => "");
|
|
45513
|
-
let json = {};
|
|
45514
|
-
if (body.length > 0) {
|
|
45515
|
-
try {
|
|
45516
|
-
json = JSON.parse(body);
|
|
45517
|
-
} catch {
|
|
45518
|
-
json = {};
|
|
45519
|
-
}
|
|
45520
|
-
}
|
|
45521
|
-
if (!response.ok || typeof json !== "object" || json === null) {
|
|
45522
|
-
throw new DeviceCodeError("failed", describeExchangeFailure(response.status, json, body));
|
|
45523
|
-
}
|
|
45524
|
-
const rec = json;
|
|
45525
|
-
if (typeof rec.token !== "string" || rec.token.length === 0) {
|
|
45526
|
-
throw new DeviceCodeError("failed", "GitHub Copilot token exchange returned no token");
|
|
45527
|
-
}
|
|
45528
|
-
const expiresInSeconds = expiresInSecondsFrom(rec, Date.now());
|
|
45529
|
-
const apiBaseUrl = copilotApiBaseFromExchange(rec.endpoints?.api);
|
|
45530
|
-
return {
|
|
45531
|
-
accessToken: rec.token,
|
|
45532
|
-
...expiresInSeconds !== undefined ? { expiresInSeconds } : {},
|
|
45533
|
-
...apiBaseUrl !== undefined ? { apiBaseUrl } : {}
|
|
45534
|
-
};
|
|
45535
|
-
}
|
|
45536
|
-
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
45537
|
-
var init_copilot = __esm(() => {
|
|
45538
|
-
init_catalog();
|
|
45539
|
-
init_device_code();
|
|
45540
|
-
});
|
|
45541
|
-
|
|
45542
|
-
// src/lib/oauth/openai-codex.ts
|
|
45543
|
-
function withSignal(base, signal) {
|
|
45544
|
-
return signal === undefined ? base : { ...base, signal };
|
|
45545
|
-
}
|
|
45546
|
-
async function requestCodexDeviceCode(http) {
|
|
45547
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
45548
|
-
method: "POST",
|
|
45549
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
45550
|
-
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
45551
|
-
}, http.signal));
|
|
45552
|
-
const json = await response.json().catch(() => ({}));
|
|
45553
|
-
if (!response.ok || typeof json !== "object" || json === null) {
|
|
45554
|
-
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
45555
|
-
}
|
|
45556
|
-
const rec = json;
|
|
45557
|
-
if (typeof rec.device_auth_id !== "string" || typeof rec.user_code !== "string") {
|
|
45558
|
-
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
45559
|
-
}
|
|
45560
|
-
const intervalSeconds = Number(rec.interval);
|
|
45561
|
-
return {
|
|
45562
|
-
deviceAuthId: rec.device_auth_id,
|
|
45563
|
-
userCode: rec.user_code,
|
|
45564
|
-
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
45565
|
-
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
45566
|
-
};
|
|
45567
|
-
}
|
|
45568
|
-
async function pollCodexDeviceToken(challenge, http) {
|
|
45569
|
-
const sleep = http.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
45570
|
-
const now = http.now ?? Date.now;
|
|
45571
|
-
const deadline = now() + 5 * 60 * 1000;
|
|
45572
|
-
while (now() < deadline) {
|
|
45573
|
-
if (http.signal?.aborted === true) {
|
|
45574
|
-
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
45575
|
-
}
|
|
45576
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
45577
|
-
method: "POST",
|
|
45578
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
45579
|
-
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
45580
|
-
}, http.signal));
|
|
45581
|
-
if (response.ok) {
|
|
45582
|
-
const data = await response.json();
|
|
45583
|
-
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
45584
|
-
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
45585
|
-
}
|
|
45586
|
-
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
45587
|
-
}
|
|
45588
|
-
if (response.status !== 403 && response.status !== 404) {
|
|
45589
|
-
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
45590
|
-
}
|
|
45591
|
-
await sleep(challenge.intervalMs + POLL_SAFETY_MARGIN_MS2);
|
|
45592
|
-
}
|
|
45593
|
-
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
45594
|
-
}
|
|
45595
|
-
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
45596
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
45597
|
-
method: "POST",
|
|
45598
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
45599
|
-
body: new URLSearchParams({
|
|
45600
|
-
grant_type: "authorization_code",
|
|
45601
|
-
code,
|
|
45602
|
-
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
45603
|
-
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
45604
|
-
code_verifier: codeVerifier
|
|
45605
|
-
}).toString()
|
|
45606
|
-
}, http.signal));
|
|
45607
|
-
const json = await response.json().catch(() => ({}));
|
|
45608
|
-
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
45609
|
-
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
45610
|
-
}
|
|
45611
|
-
const rec = json;
|
|
45612
|
-
return {
|
|
45613
|
-
accessToken: rec.access_token,
|
|
45614
|
-
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
45615
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {}
|
|
45616
|
-
};
|
|
45617
|
-
}
|
|
45618
|
-
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", POLL_SAFETY_MARGIN_MS2 = 3000;
|
|
45619
|
-
var init_openai_codex = __esm(() => {
|
|
45620
|
-
init_device_code();
|
|
45621
|
-
});
|
|
45622
|
-
|
|
45623
|
-
// src/lib/oauth/login.ts
|
|
45624
|
-
function httpOf(input) {
|
|
45625
|
-
return {
|
|
45626
|
-
fetch: input.fetch,
|
|
45627
|
-
...input.sleep !== undefined ? { sleep: input.sleep } : {},
|
|
45628
|
-
...input.now !== undefined ? { now: input.now } : {},
|
|
45629
|
-
...input.signal !== undefined ? { signal: input.signal } : {}
|
|
45630
|
-
};
|
|
45631
|
-
}
|
|
45632
|
-
function present(challenge, provider) {
|
|
45633
|
-
return {
|
|
45634
|
-
userCode: challenge.userCode,
|
|
45635
|
-
verificationUri: challenge.verificationUri,
|
|
45636
|
-
...challenge.verificationUriComplete !== undefined ? { verificationUriComplete: challenge.verificationUriComplete } : {},
|
|
45637
|
-
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)})`
|
|
45638
|
-
};
|
|
45639
|
-
}
|
|
45640
|
-
async function loginRfc8628(input) {
|
|
45641
|
-
const endpoints = input.provider === "github-copilot" ? GITHUB_COPILOT_DEVICE : XAI_GROK_DEVICE;
|
|
45642
|
-
const http = httpOf(input);
|
|
45643
|
-
const challenge = await requestDeviceCode({
|
|
45644
|
-
deviceAuthorizationEndpoint: endpoints.deviceAuthorizationEndpoint,
|
|
45645
|
-
clientId: endpoints.clientId,
|
|
45646
|
-
scope: endpoints.scope,
|
|
45647
|
-
...endpoints.extraForm !== undefined ? { extraForm: endpoints.extraForm } : {},
|
|
45648
|
-
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
45649
|
-
}, http);
|
|
45650
|
-
input.onChallenge(present(challenge, input.provider));
|
|
45651
|
-
const tokens = await pollDeviceCodeToken(challenge, {
|
|
45652
|
-
tokenEndpoint: endpoints.tokenEndpoint,
|
|
45653
|
-
clientId: endpoints.clientId,
|
|
45654
|
-
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
45655
|
-
}, http);
|
|
45656
|
-
if (endpoints.afterToken === "github-copilot") {
|
|
45657
|
-
const copilot = await exchangeGithubTokenForCopilot(tokens.accessToken, http);
|
|
45658
|
-
if (copilot.apiBaseUrl !== undefined) {
|
|
45659
|
-
saveProviderBaseUrl("github-copilot", copilot.apiBaseUrl, input.dir);
|
|
45660
|
-
}
|
|
45661
|
-
return {
|
|
45662
|
-
accessToken: copilot.accessToken,
|
|
45663
|
-
refreshToken: tokens.accessToken,
|
|
45664
|
-
...copilot.expiresInSeconds !== undefined ? { expiresInSeconds: copilot.expiresInSeconds } : {}
|
|
45665
|
-
};
|
|
45666
|
-
}
|
|
45667
|
-
return tokens;
|
|
45668
|
-
}
|
|
45669
|
-
async function loginDeviceCode(input) {
|
|
45670
|
-
const entry = authCatalogEntry(input.provider);
|
|
45671
|
-
if (entry === undefined) {
|
|
45672
|
-
return { ok: false, error: `unknown provider: ${input.provider}` };
|
|
45673
|
-
}
|
|
45674
|
-
if (!entry.methods.includes("device-code")) {
|
|
45675
|
-
const refusal = catalogRefusal(input.provider);
|
|
45676
|
-
if (refusal !== undefined) {
|
|
45677
|
-
return { ok: false, error: refusal, refused: true };
|
|
45678
|
-
}
|
|
45679
|
-
return { ok: false, error: `${input.provider} does not support device authorization` };
|
|
45680
|
-
}
|
|
45681
|
-
try {
|
|
45682
|
-
const tokens = input.provider === "openai" ? await loginCodex(input) : await loginRfc8628(input);
|
|
45683
|
-
saveOAuthGrant(input.provider, grantFromTokens("device-code", tokens, input.now), input.dir);
|
|
45684
|
-
return { ok: true, provider: input.provider };
|
|
45685
|
-
} catch (err) {
|
|
45686
|
-
const message = err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
45687
|
-
return { ok: false, error: message };
|
|
45688
|
-
}
|
|
45689
|
-
}
|
|
45690
|
-
async function loginCodex(input) {
|
|
45691
|
-
const http = httpOf(input);
|
|
45692
|
-
const challenge = await requestCodexDeviceCode(http);
|
|
45693
|
-
input.onChallenge(present({
|
|
45694
|
-
userCode: challenge.userCode,
|
|
45695
|
-
verificationUri: challenge.verificationUri
|
|
45696
|
-
}, "openai"));
|
|
45697
|
-
return pollCodexDeviceToken(challenge, http);
|
|
45698
|
-
}
|
|
45699
|
-
async function refreshProviderGrant(provider, http, dir) {
|
|
45700
|
-
const grant = loadOAuthGrant(provider, dir);
|
|
45701
|
-
if (grant === undefined || !grantNeedsRefresh(grant, http.now)) {
|
|
45702
|
-
return grant;
|
|
45703
|
-
}
|
|
45704
|
-
if (grant.refresh === undefined) {
|
|
45705
|
-
return grant;
|
|
45706
|
-
}
|
|
45707
|
-
const endpoints = provider === "github-copilot" ? GITHUB_COPILOT_DEVICE : XAI_GROK_DEVICE;
|
|
45708
|
-
if (provider === "github-copilot") {
|
|
45709
|
-
const copilot = await exchangeGithubTokenForCopilot(grant.refresh, http);
|
|
45710
|
-
if (copilot.apiBaseUrl !== undefined) {
|
|
45711
|
-
saveProviderBaseUrl("github-copilot", copilot.apiBaseUrl, dir);
|
|
45712
|
-
}
|
|
45713
|
-
const next = grantFromTokens("device-code", {
|
|
45714
|
-
accessToken: copilot.accessToken,
|
|
45715
|
-
refreshToken: grant.refresh,
|
|
45716
|
-
...copilot.expiresInSeconds !== undefined ? { expiresInSeconds: copilot.expiresInSeconds } : {}
|
|
45717
|
-
}, http.now);
|
|
45718
|
-
next.lastRefreshedAt = new Date((http.now ?? Date.now)()).toISOString();
|
|
45719
|
-
saveOAuthGrant(provider, next, dir);
|
|
45720
|
-
return next;
|
|
45721
|
-
}
|
|
45722
|
-
if (provider === "openai") {
|
|
45723
|
-
return grant;
|
|
45724
|
-
}
|
|
45725
|
-
const tokens = await refreshAccessToken({
|
|
45726
|
-
tokenEndpoint: endpoints.tokenEndpoint,
|
|
45727
|
-
clientId: endpoints.clientId,
|
|
45728
|
-
refreshToken: grant.refresh,
|
|
45729
|
-
...endpoints.headers !== undefined ? { headers: endpoints.headers } : {}
|
|
45730
|
-
}, http);
|
|
45731
|
-
const next = grantFromTokens("device-code", tokens, http.now);
|
|
45732
|
-
next.lastRefreshedAt = new Date((http.now ?? Date.now)()).toISOString();
|
|
45733
|
-
saveOAuthGrant(provider, next, dir);
|
|
45734
|
-
return next;
|
|
45735
|
-
}
|
|
45736
|
-
async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANTS) {
|
|
45737
|
-
const warnings = [];
|
|
45738
|
-
const now = http.now ?? Date.now;
|
|
45739
|
-
for (const provider of providers) {
|
|
45740
|
-
if (!SESSION_REFRESHED_GRANTS.includes(provider))
|
|
45741
|
-
continue;
|
|
45742
|
-
const grant = loadOAuthGrant(provider, dir);
|
|
45743
|
-
if (grant === undefined)
|
|
45744
|
-
continue;
|
|
45745
|
-
if (grant.refresh === undefined || grant.refresh.length === 0) {
|
|
45746
|
-
if (grant.expires !== undefined && grant.expires <= now()) {
|
|
45747
|
-
warnings.push(`${provider}: the saved login has expired and holds no refresh token. Run \`keryx auth login ${provider}\`.`);
|
|
45748
|
-
}
|
|
45749
|
-
continue;
|
|
45750
|
-
}
|
|
45751
|
-
if (!grantNeedsRefresh(grant, http.now))
|
|
45752
|
-
continue;
|
|
45753
|
-
try {
|
|
45754
|
-
await refreshProviderGrant(provider, http, dir);
|
|
45755
|
-
} catch (cause) {
|
|
45756
|
-
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
45757
|
-
warnings.push(`${provider}: the saved login is expiring or expired and could not be refreshed (${reason}). ` + `Run \`keryx auth login ${provider}\`.`);
|
|
45758
|
-
}
|
|
45759
|
-
}
|
|
45760
|
-
return warnings;
|
|
45761
|
-
}
|
|
45762
|
-
function logoutProvider(provider, dir) {
|
|
45763
|
-
deleteOAuthGrant(provider, dir);
|
|
45764
|
-
}
|
|
45765
|
-
var SESSION_REFRESHED_GRANTS;
|
|
45766
|
-
var init_login = __esm(() => {
|
|
45767
|
-
init_shell_config();
|
|
45768
|
-
init_catalog();
|
|
45769
|
-
init_copilot();
|
|
45770
|
-
init_device_code();
|
|
45771
|
-
init_grants();
|
|
45772
|
-
init_openai_codex();
|
|
45773
|
-
SESSION_REFRESHED_GRANTS = ["grok", "github-copilot"];
|
|
45774
|
-
});
|
|
45775
|
-
|
|
45776
46822
|
// src/tui/debug-watcher.ts
|
|
45777
46823
|
import { spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
|
|
45778
46824
|
import { closeSync as closeSync5, constants as constants4, openSync as openSync6, readdirSync as readdirSync5, readFileSync as readFileSync15, readlinkSync as readlinkSync3, statSync as statSync12 } from "fs";
|
|
@@ -46531,786 +47577,8 @@ function sanitizeForDisplay(value) {
|
|
|
46531
47577
|
return out;
|
|
46532
47578
|
}
|
|
46533
47579
|
|
|
46534
|
-
// src/lib/ui.ts
|
|
46535
|
-
import { stdout } from "process";
|
|
46536
|
-
|
|
46537
|
-
// src/lib/md-blocks.ts
|
|
46538
|
-
var FENCE_LINE = /^[ \t]{0,3}(```|~~~)(.*)\r?$/;
|
|
46539
|
-
var HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
46540
|
-
var MARKDOWN_LANGS = new Set(["md", "markdown", "prompt"]);
|
|
46541
|
-
var DIFF_LANGS = new Set(["diff", "patch"]);
|
|
46542
|
-
function stripTrailingCr(line) {
|
|
46543
|
-
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
46544
|
-
}
|
|
46545
|
-
function splitLines(text) {
|
|
46546
|
-
return text.split(`
|
|
46547
|
-
`).map(stripTrailingCr);
|
|
46548
|
-
}
|
|
46549
|
-
function markdownTableCells(line) {
|
|
46550
|
-
const source = line.trim();
|
|
46551
|
-
if (!source.includes("|")) {
|
|
46552
|
-
return;
|
|
46553
|
-
}
|
|
46554
|
-
const cells = [];
|
|
46555
|
-
let cell = "";
|
|
46556
|
-
let inCode = false;
|
|
46557
|
-
for (let index = 0;index < source.length; index += 1) {
|
|
46558
|
-
const char = source[index] ?? "";
|
|
46559
|
-
if (char === "\\" && source[index + 1] === "|") {
|
|
46560
|
-
cell += "|";
|
|
46561
|
-
index += 1;
|
|
46562
|
-
continue;
|
|
46563
|
-
}
|
|
46564
|
-
if (char === "`") {
|
|
46565
|
-
inCode = !inCode;
|
|
46566
|
-
cell += char;
|
|
46567
|
-
continue;
|
|
46568
|
-
}
|
|
46569
|
-
if (char === "|" && !inCode) {
|
|
46570
|
-
cells.push(cell.trim());
|
|
46571
|
-
cell = "";
|
|
46572
|
-
continue;
|
|
46573
|
-
}
|
|
46574
|
-
cell += char;
|
|
46575
|
-
}
|
|
46576
|
-
cells.push(cell.trim());
|
|
46577
|
-
if (source.startsWith("|")) {
|
|
46578
|
-
cells.shift();
|
|
46579
|
-
}
|
|
46580
|
-
if (source.endsWith("|")) {
|
|
46581
|
-
cells.pop();
|
|
46582
|
-
}
|
|
46583
|
-
return cells.length >= 2 ? cells : undefined;
|
|
46584
|
-
}
|
|
46585
|
-
function tableAlignments(line, columns) {
|
|
46586
|
-
const cells = markdownTableCells(line);
|
|
46587
|
-
if (cells === undefined || cells.length !== columns) {
|
|
46588
|
-
return;
|
|
46589
|
-
}
|
|
46590
|
-
const alignments = [];
|
|
46591
|
-
for (const cell of cells) {
|
|
46592
|
-
const marker = cell.trim();
|
|
46593
|
-
if (!/^:?-{3,}:?$/.test(marker)) {
|
|
46594
|
-
return;
|
|
46595
|
-
}
|
|
46596
|
-
alignments.push(marker.startsWith(":") && marker.endsWith(":") ? "center" : marker.endsWith(":") ? "right" : "left");
|
|
46597
|
-
}
|
|
46598
|
-
return alignments;
|
|
46599
|
-
}
|
|
46600
|
-
function parseMarkdownTables(text) {
|
|
46601
|
-
if (text.length === 0) {
|
|
46602
|
-
return [];
|
|
46603
|
-
}
|
|
46604
|
-
const lines = splitLines(text);
|
|
46605
|
-
const parts = [];
|
|
46606
|
-
let proseStart = 0;
|
|
46607
|
-
const flushProse = (end) => {
|
|
46608
|
-
if (end <= proseStart) {
|
|
46609
|
-
return;
|
|
46610
|
-
}
|
|
46611
|
-
const prose = lines.slice(proseStart, end).join(`
|
|
46612
|
-
`);
|
|
46613
|
-
if (prose.length > 0) {
|
|
46614
|
-
parts.push({ kind: "prose", text: prose });
|
|
46615
|
-
}
|
|
46616
|
-
};
|
|
46617
|
-
for (let index = 0;index + 1 < lines.length; index += 1) {
|
|
46618
|
-
const headers = markdownTableCells(lines[index] ?? "");
|
|
46619
|
-
if (headers === undefined) {
|
|
46620
|
-
continue;
|
|
46621
|
-
}
|
|
46622
|
-
const alignments = tableAlignments(lines[index + 1] ?? "", headers.length);
|
|
46623
|
-
if (alignments === undefined) {
|
|
46624
|
-
continue;
|
|
46625
|
-
}
|
|
46626
|
-
flushProse(index);
|
|
46627
|
-
const rows = [];
|
|
46628
|
-
let cursor = index + 2;
|
|
46629
|
-
while (cursor < lines.length) {
|
|
46630
|
-
const row = markdownTableCells(lines[cursor] ?? "");
|
|
46631
|
-
if (row === undefined || row.length !== headers.length) {
|
|
46632
|
-
break;
|
|
46633
|
-
}
|
|
46634
|
-
rows.push(row);
|
|
46635
|
-
cursor += 1;
|
|
46636
|
-
}
|
|
46637
|
-
parts.push({ kind: "table", table: { headers, alignments, rows } });
|
|
46638
|
-
proseStart = cursor;
|
|
46639
|
-
index = cursor - 1;
|
|
46640
|
-
}
|
|
46641
|
-
flushProse(lines.length);
|
|
46642
|
-
return parts.length > 0 ? parts : [{ kind: "prose", text }];
|
|
46643
|
-
}
|
|
46644
|
-
function fenceInfo(line) {
|
|
46645
|
-
const match = FENCE_LINE.exec(line);
|
|
46646
|
-
if (match === null) {
|
|
46647
|
-
return;
|
|
46648
|
-
}
|
|
46649
|
-
return { marker: match[1] ?? "", lang: (match[2] ?? "").trim().split(/\s+/)[0] ?? "" };
|
|
46650
|
-
}
|
|
46651
|
-
function segmentMarkdown(md) {
|
|
46652
|
-
const segments = [];
|
|
46653
|
-
let text = [];
|
|
46654
|
-
let code;
|
|
46655
|
-
let lang = "";
|
|
46656
|
-
let marker = "";
|
|
46657
|
-
const flushText = () => {
|
|
46658
|
-
const joined = text.join(`
|
|
46659
|
-
`);
|
|
46660
|
-
text = [];
|
|
46661
|
-
if (joined.length > 0) {
|
|
46662
|
-
segments.push({ kind: "text", text: joined });
|
|
46663
|
-
}
|
|
46664
|
-
};
|
|
46665
|
-
for (const line of splitLines(md)) {
|
|
46666
|
-
const fence = fenceInfo(line);
|
|
46667
|
-
if (code === undefined) {
|
|
46668
|
-
if (fence === undefined) {
|
|
46669
|
-
text.push(line);
|
|
46670
|
-
continue;
|
|
46671
|
-
}
|
|
46672
|
-
flushText();
|
|
46673
|
-
marker = fence.marker;
|
|
46674
|
-
lang = fence.lang;
|
|
46675
|
-
code = [];
|
|
46676
|
-
continue;
|
|
46677
|
-
}
|
|
46678
|
-
if (fence?.marker === marker) {
|
|
46679
|
-
segments.push({ kind: "code", lang, body: code.join(`
|
|
46680
|
-
`) });
|
|
46681
|
-
code = undefined;
|
|
46682
|
-
continue;
|
|
46683
|
-
}
|
|
46684
|
-
code.push(line);
|
|
46685
|
-
}
|
|
46686
|
-
if (code === undefined) {
|
|
46687
|
-
flushText();
|
|
46688
|
-
} else {
|
|
46689
|
-
segments.push({ kind: "code", lang, body: code.join(`
|
|
46690
|
-
`) });
|
|
46691
|
-
}
|
|
46692
|
-
return segments;
|
|
46693
|
-
}
|
|
46694
|
-
function classifyDiffLine(line) {
|
|
46695
|
-
if (line.startsWith("@@")) {
|
|
46696
|
-
return "hunk";
|
|
46697
|
-
}
|
|
46698
|
-
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
46699
|
-
return "meta";
|
|
46700
|
-
}
|
|
46701
|
-
if (line.startsWith("+")) {
|
|
46702
|
-
return "add";
|
|
46703
|
-
}
|
|
46704
|
-
if (line.startsWith("-")) {
|
|
46705
|
-
return "del";
|
|
46706
|
-
}
|
|
46707
|
-
return "context";
|
|
46708
|
-
}
|
|
46709
|
-
function looksLikeUnifiedDiff(text) {
|
|
46710
|
-
const lines = splitLines(text);
|
|
46711
|
-
for (const [index, line] of lines.entries()) {
|
|
46712
|
-
if (HUNK_HEADER.test(line)) {
|
|
46713
|
-
return true;
|
|
46714
|
-
}
|
|
46715
|
-
if (line.startsWith("--- ") && (lines[index + 1]?.startsWith("+++ ") ?? false)) {
|
|
46716
|
-
return true;
|
|
46717
|
-
}
|
|
46718
|
-
}
|
|
46719
|
-
return false;
|
|
46720
|
-
}
|
|
46721
|
-
var CODE_LANG_ALIASES = {
|
|
46722
|
-
js: "javascript",
|
|
46723
|
-
mjs: "javascript",
|
|
46724
|
-
cjs: "javascript",
|
|
46725
|
-
jsx: "javascript",
|
|
46726
|
-
ts: "typescript",
|
|
46727
|
-
tsx: "typescript",
|
|
46728
|
-
py: "python",
|
|
46729
|
-
py3: "python",
|
|
46730
|
-
sh: "bash",
|
|
46731
|
-
shell: "bash",
|
|
46732
|
-
zsh: "bash",
|
|
46733
|
-
console: "bash",
|
|
46734
|
-
yml: "yaml",
|
|
46735
|
-
rb: "ruby",
|
|
46736
|
-
rs: "rust",
|
|
46737
|
-
kt: "kotlin",
|
|
46738
|
-
cs: "csharp",
|
|
46739
|
-
"c++": "cpp",
|
|
46740
|
-
cc: "cpp",
|
|
46741
|
-
cxx: "cpp",
|
|
46742
|
-
hpp: "cpp"
|
|
46743
|
-
};
|
|
46744
|
-
var GENERIC_KEYWORDS = new Set([
|
|
46745
|
-
"if",
|
|
46746
|
-
"else",
|
|
46747
|
-
"for",
|
|
46748
|
-
"while",
|
|
46749
|
-
"return",
|
|
46750
|
-
"function",
|
|
46751
|
-
"class",
|
|
46752
|
-
"const",
|
|
46753
|
-
"let",
|
|
46754
|
-
"var",
|
|
46755
|
-
"import",
|
|
46756
|
-
"export",
|
|
46757
|
-
"from",
|
|
46758
|
-
"true",
|
|
46759
|
-
"false",
|
|
46760
|
-
"null",
|
|
46761
|
-
"new",
|
|
46762
|
-
"this",
|
|
46763
|
-
"try",
|
|
46764
|
-
"catch",
|
|
46765
|
-
"throw",
|
|
46766
|
-
"switch",
|
|
46767
|
-
"case",
|
|
46768
|
-
"break",
|
|
46769
|
-
"continue",
|
|
46770
|
-
"default",
|
|
46771
|
-
"static",
|
|
46772
|
-
"public",
|
|
46773
|
-
"private"
|
|
46774
|
-
]);
|
|
46775
|
-
var CODE_LANG_KEYWORDS = {
|
|
46776
|
-
javascript: new Set([
|
|
46777
|
-
...GENERIC_KEYWORDS,
|
|
46778
|
-
"async",
|
|
46779
|
-
"await",
|
|
46780
|
-
"yield",
|
|
46781
|
-
"typeof",
|
|
46782
|
-
"instanceof",
|
|
46783
|
-
"in",
|
|
46784
|
-
"of",
|
|
46785
|
-
"void",
|
|
46786
|
-
"undefined",
|
|
46787
|
-
"super",
|
|
46788
|
-
"extends",
|
|
46789
|
-
"finally",
|
|
46790
|
-
"do",
|
|
46791
|
-
"delete"
|
|
46792
|
-
]),
|
|
46793
|
-
typescript: new Set([
|
|
46794
|
-
...GENERIC_KEYWORDS,
|
|
46795
|
-
"async",
|
|
46796
|
-
"await",
|
|
46797
|
-
"yield",
|
|
46798
|
-
"typeof",
|
|
46799
|
-
"instanceof",
|
|
46800
|
-
"in",
|
|
46801
|
-
"of",
|
|
46802
|
-
"void",
|
|
46803
|
-
"undefined",
|
|
46804
|
-
"super",
|
|
46805
|
-
"extends",
|
|
46806
|
-
"implements",
|
|
46807
|
-
"interface",
|
|
46808
|
-
"type",
|
|
46809
|
-
"enum",
|
|
46810
|
-
"namespace",
|
|
46811
|
-
"declare",
|
|
46812
|
-
"readonly",
|
|
46813
|
-
"abstract",
|
|
46814
|
-
"as",
|
|
46815
|
-
"satisfies",
|
|
46816
|
-
"finally",
|
|
46817
|
-
"do",
|
|
46818
|
-
"delete"
|
|
46819
|
-
]),
|
|
46820
|
-
python: new Set([
|
|
46821
|
-
"def",
|
|
46822
|
-
"return",
|
|
46823
|
-
"if",
|
|
46824
|
-
"elif",
|
|
46825
|
-
"else",
|
|
46826
|
-
"for",
|
|
46827
|
-
"while",
|
|
46828
|
-
"break",
|
|
46829
|
-
"continue",
|
|
46830
|
-
"class",
|
|
46831
|
-
"import",
|
|
46832
|
-
"from",
|
|
46833
|
-
"as",
|
|
46834
|
-
"try",
|
|
46835
|
-
"except",
|
|
46836
|
-
"finally",
|
|
46837
|
-
"raise",
|
|
46838
|
-
"with",
|
|
46839
|
-
"lambda",
|
|
46840
|
-
"yield",
|
|
46841
|
-
"pass",
|
|
46842
|
-
"None",
|
|
46843
|
-
"True",
|
|
46844
|
-
"False",
|
|
46845
|
-
"and",
|
|
46846
|
-
"or",
|
|
46847
|
-
"not",
|
|
46848
|
-
"in",
|
|
46849
|
-
"is",
|
|
46850
|
-
"global",
|
|
46851
|
-
"nonlocal",
|
|
46852
|
-
"assert",
|
|
46853
|
-
"async",
|
|
46854
|
-
"await",
|
|
46855
|
-
"del",
|
|
46856
|
-
"self"
|
|
46857
|
-
]),
|
|
46858
|
-
bash: new Set([
|
|
46859
|
-
"if",
|
|
46860
|
-
"then",
|
|
46861
|
-
"else",
|
|
46862
|
-
"elif",
|
|
46863
|
-
"fi",
|
|
46864
|
-
"for",
|
|
46865
|
-
"while",
|
|
46866
|
-
"do",
|
|
46867
|
-
"done",
|
|
46868
|
-
"case",
|
|
46869
|
-
"esac",
|
|
46870
|
-
"function",
|
|
46871
|
-
"return",
|
|
46872
|
-
"local",
|
|
46873
|
-
"export",
|
|
46874
|
-
"exit",
|
|
46875
|
-
"break",
|
|
46876
|
-
"continue",
|
|
46877
|
-
"in",
|
|
46878
|
-
"select",
|
|
46879
|
-
"until"
|
|
46880
|
-
]),
|
|
46881
|
-
go: new Set([
|
|
46882
|
-
"func",
|
|
46883
|
-
"return",
|
|
46884
|
-
"if",
|
|
46885
|
-
"else",
|
|
46886
|
-
"for",
|
|
46887
|
-
"range",
|
|
46888
|
-
"switch",
|
|
46889
|
-
"case",
|
|
46890
|
-
"default",
|
|
46891
|
-
"break",
|
|
46892
|
-
"continue",
|
|
46893
|
-
"package",
|
|
46894
|
-
"import",
|
|
46895
|
-
"var",
|
|
46896
|
-
"const",
|
|
46897
|
-
"type",
|
|
46898
|
-
"struct",
|
|
46899
|
-
"interface",
|
|
46900
|
-
"map",
|
|
46901
|
-
"chan",
|
|
46902
|
-
"go",
|
|
46903
|
-
"defer",
|
|
46904
|
-
"select",
|
|
46905
|
-
"fallthrough",
|
|
46906
|
-
"nil",
|
|
46907
|
-
"true",
|
|
46908
|
-
"false"
|
|
46909
|
-
]),
|
|
46910
|
-
rust: new Set([
|
|
46911
|
-
"fn",
|
|
46912
|
-
"let",
|
|
46913
|
-
"mut",
|
|
46914
|
-
"return",
|
|
46915
|
-
"if",
|
|
46916
|
-
"else",
|
|
46917
|
-
"for",
|
|
46918
|
-
"while",
|
|
46919
|
-
"loop",
|
|
46920
|
-
"match",
|
|
46921
|
-
"struct",
|
|
46922
|
-
"enum",
|
|
46923
|
-
"impl",
|
|
46924
|
-
"trait",
|
|
46925
|
-
"pub",
|
|
46926
|
-
"use",
|
|
46927
|
-
"mod",
|
|
46928
|
-
"crate",
|
|
46929
|
-
"self",
|
|
46930
|
-
"Self",
|
|
46931
|
-
"super",
|
|
46932
|
-
"const",
|
|
46933
|
-
"static",
|
|
46934
|
-
"async",
|
|
46935
|
-
"await",
|
|
46936
|
-
"move",
|
|
46937
|
-
"ref",
|
|
46938
|
-
"dyn",
|
|
46939
|
-
"where",
|
|
46940
|
-
"unsafe",
|
|
46941
|
-
"true",
|
|
46942
|
-
"false",
|
|
46943
|
-
"None",
|
|
46944
|
-
"Some",
|
|
46945
|
-
"Ok",
|
|
46946
|
-
"Err"
|
|
46947
|
-
]),
|
|
46948
|
-
json: new Set(["true", "false", "null"])
|
|
46949
|
-
};
|
|
46950
|
-
function codeCommentPrefix(normalizedLang) {
|
|
46951
|
-
switch (normalizedLang) {
|
|
46952
|
-
case "javascript":
|
|
46953
|
-
case "typescript":
|
|
46954
|
-
case "go":
|
|
46955
|
-
case "rust":
|
|
46956
|
-
case "java":
|
|
46957
|
-
case "c":
|
|
46958
|
-
case "cpp":
|
|
46959
|
-
case "csharp":
|
|
46960
|
-
case "swift":
|
|
46961
|
-
case "kotlin":
|
|
46962
|
-
case "scala":
|
|
46963
|
-
case "php":
|
|
46964
|
-
return "//";
|
|
46965
|
-
case "python":
|
|
46966
|
-
case "bash":
|
|
46967
|
-
case "ruby":
|
|
46968
|
-
case "yaml":
|
|
46969
|
-
case "toml":
|
|
46970
|
-
case "r":
|
|
46971
|
-
case "perl":
|
|
46972
|
-
return "#";
|
|
46973
|
-
case "sql":
|
|
46974
|
-
case "lua":
|
|
46975
|
-
case "haskell":
|
|
46976
|
-
return "--";
|
|
46977
|
-
default:
|
|
46978
|
-
return "";
|
|
46979
|
-
}
|
|
46980
|
-
}
|
|
46981
|
-
function normalizeCodeLang(lang) {
|
|
46982
|
-
const key = lang.trim().toLowerCase();
|
|
46983
|
-
return CODE_LANG_ALIASES[key] ?? key;
|
|
46984
|
-
}
|
|
46985
|
-
var CODE_WORD_OR_NUMBER = /[A-Za-z_$][A-Za-z0-9_$]*|\d+(?:\.\d+)?/g;
|
|
46986
|
-
function tokenizeCodeWords(text, keywords) {
|
|
46987
|
-
const tokens = [];
|
|
46988
|
-
let last = 0;
|
|
46989
|
-
CODE_WORD_OR_NUMBER.lastIndex = 0;
|
|
46990
|
-
let m;
|
|
46991
|
-
while ((m = CODE_WORD_OR_NUMBER.exec(text)) !== null) {
|
|
46992
|
-
if (m.index > last) {
|
|
46993
|
-
tokens.push({ kind: "plain", text: text.slice(last, m.index) });
|
|
46994
|
-
}
|
|
46995
|
-
const word = m[0];
|
|
46996
|
-
if (/^[0-9]/.test(word)) {
|
|
46997
|
-
tokens.push({ kind: "number", text: word });
|
|
46998
|
-
} else if (keywords.has(word)) {
|
|
46999
|
-
tokens.push({ kind: "keyword", text: word });
|
|
47000
|
-
} else {
|
|
47001
|
-
tokens.push({ kind: "plain", text: word });
|
|
47002
|
-
}
|
|
47003
|
-
last = m.index + word.length;
|
|
47004
|
-
}
|
|
47005
|
-
if (last < text.length) {
|
|
47006
|
-
tokens.push({ kind: "plain", text: text.slice(last) });
|
|
47007
|
-
}
|
|
47008
|
-
return tokens;
|
|
47009
|
-
}
|
|
47010
|
-
function tokenizeCodeLine(line, lang) {
|
|
47011
|
-
const normalized = normalizeCodeLang(lang);
|
|
47012
|
-
const keywords = CODE_LANG_KEYWORDS[normalized] ?? GENERIC_KEYWORDS;
|
|
47013
|
-
const commentPrefix = codeCommentPrefix(normalized);
|
|
47014
|
-
const tokens = [];
|
|
47015
|
-
let plainBuf = "";
|
|
47016
|
-
const flushPlain = () => {
|
|
47017
|
-
if (plainBuf.length > 0) {
|
|
47018
|
-
tokens.push(...tokenizeCodeWords(plainBuf, keywords));
|
|
47019
|
-
plainBuf = "";
|
|
47020
|
-
}
|
|
47021
|
-
};
|
|
47022
|
-
let i = 0;
|
|
47023
|
-
while (i < line.length) {
|
|
47024
|
-
if (commentPrefix.length > 0 && line.startsWith(commentPrefix, i)) {
|
|
47025
|
-
flushPlain();
|
|
47026
|
-
tokens.push({ kind: "comment", text: line.slice(i) });
|
|
47027
|
-
break;
|
|
47028
|
-
}
|
|
47029
|
-
const ch = line[i];
|
|
47030
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
47031
|
-
flushPlain();
|
|
47032
|
-
let j = i + 1;
|
|
47033
|
-
while (j < line.length && line[j] !== ch) {
|
|
47034
|
-
j += line[j] === "\\" ? 2 : 1;
|
|
47035
|
-
}
|
|
47036
|
-
j = Math.min(j + 1, line.length);
|
|
47037
|
-
tokens.push({ kind: "string", text: line.slice(i, j) });
|
|
47038
|
-
i = j;
|
|
47039
|
-
continue;
|
|
47040
|
-
}
|
|
47041
|
-
plainBuf += ch;
|
|
47042
|
-
i += 1;
|
|
47043
|
-
}
|
|
47044
|
-
flushPlain();
|
|
47045
|
-
return tokens;
|
|
47046
|
-
}
|
|
47047
|
-
function payloadKind(lang, _lineCount) {
|
|
47048
|
-
const normalized = lang.toLowerCase();
|
|
47049
|
-
if (MARKDOWN_LANGS.has(normalized)) {
|
|
47050
|
-
return "markdown";
|
|
47051
|
-
}
|
|
47052
|
-
if (DIFF_LANGS.has(normalized)) {
|
|
47053
|
-
return "diff";
|
|
47054
|
-
}
|
|
47055
|
-
return "code";
|
|
47056
|
-
}
|
|
47057
|
-
function blockLabel({ kind, lineCount, collapsed, hint }) {
|
|
47058
|
-
const marker = collapsed ? "\u25B8" : "\u25BE";
|
|
47059
|
-
const unit = lineCount === 1 ? "line" : "lines";
|
|
47060
|
-
const suffix = hint !== undefined && hint.length > 0 ? ` \xB7 ${hint}` : "";
|
|
47061
|
-
return `${marker} ${kind} (${lineCount} ${unit})${suffix}`;
|
|
47062
|
-
}
|
|
47063
|
-
function summarizeSubmittedLine(line) {
|
|
47064
|
-
const normalized = line.replace(/\r\n/g, `
|
|
47065
|
-
`).replace(/\r/g, `
|
|
47066
|
-
`);
|
|
47067
|
-
const nonEmpty = normalized.split(`
|
|
47068
|
-
`).filter((linePart) => linePart.length > 0);
|
|
47069
|
-
if (nonEmpty.length <= 1) {
|
|
47070
|
-
return line;
|
|
47071
|
-
}
|
|
47072
|
-
const [first, ...rest] = nonEmpty;
|
|
47073
|
-
return `${first} [+ ${rest.length} pasted line${rest.length === 1 ? "" : "s"}]`;
|
|
47074
|
-
}
|
|
47075
|
-
function isZeroWidth(cp) {
|
|
47076
|
-
return cp >= 768 && cp <= 879 || cp >= 8203 && cp <= 8207 || cp >= 65024 && cp <= 65039 || cp >= 8288 && cp <= 8292;
|
|
47077
|
-
}
|
|
47078
|
-
var WIDE_RANGES = [
|
|
47079
|
-
[4352, 4447],
|
|
47080
|
-
[11904, 12350],
|
|
47081
|
-
[12353, 13311],
|
|
47082
|
-
[13312, 19903],
|
|
47083
|
-
[19968, 40959],
|
|
47084
|
-
[40960, 42191],
|
|
47085
|
-
[44032, 55203],
|
|
47086
|
-
[63744, 64255],
|
|
47087
|
-
[65072, 65135],
|
|
47088
|
-
[65280, 65376],
|
|
47089
|
-
[65504, 65510],
|
|
47090
|
-
[127744, 128591],
|
|
47091
|
-
[129280, 129535],
|
|
47092
|
-
[131072, 262141]
|
|
47093
|
-
];
|
|
47094
|
-
function codePointWidth(cp) {
|
|
47095
|
-
for (const [lo, hi] of WIDE_RANGES) {
|
|
47096
|
-
if (cp >= lo && cp <= hi) {
|
|
47097
|
-
return 2;
|
|
47098
|
-
}
|
|
47099
|
-
}
|
|
47100
|
-
return 1;
|
|
47101
|
-
}
|
|
47102
|
-
function visualWidth(text) {
|
|
47103
|
-
let width = 0;
|
|
47104
|
-
for (const ch of text) {
|
|
47105
|
-
const codePoint = ch.codePointAt(0) ?? 0;
|
|
47106
|
-
if (isZeroWidth(codePoint)) {
|
|
47107
|
-
continue;
|
|
47108
|
-
}
|
|
47109
|
-
width += codePointWidth(codePoint);
|
|
47110
|
-
}
|
|
47111
|
-
return width;
|
|
47112
|
-
}
|
|
47113
|
-
function hugWidth(text, chrome) {
|
|
47114
|
-
const lines = splitLines(text.replace(/\n+$/, ""));
|
|
47115
|
-
let widest = 0;
|
|
47116
|
-
for (const line of lines) {
|
|
47117
|
-
widest = Math.max(widest, visualWidth(line));
|
|
47118
|
-
}
|
|
47119
|
-
return widest + chrome;
|
|
47120
|
-
}
|
|
47121
|
-
|
|
47122
|
-
// src/lib/ui.ts
|
|
47123
|
-
function colorEnabled() {
|
|
47124
|
-
if (process.env.NO_COLOR !== undefined) {
|
|
47125
|
-
return false;
|
|
47126
|
-
}
|
|
47127
|
-
if (process.env.FORCE_COLOR !== undefined) {
|
|
47128
|
-
return true;
|
|
47129
|
-
}
|
|
47130
|
-
return Boolean(stdout.isTTY);
|
|
47131
|
-
}
|
|
47132
|
-
function wrap(open, close) {
|
|
47133
|
-
return (text) => colorEnabled() ? `\x1B[${open}m${text}\x1B[${close}m` : text;
|
|
47134
|
-
}
|
|
47135
|
-
var style = {
|
|
47136
|
-
bold: wrap(1, 22),
|
|
47137
|
-
dim: wrap(2, 22),
|
|
47138
|
-
red: wrap(31, 39),
|
|
47139
|
-
green: wrap(32, 39),
|
|
47140
|
-
yellow: wrap(33, 39),
|
|
47141
|
-
cyan: wrap(36, 39),
|
|
47142
|
-
gray: wrap(90, 39)
|
|
47143
|
-
};
|
|
47144
|
-
var symbols = {
|
|
47145
|
-
ok: "\u2713",
|
|
47146
|
-
cross: "\u2717",
|
|
47147
|
-
off: "\xB7",
|
|
47148
|
-
arrow: "\u2192",
|
|
47149
|
-
bullet: "\u2022"
|
|
47150
|
-
};
|
|
47151
|
-
var RULE_WIDTH = 52;
|
|
47152
|
-
function banner(title, subtitle) {
|
|
47153
|
-
const rule = style.cyan("\u2501".repeat(RULE_WIDTH));
|
|
47154
|
-
console.log(rule);
|
|
47155
|
-
console.log(` ${style.bold(title)}`);
|
|
47156
|
-
if (subtitle) {
|
|
47157
|
-
console.log(` ${style.dim(subtitle)}`);
|
|
47158
|
-
}
|
|
47159
|
-
console.log(rule);
|
|
47160
|
-
}
|
|
47161
|
-
function heading(title, emit = console.log) {
|
|
47162
|
-
emit(`
|
|
47163
|
-
${style.bold(style.cyan(title))}`);
|
|
47164
|
-
}
|
|
47165
|
-
function statusLine(label, enabled, detail) {
|
|
47166
|
-
const marker = enabled ? style.green(symbols.ok) : style.gray(symbols.off);
|
|
47167
|
-
const name = enabled ? label : style.gray(label);
|
|
47168
|
-
const suffix = detail ? style.dim(` (${detail})`) : "";
|
|
47169
|
-
console.log(` ${marker} ${name}${suffix}`);
|
|
47170
|
-
}
|
|
47171
|
-
function nextSteps(steps) {
|
|
47172
|
-
if (steps.length === 0) {
|
|
47173
|
-
return;
|
|
47174
|
-
}
|
|
47175
|
-
heading("Next steps");
|
|
47176
|
-
for (const step of steps) {
|
|
47177
|
-
console.log(` ${style.cyan(symbols.arrow)} ${step}`);
|
|
47178
|
-
}
|
|
47179
|
-
}
|
|
47180
|
-
function note(message) {
|
|
47181
|
-
console.log(` ${style.dim(message)}`);
|
|
47182
|
-
}
|
|
47183
|
-
function renderInline(text) {
|
|
47184
|
-
const withCode = text.replace(/`([^`]+)`/g, (_match, code) => style.gray(code));
|
|
47185
|
-
return withCode.replace(/\*\*([^*]+)\*\*/g, (_match, bold) => style.bold(bold));
|
|
47186
|
-
}
|
|
47187
|
-
function renderTextLine(line) {
|
|
47188
|
-
const heading = /^#{1,6}\s+(.*)$/.exec(line);
|
|
47189
|
-
if (heading !== null) {
|
|
47190
|
-
return style.bold(style.cyan(heading[1] ?? ""));
|
|
47191
|
-
}
|
|
47192
|
-
const bullet = /^(\s*)[-*]\s+(.*)$/.exec(line);
|
|
47193
|
-
if (bullet !== null) {
|
|
47194
|
-
return `${bullet[1] ?? ""}${style.cyan(symbols.bullet)} ${renderInline(bullet[2] ?? "")}`;
|
|
47195
|
-
}
|
|
47196
|
-
return renderInline(line);
|
|
47197
|
-
}
|
|
47198
|
-
function renderDiff(text) {
|
|
47199
|
-
if (!colorEnabled()) {
|
|
47200
|
-
return text;
|
|
47201
|
-
}
|
|
47202
|
-
return splitLines(text).map((line) => {
|
|
47203
|
-
switch (classifyDiffLine(line)) {
|
|
47204
|
-
case "add":
|
|
47205
|
-
return style.green(line);
|
|
47206
|
-
case "del":
|
|
47207
|
-
return style.red(line);
|
|
47208
|
-
case "hunk":
|
|
47209
|
-
return style.cyan(line);
|
|
47210
|
-
case "meta":
|
|
47211
|
-
return style.dim(line);
|
|
47212
|
-
default:
|
|
47213
|
-
return line;
|
|
47214
|
-
}
|
|
47215
|
-
}).join(`
|
|
47216
|
-
`);
|
|
47217
|
-
}
|
|
47218
|
-
function renderCodeSegment(lang, body) {
|
|
47219
|
-
const lines = body.split(`
|
|
47220
|
-
`);
|
|
47221
|
-
const out = [];
|
|
47222
|
-
if (lang.length > 0) {
|
|
47223
|
-
out.push(style.dim(lang));
|
|
47224
|
-
}
|
|
47225
|
-
if (payloadKind(lang, lines.length) === "diff" || looksLikeUnifiedDiff(body)) {
|
|
47226
|
-
out.push(renderDiff(body));
|
|
47227
|
-
return out;
|
|
47228
|
-
}
|
|
47229
|
-
for (const line of lines) {
|
|
47230
|
-
out.push(style.gray(line));
|
|
47231
|
-
}
|
|
47232
|
-
return out;
|
|
47233
|
-
}
|
|
47234
|
-
function renderMarkdown(md) {
|
|
47235
|
-
if (!colorEnabled()) {
|
|
47236
|
-
return md;
|
|
47237
|
-
}
|
|
47238
|
-
const out = [];
|
|
47239
|
-
for (const segment of segmentMarkdown(md)) {
|
|
47240
|
-
if (segment.kind === "code") {
|
|
47241
|
-
out.push(...renderCodeSegment(segment.lang, segment.body));
|
|
47242
|
-
continue;
|
|
47243
|
-
}
|
|
47244
|
-
for (const line of segment.text.split(`
|
|
47245
|
-
`)) {
|
|
47246
|
-
out.push(renderTextLine(line));
|
|
47247
|
-
}
|
|
47248
|
-
}
|
|
47249
|
-
return out.join(`
|
|
47250
|
-
`);
|
|
47251
|
-
}
|
|
47252
|
-
function collapseToolOutput(text, maxWidth = 100) {
|
|
47253
|
-
const trimmed = text.replace(/\r?\n+$/, "");
|
|
47254
|
-
const lines = trimmed.length === 0 ? [] : splitLines(trimmed);
|
|
47255
|
-
const firstNonEmpty = lines.find((line) => line.trim().length > 0) ?? "";
|
|
47256
|
-
const summary = firstNonEmpty.length > maxWidth ? `${firstNonEmpty.slice(0, maxWidth)}\u2026` : firstNonEmpty;
|
|
47257
|
-
const lineCount = lines.length;
|
|
47258
|
-
return { summary, lineCount, hidden: Math.max(0, lineCount - 1) };
|
|
47259
|
-
}
|
|
47260
|
-
function indentBlock(text, pad) {
|
|
47261
|
-
return text.split(`
|
|
47262
|
-
`).map((line) => line.length > 0 ? pad + line : line).join(`
|
|
47263
|
-
`);
|
|
47264
|
-
}
|
|
47265
|
-
function summarizeToolArgs(input, max = 80) {
|
|
47266
|
-
const clip = (s) => s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
47267
|
-
const raw = input.trim();
|
|
47268
|
-
if (raw.length === 0) {
|
|
47269
|
-
return "";
|
|
47270
|
-
}
|
|
47271
|
-
let parsed;
|
|
47272
|
-
try {
|
|
47273
|
-
parsed = JSON.parse(raw);
|
|
47274
|
-
} catch {
|
|
47275
|
-
return clip(raw);
|
|
47276
|
-
}
|
|
47277
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47278
|
-
return clip(raw);
|
|
47279
|
-
}
|
|
47280
|
-
const parts = [];
|
|
47281
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
47282
|
-
let shown;
|
|
47283
|
-
if (value === null) {
|
|
47284
|
-
shown = "null";
|
|
47285
|
-
} else if (Array.isArray(value)) {
|
|
47286
|
-
shown = "[\u2026]";
|
|
47287
|
-
} else if (typeof value === "object") {
|
|
47288
|
-
shown = "{\u2026}";
|
|
47289
|
-
} else {
|
|
47290
|
-
shown = String(value);
|
|
47291
|
-
}
|
|
47292
|
-
parts.push(`${key}=${shown}`);
|
|
47293
|
-
}
|
|
47294
|
-
return clip(parts.join(", "));
|
|
47295
|
-
}
|
|
47296
|
-
function helpTitle(command, summary) {
|
|
47297
|
-
console.log(`${style.bold(command)} ${style.dim(`\u2014 ${summary}`)}`);
|
|
47298
|
-
}
|
|
47299
|
-
function helpUsage(lines) {
|
|
47300
|
-
heading("Usage");
|
|
47301
|
-
for (const line of lines) {
|
|
47302
|
-
console.log(` ${style.cyan(line)}`);
|
|
47303
|
-
}
|
|
47304
|
-
}
|
|
47305
|
-
function helpOptions(options) {
|
|
47306
|
-
heading("Options");
|
|
47307
|
-
const width = Math.max(...options.map((option) => option.flag.length));
|
|
47308
|
-
for (const option of options) {
|
|
47309
|
-
console.log(` ${style.cyan(option.flag.padEnd(width))} ${style.dim(option.desc)}`);
|
|
47310
|
-
}
|
|
47311
|
-
}
|
|
47312
|
-
|
|
47313
47580
|
// src/commands/projects.ts
|
|
47581
|
+
init_ui();
|
|
47314
47582
|
async function projectsCommand(args = []) {
|
|
47315
47583
|
const sub = args[0];
|
|
47316
47584
|
if (args.includes("--help") || args.includes("-h") || sub === "help") {
|
|
@@ -47512,40 +47780,9 @@ var GDGRAPH_CORE_SOURCES = [
|
|
|
47512
47780
|
"types.ts"
|
|
47513
47781
|
];
|
|
47514
47782
|
|
|
47515
|
-
// src/
|
|
47516
|
-
|
|
47517
|
-
|
|
47518
|
-
async function confirm(question, defaultValue = false) {
|
|
47519
|
-
if (!input.isTTY) {
|
|
47520
|
-
return defaultValue;
|
|
47521
|
-
}
|
|
47522
|
-
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
47523
|
-
const rl = readline.createInterface({ input, output });
|
|
47524
|
-
try {
|
|
47525
|
-
const answer = await rl.question(`${style.cyan(symbols.arrow)} ${question} ${style.dim(`(${suffix})`)} `);
|
|
47526
|
-
const normalized = answer.trim().toLowerCase();
|
|
47527
|
-
if (!normalized) {
|
|
47528
|
-
return defaultValue;
|
|
47529
|
-
}
|
|
47530
|
-
return normalized === "y" || normalized === "yes";
|
|
47531
|
-
} finally {
|
|
47532
|
-
rl.close();
|
|
47533
|
-
}
|
|
47534
|
-
}
|
|
47535
|
-
async function choice(question, choices, defaultValue) {
|
|
47536
|
-
if (!input.isTTY) {
|
|
47537
|
-
return defaultValue;
|
|
47538
|
-
}
|
|
47539
|
-
const rl = readline.createInterface({ input, output });
|
|
47540
|
-
try {
|
|
47541
|
-
const answer = await rl.question(`${style.cyan(symbols.arrow)} ${question} ${style.dim(`(${choices.join("/")}; default: ${defaultValue})`)} `);
|
|
47542
|
-
const normalized = answer.trim().toLowerCase();
|
|
47543
|
-
const match = choices.find((item) => item.toLowerCase() === normalized);
|
|
47544
|
-
return match ?? defaultValue;
|
|
47545
|
-
} finally {
|
|
47546
|
-
rl.close();
|
|
47547
|
-
}
|
|
47548
|
-
}
|
|
47783
|
+
// src/commands/init.ts
|
|
47784
|
+
init_prompt();
|
|
47785
|
+
init_ui();
|
|
47549
47786
|
|
|
47550
47787
|
// src/lib/project-sandbox-policy.ts
|
|
47551
47788
|
init_paths();
|
|
@@ -53009,7 +53246,7 @@ import path47 from "path";
|
|
|
53009
53246
|
// package.json
|
|
53010
53247
|
var package_default = {
|
|
53011
53248
|
name: "@mrciphersmith/keryx",
|
|
53012
|
-
version: "0.2.
|
|
53249
|
+
version: "0.2.161",
|
|
53013
53250
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
53014
53251
|
private: false,
|
|
53015
53252
|
publishConfig: {
|
|
@@ -61452,6 +61689,9 @@ function normalizeRouteText(value) {
|
|
|
61452
61689
|
return value.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
61453
61690
|
}
|
|
61454
61691
|
|
|
61692
|
+
// src/commands/skills.ts
|
|
61693
|
+
init_ui();
|
|
61694
|
+
|
|
61455
61695
|
// src/gdskills/learn.ts
|
|
61456
61696
|
init_fs();
|
|
61457
61697
|
init_json();
|
|
@@ -66038,9 +66278,10 @@ init_github();
|
|
|
66038
66278
|
init_flow_move();
|
|
66039
66279
|
init_service7();
|
|
66040
66280
|
init_guard2();
|
|
66281
|
+
init_ui();
|
|
66282
|
+
init_machine();
|
|
66041
66283
|
import { randomInt } from "crypto";
|
|
66042
66284
|
import path155 from "path";
|
|
66043
|
-
init_machine();
|
|
66044
66285
|
|
|
66045
66286
|
// src/flow/types.ts
|
|
66046
66287
|
var ATTEMPT_CLI_OUTCOMES = ["started", "failed", "blocked"];
|
|
@@ -67281,6 +67522,7 @@ function createJobService(deps) {
|
|
|
67281
67522
|
}
|
|
67282
67523
|
|
|
67283
67524
|
// src/commands/job.ts
|
|
67525
|
+
init_ui();
|
|
67284
67526
|
function positional2(args, index) {
|
|
67285
67527
|
const value = args[index];
|
|
67286
67528
|
return value === undefined || value.startsWith("--") ? undefined : value;
|
|
@@ -73906,6 +74148,7 @@ Options:
|
|
|
73906
74148
|
}
|
|
73907
74149
|
|
|
73908
74150
|
// src/commands/standard.ts
|
|
74151
|
+
init_ui();
|
|
73909
74152
|
import { mkdir as mkdir59, writeFile as writeFile49 } from "fs/promises";
|
|
73910
74153
|
import path166 from "path";
|
|
73911
74154
|
|
|
@@ -74308,15 +74551,15 @@ function extractCapabilities(manifest) {
|
|
|
74308
74551
|
|
|
74309
74552
|
// src/standard/help-groups.ts
|
|
74310
74553
|
var HELP_GROUP_ORDER = [
|
|
74311
|
-
{ name: "Start here", slug: "start-here" },
|
|
74312
|
-
{ name: "Connect a model provider", slug: "connect" },
|
|
74313
|
-
{ name: "Look and feel", slug: "look-and-feel" },
|
|
74314
|
-
{ name: "Working in keryx shell", slug: "shell-work" },
|
|
74315
|
-
{ name: "Project knowledge", slug: "project-knowledge" },
|
|
74316
|
-
{ name: "Managed work", slug: "managed-work" },
|
|
74317
|
-
{ name: "Automation", slug: "automation" },
|
|
74318
|
-
{ name: "External agents, ACP and MCP", slug: "external-agents" },
|
|
74319
|
-
{ name: "Maintenance and diagnostics", slug: "maintenance" }
|
|
74554
|
+
{ name: "Start here", slug: "start-here", tab: "Start", tabShort: "Start" },
|
|
74555
|
+
{ name: "Connect a model provider", slug: "connect", tab: "Connect", tabShort: "Conn" },
|
|
74556
|
+
{ name: "Look and feel", slug: "look-and-feel", tab: "Look", tabShort: "Look" },
|
|
74557
|
+
{ name: "Working in keryx shell", slug: "shell-work", tab: "Shell", tabShort: "Shell" },
|
|
74558
|
+
{ name: "Project knowledge", slug: "project-knowledge", tab: "Knowledge", tabShort: "Know" },
|
|
74559
|
+
{ name: "Managed work", slug: "managed-work", tab: "Work", tabShort: "Work" },
|
|
74560
|
+
{ name: "Automation", slug: "automation", tab: "Automate", tabShort: "Auto" },
|
|
74561
|
+
{ name: "External agents, ACP and MCP", slug: "external-agents", tab: "Agents", tabShort: "Agents" },
|
|
74562
|
+
{ name: "Maintenance and diagnostics", slug: "maintenance", tab: "Maintain", tabShort: "Maint" }
|
|
74320
74563
|
];
|
|
74321
74564
|
var HELP_GROUPS = [
|
|
74322
74565
|
{ kind: "cli", name: "init", group: "Start here", summary: "Initialize .metaproject in the current project." },
|
|
@@ -74346,7 +74589,7 @@ var HELP_GROUPS = [
|
|
|
74346
74589
|
group: "Connect a model provider",
|
|
74347
74590
|
summary: "Providers this operator has configured, and cross-family review eligibility."
|
|
74348
74591
|
},
|
|
74349
|
-
{ kind: "slash", name: "/connect", group: "Connect a model provider", summary: "Switch provider /
|
|
74592
|
+
{ kind: "slash", name: "/connect", group: "Connect a model provider", summary: "Switch provider; row buttons test/disconnect it." },
|
|
74350
74593
|
{
|
|
74351
74594
|
kind: "slash",
|
|
74352
74595
|
name: "/search-provider",
|
|
@@ -74762,15 +75005,29 @@ function renderEntryRows(entries, maxColumns) {
|
|
|
74762
75005
|
}
|
|
74763
75006
|
return rows;
|
|
74764
75007
|
}
|
|
75008
|
+
var SHELL_SUBHEADING = " in keryx shell:";
|
|
75009
|
+
function renderGroupLines(group, maxColumns) {
|
|
75010
|
+
const cliEntries = entriesInGroup(group.name, "cli");
|
|
75011
|
+
const slashEntries = entriesInGroup(group.name, "slash");
|
|
75012
|
+
if (cliEntries.length === 0 && slashEntries.length === 0) {
|
|
75013
|
+
return [];
|
|
75014
|
+
}
|
|
75015
|
+
const lines = [`${group.name}:`];
|
|
75016
|
+
lines.push(...renderEntryRows(cliEntries, maxColumns));
|
|
75017
|
+
if (slashEntries.length > 0) {
|
|
75018
|
+
lines.push(SHELL_SUBHEADING);
|
|
75019
|
+
lines.push(...renderEntryRows(slashEntries, maxColumns));
|
|
75020
|
+
}
|
|
75021
|
+
return lines;
|
|
75022
|
+
}
|
|
74765
75023
|
function renderGroupedCliHelp(maxColumns = HELP_DEFAULT_COLUMNS) {
|
|
74766
75024
|
const lines = [];
|
|
74767
75025
|
for (const group of HELP_GROUP_ORDER) {
|
|
74768
|
-
const
|
|
74769
|
-
if (
|
|
75026
|
+
const groupLines = renderGroupLines(group, maxColumns);
|
|
75027
|
+
if (groupLines.length === 0) {
|
|
74770
75028
|
continue;
|
|
74771
75029
|
}
|
|
74772
|
-
lines.push(
|
|
74773
|
-
lines.push(...renderEntryRows(entries, maxColumns));
|
|
75030
|
+
lines.push(...groupLines);
|
|
74774
75031
|
lines.push("");
|
|
74775
75032
|
}
|
|
74776
75033
|
return lines.join(`
|
|
@@ -74778,8 +75035,7 @@ function renderGroupedCliHelp(maxColumns = HELP_DEFAULT_COLUMNS) {
|
|
|
74778
75035
|
`);
|
|
74779
75036
|
}
|
|
74780
75037
|
function renderCliGroupHelp(group, maxColumns = HELP_DEFAULT_COLUMNS) {
|
|
74781
|
-
const
|
|
74782
|
-
const lines = [`${group.name}:`, ...renderEntryRows(entries, maxColumns)];
|
|
75038
|
+
const lines = renderGroupLines(group, maxColumns);
|
|
74783
75039
|
return lines.join(`
|
|
74784
75040
|
`) + `
|
|
74785
75041
|
`;
|
|
@@ -74798,12 +75054,12 @@ function renderGroupedNamedHelp(options, maxColumns) {
|
|
|
74798
75054
|
`) + `
|
|
74799
75055
|
`;
|
|
74800
75056
|
}
|
|
74801
|
-
function renderEntryDetail(entry) {
|
|
74802
|
-
const lines = [`${entry.name}`, "", entry.summary, ""];
|
|
75057
|
+
function renderEntryDetail(entry, maxColumns = HELP_DEFAULT_COLUMNS) {
|
|
75058
|
+
const lines = [`${entry.name}`, "", ...wrapWords(entry.summary, maxColumns), ""];
|
|
74803
75059
|
if (entry.kind === "slash") {
|
|
74804
|
-
lines.push("This is a keryx shell command \u2014 type it inside `keryx shell` (readline chat/agent mode, or
|
|
75060
|
+
lines.push(...wrapWords("This is a keryx shell command \u2014 type it inside `keryx shell` (readline chat/agent mode, or " + "the TUI's `/`-menu). It has no standalone CLI form.", maxColumns));
|
|
74805
75061
|
} else {
|
|
74806
|
-
lines.push(`Run \`keryx ${entry.name} --help\` (or \`keryx help ${entry.name}\`) for its full usage
|
|
75062
|
+
lines.push(...wrapWords(`Run \`keryx ${entry.name} --help\` (or \`keryx help ${entry.name}\`) for its full usage.`, maxColumns));
|
|
74807
75063
|
}
|
|
74808
75064
|
return lines.join(`
|
|
74809
75065
|
`) + `
|
|
@@ -76561,13 +76817,14 @@ Usage:
|
|
|
76561
76817
|
}
|
|
76562
76818
|
|
|
76563
76819
|
// src/commands/security.ts
|
|
76564
|
-
|
|
76565
|
-
import path168 from "path";
|
|
76566
|
-
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
76820
|
+
init_ui();
|
|
76567
76821
|
init_args();
|
|
76568
76822
|
init_contained_path();
|
|
76569
76823
|
init_fs();
|
|
76570
76824
|
init_json();
|
|
76825
|
+
import { mkdir as mkdir60, readdir as readdir33, readFile as readFile96, writeFile as writeFile50 } from "fs/promises";
|
|
76826
|
+
import path168 from "path";
|
|
76827
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
76571
76828
|
|
|
76572
76829
|
// src/security/detect/mcp.ts
|
|
76573
76830
|
import { createHash as createHash31 } from "crypto";
|
|
@@ -77906,8 +78163,10 @@ async function sandboxCommand(args = [], deps = {}) {
|
|
|
77906
78163
|
|
|
77907
78164
|
// src/commands/mcp.ts
|
|
77908
78165
|
init_args();
|
|
78166
|
+
init_ui();
|
|
77909
78167
|
|
|
77910
78168
|
// src/commands/integrate.ts
|
|
78169
|
+
init_ui();
|
|
77911
78170
|
import path170 from "path";
|
|
77912
78171
|
var EDITOR_USAGE = `<cursor|claude|opencode|vscode|generic|all>`;
|
|
77913
78172
|
var DEFAULT_EDITORS = ["all"];
|
|
@@ -81231,6 +81490,7 @@ function positional3(args) {
|
|
|
81231
81490
|
|
|
81232
81491
|
// src/commands/serve-mcp.ts
|
|
81233
81492
|
init_args();
|
|
81493
|
+
init_ui();
|
|
81234
81494
|
import path183 from "path";
|
|
81235
81495
|
|
|
81236
81496
|
// src/mcp/discovery.ts
|
|
@@ -99820,6 +100080,7 @@ async function approveExternalSpawn(request) {
|
|
|
99820
100080
|
init_caller_session();
|
|
99821
100081
|
|
|
99822
100082
|
// src/lib/statusbar.ts
|
|
100083
|
+
init_ui();
|
|
99823
100084
|
import { homedir as homedir10 } from "os";
|
|
99824
100085
|
var ESC = "\x1B";
|
|
99825
100086
|
var CSI = `${ESC}[`;
|
|
@@ -100821,6 +101082,7 @@ function boldChunk(otui, text) {
|
|
|
100821
101082
|
}
|
|
100822
101083
|
|
|
100823
101084
|
// src/tui/transcript-blocks.ts
|
|
101085
|
+
init_md_blocks();
|
|
100824
101086
|
var EVICTED_BLOCK_TEXT = "(output no longer retained)";
|
|
100825
101087
|
var UNKNOWN_BLOCK_TEXT = "(no such block)";
|
|
100826
101088
|
var TRUNCATED_BLOCK_NOTICE = "\u2026 (output truncated at the retention cap)";
|
|
@@ -109997,7 +110259,32 @@ function openSessionInfo(otui, chrome, options) {
|
|
|
109997
110259
|
// src/tui/help-modal.ts
|
|
109998
110260
|
var HELP_MODAL_TITLE = "/help";
|
|
109999
110261
|
var HELP_MODAL_KEYS = "keys: \u2191/\u2193 select \xB7 enter details \xB7 \u2190/\u2192 tabs \xB7 esc close";
|
|
110000
|
-
var HELP_MODAL_TABS = HELP_GROUP_ORDER.map((g) => ({ id: g.slug, label: g.
|
|
110262
|
+
var HELP_MODAL_TABS = HELP_GROUP_ORDER.map((g) => ({ id: g.slug, label: g.tab }));
|
|
110263
|
+
var HELP_MODAL_TABS_SHORT = HELP_GROUP_ORDER.map((g) => ({ id: g.slug, label: g.tabShort }));
|
|
110264
|
+
function tabStripWidth(labels) {
|
|
110265
|
+
return labels.reduce((total, label, i) => total + label.length + 2 + (i === 0 ? 0 : 1), 0);
|
|
110266
|
+
}
|
|
110267
|
+
var READABLE_TAB_STRIP_WIDTH = tabStripWidth(HELP_MODAL_TABS.map((t) => t.label));
|
|
110268
|
+
function pickTabs(chrome, options) {
|
|
110269
|
+
let cols;
|
|
110270
|
+
let rows;
|
|
110271
|
+
if (options.renderer !== undefined) {
|
|
110272
|
+
cols = options.renderer.width;
|
|
110273
|
+
rows = options.renderer.height;
|
|
110274
|
+
} else {
|
|
110275
|
+
const c = chrome;
|
|
110276
|
+
if (c?.renderer !== undefined) {
|
|
110277
|
+
cols = resolveModalAvailableWidth(c);
|
|
110278
|
+
rows = c.renderer.height;
|
|
110279
|
+
}
|
|
110280
|
+
}
|
|
110281
|
+
if (typeof cols !== "number" || typeof rows !== "number") {
|
|
110282
|
+
return HELP_MODAL_TABS_SHORT;
|
|
110283
|
+
}
|
|
110284
|
+
const panelWidth = Math.min(cols, resolveModalPanelSize(cols, rows).width);
|
|
110285
|
+
const innerWidth = panelWidth - MODAL_PANEL_CHROME_X;
|
|
110286
|
+
return READABLE_TAB_STRIP_WIDTH <= innerWidth ? HELP_MODAL_TABS : HELP_MODAL_TABS_SHORT;
|
|
110287
|
+
}
|
|
110001
110288
|
function entriesForGroup(group) {
|
|
110002
110289
|
return [...entriesInGroup(group.name, "cli"), ...entriesInGroup(group.name, "slash")];
|
|
110003
110290
|
}
|
|
@@ -110010,9 +110297,10 @@ function openHelpModal(otui, chrome, options) {
|
|
|
110010
110297
|
const core = otui;
|
|
110011
110298
|
const r = chrome?.renderer;
|
|
110012
110299
|
const bodyRows = bodyRowsFor2(chrome, options);
|
|
110300
|
+
const tabs = pickTabs(chrome, options);
|
|
110013
110301
|
const states = new Map(HELP_GROUP_ORDER.map((group) => [
|
|
110014
110302
|
group.slug,
|
|
110015
|
-
{ entries: entriesForGroup(group), selected: 0, detail: false, scroll: 0 }
|
|
110303
|
+
{ groupName: group.name, entries: entriesForGroup(group), selected: 0, detail: false, scroll: 0 }
|
|
110016
110304
|
]));
|
|
110017
110305
|
let width;
|
|
110018
110306
|
let bodyNode;
|
|
@@ -110020,23 +110308,29 @@ function openHelpModal(otui, chrome, options) {
|
|
|
110020
110308
|
const keys = {};
|
|
110021
110309
|
const host = {};
|
|
110022
110310
|
const activeState = () => {
|
|
110023
|
-
const id = host.handle?.activeTab() ?? (
|
|
110311
|
+
const id = host.handle?.activeTab() ?? (tabs[0]?.id ?? "");
|
|
110024
110312
|
const existing = states.get(id);
|
|
110025
110313
|
if (existing !== undefined) {
|
|
110026
110314
|
return existing;
|
|
110027
110315
|
}
|
|
110028
|
-
const fallback = { entries: [], selected: 0, detail: false, scroll: 0 };
|
|
110316
|
+
const fallback = { groupName: "", entries: [], selected: 0, detail: false, scroll: 0 };
|
|
110029
110317
|
states.set(id, fallback);
|
|
110030
110318
|
return fallback;
|
|
110031
110319
|
};
|
|
110032
110320
|
const tabLines = (state) => {
|
|
110321
|
+
const heading = `${state.groupName}:`;
|
|
110033
110322
|
if (state.entries.length === 0) {
|
|
110034
|
-
return [HELP_MODAL_KEYS, "", "(no commands in this group)"];
|
|
110323
|
+
return [heading, HELP_MODAL_KEYS, "", "(no commands in this group)"];
|
|
110035
110324
|
}
|
|
110036
110325
|
if (state.detail) {
|
|
110037
110326
|
const entry = state.entries[state.selected];
|
|
110038
|
-
return [
|
|
110039
|
-
|
|
110327
|
+
return [
|
|
110328
|
+
heading,
|
|
110329
|
+
HELP_MODAL_KEYS,
|
|
110330
|
+
"",
|
|
110331
|
+
...entry === undefined ? ["(nothing selected)"] : wrapLines(renderEntryDetail(entry), width).split(`
|
|
110332
|
+
`)
|
|
110333
|
+
];
|
|
110040
110334
|
}
|
|
110041
110335
|
const rows = state.entries.map((entry, i) => {
|
|
110042
110336
|
const marker = i === state.selected ? ">" : " ";
|
|
@@ -110045,7 +110339,7 @@ function openHelpModal(otui, chrome, options) {
|
|
|
110045
110339
|
`).join(`
|
|
110046
110340
|
`);
|
|
110047
110341
|
});
|
|
110048
|
-
return [HELP_MODAL_KEYS, "", ...rows];
|
|
110342
|
+
return [heading, HELP_MODAL_KEYS, "", ...rows];
|
|
110049
110343
|
};
|
|
110050
110344
|
const paint = () => {
|
|
110051
110345
|
if (closed) {
|
|
@@ -110054,7 +110348,7 @@ function openHelpModal(otui, chrome, options) {
|
|
|
110054
110348
|
const state = activeState();
|
|
110055
110349
|
const lines = tabLines(state);
|
|
110056
110350
|
if (!state.detail) {
|
|
110057
|
-
state.scroll = scrollToReveal(state.selected +
|
|
110351
|
+
state.scroll = scrollToReveal(state.selected + 3, state.scroll, bodyRows);
|
|
110058
110352
|
}
|
|
110059
110353
|
state.scroll = clampScroll(state.scroll, lines.length, bodyRows);
|
|
110060
110354
|
if (bodyNode !== undefined) {
|
|
@@ -110065,7 +110359,7 @@ function openHelpModal(otui, chrome, options) {
|
|
|
110065
110359
|
const initialTab = options.initialGroupSlug !== undefined && groupBySlug(options.initialGroupSlug) !== undefined ? options.initialGroupSlug : undefined;
|
|
110066
110360
|
const handle = openModal(core, chrome, {
|
|
110067
110361
|
title: HELP_MODAL_TITLE,
|
|
110068
|
-
tabs
|
|
110362
|
+
tabs,
|
|
110069
110363
|
...initialTab !== undefined ? { initialTab } : {},
|
|
110070
110364
|
footer: [
|
|
110071
110365
|
{ key: "\u2191/\u2193", label: "select" },
|
|
@@ -111179,6 +111473,8 @@ function openGamesModal(otui, chrome, options = {}) {
|
|
|
111179
111473
|
}
|
|
111180
111474
|
// src/tui/tui-shell.ts
|
|
111181
111475
|
init_providers();
|
|
111476
|
+
init_ui();
|
|
111477
|
+
init_md_blocks();
|
|
111182
111478
|
init_patch_risk();
|
|
111183
111479
|
init_catalog();
|
|
111184
111480
|
init_grants();
|
|
@@ -112083,6 +112379,13 @@ function stepQueueNavAction(selected, direction) {
|
|
|
112083
112379
|
const nextIdx = direction === "left" ? idx > 0 ? idx - 1 : count - 1 : idx < count - 1 ? idx + 1 : 0;
|
|
112084
112380
|
return QUEUE_NAV_ACTIONS[nextIdx];
|
|
112085
112381
|
}
|
|
112382
|
+
var CONNECT_NAV_ACTIONS = ["label", "test", "disconnect"];
|
|
112383
|
+
function stepConnectNavAction(selected, direction) {
|
|
112384
|
+
const idx = CONNECT_NAV_ACTIONS.indexOf(selected);
|
|
112385
|
+
const count = CONNECT_NAV_ACTIONS.length;
|
|
112386
|
+
const nextIdx = direction === "left" ? idx > 0 ? idx - 1 : count - 1 : idx < count - 1 ? idx + 1 : 0;
|
|
112387
|
+
return CONNECT_NAV_ACTIONS[nextIdx];
|
|
112388
|
+
}
|
|
112086
112389
|
|
|
112087
112390
|
// src/tui/worker-fleet.ts
|
|
112088
112391
|
var STATUS_GLYPH2 = {
|
|
@@ -117653,6 +117956,28 @@ function modelPickerNotice(label, result) {
|
|
|
117653
117956
|
}
|
|
117654
117957
|
return modelsFailureLine(label, result.failure);
|
|
117655
117958
|
}
|
|
117959
|
+
function smallActionButton(otui, r, label, id, color, onMouseDown) {
|
|
117960
|
+
const box = new otui.BoxRenderable(r, {
|
|
117961
|
+
id,
|
|
117962
|
+
flexShrink: 0,
|
|
117963
|
+
marginLeft: 1,
|
|
117964
|
+
paddingLeft: 1,
|
|
117965
|
+
paddingRight: 1,
|
|
117966
|
+
onMouseDown: (event) => {
|
|
117967
|
+
event.stopPropagation();
|
|
117968
|
+
onMouseDown();
|
|
117969
|
+
}
|
|
117970
|
+
});
|
|
117971
|
+
const text = new otui.TextRenderable(r, { id: `${id}-t`, content: `[${label}]` });
|
|
117972
|
+
text.fg = color;
|
|
117973
|
+
box.add(text);
|
|
117974
|
+
const setActive = (active) => {
|
|
117975
|
+
box.backgroundColor = active ? getTheme().highlight : undefined;
|
|
117976
|
+
text.content = active ? otui.t`${boldChunk(otui, `[${label}]`)}` : `[${label}]`;
|
|
117977
|
+
text.fg = color;
|
|
117978
|
+
};
|
|
117979
|
+
return { box, setActive };
|
|
117980
|
+
}
|
|
117656
117981
|
function pickProviderStep(otui, target, detected) {
|
|
117657
117982
|
const r = stepRenderer(target);
|
|
117658
117983
|
return new Promise((resolve) => {
|
|
@@ -117691,6 +118016,221 @@ function pickProviderStep(otui, target, detected) {
|
|
|
117691
118016
|
});
|
|
117692
118017
|
});
|
|
117693
118018
|
}
|
|
118019
|
+
function pickConnectedProviderStep(otui, target, detected, opts) {
|
|
118020
|
+
const r = stepRenderer(target);
|
|
118021
|
+
return new Promise((resolve) => {
|
|
118022
|
+
let rows = [...detected];
|
|
118023
|
+
let selectedIndex = 0;
|
|
118024
|
+
let selectedAction = "label";
|
|
118025
|
+
let pendingDisconnect;
|
|
118026
|
+
let resolved = false;
|
|
118027
|
+
let rowBlocks = [];
|
|
118028
|
+
const labelOf = (d) => d.label ?? d.name;
|
|
118029
|
+
const noteOf = (d) => d.note ?? `${d.models.length} model(s)`;
|
|
118030
|
+
const unsubEscapeGuard = onKeypress4(r, (key) => {
|
|
118031
|
+
if (resolved || key.name !== "escape" || pendingDisconnect === undefined)
|
|
118032
|
+
return;
|
|
118033
|
+
clearPending();
|
|
118034
|
+
applyHighlight();
|
|
118035
|
+
key.preventDefault();
|
|
118036
|
+
key.stopPropagation();
|
|
118037
|
+
});
|
|
118038
|
+
const surface = openStepSurface(otui, target, {
|
|
118039
|
+
id: "connect-picker",
|
|
118040
|
+
title: "Connected providers",
|
|
118041
|
+
tab: "Providers",
|
|
118042
|
+
hint: "(\u2191/\u2193 row \xB7 \u2190/\u2192 Label/Test/Disconnect \xB7 Enter \xB7 Esc to cancel)",
|
|
118043
|
+
footer: [
|
|
118044
|
+
{ key: "\u2191/\u2193", label: "row" },
|
|
118045
|
+
{ key: "\u2190/\u2192", label: "Label/Test/Disconnect" },
|
|
118046
|
+
{ key: "Enter", label: "select/run" },
|
|
118047
|
+
{ key: "esc", label: "cancel" }
|
|
118048
|
+
],
|
|
118049
|
+
contentRows: selectBoxHeight(Math.max(rows.length, 1), true),
|
|
118050
|
+
onEscape: () => finish(undefined)
|
|
118051
|
+
});
|
|
118052
|
+
const body = surface.body;
|
|
118053
|
+
function finish(value) {
|
|
118054
|
+
if (resolved)
|
|
118055
|
+
return;
|
|
118056
|
+
resolved = true;
|
|
118057
|
+
unsubEscapeGuard();
|
|
118058
|
+
unsub();
|
|
118059
|
+
surface.close();
|
|
118060
|
+
resolve(value);
|
|
118061
|
+
}
|
|
118062
|
+
function clearPending() {
|
|
118063
|
+
if (pendingDisconnect === undefined)
|
|
118064
|
+
return;
|
|
118065
|
+
const armed = rowBlocks.find((b) => b.name === pendingDisconnect);
|
|
118066
|
+
pendingDisconnect = undefined;
|
|
118067
|
+
if (armed !== undefined)
|
|
118068
|
+
armed.status.content = "";
|
|
118069
|
+
}
|
|
118070
|
+
function applyHighlight() {
|
|
118071
|
+
for (let i = 0;i < rowBlocks.length; i++) {
|
|
118072
|
+
const block = rowBlocks[i];
|
|
118073
|
+
if (block === undefined)
|
|
118074
|
+
continue;
|
|
118075
|
+
const active = i === selectedIndex;
|
|
118076
|
+
block.outer.backgroundColor = active ? getTheme().highlight : undefined;
|
|
118077
|
+
block.labelText.content = active && selectedAction === "label" ? otui.t`${boldChunk(otui, `${labelOf(rows[i])}`)} ${dimChunk(otui, noteOf(rows[i]))}` : otui.t`${labelOf(rows[i])} ${dimChunk(otui, noteOf(rows[i]))}`;
|
|
118078
|
+
block.test.setActive(active && selectedAction === "test");
|
|
118079
|
+
block.disconnect.setActive(active && selectedAction === "disconnect");
|
|
118080
|
+
}
|
|
118081
|
+
}
|
|
118082
|
+
async function runTest(name) {
|
|
118083
|
+
const block = rowBlocks.find((b) => b.name === name);
|
|
118084
|
+
const provider = providerByName(name, opts.configDir);
|
|
118085
|
+
if (block === undefined)
|
|
118086
|
+
return;
|
|
118087
|
+
if (provider === undefined) {
|
|
118088
|
+
block.status.content = otui.t`${roleChunk(otui, "error", "\u2717")} unknown provider \u2014 cannot test`;
|
|
118089
|
+
return;
|
|
118090
|
+
}
|
|
118091
|
+
block.status.content = otui.t`${dimChunk(otui, "testing\u2026")}`;
|
|
118092
|
+
const result = await testProviderConnection(provider, opts.fetch ?? globalThis.fetch, opts.env ?? process.env);
|
|
118093
|
+
const current = rowBlocks.find((b) => b.name === name);
|
|
118094
|
+
if (current === undefined || resolved)
|
|
118095
|
+
return;
|
|
118096
|
+
current.status.content = result.source === "live" ? otui.t`${roleChunk(otui, "ok", "\u2713")} ok \u2014 ${String(result.models.length)} model(s)` : otui.t`${roleChunk(otui, "error", "\u2717")} ${modelsFailureLine(labelOf(rows.find((d) => d.name === name) ?? { name, models: [] }), result.failure ?? { kind: "empty" })}`;
|
|
118097
|
+
}
|
|
118098
|
+
function armOrConfirmDisconnect(name) {
|
|
118099
|
+
const block = rowBlocks.find((b) => b.name === name);
|
|
118100
|
+
if (block === undefined)
|
|
118101
|
+
return;
|
|
118102
|
+
if (pendingDisconnect !== name) {
|
|
118103
|
+
clearPending();
|
|
118104
|
+
pendingDisconnect = name;
|
|
118105
|
+
const env = opts.env ?? process.env;
|
|
118106
|
+
const classification = classifyProviderConnection(name, env, opts.configDir);
|
|
118107
|
+
const warning = sharedCredentialWarning(classification.sharedWith, classification.envKey);
|
|
118108
|
+
const label = labelOf(rows.find((d) => d.name === name) ?? { name, models: [] });
|
|
118109
|
+
block.status.content = warning === undefined ? otui.t`${roleChunk(otui, "attention", "?")} disconnect '${label}'? ${dimChunk(otui, "click/Enter Disconnect again to confirm, Esc to cancel")}` : otui.t`${roleChunk(otui, "attention", "?")} disconnect '${label}'? ${roleChunk(otui, "attention", `Note: ${warning}.`)} ${dimChunk(otui, "click/Enter Disconnect again to confirm, Esc to cancel")}`;
|
|
118110
|
+
return;
|
|
118111
|
+
}
|
|
118112
|
+
pendingDisconnect = undefined;
|
|
118113
|
+
const result = disconnectProvider(name, opts.env ?? process.env, opts.configDir);
|
|
118114
|
+
if (!result.ok) {
|
|
118115
|
+
block.status.content = otui.t`${roleChunk(otui, "error", "\u2717")} not removed \u2014 ${result.reason ?? "unknown reason"}`;
|
|
118116
|
+
return;
|
|
118117
|
+
}
|
|
118118
|
+
opts.onDisconnected?.(name, result.sharedWith);
|
|
118119
|
+
rows = rows.filter((d) => d.name !== name);
|
|
118120
|
+
paint();
|
|
118121
|
+
}
|
|
118122
|
+
function paint() {
|
|
118123
|
+
for (const block of rowBlocks) {
|
|
118124
|
+
try {
|
|
118125
|
+
body.remove(block.outer);
|
|
118126
|
+
} catch {}
|
|
118127
|
+
}
|
|
118128
|
+
rowBlocks = [];
|
|
118129
|
+
if (rows.length === 0) {
|
|
118130
|
+
finish(undefined);
|
|
118131
|
+
return;
|
|
118132
|
+
}
|
|
118133
|
+
selectedIndex = clampQueueNavIndex(selectedIndex, rows.length);
|
|
118134
|
+
for (let i = 0;i < rows.length; i++) {
|
|
118135
|
+
const d = rows[i];
|
|
118136
|
+
const outer = new otui.BoxRenderable(r, { id: `cp-${d.name}`, width: "100%", flexDirection: "column" });
|
|
118137
|
+
const row = new otui.BoxRenderable(r, { id: `cp-${d.name}-row`, width: "100%", flexDirection: "row" });
|
|
118138
|
+
const labelText = new otui.TextRenderable(r, {
|
|
118139
|
+
id: `cp-${d.name}-label-t`,
|
|
118140
|
+
content: otui.t`${labelOf(d)} ${dimChunk(otui, noteOf(d))}`
|
|
118141
|
+
});
|
|
118142
|
+
const label = new otui.BoxRenderable(r, {
|
|
118143
|
+
id: `cp-${d.name}-label`,
|
|
118144
|
+
flexGrow: 1,
|
|
118145
|
+
minWidth: 0,
|
|
118146
|
+
onMouseDown: (event) => {
|
|
118147
|
+
event.stopPropagation();
|
|
118148
|
+
clearPending();
|
|
118149
|
+
selectedIndex = i;
|
|
118150
|
+
selectedAction = "label";
|
|
118151
|
+
finish(d);
|
|
118152
|
+
}
|
|
118153
|
+
});
|
|
118154
|
+
label.add(labelText);
|
|
118155
|
+
row.add(label);
|
|
118156
|
+
const test = smallActionButton(otui, r, "Test", `cp-test-${d.name}`, getTheme().focus, () => {
|
|
118157
|
+
clearPending();
|
|
118158
|
+
selectedIndex = i;
|
|
118159
|
+
selectedAction = "test";
|
|
118160
|
+
applyHighlight();
|
|
118161
|
+
runTest(d.name);
|
|
118162
|
+
});
|
|
118163
|
+
const disconnect = smallActionButton(otui, r, "Disconnect", `cp-disc-${d.name}`, getTheme().error, () => {
|
|
118164
|
+
selectedIndex = i;
|
|
118165
|
+
selectedAction = "disconnect";
|
|
118166
|
+
applyHighlight();
|
|
118167
|
+
armOrConfirmDisconnect(d.name);
|
|
118168
|
+
});
|
|
118169
|
+
row.add(test.box);
|
|
118170
|
+
row.add(disconnect.box);
|
|
118171
|
+
outer.add(row);
|
|
118172
|
+
const status = new otui.TextRenderable(r, { id: `cp-${d.name}-status`, content: "", marginLeft: 1 });
|
|
118173
|
+
outer.add(status);
|
|
118174
|
+
body.add(outer);
|
|
118175
|
+
rowBlocks.push({ name: d.name, outer, label, labelText, status, test, disconnect });
|
|
118176
|
+
}
|
|
118177
|
+
applyHighlight();
|
|
118178
|
+
}
|
|
118179
|
+
const unsub = onKeypress4(r, (key) => {
|
|
118180
|
+
if (resolved)
|
|
118181
|
+
return;
|
|
118182
|
+
if (key.name === "up") {
|
|
118183
|
+
clearPending();
|
|
118184
|
+
selectedIndex = stepQueueNavIndex(selectedIndex, rows.length, "up");
|
|
118185
|
+
applyHighlight();
|
|
118186
|
+
key.preventDefault();
|
|
118187
|
+
key.stopPropagation();
|
|
118188
|
+
return;
|
|
118189
|
+
}
|
|
118190
|
+
if (key.name === "down") {
|
|
118191
|
+
clearPending();
|
|
118192
|
+
selectedIndex = stepQueueNavIndex(selectedIndex, rows.length, "down");
|
|
118193
|
+
applyHighlight();
|
|
118194
|
+
key.preventDefault();
|
|
118195
|
+
key.stopPropagation();
|
|
118196
|
+
return;
|
|
118197
|
+
}
|
|
118198
|
+
if (key.name === "left") {
|
|
118199
|
+
clearPending();
|
|
118200
|
+
selectedAction = stepConnectNavAction(selectedAction, "left");
|
|
118201
|
+
applyHighlight();
|
|
118202
|
+
key.preventDefault();
|
|
118203
|
+
key.stopPropagation();
|
|
118204
|
+
return;
|
|
118205
|
+
}
|
|
118206
|
+
if (key.name === "right") {
|
|
118207
|
+
clearPending();
|
|
118208
|
+
selectedAction = stepConnectNavAction(selectedAction, "right");
|
|
118209
|
+
applyHighlight();
|
|
118210
|
+
key.preventDefault();
|
|
118211
|
+
key.stopPropagation();
|
|
118212
|
+
return;
|
|
118213
|
+
}
|
|
118214
|
+
if (key.name === "return" || key.name === "linefeed" || key.name === "kpenter") {
|
|
118215
|
+
const row = rows[selectedIndex];
|
|
118216
|
+
if (row === undefined)
|
|
118217
|
+
return;
|
|
118218
|
+
if (selectedAction === "label") {
|
|
118219
|
+
clearPending();
|
|
118220
|
+
finish(row);
|
|
118221
|
+
} else if (selectedAction === "test") {
|
|
118222
|
+
clearPending();
|
|
118223
|
+
runTest(row.name);
|
|
118224
|
+
} else {
|
|
118225
|
+
armOrConfirmDisconnect(row.name);
|
|
118226
|
+
}
|
|
118227
|
+
key.preventDefault();
|
|
118228
|
+
key.stopPropagation();
|
|
118229
|
+
}
|
|
118230
|
+
});
|
|
118231
|
+
paint();
|
|
118232
|
+
});
|
|
118233
|
+
}
|
|
117694
118234
|
function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
117695
118235
|
return new Promise((resolve) => {
|
|
117696
118236
|
if (detected.length === 0) {
|
|
@@ -117709,7 +118249,12 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
117709
118249
|
}
|
|
117710
118250
|
const allCandidates = options.onlyConnected ? candidates : [...candidates, CUSTOM_ADD_ENTRY];
|
|
117711
118251
|
while (true) {
|
|
117712
|
-
const prov = await
|
|
118252
|
+
const prov = options.onlyConnected ? await pickConnectedProviderStep(otui, rOrChrome, allCandidates, {
|
|
118253
|
+
...options.fetch !== undefined ? { fetch: options.fetch } : {},
|
|
118254
|
+
...options.env !== undefined ? { env: options.env } : {},
|
|
118255
|
+
...options.configDir !== undefined ? { configDir: options.configDir } : {},
|
|
118256
|
+
...options.onDisconnected !== undefined ? { onDisconnected: options.onDisconnected } : {}
|
|
118257
|
+
}) : await pickProviderStep(otui, rOrChrome, allCandidates);
|
|
117713
118258
|
if (prov === undefined) {
|
|
117714
118259
|
resolve(undefined);
|
|
117715
118260
|
return;
|
|
@@ -119793,28 +120338,7 @@ Staying in the current session.
|
|
|
119793
120338
|
let selectedQueueAction = "force";
|
|
119794
120339
|
chrome.addOverlaySource(() => queueNavActive);
|
|
119795
120340
|
let mainQueueBlocks = [];
|
|
119796
|
-
const mainQueueButton = (label, id, color, onMouseDown) =>
|
|
119797
|
-
const box = new otui.BoxRenderable(r, {
|
|
119798
|
-
id,
|
|
119799
|
-
flexShrink: 0,
|
|
119800
|
-
marginLeft: 1,
|
|
119801
|
-
paddingLeft: 1,
|
|
119802
|
-
paddingRight: 1,
|
|
119803
|
-
onMouseDown: (event) => {
|
|
119804
|
-
event.stopPropagation();
|
|
119805
|
-
onMouseDown();
|
|
119806
|
-
}
|
|
119807
|
-
});
|
|
119808
|
-
const text = new otui.TextRenderable(r, { id: `${id}-t`, content: `[${label}]` });
|
|
119809
|
-
text.fg = color;
|
|
119810
|
-
box.add(text);
|
|
119811
|
-
const setActive = (active) => {
|
|
119812
|
-
box.backgroundColor = active ? getTheme().highlight : undefined;
|
|
119813
|
-
text.content = active ? otui.t`${boldChunk(otui, `[${label}]`)}` : `[${label}]`;
|
|
119814
|
-
text.fg = color;
|
|
119815
|
-
};
|
|
119816
|
-
return { box, setActive };
|
|
119817
|
-
};
|
|
120341
|
+
const mainQueueButton = (label, id, color, onMouseDown) => smallActionButton(otui, r, label, id, color, onMouseDown);
|
|
119818
120342
|
const applyQueueNavHighlight = () => {
|
|
119819
120343
|
for (let i = 0;i < mainQueueBlocks.length; i++) {
|
|
119820
120344
|
const entry = mainQueueBlocks[i];
|
|
@@ -120711,7 +121235,20 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
120711
121235
|
if (command.name === "/connect" || command.name === "/provider") {
|
|
120712
121236
|
(async () => {
|
|
120713
121237
|
const detected = opts.redetect !== undefined ? await opts.redetect() : opts.detected;
|
|
120714
|
-
const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, chrome, detected, {
|
|
121238
|
+
const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, chrome, detected, {
|
|
121239
|
+
onlyConnected: true,
|
|
121240
|
+
env: process.env,
|
|
121241
|
+
onDisconnected: (name, sharedWith) => {
|
|
121242
|
+
if (sharedWith.length > 0) {
|
|
121243
|
+
io.onSystem?.(`\u25C7 ${name} disconnected \u2014 this also disconnected: ${sharedWith.join(", ")}.
|
|
121244
|
+
`);
|
|
121245
|
+
}
|
|
121246
|
+
if (name === currentSel.provider) {
|
|
121247
|
+
io.onSystem?.(`\u25C7 ${name} disconnected \u2014 this session keeps its already-loaded credential until you /connect another provider or restart.
|
|
121248
|
+
`);
|
|
121249
|
+
}
|
|
121250
|
+
}
|
|
121251
|
+
}) : selectProviderModelInTui(otui, chrome, detected));
|
|
120715
121252
|
if (ns !== undefined) {
|
|
120716
121253
|
await switchTo(ns);
|
|
120717
121254
|
} else if (command.name === "/connect") {
|
|
@@ -121055,6 +121592,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
121055
121592
|
|
|
121056
121593
|
// src/tui/chat-shell.ts
|
|
121057
121594
|
init_shell_config();
|
|
121595
|
+
init_md_blocks();
|
|
121058
121596
|
var TURN_SEPARATOR = `
|
|
121059
121597
|
|
|
121060
121598
|
`;
|
|
@@ -121452,6 +121990,8 @@ async function launchTuiChatShell(opts) {
|
|
|
121452
121990
|
init_shell_config();
|
|
121453
121991
|
init_patch_risk();
|
|
121454
121992
|
init_permission_mode();
|
|
121993
|
+
init_ui();
|
|
121994
|
+
init_md_blocks();
|
|
121455
121995
|
init_agent();
|
|
121456
121996
|
init_slate_lifecycle();
|
|
121457
121997
|
init_slate_lifecycle();
|
|
@@ -126528,9 +127068,11 @@ most one CLI-origin pause lease can be active in the clone at a time.
|
|
|
126528
127068
|
|
|
126529
127069
|
// src/commands/modules.ts
|
|
126530
127070
|
init_fs();
|
|
127071
|
+
init_prompt();
|
|
126531
127072
|
import { readFile as readFile110 } from "fs/promises";
|
|
126532
127073
|
import { stdin } from "process";
|
|
126533
127074
|
import path224 from "path";
|
|
127075
|
+
init_ui();
|
|
126534
127076
|
var MODULES = [
|
|
126535
127077
|
{ name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
|
|
126536
127078
|
{ name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
|
|
@@ -127777,6 +128319,7 @@ function assembleSubmitTurn(profile, dir, seams = {}) {
|
|
|
127777
128319
|
}
|
|
127778
128320
|
|
|
127779
128321
|
// src/commands/serve.ts
|
|
128322
|
+
init_ui();
|
|
127780
128323
|
function parseArgs3(args, valueFlags, booleanFlags) {
|
|
127781
128324
|
const values = new Map;
|
|
127782
128325
|
const flags = new Set;
|
|
@@ -128254,6 +128797,7 @@ init_templates2();
|
|
|
128254
128797
|
init_templates();
|
|
128255
128798
|
init_fs();
|
|
128256
128799
|
init_git_hooks();
|
|
128800
|
+
init_ui();
|
|
128257
128801
|
init_templates3();
|
|
128258
128802
|
async function updateCommand(args = []) {
|
|
128259
128803
|
const options = parseUpdateArgs(args);
|
|
@@ -129495,6 +130039,7 @@ function printHelp22() {
|
|
|
129495
130039
|
import { spawn as spawn8 } from "child_process";
|
|
129496
130040
|
import path229 from "path";
|
|
129497
130041
|
init_args();
|
|
130042
|
+
init_ui();
|
|
129498
130043
|
async function dashboardCommand(args = []) {
|
|
129499
130044
|
const options = parseOptions(args);
|
|
129500
130045
|
const subcommand = options.positionals[0];
|
|
@@ -129811,6 +130356,7 @@ ${settled.stderr}` };
|
|
|
129811
130356
|
|
|
129812
130357
|
// src/commands/agents-external.ts
|
|
129813
130358
|
init_args();
|
|
130359
|
+
init_ui();
|
|
129814
130360
|
function describeAvailability(entry, availability) {
|
|
129815
130361
|
if (availability.state === "not-probed") {
|
|
129816
130362
|
return "not probed \u2014 run `keryx agents external probe " + entry.id + "`";
|
|
@@ -130174,6 +130720,7 @@ function reduceAgents(events) {
|
|
|
130174
130720
|
|
|
130175
130721
|
// src/commands/agents.ts
|
|
130176
130722
|
init_args();
|
|
130723
|
+
init_ui();
|
|
130177
130724
|
var RUNTIME_USAGE = "<claude|opencode|zcode|codex|antigravity|all>";
|
|
130178
130725
|
async function agentsCommand(args = []) {
|
|
130179
130726
|
const subcommand = args[0];
|
|
@@ -133407,6 +133954,8 @@ var USAGE_BODY = `Usage:
|
|
|
133407
133954
|
keryx sync install-hooks | uninstall-hooks
|
|
133408
133955
|
keryx providers list [--json]
|
|
133409
133956
|
keryx providers cross-family [--opt-in] [--json]
|
|
133957
|
+
keryx providers test <name> [--json]
|
|
133958
|
+
keryx providers remove <name> [--yes] [--json]
|
|
133410
133959
|
keryx auth list [--json]
|
|
133411
133960
|
keryx auth login <provider>
|
|
133412
133961
|
keryx auth logout <provider>
|