theclawbay 0.3.44 → 0.3.45

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";
@@ -1097,7 +1096,7 @@ async function writeCodexConfig(params) {
1097
1096
  next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
1098
1097
  const managedBlock = appendManagedBlock("", [
1099
1098
  MANAGED_START,
1100
- ...(params.enableUsageUiPatch ? [`chatgpt_base_url = "${proxyRoot}"`] : []),
1099
+ `chatgpt_base_url = "${proxyRoot}"`,
1101
1100
  `[model_providers.${DEFAULT_PROVIDER_ID}]`,
1102
1101
  'name = "OpenAI"',
1103
1102
  `base_url = "${proxyRoot}/backend-api/codex"`,
@@ -1869,13 +1868,13 @@ class SetupCommand extends base_command_1.BaseCommand {
1869
1868
  flagSelection: parseSetupClientFlags(flags.clients),
1870
1869
  skipPrompt: flags.yes,
1871
1870
  });
1872
- const codexUsageUiEnabled = await resolveCodexUsageUiSelection({
1871
+ const migrateCodexConversations = await resolveCodexConversationMigrationSelection({
1873
1872
  codexSelected: selectedSetupClients.has("codex"),
1874
- flagValue: flags["codex-usage-ui"],
1873
+ flagValue: flags["migrate-conversations"],
1875
1874
  skipPrompt: flags.yes,
1876
1875
  });
1877
- if (flags["codex-usage-ui"] && !selectedSetupClients.has("codex")) {
1878
- throw new Error("--codex-usage-ui requires Codex to be selected for setup.");
1876
+ if (flags["migrate-conversations"] !== undefined && !selectedSetupClients.has("codex")) {
1877
+ throw new Error("--migrate-conversations requires Codex to be selected for setup.");
1879
1878
  }
1880
1879
  if (!authCredential) {
1881
1880
  deviceLabel = await resolveDeviceLabel({
@@ -1887,7 +1886,6 @@ class SetupCommand extends base_command_1.BaseCommand {
1887
1886
  backendUrl,
1888
1887
  deviceLabel,
1889
1888
  selectedClients: Array.from(selectedSetupClients),
1890
- usagePatchRequested: codexUsageUiEnabled,
1891
1889
  log: (message) => this.log(message),
1892
1890
  });
1893
1891
  authType = browserAuth.authType;
@@ -1905,7 +1903,6 @@ class SetupCommand extends base_command_1.BaseCommand {
1905
1903
  let authSeedCleanup = null;
1906
1904
  let stateDbMigration = null;
1907
1905
  let modelCacheMigration = null;
1908
- let codexVsCodeCleanup = null;
1909
1906
  let continueConfigPath = null;
1910
1907
  let clineConfigPaths = [];
1911
1908
  let openClawConfigPath = null;
@@ -1936,36 +1933,29 @@ class SetupCommand extends base_command_1.BaseCommand {
1936
1933
  backendUrl,
1937
1934
  model: resolved?.model ?? DEFAULT_CODEX_MODEL,
1938
1935
  apiKey: authCredential,
1939
- enableUsageUiPatch: codexUsageUiEnabled,
1940
1936
  });
1941
1937
  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)({
1938
+ if (migrateCodexConversations) {
1939
+ sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
1949
1940
  codexHome: paths_1.codexDir,
1950
- apiKey: authCredential,
1941
+ migrationStateFile: MIGRATION_STATE_FILE,
1942
+ neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
1951
1943
  });
1952
- codexVsCodeCleanup = await (0, codex_vscode_patch_1.cleanupCodexVsCodeExtensions)();
1953
1944
  }
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)({
1945
+ authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
1946
+ codexHome: paths_1.codexDir,
1947
+ });
1948
+ authSeed = await (0, codex_auth_seeding_1.ensureCodexUsageLimitAuth)({
1949
+ codexHome: paths_1.codexDir,
1950
+ apiKey: authCredential,
1951
+ });
1952
+ if (migrateCodexConversations) {
1953
+ stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
1959
1954
  codexHome: paths_1.codexDir,
1960
- apiKey: authCredential,
1955
+ targetProvider: DEFAULT_PROVIDER_ID,
1956
+ sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
1961
1957
  });
1962
- codexVsCodeCleanup = await (0, codex_vscode_patch_1.cleanupCodexVsCodeExtensions)();
1963
1958
  }
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
1959
  modelCacheMigration = await (0, codex_model_cache_migration_1.ensureCodexModelCacheHasGpt54)({
1970
1960
  codexHome: paths_1.codexDir,
1971
1961
  });
@@ -2081,13 +2071,11 @@ class SetupCommand extends base_command_1.BaseCommand {
2081
2071
  summaryNotes.add(authSeed.warning);
2082
2072
  if (authSeedCleanup?.warning)
2083
2073
  summaryNotes.add(authSeedCleanup.warning);
2084
- if (codexVsCodeCleanup?.warning)
2085
- summaryNotes.add(codexVsCodeCleanup.warning);
2086
2074
  if (authSeed?.replacedExistingAuth) {
2087
- summaryNotes.add("Codex usage patch temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
2075
+ summaryNotes.add("Codex usage visibility temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
2088
2076
  }
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.");
2077
+ if (selectedSetupClients.has("codex") && !migrateCodexConversations) {
2078
+ summaryNotes.add("Left existing Codex conversations untouched.");
2091
2079
  }
2092
2080
  if (authType === "device-session" && deviceLabel) {
2093
2081
  summaryNotes.add(`This machine is linked as "${deviceLabel}". You can revoke it from the dashboard Devices section or with \`theclawbay logout\`.`);
@@ -2129,6 +2117,9 @@ class SetupCommand extends base_command_1.BaseCommand {
2129
2117
  else if (sessionMigration) {
2130
2118
  this.log("- Conversations: no local sessions required migration.");
2131
2119
  }
2120
+ if (!migrateCodexConversations) {
2121
+ this.log("- Conversations: left existing local Codex history untouched.");
2122
+ }
2132
2123
  if ((sessionMigration?.retimed ?? 0) > 0) {
2133
2124
  this.log(`- Conversation ordering: repaired timestamps on ${sessionMigration?.retimed ?? 0} local sessions.`);
2134
2125
  }
@@ -2160,18 +2151,6 @@ class SetupCommand extends base_command_1.BaseCommand {
2160
2151
  else if (modelCacheMigration) {
2161
2152
  this.log("- Codex model cache: no change.");
2162
2153
  }
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
2154
  }
2176
2155
  else if (codexDetected) {
2177
2156
  this.log("- Codex: detected but skipped");
@@ -2281,22 +2260,22 @@ class SetupCommand extends base_command_1.BaseCommand {
2281
2260
  this.log("- Zo: not detected (skipped)");
2282
2261
  }
2283
2262
  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.");
2263
+ this.log("- Codex login state: seeded local Codex auth so remaining usage can be shown.");
2285
2264
  }
2286
2265
  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.");
2266
+ this.log("- Codex login state: refreshed the local usage-limit auth seed.");
2288
2267
  }
2289
2268
  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.");
2269
+ this.log("- Codex login state: the local usage-limit auth seed was already present.");
2291
2270
  }
2292
2271
  else if (authSeed?.action === "seeded") {
2293
- this.log("- Codex login state: seeded local API-key auth for Codex UI.");
2272
+ this.log("- Codex login state: seeded local API-key auth.");
2294
2273
  }
2295
2274
  else if (authSeed?.action === "updated") {
2296
- this.log("- Codex login state: refreshed the Claw Bay API-key auth seed.");
2275
+ this.log("- Codex login state: refreshed the local API-key auth seed.");
2297
2276
  }
2298
2277
  else if (authSeed?.action === "already_seeded") {
2299
- this.log("- Codex login state: the Claw Bay API-key auth was already seeded.");
2278
+ this.log("- Codex login state: the local API-key auth seed was already present.");
2300
2279
  }
2301
2280
  else if (authSeed?.action === "already_present") {
2302
2281
  this.log("- Codex login state: preserved existing local Codex auth.");
@@ -2308,7 +2287,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2308
2287
  this.log("- Codex login state: no change.");
2309
2288
  }
2310
2289
  if (authSeedCleanup?.action === "restored_backup") {
2311
- this.log("- Codex login state: restored the auth that existed before the The Claw Bay usage patch.");
2290
+ this.log("- Codex login state: restored the auth that existed before an older The Claw Bay Codex auth override.");
2312
2291
  }
2313
2292
  this.log("Next: restart Continue, Cline, Roo Code, Trae, or Zo only if they were already open during setup.");
2314
2293
  });
@@ -2340,10 +2319,10 @@ SetupCommand.flags = {
2340
2319
  default: false,
2341
2320
  description: "Accept the recommended detected setup targets without prompting",
2342
2321
  }),
2343
- "codex-usage-ui": core_1.Flags.boolean({
2322
+ "migrate-conversations": core_1.Flags.boolean({
2344
2323
  required: false,
2345
2324
  allowNo: true,
2346
- description: "Patch Codex to show The Claw Bay usage remaining.",
2325
+ description: "Convert old Codex conversations so they stay visible under The Claw Bay.",
2347
2326
  }),
2348
2327
  "debug-output": core_1.Flags.boolean({
2349
2328
  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,6 +126,66 @@ 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
+ async function delay(ms) {
156
+ await new Promise((resolve) => setTimeout(resolve, ms));
157
+ }
158
+ async function waitForFirstSuccessfulExchange(promises) {
159
+ return await new Promise((resolve, reject) => {
160
+ const errors = [];
161
+ let pending = promises.length;
162
+ let settled = false;
163
+ const fail = (error) => {
164
+ if (settled)
165
+ return;
166
+ if (error instanceof Error) {
167
+ errors.push(error);
168
+ }
169
+ else {
170
+ errors.push(new Error(String(error)));
171
+ }
172
+ pending -= 1;
173
+ if (pending <= 0) {
174
+ reject(errors[0] ?? new Error("browser setup timed out"));
175
+ }
176
+ };
177
+ for (const promise of promises) {
178
+ promise.then((value) => {
179
+ if (settled)
180
+ return;
181
+ settled = true;
182
+ resolve(value);
183
+ }, (error) => {
184
+ fail(error);
185
+ });
186
+ }
187
+ });
188
+ }
127
189
  async function createBrowserLinkedDeviceSession(params) {
128
190
  const label = (params.deviceLabel ?? "").trim() || node_os_1.default.hostname() || "This device";
129
191
  const state = node_crypto_1.default.randomUUID();
@@ -137,7 +199,6 @@ async function createBrowserLinkedDeviceSession(params) {
137
199
  state,
138
200
  deviceLabel: label,
139
201
  selectedClients: params.selectedClients,
140
- usagePatchRequested: params.usagePatchRequested,
141
202
  }),
142
203
  signal: AbortSignal.timeout(20000),
143
204
  });
@@ -145,39 +206,60 @@ async function createBrowserLinkedDeviceSession(params) {
145
206
  if (!startResponse.ok || !startBody.authUrl || !startBody.sessionId) {
146
207
  throw new Error(startBody.error ?? `failed to start browser auth (HTTP ${startResponse.status})`);
147
208
  }
209
+ const setupSessionId = startBody.sessionId;
148
210
  const opened = openUrlInBrowser(startBody.authUrl);
149
211
  params.log(opened
150
212
  ? `Open browser auth if it did not appear automatically: ${startBody.authUrl}`
151
213
  : `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({
214
+ const deadlineMs = Date.now() + BROWSER_SETUP_TIMEOUT_MS;
215
+ let fallbackNoticeShown = false;
216
+ const timeoutError = new Error("browser setup timed out");
217
+ const waitForLocalCallbackExchange = async () => {
218
+ const callback = await Promise.race([
219
+ callbackServer.waitForCallback(),
220
+ new Promise((_, reject) => {
221
+ setTimeout(() => reject(timeoutError), BROWSER_SETUP_TIMEOUT_MS);
222
+ }),
223
+ ]);
224
+ return exchangeBrowserSetupSession({
225
+ backendUrl: params.backendUrl,
162
226
  sessionId: callback.sessionId,
163
227
  state: callback.state,
164
228
  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
- }
229
+ });
230
+ };
231
+ const waitForPolledExchange = async () => {
232
+ while (Date.now() < deadlineMs) {
233
+ try {
234
+ return await exchangeBrowserSetupSession({
235
+ backendUrl: params.backendUrl,
236
+ sessionId: setupSessionId,
237
+ state,
238
+ });
239
+ }
240
+ catch (error) {
241
+ const message = error.message || "";
242
+ if (!message.includes("setup session is not approved")) {
243
+ throw error;
244
+ }
245
+ }
246
+ if (!fallbackNoticeShown) {
247
+ params.log("Waiting for browser approval. If the browser cannot reach localhost, setup will still finish here after you connect the device.");
248
+ fallbackNoticeShown = true;
249
+ }
250
+ await delay(BROWSER_SETUP_POLL_INTERVAL_MS);
251
+ }
252
+ throw timeoutError;
253
+ };
254
+ const linked = await waitForFirstSuccessfulExchange([
255
+ waitForLocalCallbackExchange(),
256
+ waitForPolledExchange(),
257
+ ]);
176
258
  return {
177
- credential: exchangeBody.credential,
178
- authType: "device-session",
179
- deviceSessionId: exchangeBody.deviceSessionId,
180
- deviceLabel: exchangeBody.deviceLabel?.trim() || label,
259
+ credential: linked.credential,
260
+ authType: linked.authType,
261
+ deviceSessionId: linked.deviceSessionId,
262
+ deviceLabel: linked.deviceLabel?.trim() || label,
181
263
  };
182
264
  }
183
265
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.44",
3
+ "version": "0.3.45",
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
- }