@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.
Files changed (3) hide show
  1. package/dist/cli.js +1842 -1338
  2. package/dist/core.js +1 -1
  3. 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
- writeOwnerOnlyFile(llmProvidersConfigPath(dir), `${JSON.stringify({ schemaVersion: 1, providers: current }, null, 2)}
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
- writeOwnerOnlyFile(shellConfigPath(dir), `${JSON.stringify(next, null, 2)}
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/lib/prompt.ts
47516
- import { stdin as input, stdout as output } from "process";
47517
- import readline from "readline/promises";
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.160",
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 / API key." },
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
- import { mkdir as mkdir60, readdir as readdir33, readFile as readFile96, writeFile as writeFile50 } from "fs/promises";
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 pickProviderStep(otui, rOrChrome, allCandidates);
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, { onlyConnected: true, env: process.env }) : 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>