theclawbay 0.3.44 → 0.3.46

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.
@@ -15,7 +15,6 @@ const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migratio
15
15
  const config_1 = require("../lib/managed/config");
16
16
  const errors_1 = require("../lib/managed/errors");
17
17
  const supported_models_1 = require("../lib/supported-models");
18
- const codex_vscode_patch_1 = require("../lib/codex-vscode-patch");
19
18
  const paths_1 = require("../lib/config/paths");
20
19
  const OPENAI_PROVIDER_ID = "openai";
21
20
  const DEFAULT_PROVIDER_ID = "theclawbay";
@@ -738,7 +737,6 @@ class LogoutCommand extends base_command_1.BaseCommand {
738
737
  let stateDbMigration;
739
738
  let authSeedCleanup;
740
739
  let modelCacheCleanup;
741
- let codexVsCodePatchCleanup;
742
740
  let remoteRevokeWarning = null;
743
741
  try {
744
742
  if (managed?.authType === "device-session" && managed.credential.trim()) {
@@ -788,7 +786,6 @@ class LogoutCommand extends base_command_1.BaseCommand {
788
786
  authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
789
787
  codexHome: paths_1.codexDir,
790
788
  });
791
- codexVsCodePatchCleanup = await (0, codex_vscode_patch_1.cleanupCodexVsCodeExtensions)();
792
789
  modelCacheCleanup = await (0, codex_model_cache_migration_1.cleanupSeededCodexModelCache)({
793
790
  codexHome: paths_1.codexDir,
794
791
  });
@@ -828,7 +825,6 @@ class LogoutCommand extends base_command_1.BaseCommand {
828
825
  stateDbMigration.updated > 0 ||
829
826
  authSeedCleanup.action === "removed" ||
830
827
  authSeedCleanup.action === "restored_backup" ||
831
- codexVsCodePatchCleanup.action === "removed" ||
832
828
  modelCacheCleanup.action === "removed";
833
829
  if (codexChanged)
834
830
  revertedTargets.push("Codex");
@@ -856,8 +852,6 @@ class LogoutCommand extends base_command_1.BaseCommand {
856
852
  }
857
853
  if (authSeedCleanup.warning)
858
854
  summaryNotes.add(authSeedCleanup.warning);
859
- if (codexVsCodePatchCleanup.warning)
860
- summaryNotes.add(codexVsCodePatchCleanup.warning);
861
855
  if (modelCacheCleanup.warning)
862
856
  summaryNotes.add(modelCacheCleanup.warning);
863
857
  if (remoteRevokeWarning) {
@@ -903,7 +897,7 @@ class LogoutCommand extends base_command_1.BaseCommand {
903
897
  this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
904
898
  }
905
899
  if (authSeedCleanup.action === "restored_backup") {
906
- this.log("- Codex login state: restored the auth that existed before the The Claw Bay usage patch.");
900
+ this.log("- Codex login state: restored the auth that existed before an older The Claw Bay Codex auth override.");
907
901
  }
908
902
  else if (authSeedCleanup.action === "removed") {
909
903
  this.log("- Codex login state: removed the Claw Bay auth seed.");
@@ -919,15 +913,6 @@ class LogoutCommand extends base_command_1.BaseCommand {
919
913
  else {
920
914
  this.log("- Codex login state: no cleanup needed.");
921
915
  }
922
- if (codexVsCodePatchCleanup.action === "removed") {
923
- this.log(`- Codex VS Code extension: removed the The Claw Bay patch from ${codexVsCodePatchCleanup.restoredRoots.join(", ")}.`);
924
- }
925
- else if (codexVsCodePatchCleanup.warning) {
926
- this.log(`- Codex VS Code extension: ${codexVsCodePatchCleanup.warning}`);
927
- }
928
- else {
929
- this.log("- Codex VS Code extension: no cleanup needed.");
930
- }
931
916
  if (modelCacheCleanup.action === "removed") {
932
917
  this.log("- Codex model cache: removed the Claw Bay GPT-5.4 / GPT-5.4 Mini seed.");
933
918
  }
@@ -7,7 +7,7 @@ export default class SetupCommand extends BaseCommand {
7
7
  "device-name": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
8
  clients: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
9
  yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
- "codex-usage-ui": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ "migrate-conversations": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
11
  "debug-output": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
12
  };
13
13
  run(): Promise<void>;
@@ -17,7 +17,6 @@ const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
17
17
  const codex_history_migration_1 = require("../lib/codex-history-migration");
18
18
  const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
19
19
  const paths_1 = require("../lib/config/paths");
20
- const codex_vscode_patch_1 = require("../lib/codex-vscode-patch");
21
20
  const api_key_1 = require("../lib/managed/api-key");
22
21
  const config_1 = require("../lib/managed/config");
23
22
  const errors_1 = require("../lib/managed/errors");
@@ -849,7 +848,7 @@ function resolveSetupClientSelection(params) {
849
848
  return Promise.resolve(defaults);
850
849
  return promptForSetupClients(setupClients);
851
850
  }
852
- async function promptForCodexUsageUiPatch() {
851
+ async function promptForCodexConversationMigration() {
853
852
  if (!process.stdin.isTTY || !process.stdout.isTTY)
854
853
  return false;
855
854
  const stdin = process.stdin;
@@ -879,20 +878,20 @@ async function promptForCodexUsageUiPatch() {
879
878
  finish(false);
880
879
  }
881
880
  };
882
- process.stdout.write("\nPatch Codex to show The Claw Bay usage remaining? [y/N] ");
881
+ process.stdout.write("\nConvert old Codex conversations so they stay visible under The Claw Bay? [y/N] ");
883
882
  if (stdin.isTTY)
884
883
  stdin.setRawMode(true);
885
884
  stdin.on("data", onData);
886
885
  });
887
886
  }
888
- async function resolveCodexUsageUiSelection(params) {
887
+ async function resolveCodexConversationMigrationSelection(params) {
889
888
  if (!params.codexSelected)
890
889
  return false;
891
890
  if (params.flagValue !== undefined)
892
891
  return params.flagValue;
893
892
  if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY)
894
893
  return false;
895
- return promptForCodexUsageUiPatch();
894
+ return promptForCodexConversationMigration();
896
895
  }
897
896
  async function resolveDeviceLabel(params) {
898
897
  const fallback = node_os_1.default.hostname().trim() || "This device";
@@ -914,6 +913,14 @@ async function resolveDeviceLabel(params) {
914
913
  rl.close();
915
914
  }
916
915
  }
916
+ function summarizeModelFetchFailure(detail) {
917
+ const normalized = detail.replace(/\s+/g, " ").trim();
918
+ if (!normalized)
919
+ return "unknown error";
920
+ if (normalized.length <= 140)
921
+ return normalized;
922
+ return `${normalized.slice(0, 137).trimEnd()}...`;
923
+ }
917
924
  async function fetchBackendModelIds(backendUrl, apiKey) {
918
925
  const url = `${trimTrailingSlash(backendUrl)}/api/codex-auth/v1/proxy/v1/models`;
919
926
  try {
@@ -923,18 +930,45 @@ async function fetchBackendModelIds(backendUrl, apiKey) {
923
930
  Accept: "application/json",
924
931
  "User-Agent": CLI_HTTP_USER_AGENT,
925
932
  },
926
- signal: AbortSignal.timeout(4500),
933
+ signal: AbortSignal.timeout(10000),
927
934
  });
928
- if (!response.ok)
929
- return null;
935
+ if (!response.ok) {
936
+ const responseText = await response.text().catch(() => "");
937
+ let detail = responseText.trim();
938
+ try {
939
+ const parsed = JSON.parse(responseText);
940
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
941
+ detail = parsed.error.trim();
942
+ }
943
+ else if (typeof parsed.code === "string" && parsed.code.trim()) {
944
+ detail = parsed.code.trim();
945
+ }
946
+ }
947
+ catch {
948
+ // Keep the raw response text when the body is not JSON.
949
+ }
950
+ return {
951
+ ids: null,
952
+ failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
953
+ };
954
+ }
930
955
  const body = (await response.json());
931
956
  const ids = (body.data ?? [])
932
957
  .map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
933
958
  .filter((id) => id.length > 0);
934
- return ids.length ? ids : null;
959
+ if (!ids.length) {
960
+ return {
961
+ ids: null,
962
+ failure: "backend returned an empty model list",
963
+ };
964
+ }
965
+ return { ids };
935
966
  }
936
- catch {
937
- return null;
967
+ catch (error) {
968
+ return {
969
+ ids: null,
970
+ failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
971
+ };
938
972
  }
939
973
  }
940
974
  function kiloStorageCandidates() {
@@ -1043,7 +1077,7 @@ async function detectZoClient() {
1043
1077
  return false;
1044
1078
  }
1045
1079
  async function resolveModels(backendUrl, apiKey) {
1046
- const ids = await fetchBackendModelIds(backendUrl, apiKey);
1080
+ const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
1047
1081
  const available = new Set(ids ?? []);
1048
1082
  let selected = DEFAULT_CODEX_MODEL;
1049
1083
  let note;
@@ -1058,7 +1092,9 @@ async function resolveModels(backendUrl, apiKey) {
1058
1092
  note = "No preferred Codex model advertised by backend; selected first available model.";
1059
1093
  }
1060
1094
  else if (!ids) {
1061
- note = `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
1095
+ note = failure
1096
+ ? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
1097
+ : `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
1062
1098
  }
1063
1099
  const unique = [];
1064
1100
  const pushUnique = (modelId) => {
@@ -1095,9 +1131,9 @@ async function writeCodexConfig(params) {
1095
1131
  next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
1096
1132
  next = upsertFirstKeyLine(next, "model_provider", `"${DEFAULT_PROVIDER_ID}"`);
1097
1133
  next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
1134
+ next = upsertFirstKeyLine(next, "chatgpt_base_url", `"${proxyRoot}"`);
1098
1135
  const managedBlock = appendManagedBlock("", [
1099
1136
  MANAGED_START,
1100
- ...(params.enableUsageUiPatch ? [`chatgpt_base_url = "${proxyRoot}"`] : []),
1101
1137
  `[model_providers.${DEFAULT_PROVIDER_ID}]`,
1102
1138
  'name = "OpenAI"',
1103
1139
  `base_url = "${proxyRoot}/backend-api/codex"`,
@@ -1869,13 +1905,13 @@ class SetupCommand extends base_command_1.BaseCommand {
1869
1905
  flagSelection: parseSetupClientFlags(flags.clients),
1870
1906
  skipPrompt: flags.yes,
1871
1907
  });
1872
- const codexUsageUiEnabled = await resolveCodexUsageUiSelection({
1908
+ const migrateCodexConversations = await resolveCodexConversationMigrationSelection({
1873
1909
  codexSelected: selectedSetupClients.has("codex"),
1874
- flagValue: flags["codex-usage-ui"],
1910
+ flagValue: flags["migrate-conversations"],
1875
1911
  skipPrompt: flags.yes,
1876
1912
  });
1877
- if (flags["codex-usage-ui"] && !selectedSetupClients.has("codex")) {
1878
- throw new Error("--codex-usage-ui requires Codex to be selected for setup.");
1913
+ if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
1914
+ throw new Error("--migrate-conversations requires Codex to be selected for setup.");
1879
1915
  }
1880
1916
  if (!authCredential) {
1881
1917
  deviceLabel = await resolveDeviceLabel({
@@ -1887,7 +1923,6 @@ class SetupCommand extends base_command_1.BaseCommand {
1887
1923
  backendUrl,
1888
1924
  deviceLabel,
1889
1925
  selectedClients: Array.from(selectedSetupClients),
1890
- usagePatchRequested: codexUsageUiEnabled,
1891
1926
  log: (message) => this.log(message),
1892
1927
  });
1893
1928
  authType = browserAuth.authType;
@@ -1905,7 +1940,6 @@ class SetupCommand extends base_command_1.BaseCommand {
1905
1940
  let authSeedCleanup = null;
1906
1941
  let stateDbMigration = null;
1907
1942
  let modelCacheMigration = null;
1908
- let codexVsCodeCleanup = null;
1909
1943
  let continueConfigPath = null;
1910
1944
  let clineConfigPaths = [];
1911
1945
  let openClawConfigPath = null;
@@ -1936,36 +1970,29 @@ class SetupCommand extends base_command_1.BaseCommand {
1936
1970
  backendUrl,
1937
1971
  model: resolved?.model ?? DEFAULT_CODEX_MODEL,
1938
1972
  apiKey: authCredential,
1939
- enableUsageUiPatch: codexUsageUiEnabled,
1940
1973
  });
1941
1974
  updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
1942
- sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
1943
- codexHome: paths_1.codexDir,
1944
- migrationStateFile: MIGRATION_STATE_FILE,
1945
- neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
1946
- });
1947
- if (codexUsageUiEnabled) {
1948
- authSeed = await (0, codex_auth_seeding_1.ensureCodexUsageLimitAuth)({
1975
+ if (migrateCodexConversations) {
1976
+ sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
1949
1977
  codexHome: paths_1.codexDir,
1950
- apiKey: authCredential,
1978
+ migrationStateFile: MIGRATION_STATE_FILE,
1979
+ neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
1951
1980
  });
1952
- codexVsCodeCleanup = await (0, codex_vscode_patch_1.cleanupCodexVsCodeExtensions)();
1953
1981
  }
1954
- else {
1955
- authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
1956
- codexHome: paths_1.codexDir,
1957
- });
1958
- authSeed = await (0, codex_auth_seeding_1.ensureCodexApiKeyAuth)({
1982
+ authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
1983
+ codexHome: paths_1.codexDir,
1984
+ });
1985
+ authSeed = await (0, codex_auth_seeding_1.ensureCodexUsageLimitAuth)({
1986
+ codexHome: paths_1.codexDir,
1987
+ apiKey: authCredential,
1988
+ });
1989
+ if (migrateCodexConversations) {
1990
+ stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
1959
1991
  codexHome: paths_1.codexDir,
1960
- apiKey: authCredential,
1992
+ targetProvider: DEFAULT_PROVIDER_ID,
1993
+ sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
1961
1994
  });
1962
- codexVsCodeCleanup = await (0, codex_vscode_patch_1.cleanupCodexVsCodeExtensions)();
1963
1995
  }
1964
- stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
1965
- codexHome: paths_1.codexDir,
1966
- targetProvider: DEFAULT_PROVIDER_ID,
1967
- sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
1968
- });
1969
1996
  modelCacheMigration = await (0, codex_model_cache_migration_1.ensureCodexModelCacheHasGpt54)({
1970
1997
  codexHome: paths_1.codexDir,
1971
1998
  });
@@ -2081,13 +2108,11 @@ class SetupCommand extends base_command_1.BaseCommand {
2081
2108
  summaryNotes.add(authSeed.warning);
2082
2109
  if (authSeedCleanup?.warning)
2083
2110
  summaryNotes.add(authSeedCleanup.warning);
2084
- if (codexVsCodeCleanup?.warning)
2085
- summaryNotes.add(codexVsCodeCleanup.warning);
2086
2111
  if (authSeed?.replacedExistingAuth) {
2087
- summaryNotes.add("Codex usage patch temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
2112
+ summaryNotes.add("Codex usage visibility temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
2088
2113
  }
2089
- if (codexVsCodeCleanup?.action === "removed") {
2090
- summaryNotes.add("Removed an older The Claw Bay Codex VS Code patch. Reload any open Codex extension windows if the sidebar had disappeared.");
2114
+ if (selectedSetupClients.has("codex") && !migrateCodexConversations) {
2115
+ summaryNotes.add("Left existing Codex conversations untouched.");
2091
2116
  }
2092
2117
  if (authType === "device-session" && deviceLabel) {
2093
2118
  summaryNotes.add(`This machine is linked as "${deviceLabel}". You can revoke it from the dashboard Devices section or with \`theclawbay logout\`.`);
@@ -2129,6 +2154,9 @@ class SetupCommand extends base_command_1.BaseCommand {
2129
2154
  else if (sessionMigration) {
2130
2155
  this.log("- Conversations: no local sessions required migration.");
2131
2156
  }
2157
+ if (!migrateCodexConversations) {
2158
+ this.log("- Conversations: left existing local Codex history untouched.");
2159
+ }
2132
2160
  if ((sessionMigration?.retimed ?? 0) > 0) {
2133
2161
  this.log(`- Conversation ordering: repaired timestamps on ${sessionMigration?.retimed ?? 0} local sessions.`);
2134
2162
  }
@@ -2160,18 +2188,6 @@ class SetupCommand extends base_command_1.BaseCommand {
2160
2188
  else if (modelCacheMigration) {
2161
2189
  this.log("- Codex model cache: no change.");
2162
2190
  }
2163
- if (codexUsageUiEnabled) {
2164
- this.log("- Codex usage UI: patched to show The Claw Bay usage remaining.");
2165
- }
2166
- if (codexVsCodeCleanup?.action === "removed") {
2167
- this.log(`- Codex VS Code extension: removed the prior The Claw Bay patch (${codexVsCodeCleanup.restoredRoots.join(", ")})`);
2168
- }
2169
- else if (codexVsCodeCleanup?.warning) {
2170
- this.log(`- Codex VS Code extension: ${codexVsCodeCleanup.warning}`);
2171
- }
2172
- else {
2173
- this.log("- Codex VS Code extension: no cleanup needed.");
2174
- }
2175
2191
  }
2176
2192
  else if (codexDetected) {
2177
2193
  this.log("- Codex: detected but skipped");
@@ -2281,22 +2297,22 @@ class SetupCommand extends base_command_1.BaseCommand {
2281
2297
  this.log("- Zo: not detected (skipped)");
2282
2298
  }
2283
2299
  if (authSeed?.action === "seeded" && authSeed.mode === "chatgpt-auth-tokens") {
2284
- this.log("- Codex login state: seeded temporary The Claw Bay auth so the usage UI can read your remaining limits.");
2300
+ this.log("- Codex login state: seeded local Codex auth so remaining usage can be shown.");
2285
2301
  }
2286
2302
  else if (authSeed?.action === "updated" && authSeed.mode === "chatgpt-auth-tokens") {
2287
- this.log("- Codex login state: refreshed the temporary The Claw Bay usage-limit auth seed.");
2303
+ this.log("- Codex login state: refreshed the local usage-limit auth seed.");
2288
2304
  }
2289
2305
  else if (authSeed?.action === "already_seeded" && authSeed.mode === "chatgpt-auth-tokens") {
2290
- this.log("- Codex login state: the temporary The Claw Bay usage-limit auth was already seeded.");
2306
+ this.log("- Codex login state: the local usage-limit auth seed was already present.");
2291
2307
  }
2292
2308
  else if (authSeed?.action === "seeded") {
2293
- this.log("- Codex login state: seeded local API-key auth for Codex UI.");
2309
+ this.log("- Codex login state: seeded local API-key auth.");
2294
2310
  }
2295
2311
  else if (authSeed?.action === "updated") {
2296
- this.log("- Codex login state: refreshed the Claw Bay API-key auth seed.");
2312
+ this.log("- Codex login state: refreshed the local API-key auth seed.");
2297
2313
  }
2298
2314
  else if (authSeed?.action === "already_seeded") {
2299
- this.log("- Codex login state: the Claw Bay API-key auth was already seeded.");
2315
+ this.log("- Codex login state: the local API-key auth seed was already present.");
2300
2316
  }
2301
2317
  else if (authSeed?.action === "already_present") {
2302
2318
  this.log("- Codex login state: preserved existing local Codex auth.");
@@ -2308,7 +2324,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2308
2324
  this.log("- Codex login state: no change.");
2309
2325
  }
2310
2326
  if (authSeedCleanup?.action === "restored_backup") {
2311
- this.log("- Codex login state: restored the auth that existed before the The Claw Bay usage patch.");
2327
+ this.log("- Codex login state: restored the auth that existed before an older The Claw Bay Codex auth override.");
2312
2328
  }
2313
2329
  this.log("Next: restart Continue, Cline, Roo Code, Trae, or Zo only if they were already open during setup.");
2314
2330
  });
@@ -2340,10 +2356,10 @@ SetupCommand.flags = {
2340
2356
  default: false,
2341
2357
  description: "Accept the recommended detected setup targets without prompting",
2342
2358
  }),
2343
- "codex-usage-ui": core_1.Flags.boolean({
2359
+ "migrate-conversations": core_1.Flags.boolean({
2344
2360
  required: false,
2345
2361
  allowNo: true,
2346
- description: "Patch Codex to show The Claw Bay usage remaining.",
2362
+ description: "Convert old Codex conversations so they stay visible under The Claw Bay.",
2347
2363
  }),
2348
2364
  "debug-output": core_1.Flags.boolean({
2349
2365
  required: false,
@@ -2,7 +2,6 @@ export declare function createBrowserLinkedDeviceSession(params: {
2
2
  backendUrl: string;
3
3
  deviceLabel?: string | null;
4
4
  selectedClients: string[];
5
- usagePatchRequested: boolean;
6
5
  log: (message: string) => void;
7
6
  }): Promise<{
8
7
  credential: string;
@@ -8,6 +8,8 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
8
8
  const node_http_1 = __importDefault(require("node:http"));
9
9
  const node_os_1 = __importDefault(require("node:os"));
10
10
  const node_child_process_1 = require("node:child_process");
11
+ const BROWSER_SETUP_TIMEOUT_MS = 15 * 60000;
12
+ const BROWSER_SETUP_POLL_INTERVAL_MS = 2000;
11
13
  function preferredBrowserCallbackPort() {
12
14
  const parsed = Number(process.env.THECLAWBAY_CALLBACK_PORT?.trim() || "");
13
15
  if (Number.isFinite(parsed) && parsed > 0 && parsed < 65536) {
@@ -124,10 +126,117 @@ async function createLocalCallbackServer(expectedState) {
124
126
  }),
125
127
  };
126
128
  }
129
+ async function exchangeBrowserSetupSession(params) {
130
+ const exchangeResponse = await fetch(`${params.backendUrl}/api/setup/device-session/exchange`, {
131
+ method: "POST",
132
+ headers: { "Content-Type": "application/json" },
133
+ body: JSON.stringify({
134
+ sessionId: params.sessionId,
135
+ state: params.state,
136
+ ...(params.code ? { code: params.code } : {}),
137
+ }),
138
+ signal: AbortSignal.timeout(20000),
139
+ });
140
+ const exchangeBody = (await exchangeResponse.json().catch(() => ({})));
141
+ if (!exchangeResponse.ok ||
142
+ exchangeBody.authType !== "device-session" ||
143
+ !exchangeBody.credential ||
144
+ !exchangeBody.deviceSessionId) {
145
+ throw new Error(exchangeBody.error ??
146
+ `failed to exchange browser setup session (HTTP ${exchangeResponse.status})`);
147
+ }
148
+ return {
149
+ credential: exchangeBody.credential,
150
+ authType: "device-session",
151
+ deviceSessionId: exchangeBody.deviceSessionId,
152
+ deviceLabel: exchangeBody.deviceLabel,
153
+ };
154
+ }
155
+ function createSetupAbortError() {
156
+ const error = new Error("browser setup cancelled");
157
+ error.name = "AbortError";
158
+ return error;
159
+ }
160
+ async function delay(ms, signal) {
161
+ if (signal?.aborted)
162
+ throw createSetupAbortError();
163
+ await new Promise((resolve, reject) => {
164
+ const timer = setTimeout(() => {
165
+ cleanup();
166
+ resolve();
167
+ }, ms);
168
+ const onAbort = () => {
169
+ clearTimeout(timer);
170
+ cleanup();
171
+ reject(createSetupAbortError());
172
+ };
173
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
174
+ signal?.addEventListener("abort", onAbort, { once: true });
175
+ });
176
+ }
177
+ async function withTimeout(promise, timeoutMs, signal) {
178
+ if (signal?.aborted)
179
+ throw createSetupAbortError();
180
+ return await new Promise((resolve, reject) => {
181
+ const timer = setTimeout(() => {
182
+ cleanup();
183
+ reject(new Error("browser setup timed out"));
184
+ }, timeoutMs);
185
+ const onAbort = () => {
186
+ clearTimeout(timer);
187
+ cleanup();
188
+ reject(createSetupAbortError());
189
+ };
190
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
191
+ signal?.addEventListener("abort", onAbort, { once: true });
192
+ promise.then((value) => {
193
+ clearTimeout(timer);
194
+ cleanup();
195
+ resolve(value);
196
+ }, (error) => {
197
+ clearTimeout(timer);
198
+ cleanup();
199
+ reject(error);
200
+ });
201
+ });
202
+ }
203
+ async function waitForFirstSuccessfulExchange(promises) {
204
+ return await new Promise((resolve, reject) => {
205
+ const errors = [];
206
+ let pending = promises.length;
207
+ let settled = false;
208
+ const fail = (error) => {
209
+ if (settled)
210
+ return;
211
+ if (error instanceof Error) {
212
+ errors.push(error);
213
+ }
214
+ else {
215
+ errors.push(new Error(String(error)));
216
+ }
217
+ pending -= 1;
218
+ if (pending <= 0) {
219
+ reject(errors[0] ?? new Error("browser setup timed out"));
220
+ }
221
+ };
222
+ for (const promise of promises) {
223
+ promise.then((value) => {
224
+ if (settled)
225
+ return;
226
+ settled = true;
227
+ resolve(value);
228
+ }, (error) => {
229
+ fail(error);
230
+ });
231
+ }
232
+ });
233
+ }
127
234
  async function createBrowserLinkedDeviceSession(params) {
128
235
  const label = (params.deviceLabel ?? "").trim() || node_os_1.default.hostname() || "This device";
129
236
  const state = node_crypto_1.default.randomUUID();
130
237
  const callbackServer = await createLocalCallbackServer(state);
238
+ const exchangeAbortController = new AbortController();
239
+ const exchangeSignal = exchangeAbortController.signal;
131
240
  try {
132
241
  const startResponse = await fetch(`${params.backendUrl}/api/setup/device-session/start`, {
133
242
  method: "POST",
@@ -137,7 +246,6 @@ async function createBrowserLinkedDeviceSession(params) {
137
246
  state,
138
247
  deviceLabel: label,
139
248
  selectedClients: params.selectedClients,
140
- usagePatchRequested: params.usagePatchRequested,
141
249
  }),
142
250
  signal: AbortSignal.timeout(20000),
143
251
  });
@@ -145,39 +253,64 @@ async function createBrowserLinkedDeviceSession(params) {
145
253
  if (!startResponse.ok || !startBody.authUrl || !startBody.sessionId) {
146
254
  throw new Error(startBody.error ?? `failed to start browser auth (HTTP ${startResponse.status})`);
147
255
  }
256
+ const setupSessionId = startBody.sessionId;
148
257
  const opened = openUrlInBrowser(startBody.authUrl);
149
258
  params.log(opened
150
259
  ? `Open browser auth if it did not appear automatically: ${startBody.authUrl}`
151
260
  : `Open this URL to continue setup: ${startBody.authUrl}`);
152
- const callback = await Promise.race([
153
- callbackServer.waitForCallback(),
154
- new Promise((_, reject) => {
155
- setTimeout(() => reject(new Error("browser setup timed out")), 15 * 60000);
156
- }),
157
- ]);
158
- const exchangeResponse = await fetch(`${params.backendUrl}/api/setup/device-session/exchange`, {
159
- method: "POST",
160
- headers: { "Content-Type": "application/json" },
161
- body: JSON.stringify({
261
+ const deadlineMs = Date.now() + BROWSER_SETUP_TIMEOUT_MS;
262
+ let fallbackNoticeShown = false;
263
+ const timeoutError = new Error("browser setup timed out");
264
+ const waitForLocalCallbackExchange = async () => {
265
+ const callback = await withTimeout(callbackServer.waitForCallback(), BROWSER_SETUP_TIMEOUT_MS, exchangeSignal);
266
+ return exchangeBrowserSetupSession({
267
+ backendUrl: params.backendUrl,
162
268
  sessionId: callback.sessionId,
163
269
  state: callback.state,
164
270
  code: callback.code,
165
- }),
166
- signal: AbortSignal.timeout(20000),
167
- });
168
- const exchangeBody = (await exchangeResponse.json().catch(() => ({})));
169
- if (!exchangeResponse.ok ||
170
- exchangeBody.authType !== "device-session" ||
171
- !exchangeBody.credential ||
172
- !exchangeBody.deviceSessionId) {
173
- throw new Error(exchangeBody.error ??
174
- `failed to exchange browser setup session (HTTP ${exchangeResponse.status})`);
175
- }
271
+ });
272
+ };
273
+ const waitForPolledExchange = async () => {
274
+ while (Date.now() < deadlineMs) {
275
+ if (exchangeSignal.aborted)
276
+ throw createSetupAbortError();
277
+ try {
278
+ return await exchangeBrowserSetupSession({
279
+ backendUrl: params.backendUrl,
280
+ sessionId: setupSessionId,
281
+ state,
282
+ });
283
+ }
284
+ catch (error) {
285
+ const message = error.message || "";
286
+ if (!message.includes("setup session is not approved")) {
287
+ throw error;
288
+ }
289
+ }
290
+ if (!fallbackNoticeShown) {
291
+ params.log("Waiting for browser approval. If the browser cannot reach localhost, setup will still finish here after you connect the device.");
292
+ fallbackNoticeShown = true;
293
+ }
294
+ await delay(BROWSER_SETUP_POLL_INTERVAL_MS, exchangeSignal);
295
+ }
296
+ throw timeoutError;
297
+ };
298
+ const linked = await (async () => {
299
+ try {
300
+ return await waitForFirstSuccessfulExchange([
301
+ waitForLocalCallbackExchange(),
302
+ waitForPolledExchange(),
303
+ ]);
304
+ }
305
+ finally {
306
+ exchangeAbortController.abort();
307
+ }
308
+ })();
176
309
  return {
177
- credential: exchangeBody.credential,
178
- authType: "device-session",
179
- deviceSessionId: exchangeBody.deviceSessionId,
180
- deviceLabel: exchangeBody.deviceLabel?.trim() || label,
310
+ credential: linked.credential,
311
+ authType: linked.authType,
312
+ deviceSessionId: linked.deviceSessionId,
313
+ deviceLabel: linked.deviceLabel?.trim() || label,
181
314
  };
182
315
  }
183
316
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "types": "dist/index.d.ts",
11
11
  "scripts": {
12
12
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/index.js',0o755)\"",
13
- "prepublishOnly": "npm run build",
13
+ "verify:npm-package": "node ./scripts/verify-npm-package.js",
14
+ "prepublishOnly": "npm run build && npm run verify:npm-package",
14
15
  "release:patch": "./scripts/release-npm.sh patch",
15
16
  "release:minor": "./scripts/release-npm.sh minor",
16
17
  "release:major": "./scripts/release-npm.sh major",
@@ -45,6 +46,10 @@
45
46
  "preferGlobal": true,
46
47
  "dependencies": {
47
48
  "@oclif/core": "^4.8.0",
49
+ "lucide-react": "^1.7.0",
50
+ "next": "^16.2.1",
51
+ "react": "^19.2.4",
52
+ "react-dom": "^19.2.4",
48
53
  "tslib": "^2.8.1",
49
54
  "yaml": "^2.8.2"
50
55
  },
@@ -1,14 +0,0 @@
1
- export type PatchCodexVsCodeExtensionsResult = {
2
- action: "patched" | "already_patched" | "none";
3
- patchedRoots: string[];
4
- warning?: string;
5
- };
6
- export type CleanupCodexVsCodeExtensionsResult = {
7
- action: "removed" | "none";
8
- restoredRoots: string[];
9
- warning?: string;
10
- };
11
- export declare function patchCodexVsCodeExtensions(params: {
12
- logoutCommandPath?: string | null;
13
- }): Promise<PatchCodexVsCodeExtensionsResult>;
14
- export declare function cleanupCodexVsCodeExtensions(): Promise<CleanupCodexVsCodeExtensionsResult>;
@@ -1,510 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.patchCodexVsCodeExtensions = patchCodexVsCodeExtensions;
7
- exports.cleanupCodexVsCodeExtensions = cleanupCodexVsCodeExtensions;
8
- const promises_1 = __importDefault(require("node:fs/promises"));
9
- const node_os_1 = __importDefault(require("node:os"));
10
- const node_path_1 = __importDefault(require("node:path"));
11
- const EXTENSION_PREFIX = "openai.chatgpt-";
12
- const BACKUP_SUFFIX = ".theclawbay-managed-backup";
13
- const EXTENSION_PATCH_MARKER = "theclawbay-codex-extension-host-patch";
14
- const WEBVIEW_PATCH_MARKER = "theclawbay-codex-webview-patch";
15
- const LOGOUT_COMMAND_ID = "chatgpt.theClawBayLogout";
16
- function uniqueStrings(values) {
17
- const output = [];
18
- const seen = new Set();
19
- for (const value of values) {
20
- const normalized = value.trim();
21
- if (!normalized || seen.has(normalized))
22
- continue;
23
- seen.add(normalized);
24
- output.push(normalized);
25
- }
26
- return output;
27
- }
28
- async function pathExists(filePath) {
29
- try {
30
- await promises_1.default.access(filePath);
31
- return true;
32
- }
33
- catch {
34
- return false;
35
- }
36
- }
37
- async function readFileIfExists(filePath) {
38
- try {
39
- return await promises_1.default.readFile(filePath, "utf8");
40
- }
41
- catch (error) {
42
- const err = error;
43
- if (err.code === "ENOENT")
44
- return null;
45
- throw error;
46
- }
47
- }
48
- function extensionSearchRoots() {
49
- const home = node_os_1.default.homedir();
50
- return uniqueStrings([
51
- node_path_1.default.join(home, ".vscode", "extensions"),
52
- node_path_1.default.join(home, ".vscode-insiders", "extensions"),
53
- node_path_1.default.join(home, ".vscode-server", "extensions"),
54
- node_path_1.default.join(home, ".vscode-server-insiders", "extensions"),
55
- node_path_1.default.join(home, ".cursor", "extensions"),
56
- node_path_1.default.join(home, ".windsurf", "extensions"),
57
- node_path_1.default.join(home, ".vscode-oss", "extensions"),
58
- ]);
59
- }
60
- async function findCodexExtensionRoots() {
61
- const roots = [];
62
- for (const extensionsDir of extensionSearchRoots()) {
63
- if (!(await pathExists(extensionsDir)))
64
- continue;
65
- const entries = await promises_1.default.readdir(extensionsDir, { withFileTypes: true });
66
- for (const entry of entries) {
67
- if (!entry.isDirectory())
68
- continue;
69
- if (!entry.name.toLowerCase().startsWith(EXTENSION_PREFIX))
70
- continue;
71
- roots.push(node_path_1.default.join(extensionsDir, entry.name));
72
- }
73
- }
74
- return uniqueStrings(roots).sort();
75
- }
76
- async function resolveWebviewEntryScriptPath(extensionRoot) {
77
- const indexHtmlPath = node_path_1.default.join(extensionRoot, "webview", "index.html");
78
- const indexHtml = await readFileIfExists(indexHtmlPath);
79
- if (!indexHtml)
80
- return null;
81
- const match = indexHtml.match(/<script[^>]+src="\.\/([^"]+index-[^"]+\.js)"/i);
82
- if (!match?.[1])
83
- return null;
84
- return node_path_1.default.join(extensionRoot, "webview", match[1]);
85
- }
86
- function backupPath(filePath) {
87
- return `${filePath}${BACKUP_SUFFIX}`;
88
- }
89
- async function patchFile(params) {
90
- const existing = await readFileIfExists(params.filePath);
91
- if (existing === null) {
92
- throw new Error(`required Codex extension file is missing: ${params.filePath}`);
93
- }
94
- const fileBackupPath = backupPath(params.filePath);
95
- const backupExists = await pathExists(fileBackupPath);
96
- const baseSource = backupExists ? ((await readFileIfExists(fileBackupPath)) ?? existing) : existing;
97
- const next = params.transform(baseSource);
98
- if (!backupExists) {
99
- await promises_1.default.writeFile(fileBackupPath, existing, "utf8");
100
- }
101
- if (next === existing && existing.includes(params.marker)) {
102
- return "already_patched";
103
- }
104
- await promises_1.default.writeFile(params.filePath, next, "utf8");
105
- return "patched";
106
- }
107
- function buildExtensionHostPatch(logoutCommandPath) {
108
- const embeddedCommandPath = JSON.stringify(logoutCommandPath);
109
- return `
110
- ;/* ${EXTENSION_PATCH_MARKER}:start */
111
- (()=> {
112
- const vscode = require("vscode");
113
- const childProcess = require("node:child_process");
114
- const os = require("node:os");
115
- const commandId = ${JSON.stringify(LOGOUT_COMMAND_ID)};
116
- const embeddedCommandPath = ${embeddedCommandPath};
117
-
118
- function quoteShellArg(value) {
119
- if (process.platform === "win32") {
120
- return '"' + String(value).replace(/"/g, '""') + '"';
121
- }
122
- return "'" + String(value).replace(/'/g, "'\\\\''") + "'";
123
- }
124
-
125
- function buildLogoutCommands() {
126
- const commands = [];
127
- if (embeddedCommandPath) {
128
- commands.push(\`\${quoteShellArg(embeddedCommandPath)} logout\`);
129
- }
130
- commands.push("theclawbay logout");
131
- return commands;
132
- }
133
-
134
- function runLogoutCommand(commandLine, cwd, outputChannel) {
135
- return new Promise((resolve, reject) => {
136
- childProcess.exec(
137
- commandLine,
138
- {
139
- cwd,
140
- windowsHide: true,
141
- maxBuffer: 1024 * 1024,
142
- shell: process.platform === "win32" ? (process.env.ComSpec || "cmd.exe") : (process.env.SHELL || "/bin/sh"),
143
- },
144
- (error, stdout, stderr) => {
145
- if (stdout) outputChannel.append(stdout);
146
- if (stderr) outputChannel.append(stderr);
147
- if (error) {
148
- reject(error);
149
- return;
150
- }
151
- resolve();
152
- },
153
- );
154
- });
155
- }
156
-
157
- async function executeLogout(outputChannel) {
158
- const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
159
- const cwd = workspaceFolder || os.homedir();
160
- const errors = [];
161
- for (const commandLine of buildLogoutCommands()) {
162
- outputChannel.appendLine(\`$ \${commandLine}\`);
163
- try {
164
- await runLogoutCommand(commandLine, cwd, outputChannel);
165
- return;
166
- } catch (error) {
167
- const message = error instanceof Error ? error.message : String(error);
168
- errors.push(\`\${commandLine}: \${message}\`);
169
- outputChannel.appendLine(message);
170
- }
171
- }
172
- throw new Error(errors.join("\\n"));
173
- }
174
-
175
- function patchActivate(exportsObject) {
176
- if (!exportsObject || typeof exportsObject.activate !== "function" || exportsObject.__theclawbayPatched) {
177
- return;
178
- }
179
- const originalActivate = exportsObject.activate;
180
- exportsObject.activate = async function patchedActivate(context, ...rest) {
181
- const result = await originalActivate.apply(this, [context, ...rest]);
182
- if (context && !context.__theclawbayLogoutRegistered) {
183
- context.__theclawbayLogoutRegistered = true;
184
- const outputChannel = vscode.window.createOutputChannel("The Claw Bay");
185
- context.subscriptions.push(outputChannel);
186
- context.subscriptions.push(
187
- vscode.commands.registerCommand(commandId, async () => {
188
- const confirmed = await vscode.window.showWarningMessage(
189
- "Run The Claw Bay logout and remove the Codex usage/UI patch?",
190
- { modal: true },
191
- "Log out",
192
- );
193
- if (confirmed !== "Log out") return;
194
-
195
- try {
196
- await vscode.window.withProgress(
197
- {
198
- location: vscode.ProgressLocation.Notification,
199
- title: "Removing The Claw Bay Codex patch…",
200
- },
201
- async () => {
202
- await executeLogout(outputChannel);
203
- },
204
- );
205
- const reloadChoice = await vscode.window.showInformationMessage(
206
- "The Claw Bay logout completed. Reload this window to finish removing the Codex patch.",
207
- "Reload Window",
208
- "Show Output",
209
- );
210
- if (reloadChoice === "Reload Window") {
211
- await vscode.commands.executeCommand("workbench.action.reloadWindow");
212
- } else if (reloadChoice === "Show Output") {
213
- outputChannel.show(true);
214
- }
215
- } catch (error) {
216
- outputChannel.show(true);
217
- const message = error instanceof Error ? error.message : String(error);
218
- await vscode.window.showErrorMessage(\`The Claw Bay logout failed: \${message}\`, "Show Output");
219
- }
220
- }),
221
- );
222
- }
223
- return result;
224
- };
225
- exportsObject.__theclawbayPatched = true;
226
- }
227
-
228
- patchActivate(module.exports);
229
- })();
230
- /* ${EXTENSION_PATCH_MARKER}:end */
231
- `;
232
- }
233
- function buildWebviewPatch() {
234
- return `
235
- ;/* ${WEBVIEW_PATCH_MARKER}:start */
236
- (()=> {
237
- const cardId = "theclawbay-codex-settings-card";
238
- const styleId = "theclawbay-codex-settings-style";
239
- const commandId = ${JSON.stringify(LOGOUT_COMMAND_ID)};
240
- const vscodeApi = typeof acquireVsCodeApi === "function" ? acquireVsCodeApi() : null;
241
-
242
- function currentRoute() {
243
- return \`\${window.location.pathname}\${window.location.hash}\`;
244
- }
245
-
246
- function isAgentSettingsRoute() {
247
- return currentRoute().includes("/settings/general") || currentRoute().includes("/settings/agent");
248
- }
249
-
250
- function ensureStyle() {
251
- if (document.getElementById(styleId)) return;
252
- const style = document.createElement("style");
253
- style.id = styleId;
254
- style.textContent = \`
255
- #\${cardId} {
256
- margin: 20px 0 28px;
257
- border: 1px solid var(--vscode-panel-border, rgba(255,255,255,0.12));
258
- border-radius: 12px;
259
- background: var(--vscode-editorWidget-background, rgba(255,255,255,0.03));
260
- color: var(--vscode-foreground, inherit);
261
- overflow: hidden;
262
- }
263
- #\${cardId} .theclawbay-header {
264
- display: flex;
265
- align-items: center;
266
- justify-content: space-between;
267
- gap: 12px;
268
- padding: 14px 16px;
269
- }
270
- #\${cardId} .theclawbay-title {
271
- font-size: 14px;
272
- font-weight: 700;
273
- color: var(--vscode-foreground, inherit);
274
- }
275
- #\${cardId} .theclawbay-topline {
276
- display: flex;
277
- align-items: center;
278
- gap: 10px;
279
- }
280
- #\${cardId} .theclawbay-badge {
281
- border-radius: 999px;
282
- padding: 2px 8px;
283
- font-size: 12px;
284
- font-weight: 600;
285
- color: var(--vscode-button-foreground, white);
286
- background: var(--vscode-button-background, #0e639c);
287
- }
288
- #\${cardId} .theclawbay-body {
289
- padding: 0 16px 16px;
290
- display: flex;
291
- flex-direction: column;
292
- gap: 12px;
293
- }
294
- #\${cardId} .theclawbay-copy {
295
- font-size: 13px;
296
- line-height: 1.5;
297
- color: var(--vscode-descriptionForeground, var(--vscode-foreground, inherit));
298
- }
299
- #\${cardId} .theclawbay-button {
300
- width: fit-content;
301
- border: 1px solid transparent;
302
- border-radius: 8px;
303
- padding: 8px 12px;
304
- cursor: pointer;
305
- font: inherit;
306
- font-weight: 600;
307
- color: var(--vscode-button-foreground, white);
308
- background: var(--vscode-button-background, #0e639c);
309
- }
310
- #\${cardId} .theclawbay-button:hover {
311
- background: var(--vscode-button-hoverBackground, #1177bb);
312
- }
313
- #\${cardId} .theclawbay-row {
314
- display: flex;
315
- align-items: center;
316
- justify-content: space-between;
317
- gap: 16px;
318
- flex-wrap: wrap;
319
- }
320
- #\${cardId} .theclawbay-toggle {
321
- display: inline-flex;
322
- align-items: center;
323
- gap: 10px;
324
- font-size: 13px;
325
- font-weight: 600;
326
- color: var(--vscode-foreground, inherit);
327
- }
328
- #\${cardId} .theclawbay-toggle input {
329
- width: 34px;
330
- height: 18px;
331
- accent-color: var(--vscode-button-background, #0e639c);
332
- cursor: pointer;
333
- }
334
- \`;
335
- document.head.appendChild(style);
336
- }
337
-
338
- function findMount() {
339
- const selectors = [
340
- "div.min-w-0.flex-1.overflow-visible",
341
- "div.min-w-0.flex-1",
342
- "main",
343
- ];
344
- for (const selector of selectors) {
345
- const matches = Array.from(document.querySelectorAll(selector));
346
- for (let index = matches.length - 1; index >= 0; index -= 1) {
347
- const match = matches[index];
348
- if (!(match instanceof HTMLElement)) continue;
349
- if (match.closest("#root") || match.id === "root") {
350
- return match;
351
- }
352
- }
353
- }
354
- return document.getElementById("root");
355
- }
356
-
357
- function buildCard() {
358
- const wrapper = document.createElement("section");
359
- wrapper.id = cardId;
360
-
361
- const header = document.createElement("div");
362
- header.className = "theclawbay-header";
363
-
364
- const topline = document.createElement("div");
365
- topline.className = "theclawbay-topline";
366
- const title = document.createElement("span");
367
- title.className = "theclawbay-title";
368
- title.textContent = "The Claw Bay";
369
- const badge = document.createElement("span");
370
- badge.className = "theclawbay-badge";
371
- badge.textContent = "Patched";
372
- topline.append(title, badge);
373
- header.appendChild(topline);
374
-
375
- const body = document.createElement("div");
376
- body.className = "theclawbay-body";
377
-
378
- const copy = document.createElement("p");
379
- copy.className = "theclawbay-copy";
380
- copy.textContent = "Shows The Claw Bay usage remaining inside Codex.";
381
-
382
- const row = document.createElement("div");
383
- row.className = "theclawbay-row";
384
-
385
- const toggleLabel = document.createElement("label");
386
- toggleLabel.className = "theclawbay-toggle";
387
-
388
- const toggle = document.createElement("input");
389
- toggle.type = "checkbox";
390
- toggle.checked = true;
391
- toggle.addEventListener("change", () => {
392
- if (!toggle.checked) {
393
- vscodeApi?.postMessage({ type: "open-vscode-command", command: commandId, args: [] });
394
- }
395
- window.setTimeout(() => {
396
- toggle.checked = true;
397
- }, 0);
398
- });
399
-
400
- const toggleText = document.createElement("span");
401
- toggleText.textContent = "Usage remaining patch";
402
- toggleLabel.append(toggle, toggleText);
403
-
404
- const button = document.createElement("button");
405
- button.type = "button";
406
- button.className = "theclawbay-button";
407
- button.textContent = "Remove patch";
408
- button.addEventListener("click", () => {
409
- vscodeApi?.postMessage({ type: "open-vscode-command", command: commandId, args: [] });
410
- });
411
-
412
- row.append(toggleLabel, button);
413
- body.append(copy, row);
414
- wrapper.append(header, body);
415
- return wrapper;
416
- }
417
-
418
- function syncCard() {
419
- const existing = document.getElementById(cardId);
420
- if (!isAgentSettingsRoute()) {
421
- existing?.remove();
422
- return;
423
- }
424
- ensureStyle();
425
- if (existing) return;
426
- const mount = findMount();
427
- if (!mount) return;
428
- mount.appendChild(buildCard());
429
- }
430
-
431
- let lastRoute = currentRoute();
432
- const observer = new MutationObserver(() => {
433
- const nextRoute = currentRoute();
434
- if (nextRoute !== lastRoute) {
435
- lastRoute = nextRoute;
436
- }
437
- syncCard();
438
- });
439
-
440
- if (document.body) {
441
- observer.observe(document.body, { childList: true, subtree: true });
442
- } else {
443
- window.addEventListener("DOMContentLoaded", () => {
444
- if (document.body) observer.observe(document.body, { childList: true, subtree: true });
445
- syncCard();
446
- }, { once: true });
447
- }
448
-
449
- window.addEventListener("popstate", syncCard);
450
- window.setInterval(syncCard, 1500);
451
- syncCard();
452
- })();
453
- /* ${WEBVIEW_PATCH_MARKER}:end */
454
- `;
455
- }
456
- async function patchCodexVsCodeExtensions(params) {
457
- void params;
458
- return {
459
- action: "none",
460
- patchedRoots: [],
461
- warning: "The Codex VS Code bundle patch has been disabled for safety. Run `theclawbay setup` or `theclawbay logout` to remove any older The Claw Bay VS Code patch.",
462
- };
463
- }
464
- async function cleanupCodexVsCodeExtensions() {
465
- try {
466
- const extensionRoots = await findCodexExtensionRoots();
467
- if (extensionRoots.length === 0) {
468
- return { action: "none", restoredRoots: [] };
469
- }
470
- const restoredRoots = new Set();
471
- for (const extensionRoot of extensionRoots) {
472
- const candidates = [
473
- node_path_1.default.join(extensionRoot, "out", "extension.js"),
474
- ];
475
- const webviewAssetsDir = node_path_1.default.join(extensionRoot, "webview", "assets");
476
- if (await pathExists(webviewAssetsDir)) {
477
- const webviewFiles = await promises_1.default.readdir(webviewAssetsDir);
478
- for (const fileName of webviewFiles) {
479
- if (!fileName.startsWith("index-") || !fileName.endsWith(".js"))
480
- continue;
481
- candidates.push(node_path_1.default.join(webviewAssetsDir, fileName));
482
- }
483
- }
484
- for (const filePath of candidates) {
485
- const fileBackupPath = backupPath(filePath);
486
- const backup = await readFileIfExists(fileBackupPath);
487
- if (backup === null)
488
- continue;
489
- const existing = await readFileIfExists(filePath);
490
- if (existing !== null &&
491
- (existing.includes(EXTENSION_PATCH_MARKER) || existing.includes(WEBVIEW_PATCH_MARKER))) {
492
- await promises_1.default.writeFile(filePath, backup, "utf8");
493
- restoredRoots.add(extensionRoot);
494
- }
495
- await promises_1.default.unlink(fileBackupPath).catch(() => { });
496
- }
497
- }
498
- return {
499
- action: restoredRoots.size > 0 ? "removed" : "none",
500
- restoredRoots: Array.from(restoredRoots).sort(),
501
- };
502
- }
503
- catch (error) {
504
- return {
505
- action: "none",
506
- restoredRoots: [],
507
- warning: `Could not clean up the Codex VS Code extension patch: ${error.message}`,
508
- };
509
- }
510
- }