@mrciphersmith/keryx 0.2.160 → 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 +1842 -1338
- package/dist/core.js +1 -1
- 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
|
|
|
@@ -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",
|
|
@@ -76574,13 +76817,14 @@ Usage:
|
|
|
76574
76817
|
}
|
|
76575
76818
|
|
|
76576
76819
|
// src/commands/security.ts
|
|
76577
|
-
|
|
76578
|
-
import path168 from "path";
|
|
76579
|
-
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
76820
|
+
init_ui();
|
|
76580
76821
|
init_args();
|
|
76581
76822
|
init_contained_path();
|
|
76582
76823
|
init_fs();
|
|
76583
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";
|
|
76584
76828
|
|
|
76585
76829
|
// src/security/detect/mcp.ts
|
|
76586
76830
|
import { createHash as createHash31 } from "crypto";
|
|
@@ -77919,8 +78163,10 @@ async function sandboxCommand(args = [], deps = {}) {
|
|
|
77919
78163
|
|
|
77920
78164
|
// src/commands/mcp.ts
|
|
77921
78165
|
init_args();
|
|
78166
|
+
init_ui();
|
|
77922
78167
|
|
|
77923
78168
|
// src/commands/integrate.ts
|
|
78169
|
+
init_ui();
|
|
77924
78170
|
import path170 from "path";
|
|
77925
78171
|
var EDITOR_USAGE = `<cursor|claude|opencode|vscode|generic|all>`;
|
|
77926
78172
|
var DEFAULT_EDITORS = ["all"];
|
|
@@ -81244,6 +81490,7 @@ function positional3(args) {
|
|
|
81244
81490
|
|
|
81245
81491
|
// src/commands/serve-mcp.ts
|
|
81246
81492
|
init_args();
|
|
81493
|
+
init_ui();
|
|
81247
81494
|
import path183 from "path";
|
|
81248
81495
|
|
|
81249
81496
|
// src/mcp/discovery.ts
|
|
@@ -99833,6 +100080,7 @@ async function approveExternalSpawn(request) {
|
|
|
99833
100080
|
init_caller_session();
|
|
99834
100081
|
|
|
99835
100082
|
// src/lib/statusbar.ts
|
|
100083
|
+
init_ui();
|
|
99836
100084
|
import { homedir as homedir10 } from "os";
|
|
99837
100085
|
var ESC = "\x1B";
|
|
99838
100086
|
var CSI = `${ESC}[`;
|
|
@@ -100834,6 +101082,7 @@ function boldChunk(otui, text) {
|
|
|
100834
101082
|
}
|
|
100835
101083
|
|
|
100836
101084
|
// src/tui/transcript-blocks.ts
|
|
101085
|
+
init_md_blocks();
|
|
100837
101086
|
var EVICTED_BLOCK_TEXT = "(output no longer retained)";
|
|
100838
101087
|
var UNKNOWN_BLOCK_TEXT = "(no such block)";
|
|
100839
101088
|
var TRUNCATED_BLOCK_NOTICE = "\u2026 (output truncated at the retention cap)";
|
|
@@ -111224,6 +111473,8 @@ function openGamesModal(otui, chrome, options = {}) {
|
|
|
111224
111473
|
}
|
|
111225
111474
|
// src/tui/tui-shell.ts
|
|
111226
111475
|
init_providers();
|
|
111476
|
+
init_ui();
|
|
111477
|
+
init_md_blocks();
|
|
111227
111478
|
init_patch_risk();
|
|
111228
111479
|
init_catalog();
|
|
111229
111480
|
init_grants();
|
|
@@ -112128,6 +112379,13 @@ function stepQueueNavAction(selected, direction) {
|
|
|
112128
112379
|
const nextIdx = direction === "left" ? idx > 0 ? idx - 1 : count - 1 : idx < count - 1 ? idx + 1 : 0;
|
|
112129
112380
|
return QUEUE_NAV_ACTIONS[nextIdx];
|
|
112130
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
|
+
}
|
|
112131
112389
|
|
|
112132
112390
|
// src/tui/worker-fleet.ts
|
|
112133
112391
|
var STATUS_GLYPH2 = {
|
|
@@ -117698,6 +117956,28 @@ function modelPickerNotice(label, result) {
|
|
|
117698
117956
|
}
|
|
117699
117957
|
return modelsFailureLine(label, result.failure);
|
|
117700
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
|
+
}
|
|
117701
117981
|
function pickProviderStep(otui, target, detected) {
|
|
117702
117982
|
const r = stepRenderer(target);
|
|
117703
117983
|
return new Promise((resolve) => {
|
|
@@ -117736,6 +118016,221 @@ function pickProviderStep(otui, target, detected) {
|
|
|
117736
118016
|
});
|
|
117737
118017
|
});
|
|
117738
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
|
+
}
|
|
117739
118234
|
function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
117740
118235
|
return new Promise((resolve) => {
|
|
117741
118236
|
if (detected.length === 0) {
|
|
@@ -117754,7 +118249,12 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
117754
118249
|
}
|
|
117755
118250
|
const allCandidates = options.onlyConnected ? candidates : [...candidates, CUSTOM_ADD_ENTRY];
|
|
117756
118251
|
while (true) {
|
|
117757
|
-
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);
|
|
117758
118258
|
if (prov === undefined) {
|
|
117759
118259
|
resolve(undefined);
|
|
117760
118260
|
return;
|
|
@@ -119838,28 +120338,7 @@ Staying in the current session.
|
|
|
119838
120338
|
let selectedQueueAction = "force";
|
|
119839
120339
|
chrome.addOverlaySource(() => queueNavActive);
|
|
119840
120340
|
let mainQueueBlocks = [];
|
|
119841
|
-
const mainQueueButton = (label, id, color, onMouseDown) =>
|
|
119842
|
-
const box = new otui.BoxRenderable(r, {
|
|
119843
|
-
id,
|
|
119844
|
-
flexShrink: 0,
|
|
119845
|
-
marginLeft: 1,
|
|
119846
|
-
paddingLeft: 1,
|
|
119847
|
-
paddingRight: 1,
|
|
119848
|
-
onMouseDown: (event) => {
|
|
119849
|
-
event.stopPropagation();
|
|
119850
|
-
onMouseDown();
|
|
119851
|
-
}
|
|
119852
|
-
});
|
|
119853
|
-
const text = new otui.TextRenderable(r, { id: `${id}-t`, content: `[${label}]` });
|
|
119854
|
-
text.fg = color;
|
|
119855
|
-
box.add(text);
|
|
119856
|
-
const setActive = (active) => {
|
|
119857
|
-
box.backgroundColor = active ? getTheme().highlight : undefined;
|
|
119858
|
-
text.content = active ? otui.t`${boldChunk(otui, `[${label}]`)}` : `[${label}]`;
|
|
119859
|
-
text.fg = color;
|
|
119860
|
-
};
|
|
119861
|
-
return { box, setActive };
|
|
119862
|
-
};
|
|
120341
|
+
const mainQueueButton = (label, id, color, onMouseDown) => smallActionButton(otui, r, label, id, color, onMouseDown);
|
|
119863
120342
|
const applyQueueNavHighlight = () => {
|
|
119864
120343
|
for (let i = 0;i < mainQueueBlocks.length; i++) {
|
|
119865
120344
|
const entry = mainQueueBlocks[i];
|
|
@@ -120756,7 +121235,20 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
120756
121235
|
if (command.name === "/connect" || command.name === "/provider") {
|
|
120757
121236
|
(async () => {
|
|
120758
121237
|
const detected = opts.redetect !== undefined ? await opts.redetect() : opts.detected;
|
|
120759
|
-
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));
|
|
120760
121252
|
if (ns !== undefined) {
|
|
120761
121253
|
await switchTo(ns);
|
|
120762
121254
|
} else if (command.name === "/connect") {
|
|
@@ -121100,6 +121592,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
121100
121592
|
|
|
121101
121593
|
// src/tui/chat-shell.ts
|
|
121102
121594
|
init_shell_config();
|
|
121595
|
+
init_md_blocks();
|
|
121103
121596
|
var TURN_SEPARATOR = `
|
|
121104
121597
|
|
|
121105
121598
|
`;
|
|
@@ -121497,6 +121990,8 @@ async function launchTuiChatShell(opts) {
|
|
|
121497
121990
|
init_shell_config();
|
|
121498
121991
|
init_patch_risk();
|
|
121499
121992
|
init_permission_mode();
|
|
121993
|
+
init_ui();
|
|
121994
|
+
init_md_blocks();
|
|
121500
121995
|
init_agent();
|
|
121501
121996
|
init_slate_lifecycle();
|
|
121502
121997
|
init_slate_lifecycle();
|
|
@@ -126573,9 +127068,11 @@ most one CLI-origin pause lease can be active in the clone at a time.
|
|
|
126573
127068
|
|
|
126574
127069
|
// src/commands/modules.ts
|
|
126575
127070
|
init_fs();
|
|
127071
|
+
init_prompt();
|
|
126576
127072
|
import { readFile as readFile110 } from "fs/promises";
|
|
126577
127073
|
import { stdin } from "process";
|
|
126578
127074
|
import path224 from "path";
|
|
127075
|
+
init_ui();
|
|
126579
127076
|
var MODULES = [
|
|
126580
127077
|
{ name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
|
|
126581
127078
|
{ name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
|
|
@@ -127822,6 +128319,7 @@ function assembleSubmitTurn(profile, dir, seams = {}) {
|
|
|
127822
128319
|
}
|
|
127823
128320
|
|
|
127824
128321
|
// src/commands/serve.ts
|
|
128322
|
+
init_ui();
|
|
127825
128323
|
function parseArgs3(args, valueFlags, booleanFlags) {
|
|
127826
128324
|
const values = new Map;
|
|
127827
128325
|
const flags = new Set;
|
|
@@ -128299,6 +128797,7 @@ init_templates2();
|
|
|
128299
128797
|
init_templates();
|
|
128300
128798
|
init_fs();
|
|
128301
128799
|
init_git_hooks();
|
|
128800
|
+
init_ui();
|
|
128302
128801
|
init_templates3();
|
|
128303
128802
|
async function updateCommand(args = []) {
|
|
128304
128803
|
const options = parseUpdateArgs(args);
|
|
@@ -129540,6 +130039,7 @@ function printHelp22() {
|
|
|
129540
130039
|
import { spawn as spawn8 } from "child_process";
|
|
129541
130040
|
import path229 from "path";
|
|
129542
130041
|
init_args();
|
|
130042
|
+
init_ui();
|
|
129543
130043
|
async function dashboardCommand(args = []) {
|
|
129544
130044
|
const options = parseOptions(args);
|
|
129545
130045
|
const subcommand = options.positionals[0];
|
|
@@ -129856,6 +130356,7 @@ ${settled.stderr}` };
|
|
|
129856
130356
|
|
|
129857
130357
|
// src/commands/agents-external.ts
|
|
129858
130358
|
init_args();
|
|
130359
|
+
init_ui();
|
|
129859
130360
|
function describeAvailability(entry, availability) {
|
|
129860
130361
|
if (availability.state === "not-probed") {
|
|
129861
130362
|
return "not probed \u2014 run `keryx agents external probe " + entry.id + "`";
|
|
@@ -130219,6 +130720,7 @@ function reduceAgents(events) {
|
|
|
130219
130720
|
|
|
130220
130721
|
// src/commands/agents.ts
|
|
130221
130722
|
init_args();
|
|
130723
|
+
init_ui();
|
|
130222
130724
|
var RUNTIME_USAGE = "<claude|opencode|zcode|codex|antigravity|all>";
|
|
130223
130725
|
async function agentsCommand(args = []) {
|
|
130224
130726
|
const subcommand = args[0];
|
|
@@ -133452,6 +133954,8 @@ var USAGE_BODY = `Usage:
|
|
|
133452
133954
|
keryx sync install-hooks | uninstall-hooks
|
|
133453
133955
|
keryx providers list [--json]
|
|
133454
133956
|
keryx providers cross-family [--opt-in] [--json]
|
|
133957
|
+
keryx providers test <name> [--json]
|
|
133958
|
+
keryx providers remove <name> [--yes] [--json]
|
|
133455
133959
|
keryx auth list [--json]
|
|
133456
133960
|
keryx auth login <provider>
|
|
133457
133961
|
keryx auth logout <provider>
|