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.
- package/dist/commands/logout.js +1 -16
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +84 -68
- package/dist/lib/device-session-auth.d.ts +0 -1
- package/dist/lib/device-session-auth.js +159 -26
- package/package.json +7 -2
- package/dist/lib/codex-vscode-patch.d.ts +0 -14
- package/dist/lib/codex-vscode-patch.js +0 -510
package/dist/commands/logout.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -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
|
-
"
|
|
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>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
|
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("\
|
|
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
|
|
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
|
|
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(
|
|
933
|
+
signal: AbortSignal.timeout(10000),
|
|
927
934
|
});
|
|
928
|
-
if (!response.ok)
|
|
929
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
1908
|
+
const migrateCodexConversations = await resolveCodexConversationMigrationSelection({
|
|
1873
1909
|
codexSelected: selectedSetupClients.has("codex"),
|
|
1874
|
-
flagValue: flags["
|
|
1910
|
+
flagValue: flags["migrate-conversations"],
|
|
1875
1911
|
skipPrompt: flags.yes,
|
|
1876
1912
|
});
|
|
1877
|
-
if (flags["
|
|
1878
|
-
throw new Error("--
|
|
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
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
-
|
|
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
|
|
2112
|
+
summaryNotes.add("Codex usage visibility temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
|
|
2088
2113
|
}
|
|
2089
|
-
if (
|
|
2090
|
-
summaryNotes.add("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
2359
|
+
"migrate-conversations": core_1.Flags.boolean({
|
|
2344
2360
|
required: false,
|
|
2345
2361
|
allowNo: true,
|
|
2346
|
-
description: "
|
|
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,
|
|
@@ -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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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:
|
|
178
|
-
authType:
|
|
179
|
-
deviceSessionId:
|
|
180
|
-
deviceLabel:
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
}
|