theclawbay 0.3.87 → 0.4.0
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/README.md +3 -2
- package/dist/commands/logout.js +82 -1466
- package/dist/commands/setup.js +143 -3604
- package/dist/lib/base-command.js +7 -5
- package/dist/lib/clients/aider-cleanup.d.ts +1 -0
- package/dist/lib/clients/aider-cleanup.js +15 -0
- package/dist/lib/clients/aider.d.ts +14 -0
- package/dist/lib/clients/aider.js +57 -0
- package/dist/lib/clients/claude-cleanup.d.ts +2 -0
- package/dist/lib/clients/claude-cleanup.js +88 -0
- package/dist/lib/clients/claude.d.ts +49 -0
- package/dist/lib/clients/claude.js +400 -0
- package/dist/lib/clients/cline-cleanup.d.ts +1 -0
- package/dist/lib/clients/cline-cleanup.js +80 -0
- package/dist/lib/clients/cline.d.ts +15 -0
- package/dist/lib/clients/cline.js +73 -0
- package/dist/lib/clients/codex-cleanup.d.ts +7 -0
- package/dist/lib/clients/codex-cleanup.js +145 -0
- package/dist/lib/clients/codex.d.ts +28 -0
- package/dist/lib/clients/codex.js +176 -0
- package/dist/lib/clients/continue-cleanup.d.ts +1 -0
- package/dist/lib/clients/continue-cleanup.js +40 -0
- package/dist/lib/clients/continue.d.ts +22 -0
- package/dist/lib/clients/continue.js +81 -0
- package/dist/lib/clients/gsd-cleanup.d.ts +4 -0
- package/dist/lib/clients/gsd-cleanup.js +168 -0
- package/dist/lib/clients/gsd.d.ts +30 -0
- package/dist/lib/clients/gsd.js +136 -0
- package/dist/lib/clients/hermes-cleanup.d.ts +2 -0
- package/dist/lib/clients/hermes-cleanup.js +112 -0
- package/dist/lib/clients/hermes.d.ts +18 -0
- package/dist/lib/clients/hermes.js +145 -0
- package/dist/lib/clients/openclaw-cleanup.d.ts +1 -0
- package/dist/lib/clients/openclaw-cleanup.js +52 -0
- package/dist/lib/clients/openclaw.d.ts +34 -0
- package/dist/lib/clients/openclaw.js +100 -0
- package/dist/lib/clients/opencode-family-cleanup.d.ts +12 -0
- package/dist/lib/clients/opencode-family-cleanup.js +201 -0
- package/dist/lib/clients/opencode-family.d.ts +86 -0
- package/dist/lib/clients/opencode-family.js +427 -0
- package/dist/lib/clients/roo-cleanup.d.ts +1 -0
- package/dist/lib/clients/roo-cleanup.js +48 -0
- package/dist/lib/clients/roo.d.ts +21 -0
- package/dist/lib/clients/roo.js +91 -0
- package/dist/lib/clients/trae-cleanup.d.ts +2 -0
- package/dist/lib/clients/trae-cleanup.js +40 -0
- package/dist/lib/clients/trae.d.ts +27 -0
- package/dist/lib/clients/trae.js +118 -0
- package/dist/lib/clients/zo.d.ts +39 -0
- package/dist/lib/clients/zo.js +210 -0
- package/dist/lib/shared/backend.d.ts +31 -0
- package/dist/lib/shared/backend.js +273 -0
- package/dist/lib/shared/client-registry.d.ts +23 -0
- package/dist/lib/shared/client-registry.js +68 -0
- package/dist/lib/shared/editor-env-cleanup.d.ts +2 -0
- package/dist/lib/shared/editor-env-cleanup.js +86 -0
- package/dist/lib/shared/editor-env.d.ts +27 -0
- package/dist/lib/shared/editor-env.js +224 -0
- package/dist/lib/shared/env-persist-cleanup.d.ts +4 -0
- package/dist/lib/shared/env-persist-cleanup.js +77 -0
- package/dist/lib/shared/env-persist.d.ts +26 -0
- package/dist/lib/shared/env-persist.js +179 -0
- package/dist/lib/shared/format.d.ts +5 -0
- package/dist/lib/shared/format.js +48 -0
- package/dist/lib/shared/fsx-cleanup.d.ts +3 -0
- package/dist/lib/shared/fsx-cleanup.js +36 -0
- package/dist/lib/shared/fsx.d.ts +9 -0
- package/dist/lib/shared/fsx.js +103 -0
- package/dist/lib/shared/models.d.ts +60 -0
- package/dist/lib/shared/models.js +124 -0
- package/dist/lib/shared/platform.d.ts +16 -0
- package/dist/lib/shared/platform.js +116 -0
- package/dist/lib/shared/prompts.d.ts +29 -0
- package/dist/lib/shared/prompts.js +504 -0
- package/dist/lib/shared/providers.d.ts +3 -0
- package/dist/lib/shared/providers.js +8 -0
- package/dist/lib/shared/toml.d.ts +7 -0
- package/dist/lib/shared/toml.js +157 -0
- package/dist/lib/shared/tui.d.ts +42 -0
- package/dist/lib/shared/tui.js +173 -0
- package/dist/lib/shared/urls.d.ts +18 -0
- package/dist/lib/shared/urls.js +149 -0
- package/dist/lib/supported-models.d.ts +1 -1
- package/dist/lib/supported-models.js +1 -1
- package/package.json +1 -6
- package/theclawbay-supported-models.json +202 -5
package/dist/commands/setup.js
CHANGED
|
@@ -3,194 +3,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const node_readline_1 = require("node:readline");
|
|
11
|
-
const promises_2 = require("node:readline/promises");
|
|
12
|
-
const node_child_process_1 = require("node:child_process");
|
|
13
|
-
const core_1 = require("@oclif/core");
|
|
14
|
-
const yaml_1 = require("yaml");
|
|
6
|
+
const client_registry_1 = require("../lib/shared/client-registry");
|
|
7
|
+
const format_1 = require("../lib/shared/format");
|
|
15
8
|
const base_command_1 = require("../lib/base-command");
|
|
9
|
+
const core_1 = require("@oclif/core");
|
|
10
|
+
const config_1 = require("../lib/managed/config");
|
|
11
|
+
const errors_1 = require("../lib/managed/errors");
|
|
12
|
+
const api_key_1 = require("../lib/managed/api-key");
|
|
13
|
+
const urls_1 = require("../lib/shared/urls");
|
|
14
|
+
const codex_1 = require("../lib/clients/codex");
|
|
15
|
+
const claude_1 = require("../lib/clients/claude");
|
|
16
|
+
const continue_1 = require("../lib/clients/continue");
|
|
17
|
+
const cline_1 = require("../lib/clients/cline");
|
|
18
|
+
const gsd_1 = require("../lib/clients/gsd");
|
|
19
|
+
const opencode_family_1 = require("../lib/clients/opencode-family");
|
|
20
|
+
const roo_1 = require("../lib/clients/roo");
|
|
21
|
+
const trae_1 = require("../lib/clients/trae");
|
|
22
|
+
const aider_1 = require("../lib/clients/aider");
|
|
23
|
+
const zo_1 = require("../lib/clients/zo");
|
|
24
|
+
const hermes_1 = require("../lib/clients/hermes");
|
|
25
|
+
const platform_1 = require("../lib/shared/platform");
|
|
26
|
+
const prompts_1 = require("../lib/shared/prompts");
|
|
27
|
+
const backend_1 = require("../lib/shared/backend");
|
|
28
|
+
const device_session_auth_1 = require("../lib/device-session-auth");
|
|
29
|
+
const models_1 = require("../lib/shared/models");
|
|
16
30
|
const backups_1 = require("../lib/backups");
|
|
17
|
-
const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
18
31
|
const codex_history_migration_1 = require("../lib/codex-history-migration");
|
|
32
|
+
const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
19
33
|
const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
|
|
34
|
+
const env_persist_1 = require("../lib/shared/env-persist");
|
|
35
|
+
const editor_env_1 = require("../lib/shared/editor-env");
|
|
20
36
|
const paths_1 = require("../lib/config/paths");
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const device_session_auth_1 = require("../lib/device-session-auth");
|
|
26
|
-
const DEFAULT_BACKEND_URL = "https://theclawbay.com";
|
|
27
|
-
const DEFAULT_PROVIDER_ID = "theclawbay";
|
|
28
|
-
const CLI_HTTP_USER_AGENT = "theclawbay-cli";
|
|
29
|
-
const SUPPORTED_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
|
|
30
|
-
const MODEL_DISPLAY_NAMES = (0, supported_models_1.getSupportedModelDisplayNames)();
|
|
31
|
-
const DEFAULT_CODEX_MODEL = SUPPORTED_MODEL_IDS[0] ?? "gpt-5.5";
|
|
32
|
-
const DEFAULT_CONTINUE_MODEL = DEFAULT_CODEX_MODEL;
|
|
33
|
-
const DEFAULT_CLINE_MODEL = DEFAULT_CODEX_MODEL;
|
|
34
|
-
const DEFAULT_OPENCLAW_MODEL = DEFAULT_CODEX_MODEL;
|
|
35
|
-
const DEFAULT_KILO_MODEL = DEFAULT_CODEX_MODEL;
|
|
36
|
-
const DEFAULT_ROO_MODEL = DEFAULT_CODEX_MODEL;
|
|
37
|
-
const DEFAULT_TRAE_MODEL = DEFAULT_CODEX_MODEL;
|
|
38
|
-
const DEFAULT_AIDER_MODEL = DEFAULT_CODEX_MODEL;
|
|
39
|
-
const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
|
|
40
|
-
const DEFAULT_HERMES_MODEL = DEFAULT_CODEX_MODEL;
|
|
41
|
-
const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
42
|
-
const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
43
|
-
const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
|
|
44
|
-
const GEMINI_ENV_API_KEY_NAME = "GEMINI_API_KEY";
|
|
45
|
-
const GOOGLE_ENV_API_KEY_NAME = "GOOGLE_API_KEY";
|
|
46
|
-
const GOOGLE_GEMINI_BASE_URL_NAME = "GOOGLE_GEMINI_BASE_URL";
|
|
47
|
-
const CLAUDE_ENV_API_KEY_NAME = "ANTHROPIC_API_KEY";
|
|
48
|
-
const CLAUDE_ENV_BASE_URL_NAME = "ANTHROPIC_BASE_URL";
|
|
49
|
-
const CLAUDE_ENV_SIMPLE_MODE_NAME = "CLAUDE_CODE_SIMPLE";
|
|
50
|
-
const CLAUDE_ENV_DISABLE_NONESSENTIAL_TRAFFIC_NAME = "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC";
|
|
51
|
-
const CLAUDE_ENV_ENABLE_TELEMETRY_NAME = "CLAUDE_CODE_ENABLE_TELEMETRY";
|
|
52
|
-
const CLAUDE_CODE_EDITOR_ENV_SETTING = "claudeCode.environmentVariables";
|
|
53
|
-
const CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING = "claudeCode.disableLoginPrompt";
|
|
54
|
-
const ANTHROPIC_PROVIDER_ID = "anthropic";
|
|
55
|
-
const MANAGED_EDITOR_TERMINAL_ENV_NAMES = [
|
|
56
|
-
ENV_KEY_NAME,
|
|
57
|
-
GEMINI_ENV_API_KEY_NAME,
|
|
58
|
-
GOOGLE_ENV_API_KEY_NAME,
|
|
59
|
-
GOOGLE_GEMINI_BASE_URL_NAME,
|
|
60
|
-
];
|
|
61
|
-
const MANAGED_CLAUDE_ENV_NAMES = [
|
|
62
|
-
CLAUDE_ENV_API_KEY_NAME,
|
|
63
|
-
CLAUDE_ENV_BASE_URL_NAME,
|
|
64
|
-
CLAUDE_ENV_SIMPLE_MODE_NAME,
|
|
65
|
-
CLAUDE_ENV_DISABLE_NONESSENTIAL_TRAFFIC_NAME,
|
|
66
|
-
CLAUDE_ENV_ENABLE_TELEMETRY_NAME,
|
|
67
|
-
];
|
|
68
|
-
const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
|
|
69
|
-
const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
|
|
70
|
-
const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
|
|
71
|
-
const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
|
|
72
|
-
const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
|
|
73
|
-
const GSD_AGENT_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".gsd", "agent");
|
|
74
|
-
const GSD_MODELS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "models.json");
|
|
75
|
-
const GSD_AUTH_PATH = node_path_1.default.join(GSD_AGENT_DIR, "auth.json");
|
|
76
|
-
const GSD_SETTINGS_PATH = node_path_1.default.join(GSD_AGENT_DIR, "settings.json");
|
|
77
|
-
const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
|
|
78
|
-
const GSD_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "gsd.restore.json");
|
|
79
|
-
const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
|
|
80
|
-
const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
|
|
81
|
-
const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
|
|
82
|
-
const EDITOR_TERMINAL_ENV_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "editor-terminal-env.restore.json");
|
|
83
|
-
const CLAUDE_EDITOR_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "claude-editor-settings.restore.json");
|
|
84
|
-
const CLAUDE_DESKTOP_3P_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "claude-desktop-3p.restore.json");
|
|
85
|
-
const CODEX_FEATURE_FLAGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "codex-features.restore.json");
|
|
86
|
-
const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
|
|
87
|
-
const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
|
|
88
|
-
const MANAGED_START = "# theclawbay-managed:start";
|
|
89
|
-
const MANAGED_END = "# theclawbay-managed:end";
|
|
90
|
-
const AIDER_MANAGED_START = "# theclawbay-aider-managed:start";
|
|
91
|
-
const AIDER_MANAGED_END = "# theclawbay-aider-managed:end";
|
|
92
|
-
const SHELL_START = "# theclawbay-shell-managed:start";
|
|
93
|
-
const SHELL_END = "# theclawbay-shell-managed:end";
|
|
94
|
-
const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
|
|
95
|
-
const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
|
|
96
|
-
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", DEFAULT_PROVIDER_ID, "theclawbay-wan"];
|
|
97
|
-
const SETUP_CLIENT_IDS = ["codex", "claude", "continue", "cline", "gsd", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo", "hermes"];
|
|
98
|
-
const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
99
|
-
const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
|
|
100
|
-
const CANONICAL_CODEX_NATIVE_PROXY_SUFFIX = "/backend-api/codex";
|
|
101
|
-
const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
|
|
102
|
-
const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
|
|
103
|
-
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
104
|
-
const ROO_PROFILE_NAME = "The Claw Bay";
|
|
105
|
-
const ROO_PROFILE_ID = "theclawbay-openai-compatible";
|
|
106
|
-
const ROO_MODE_SLUGS = ["architect", "code", "ask", "debug", "orchestrator"];
|
|
107
|
-
const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
|
|
108
|
-
const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
|
|
109
|
-
const TRAE_TARGET_FUNCTION_START = "async setOriginModelListMapAndCache(e,t=!0){";
|
|
110
|
-
const TRAE_TARGET_FUNCTION_END = "}async refreshModelListConfig";
|
|
111
|
-
const OPENAI_PROVIDER_ID = "openai";
|
|
112
|
-
const OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
113
|
-
const KILO_CONFIG_SCHEMA_URL = "https://kilo.ai/config.json";
|
|
114
|
-
const ZO_CONFIG_NAME_PREFIX = "The Claw Bay";
|
|
115
|
-
const ZO_COOKIE_HOST = ".zo.computer";
|
|
116
|
-
const ZO_ACCESS_TOKEN_COOKIE = "access_token";
|
|
117
|
-
const ZO_API_BASE_URL = "https://api.zo.computer";
|
|
118
|
-
const HERMES_PROVIDER_NAME = "The Claw Bay";
|
|
119
|
-
const HERMES_KEY_ENV_NAME = "HERMES_THECLAWBAY_API_KEY";
|
|
120
|
-
function trimTrailingSlash(value) {
|
|
121
|
-
return value.replace(/\/+$/g, "");
|
|
122
|
-
}
|
|
123
|
-
function normalizeUrl(raw, label) {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = new URL(raw.trim());
|
|
126
|
-
return trimTrailingSlash(parsed.toString());
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
throw new Error(`invalid ${label} URL: ${raw}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function hasCommand(name) {
|
|
133
|
-
const lookupCommand = node_os_1.default.platform() === "win32" ? "where.exe" : "which";
|
|
134
|
-
const result = (0, node_child_process_1.spawnSync)(lookupCommand, [name], { stdio: "ignore" });
|
|
135
|
-
return result.status === 0;
|
|
136
|
-
}
|
|
137
|
-
function resolveCommandPath(name) {
|
|
138
|
-
const lookupCommand = node_os_1.default.platform() === "win32" ? "where.exe" : "which";
|
|
139
|
-
const result = (0, node_child_process_1.spawnSync)(lookupCommand, [name], { encoding: "utf8" });
|
|
140
|
-
if (result.status !== 0)
|
|
141
|
-
return null;
|
|
142
|
-
const match = result.stdout
|
|
143
|
-
.split(/\r?\n/)
|
|
144
|
-
.map((line) => line.trim())
|
|
145
|
-
.find(Boolean);
|
|
146
|
-
return match ?? null;
|
|
147
|
-
}
|
|
148
|
-
function supportsTerminalHyperlinks() {
|
|
149
|
-
if (!process.stdout.isTTY)
|
|
150
|
-
return false;
|
|
151
|
-
if (process.env.FORCE_HYPERLINK === "1")
|
|
152
|
-
return true;
|
|
153
|
-
if (process.env.NO_HYPERLINK === "1")
|
|
154
|
-
return false;
|
|
155
|
-
const term = (process.env.TERM ?? "").toLowerCase();
|
|
156
|
-
if (!term || term === "dumb")
|
|
157
|
-
return false;
|
|
158
|
-
const termProgram = (process.env.TERM_PROGRAM ?? "").toLowerCase();
|
|
159
|
-
if (termProgram === "iterm.app" || termProgram === "wezterm" || termProgram === "vscode") {
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
if (process.env.WT_SESSION ||
|
|
163
|
-
process.env.KITTY_WINDOW_ID ||
|
|
164
|
-
process.env.KONSOLE_VERSION ||
|
|
165
|
-
process.env.VTE_VERSION ||
|
|
166
|
-
process.env.DOMTERM ||
|
|
167
|
-
process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm") {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
return term.includes("xterm-kitty") || term.includes("foot") || term.includes("wezterm");
|
|
171
|
-
}
|
|
172
|
-
function formatTerminalHyperlink(label, url) {
|
|
173
|
-
return `\u001B]8;;${url}\u0007${label}\u001B]8;;\u0007`;
|
|
174
|
-
}
|
|
175
|
-
function formatDim(value) {
|
|
176
|
-
return `\u001B[2m${value}\u001B[0m`;
|
|
177
|
-
}
|
|
178
|
-
function officialSiteLabel(siteUrl) {
|
|
179
|
-
const parsed = new URL(siteUrl);
|
|
180
|
-
const hostname = parsed.hostname.replace(/^www\./, "");
|
|
181
|
-
const pathname = parsed.pathname.replace(/\/+$/g, "");
|
|
182
|
-
return pathname && pathname !== "/" ? `${hostname}${pathname}` : hostname;
|
|
183
|
-
}
|
|
184
|
-
function formatSetupClientLabel(client, hyperlinksEnabled) {
|
|
185
|
-
const iconAndLabel = `${client.icon} ${client.label}`;
|
|
186
|
-
if (hyperlinksEnabled) {
|
|
187
|
-
return `${formatTerminalHyperlink(iconAndLabel, client.siteUrl)} ${formatDim(`(${officialSiteLabel(client.siteUrl)})`)}`;
|
|
188
|
-
}
|
|
189
|
-
return `${iconAndLabel} - ${client.siteUrl}`;
|
|
190
|
-
}
|
|
191
|
-
function formatSummaryList(items) {
|
|
192
|
-
return items.join(", ");
|
|
193
|
-
}
|
|
37
|
+
const providers_1 = require("../lib/shared/providers");
|
|
38
|
+
const openclaw_1 = require("../lib/clients/openclaw");
|
|
39
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
40
|
+
const tui_1 = require("../lib/shared/tui");
|
|
194
41
|
function logSetupCompactSummary(params) {
|
|
195
42
|
const configured = params.setupClients
|
|
196
43
|
.filter((client) => params.selectedSetupClients.has(client.id))
|
|
@@ -198,3362 +45,49 @@ function logSetupCompactSummary(params) {
|
|
|
198
45
|
const restartTargets = params.setupClients
|
|
199
46
|
.filter((client) => params.selectedSetupClients.has(client.id) && ["continue", "cline", "gsd", "roo", "trae", "zo"].includes(client.id))
|
|
200
47
|
.map((client) => client.summaryLabel);
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
else {
|
|
205
|
-
params.log("Configured: no local tools selected.");
|
|
206
|
-
}
|
|
207
|
-
params.log("Shared config: saved your The Claw Bay credential and managed machine setup.");
|
|
208
|
-
if (restartTargets.length > 0) {
|
|
209
|
-
params.log(`Restart if already open: ${formatSummaryList(restartTargets)}`);
|
|
210
|
-
}
|
|
211
|
-
for (const note of params.notes) {
|
|
212
|
-
params.log(`Note: ${note}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
function localAppDataDir() {
|
|
216
|
-
if (process.env.LOCALAPPDATA?.trim())
|
|
217
|
-
return process.env.LOCALAPPDATA;
|
|
218
|
-
return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Local");
|
|
219
|
-
}
|
|
220
|
-
function roamingAppDataDir() {
|
|
221
|
-
if (process.env.APPDATA?.trim())
|
|
222
|
-
return process.env.APPDATA;
|
|
223
|
-
return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
|
|
224
|
-
}
|
|
225
|
-
function zoCookieDbCandidates() {
|
|
226
|
-
const home = node_os_1.default.homedir();
|
|
227
|
-
const candidates = new Set();
|
|
228
|
-
switch (node_os_1.default.platform()) {
|
|
229
|
-
case "win32":
|
|
230
|
-
candidates.add(node_path_1.default.join(roamingAppDataDir(), "Zo", "Network", "Cookies"));
|
|
231
|
-
break;
|
|
232
|
-
case "darwin":
|
|
233
|
-
candidates.add(node_path_1.default.join(home, "Library", "Application Support", "Zo", "Network", "Cookies"));
|
|
234
|
-
break;
|
|
235
|
-
default:
|
|
236
|
-
candidates.add(node_path_1.default.join(home, ".config", "Zo", "Network", "Cookies"));
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
return Array.from(candidates);
|
|
240
|
-
}
|
|
241
|
-
function zoProgramCandidates() {
|
|
242
|
-
const candidates = new Set();
|
|
243
|
-
if (process.env.THECLAWBAY_ZO_PATH?.trim()) {
|
|
244
|
-
candidates.add(process.env.THECLAWBAY_ZO_PATH.trim());
|
|
245
|
-
}
|
|
246
|
-
if (node_os_1.default.platform() === "win32") {
|
|
247
|
-
candidates.add(node_path_1.default.join(localAppDataDir(), "Programs", "Zo"));
|
|
248
|
-
for (const envKey of ["ProgramFiles", "ProgramFiles(x86)"]) {
|
|
249
|
-
const root = process.env[envKey]?.trim();
|
|
250
|
-
if (root)
|
|
251
|
-
candidates.add(node_path_1.default.join(root, "Zo"));
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else if (node_os_1.default.platform() === "darwin") {
|
|
255
|
-
candidates.add("/Applications/Zo.app");
|
|
256
|
-
}
|
|
257
|
-
return Array.from(candidates);
|
|
258
|
-
}
|
|
259
|
-
function pythonCommandCandidates() {
|
|
260
|
-
if (node_os_1.default.platform() === "win32") {
|
|
261
|
-
return [
|
|
262
|
-
{ command: "py", baseArgs: ["-3"] },
|
|
263
|
-
{ command: "python", baseArgs: [] },
|
|
264
|
-
{ command: "python3", baseArgs: [] },
|
|
265
|
-
];
|
|
266
|
-
}
|
|
267
|
-
return [
|
|
268
|
-
{ command: "python3", baseArgs: [] },
|
|
269
|
-
{ command: "python", baseArgs: [] },
|
|
270
|
-
];
|
|
271
|
-
}
|
|
272
|
-
function readZoCookieValue(cookieName) {
|
|
273
|
-
const dbPath = zoCookieDbCandidates().find((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
274
|
-
if (!dbPath)
|
|
275
|
-
return null;
|
|
276
|
-
const pythonScript = [
|
|
277
|
-
"import json, sqlite3, sys",
|
|
278
|
-
"db_path, host_key, cookie_name = sys.argv[1:4]",
|
|
279
|
-
'conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)',
|
|
280
|
-
"row = conn.execute(",
|
|
281
|
-
' "select value from cookies where host_key=? and name=? order by length(value) desc limit 1",',
|
|
282
|
-
" (host_key, cookie_name),",
|
|
283
|
-
").fetchone()",
|
|
284
|
-
'print(json.dumps({"value": row[0] if row else None}))',
|
|
285
|
-
].join("\n");
|
|
286
|
-
for (const candidate of pythonCommandCandidates()) {
|
|
287
|
-
const result = (0, node_child_process_1.spawnSync)(candidate.command, [...candidate.baseArgs, "-c", pythonScript, dbPath, ZO_COOKIE_HOST, cookieName], {
|
|
288
|
-
encoding: "utf8",
|
|
289
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
290
|
-
});
|
|
291
|
-
if (result.status !== 0 || !result.stdout.trim())
|
|
292
|
-
continue;
|
|
293
|
-
try {
|
|
294
|
-
const parsed = JSON.parse(result.stdout);
|
|
295
|
-
if (typeof parsed.value === "string" && parsed.value.trim()) {
|
|
296
|
-
return parsed.value.trim();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (hasCommand("sqlite3")) {
|
|
304
|
-
const result = (0, node_child_process_1.spawnSync)("sqlite3", [
|
|
305
|
-
dbPath,
|
|
306
|
-
`select value from cookies where host_key='${ZO_COOKIE_HOST}' and name='${cookieName}' order by length(value) desc limit 1;`,
|
|
307
|
-
], {
|
|
308
|
-
encoding: "utf8",
|
|
309
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
310
|
-
});
|
|
311
|
-
if (result.status === 0 && result.stdout.trim()) {
|
|
312
|
-
return result.stdout.trim();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
function parseZoByokConfig(value) {
|
|
318
|
-
if (!value || typeof value !== "object")
|
|
319
|
-
return null;
|
|
320
|
-
return value;
|
|
321
|
-
}
|
|
322
|
-
async function zoApiRequest(params) {
|
|
323
|
-
const response = await fetch(`${ZO_API_BASE_URL}${params.pathname}`, {
|
|
324
|
-
method: params.method ?? "GET",
|
|
325
|
-
headers: {
|
|
326
|
-
Authorization: `Bearer ${params.token}`,
|
|
327
|
-
Accept: "application/json",
|
|
328
|
-
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
329
|
-
...(params.body ? { "Content-Type": "application/json" } : {}),
|
|
330
|
-
},
|
|
331
|
-
body: params.body ? JSON.stringify(params.body) : undefined,
|
|
332
|
-
signal: AbortSignal.timeout(10000),
|
|
333
|
-
});
|
|
334
|
-
const responseText = await response.text();
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
const detail = responseText ? ` ${responseText.slice(0, 400)}` : "";
|
|
337
|
-
throw new Error(`Zo API ${params.method ?? "GET"} ${params.pathname} failed with ${response.status}.${detail}`.trim());
|
|
338
|
-
}
|
|
339
|
-
return (responseText ? JSON.parse(responseText) : null);
|
|
340
|
-
}
|
|
341
|
-
async function configureZoByok(params) {
|
|
342
|
-
const accessToken = readZoCookieValue(ZO_ACCESS_TOKEN_COOKIE);
|
|
343
|
-
if (!accessToken) {
|
|
344
|
-
throw new Error("Zo was detected, but no active Zo desktop session token could be read. Open Zo, sign in, and rerun setup.");
|
|
345
|
-
}
|
|
346
|
-
const desiredName = `${ZO_CONFIG_NAME_PREFIX} ${modelDisplayName(params.model)}`;
|
|
347
|
-
const desiredBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
348
|
-
const body = {
|
|
349
|
-
provider: "openai-style",
|
|
350
|
-
format: "openai",
|
|
351
|
-
name: desiredName,
|
|
352
|
-
base_url: desiredBaseUrl,
|
|
353
|
-
api_key: params.apiKey,
|
|
354
|
-
model_id: params.model,
|
|
355
|
-
supports_images: false,
|
|
356
|
-
};
|
|
357
|
-
const existing = await zoApiRequest({
|
|
358
|
-
token: accessToken,
|
|
359
|
-
pathname: "/byok/",
|
|
360
|
-
});
|
|
361
|
-
const existingConfigs = (Array.isArray(existing) ? existing : []).map(parseZoByokConfig).filter(Boolean);
|
|
362
|
-
const matched = existingConfigs.find((entry) => {
|
|
363
|
-
const name = typeof entry.name === "string" ? entry.name : "";
|
|
364
|
-
const baseUrl = typeof entry.base_url === "string" ? entry.base_url : "";
|
|
365
|
-
const modelId = typeof entry.model_id === "string" ? entry.model_id : "";
|
|
366
|
-
return name === desiredName || (baseUrl === desiredBaseUrl && modelId === params.model);
|
|
367
|
-
});
|
|
368
|
-
if (matched && typeof matched.id === "string" && matched.id) {
|
|
369
|
-
await zoApiRequest({
|
|
370
|
-
token: accessToken,
|
|
371
|
-
pathname: `/byok/${matched.id}`,
|
|
372
|
-
method: "PATCH",
|
|
373
|
-
body,
|
|
374
|
-
});
|
|
375
|
-
return desiredName;
|
|
376
|
-
}
|
|
377
|
-
await zoApiRequest({
|
|
378
|
-
token: accessToken,
|
|
379
|
-
pathname: "/byok/",
|
|
380
|
-
method: "POST",
|
|
381
|
-
body,
|
|
382
|
-
});
|
|
383
|
-
return desiredName;
|
|
384
|
-
}
|
|
385
|
-
const TRAE_BUNDLE_RELATIVE_PATH = node_path_1.default.join("resources", "app", "node_modules", "@byted-icube", "ai-modules-chat", "dist", "index.js");
|
|
386
|
-
function appendTraeBundlePath(basePath) {
|
|
387
|
-
if (basePath.toLowerCase().endsWith(node_path_1.default.normalize(TRAE_BUNDLE_RELATIVE_PATH).toLowerCase())) {
|
|
388
|
-
return basePath;
|
|
389
|
-
}
|
|
390
|
-
return node_path_1.default.join(basePath, TRAE_BUNDLE_RELATIVE_PATH);
|
|
391
|
-
}
|
|
392
|
-
function traeBundleCandidateRoots() {
|
|
393
|
-
const candidates = new Set();
|
|
394
|
-
if (process.env.THECLAWBAY_TRAE_PATH?.trim()) {
|
|
395
|
-
candidates.add(process.env.THECLAWBAY_TRAE_PATH.trim());
|
|
396
|
-
}
|
|
397
|
-
const localPrograms = node_path_1.default.join(localAppDataDir(), "Programs");
|
|
398
|
-
candidates.add(node_path_1.default.join(localPrograms, "Trae"));
|
|
399
|
-
for (const envKey of ["ProgramFiles", "ProgramFiles(x86)"]) {
|
|
400
|
-
const root = process.env[envKey]?.trim();
|
|
401
|
-
if (root)
|
|
402
|
-
candidates.add(node_path_1.default.join(root, "Trae"));
|
|
403
|
-
}
|
|
404
|
-
if ((0, node_fs_1.existsSync)(localPrograms)) {
|
|
405
|
-
for (const entry of (0, node_fs_1.readdirSync)(localPrograms, { withFileTypes: true })) {
|
|
406
|
-
if (!entry.isDirectory())
|
|
407
|
-
continue;
|
|
408
|
-
if (!entry.name.toLowerCase().includes("trae"))
|
|
409
|
-
continue;
|
|
410
|
-
candidates.add(node_path_1.default.join(localPrograms, entry.name));
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return Array.from(candidates);
|
|
414
|
-
}
|
|
415
|
-
function traeBundlePath() {
|
|
416
|
-
if (node_os_1.default.platform() !== "win32")
|
|
417
|
-
return null;
|
|
418
|
-
for (const candidateRoot of traeBundleCandidateRoots()) {
|
|
419
|
-
const bundlePath = appendTraeBundlePath(candidateRoot);
|
|
420
|
-
if ((0, node_fs_1.existsSync)(bundlePath))
|
|
421
|
-
return bundlePath;
|
|
422
|
-
}
|
|
423
|
-
return appendTraeBundlePath(node_path_1.default.join(localAppDataDir(), "Programs", "Trae"));
|
|
424
|
-
}
|
|
425
|
-
function traeBundleBackupPath(bundlePath) {
|
|
426
|
-
return `${bundlePath}${TRAE_BUNDLE_BACKUP_SUFFIX}`;
|
|
427
|
-
}
|
|
428
|
-
async function pathExists(filePath) {
|
|
429
|
-
try {
|
|
430
|
-
await promises_1.default.access(filePath);
|
|
431
|
-
return true;
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
const err = error;
|
|
435
|
-
if (err.code === "ENOENT")
|
|
436
|
-
return false;
|
|
437
|
-
throw error;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
function removeManagedBlock(source, start, end) {
|
|
441
|
-
const markerStart = source.indexOf(start);
|
|
442
|
-
if (markerStart < 0)
|
|
443
|
-
return source;
|
|
444
|
-
const markerEnd = source.indexOf(end, markerStart);
|
|
445
|
-
if (markerEnd < 0)
|
|
446
|
-
return source.slice(0, markerStart).trimEnd() + "\n";
|
|
447
|
-
return (source.slice(0, markerStart) + source.slice(markerEnd + end.length)).trimEnd() + "\n";
|
|
448
|
-
}
|
|
449
|
-
function appendManagedBlock(source, lines) {
|
|
450
|
-
const body = lines.join("\n").trimEnd() + "\n";
|
|
451
|
-
if (!source.trim())
|
|
452
|
-
return body;
|
|
453
|
-
return `${source.trimEnd()}\n\n${body}`;
|
|
454
|
-
}
|
|
455
|
-
function removeProviderTable(source, providerId) {
|
|
456
|
-
const header = `[model_providers.${providerId}]`;
|
|
457
|
-
const lines = source.split(/\r?\n/);
|
|
458
|
-
const output = [];
|
|
459
|
-
for (let i = 0; i < lines.length; i++) {
|
|
460
|
-
if ((lines[i] ?? "").trim() !== header) {
|
|
461
|
-
output.push(lines[i] ?? "");
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
i++;
|
|
465
|
-
while (i < lines.length && !/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? "")) {
|
|
466
|
-
i++;
|
|
467
|
-
}
|
|
468
|
-
i--;
|
|
469
|
-
}
|
|
470
|
-
return `${output.join("\n").trimEnd()}\n`;
|
|
471
|
-
}
|
|
472
|
-
function upsertFirstKeyLine(source, key, tomlValue) {
|
|
473
|
-
const lines = source.split(/\r?\n/);
|
|
474
|
-
for (let i = 0; i < lines.length; i++) {
|
|
475
|
-
const line = lines[i] ?? "";
|
|
476
|
-
if (/^\s*#/.test(line))
|
|
477
|
-
continue;
|
|
478
|
-
if (/^\s*\[/.test(line))
|
|
479
|
-
break;
|
|
480
|
-
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
481
|
-
lines[i] = `${key} = ${tomlValue}`;
|
|
482
|
-
return `${lines.join("\n").trimEnd()}\n`;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return `${`${key} = ${tomlValue}\n${source}`.trimEnd()}\n`;
|
|
486
|
-
}
|
|
487
|
-
function parseTomlScalarValue(line) {
|
|
488
|
-
const match = line.match(/^\s*[A-Za-z0-9_.-]+\s*=\s*(.+?)\s*$/);
|
|
489
|
-
return match?.[1]?.trim() ?? null;
|
|
490
|
-
}
|
|
491
|
-
function upsertTopLevelTableKey(source, tableName, key, tomlValue) {
|
|
492
|
-
const header = `[${tableName}]`;
|
|
493
|
-
const lines = source.split(/\r?\n/);
|
|
494
|
-
let tableStart = -1;
|
|
495
|
-
for (let i = 0; i < lines.length; i++) {
|
|
496
|
-
if ((lines[i] ?? "").trim() === header) {
|
|
497
|
-
tableStart = i;
|
|
498
|
-
break;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (tableStart < 0) {
|
|
502
|
-
const block = `${header}\n${key} = ${tomlValue}\n`;
|
|
503
|
-
if (!source.trim())
|
|
504
|
-
return block;
|
|
505
|
-
return `${source.trimEnd()}\n\n${block}`;
|
|
506
|
-
}
|
|
507
|
-
let tableEnd = lines.length;
|
|
508
|
-
for (let i = tableStart + 1; i < lines.length; i++) {
|
|
509
|
-
if (/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? "")) {
|
|
510
|
-
tableEnd = i;
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
for (let i = tableStart + 1; i < tableEnd; i++) {
|
|
515
|
-
const line = lines[i] ?? "";
|
|
516
|
-
if (/^\s*#/.test(line))
|
|
517
|
-
continue;
|
|
518
|
-
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
519
|
-
lines[i] = `${key} = ${tomlValue}`;
|
|
520
|
-
return `${lines.join("\n").trimEnd()}\n`;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
lines.splice(tableEnd, 0, `${key} = ${tomlValue}`);
|
|
524
|
-
return `${lines.join("\n").trimEnd()}\n`;
|
|
525
|
-
}
|
|
526
|
-
function setTopLevelTableKey(source, tableName, key, tomlValue) {
|
|
527
|
-
return upsertTopLevelTableKey(source, tableName, key, tomlValue);
|
|
528
|
-
}
|
|
529
|
-
function getTopLevelTableKeyValue(source, tableName, key) {
|
|
530
|
-
const header = `[${tableName}]`;
|
|
531
|
-
const lines = source.split(/\r?\n/);
|
|
532
|
-
let tableStart = -1;
|
|
533
|
-
for (let i = 0; i < lines.length; i++) {
|
|
534
|
-
if ((lines[i] ?? "").trim() === header) {
|
|
535
|
-
tableStart = i;
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (tableStart < 0)
|
|
540
|
-
return null;
|
|
541
|
-
let tableEnd = lines.length;
|
|
542
|
-
for (let i = tableStart + 1; i < lines.length; i++) {
|
|
543
|
-
if (/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? "")) {
|
|
544
|
-
tableEnd = i;
|
|
545
|
-
break;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
for (let i = tableStart + 1; i < tableEnd; i++) {
|
|
549
|
-
const line = lines[i] ?? "";
|
|
550
|
-
if (/^\s*#/.test(line))
|
|
551
|
-
continue;
|
|
552
|
-
if (!new RegExp(`^\\s*${key}\\s*=`).test(line))
|
|
553
|
-
continue;
|
|
554
|
-
return parseTomlScalarValue(line);
|
|
555
|
-
}
|
|
556
|
-
return null;
|
|
557
|
-
}
|
|
558
|
-
function removeTopLevelTableKey(source, tableName, key) {
|
|
559
|
-
const header = `[${tableName}]`;
|
|
560
|
-
const lines = source.split(/\r?\n/);
|
|
561
|
-
let tableStart = -1;
|
|
562
|
-
for (let i = 0; i < lines.length; i++) {
|
|
563
|
-
if ((lines[i] ?? "").trim() === header) {
|
|
564
|
-
tableStart = i;
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (tableStart < 0)
|
|
569
|
-
return source;
|
|
570
|
-
let tableEnd = lines.length;
|
|
571
|
-
for (let i = tableStart + 1; i < lines.length; i++) {
|
|
572
|
-
if (/^\s*\[[^\]]+\]\s*$/.test(lines[i] ?? "")) {
|
|
573
|
-
tableEnd = i;
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
const nextLines = [...lines];
|
|
578
|
-
for (let i = tableStart + 1; i < tableEnd; i++) {
|
|
579
|
-
const line = nextLines[i] ?? "";
|
|
580
|
-
if (/^\s*#/.test(line))
|
|
581
|
-
continue;
|
|
582
|
-
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
583
|
-
nextLines.splice(i, 1);
|
|
584
|
-
return `${nextLines.join("\n").trimEnd()}\n`;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return source;
|
|
588
|
-
}
|
|
589
|
-
async function restoreManagedCodexFeatureFlags(source) {
|
|
590
|
-
const snapshotRaw = await readFileIfExists(CODEX_FEATURE_FLAGS_STATE_PATH);
|
|
591
|
-
if (snapshotRaw === null || !snapshotRaw.trim())
|
|
592
|
-
return source;
|
|
593
|
-
let snapshot = null;
|
|
594
|
-
try {
|
|
595
|
-
snapshot = JSON.parse(snapshotRaw);
|
|
596
|
-
}
|
|
597
|
-
catch {
|
|
598
|
-
snapshot = null;
|
|
599
|
-
}
|
|
600
|
-
await removeFileIfExists(CODEX_FEATURE_FLAGS_STATE_PATH);
|
|
601
|
-
if (!snapshot)
|
|
602
|
-
return source;
|
|
603
|
-
let next = source;
|
|
604
|
-
if (snapshot.appsHadKey && snapshot.appsPreviousValue !== null) {
|
|
605
|
-
next = setTopLevelTableKey(next, "features", "apps", snapshot.appsPreviousValue);
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
next = removeTopLevelTableKey(next, "features", "apps");
|
|
609
|
-
}
|
|
610
|
-
if (snapshot.appsMcpGatewayHadKey && snapshot.appsMcpGatewayPreviousValue !== null) {
|
|
611
|
-
next = setTopLevelTableKey(next, "features", "apps_mcp_gateway", snapshot.appsMcpGatewayPreviousValue);
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
next = removeTopLevelTableKey(next, "features", "apps_mcp_gateway");
|
|
615
|
-
}
|
|
616
|
-
return next;
|
|
617
|
-
}
|
|
618
|
-
async function ensureManagedCodexFeatureFlags(source) {
|
|
619
|
-
const existingSnapshotRaw = await readFileIfExists(CODEX_FEATURE_FLAGS_STATE_PATH);
|
|
620
|
-
if (existingSnapshotRaw === null || !existingSnapshotRaw.trim()) {
|
|
621
|
-
const featuresHeader = "[features]";
|
|
622
|
-
const lines = source.split(/\r?\n/);
|
|
623
|
-
const hadFeaturesTable = lines.some((line) => (line ?? "").trim() === featuresHeader);
|
|
624
|
-
const appsPreviousValue = getTopLevelTableKeyValue(source, "features", "apps");
|
|
625
|
-
const appsMcpGatewayPreviousValue = getTopLevelTableKeyValue(source, "features", "apps_mcp_gateway");
|
|
626
|
-
const snapshot = {
|
|
627
|
-
hadFeaturesTable,
|
|
628
|
-
appsHadKey: appsPreviousValue !== null,
|
|
629
|
-
appsPreviousValue,
|
|
630
|
-
appsMcpGatewayHadKey: appsMcpGatewayPreviousValue !== null,
|
|
631
|
-
appsMcpGatewayPreviousValue,
|
|
632
|
-
};
|
|
633
|
-
await writeJsonObjectFile(CODEX_FEATURE_FLAGS_STATE_PATH, snapshot, 0o600);
|
|
634
|
-
}
|
|
635
|
-
let next = source;
|
|
636
|
-
next = setTopLevelTableKey(next, "features", "apps", "true");
|
|
637
|
-
next = setTopLevelTableKey(next, "features", "apps_mcp_gateway", "true");
|
|
638
|
-
return next;
|
|
639
|
-
}
|
|
640
|
-
function removeTopLevelKeyLineIf(source, key, shouldRemove) {
|
|
641
|
-
const lines = source.split(/\r?\n/);
|
|
642
|
-
const filtered = [];
|
|
643
|
-
let removed = false;
|
|
644
|
-
for (let i = 0; i < lines.length; i++) {
|
|
645
|
-
const line = lines[i] ?? "";
|
|
646
|
-
if (/^\s*\[/.test(line)) {
|
|
647
|
-
filtered.push(...lines.slice(i));
|
|
648
|
-
break;
|
|
48
|
+
if (!process.stdout.isTTY) {
|
|
49
|
+
if (configured.length > 0) {
|
|
50
|
+
params.log(`Configured: ${(0, format_1.formatSummaryList)(configured)}`);
|
|
649
51
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
continue;
|
|
52
|
+
else {
|
|
53
|
+
params.log("Configured: no local tools selected.");
|
|
653
54
|
}
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
continue;
|
|
55
|
+
params.log("Shared config: saved your The Claw Bay credential and managed machine setup.");
|
|
56
|
+
if (restartTargets.length > 0) {
|
|
57
|
+
params.log(`Restart if already open: ${(0, format_1.formatSummaryList)(restartTargets)}`);
|
|
658
58
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
continue;
|
|
59
|
+
for (const note of params.notes) {
|
|
60
|
+
params.log(`Note: ${note}`);
|
|
662
61
|
}
|
|
663
|
-
filtered.push(line);
|
|
664
|
-
}
|
|
665
|
-
if (!removed)
|
|
666
|
-
return source;
|
|
667
|
-
return `${filtered.join("\n").trimEnd()}\n`;
|
|
668
|
-
}
|
|
669
|
-
function shellQuote(value) {
|
|
670
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
671
|
-
}
|
|
672
|
-
function powerShellQuote(value) {
|
|
673
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
674
|
-
}
|
|
675
|
-
function shellCommandForCurrentPlatform(command) {
|
|
676
|
-
if (node_os_1.default.platform() === "win32") {
|
|
677
|
-
return { executable: "cmd.exe", args: ["/c", command] };
|
|
678
|
-
}
|
|
679
|
-
return { executable: "bash", args: ["-lc", command] };
|
|
680
|
-
}
|
|
681
|
-
function installPlanForSetupClient(clientId) {
|
|
682
|
-
if (clientId === "codex") {
|
|
683
|
-
return {
|
|
684
|
-
summary: "Install Codex CLI",
|
|
685
|
-
command: "npm install -g @openai/codex",
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
if (clientId === "claude") {
|
|
689
|
-
return {
|
|
690
|
-
summary: "Install Claude Code CLI",
|
|
691
|
-
command: "npm install -g @anthropic-ai/claude-code",
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
if (clientId === "opencode") {
|
|
695
|
-
return {
|
|
696
|
-
summary: "Install OpenCode CLI",
|
|
697
|
-
command: "npm install -g opencode-ai",
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
if (clientId === "aider") {
|
|
701
|
-
return {
|
|
702
|
-
summary: "Install Aider",
|
|
703
|
-
command: node_os_1.default.platform() === "win32"
|
|
704
|
-
? "py -m pip install --user --upgrade aider-chat"
|
|
705
|
-
: "python3 -m pip install --user --upgrade aider-chat",
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
return null;
|
|
709
|
-
}
|
|
710
|
-
function isSetupClientInstallable(clientId) {
|
|
711
|
-
return installPlanForSetupClient(clientId) !== null;
|
|
712
|
-
}
|
|
713
|
-
function runInstallPlan(plan) {
|
|
714
|
-
const { executable, args } = shellCommandForCurrentPlatform(plan.command);
|
|
715
|
-
const run = (0, node_child_process_1.spawnSync)(executable, args, {
|
|
716
|
-
stdio: "inherit",
|
|
717
|
-
env: process.env,
|
|
718
|
-
});
|
|
719
|
-
if (run.status === 0)
|
|
720
62
|
return;
|
|
721
|
-
throw new Error(`${plan.summary} failed.`);
|
|
722
|
-
}
|
|
723
|
-
function modelDisplayName(modelId) {
|
|
724
|
-
return MODEL_DISPLAY_NAMES[modelId] ?? prettyModelId(modelId);
|
|
725
|
-
}
|
|
726
|
-
function normalizeClaudeCatalogModelId(modelId) {
|
|
727
|
-
const trimmed = modelId.trim();
|
|
728
|
-
if (!trimmed.startsWith("claude-"))
|
|
729
|
-
return trimmed;
|
|
730
|
-
return trimmed
|
|
731
|
-
.replace(/\./g, "-")
|
|
732
|
-
.replace(/-+/g, "-")
|
|
733
|
-
.replace(/-(20\d{6})$/, "");
|
|
734
|
-
}
|
|
735
|
-
function dedupeClaudeCatalogModelIds(modelIds) {
|
|
736
|
-
const seen = new Set();
|
|
737
|
-
const deduped = [];
|
|
738
|
-
for (const modelId of modelIds) {
|
|
739
|
-
const normalized = normalizeClaudeCatalogModelId(modelId);
|
|
740
|
-
if (!normalized || seen.has(normalized))
|
|
741
|
-
continue;
|
|
742
|
-
seen.add(normalized);
|
|
743
|
-
deduped.push(normalized);
|
|
744
|
-
}
|
|
745
|
-
return deduped;
|
|
746
|
-
}
|
|
747
|
-
function supportedModelProviderMap() {
|
|
748
|
-
return new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model.provider]));
|
|
749
|
-
}
|
|
750
|
-
function groupForModelId(modelId) {
|
|
751
|
-
const knownProvider = supportedModelProviderMap().get(modelId);
|
|
752
|
-
if (knownProvider === "openai") {
|
|
753
|
-
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
754
|
-
}
|
|
755
|
-
if (knownProvider === "google") {
|
|
756
|
-
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
757
|
-
}
|
|
758
|
-
if (knownProvider === "deepseek") {
|
|
759
|
-
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
760
|
-
}
|
|
761
|
-
if (modelId.startsWith("claude-")) {
|
|
762
|
-
return { id: "anthropic", label: "Anthropic Claude", icon: "✳", order: 3 };
|
|
763
|
-
}
|
|
764
|
-
if (modelId.startsWith("gemini-")) {
|
|
765
|
-
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
766
|
-
}
|
|
767
|
-
if (modelId.startsWith("deepseek-")) {
|
|
768
|
-
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
769
|
-
}
|
|
770
|
-
if (modelId.startsWith("gpt-") || modelId.startsWith("codex-")) {
|
|
771
|
-
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
772
|
-
}
|
|
773
|
-
return { id: "other", label: "Other models", icon: "•", order: 9 };
|
|
774
|
-
}
|
|
775
|
-
function groupModelOptions(models) {
|
|
776
|
-
return models.map((model) => ({
|
|
777
|
-
...model,
|
|
778
|
-
provider: groupForModelId(model.id),
|
|
779
|
-
}));
|
|
780
|
-
}
|
|
781
|
-
function groupModelSections(models) {
|
|
782
|
-
const sections = new Map();
|
|
783
|
-
for (const model of groupModelOptions(models)) {
|
|
784
|
-
const existing = sections.get(model.provider.id);
|
|
785
|
-
if (existing) {
|
|
786
|
-
existing.models.push(model);
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
sections.set(model.provider.id, { provider: model.provider, models: [model] });
|
|
790
|
-
}
|
|
791
|
-
return [...sections.values()]
|
|
792
|
-
.sort((left, right) => left.provider.order - right.provider.order || left.provider.label.localeCompare(right.provider.label))
|
|
793
|
-
.map((section) => ({
|
|
794
|
-
provider: section.provider,
|
|
795
|
-
models: [...section.models].sort((left, right) => left.name.localeCompare(right.name)),
|
|
796
|
-
}));
|
|
797
|
-
}
|
|
798
|
-
function prettyModelId(modelId) {
|
|
799
|
-
return modelId
|
|
800
|
-
.replace(/^claude-/, "Claude ")
|
|
801
|
-
.replace(/^gemini-/, "Gemini ")
|
|
802
|
-
.replace(/^deepseek-/, "DeepSeek ")
|
|
803
|
-
.replace(/-/g, " ")
|
|
804
|
-
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
805
|
-
}
|
|
806
|
-
function publicApiOriginForBackendUrl(backendUrl) {
|
|
807
|
-
const trimmed = trimTrailingSlash(backendUrl);
|
|
808
|
-
try {
|
|
809
|
-
const parsed = new URL(trimmed);
|
|
810
|
-
parsed.hash = "";
|
|
811
|
-
parsed.search = "";
|
|
812
|
-
parsed.pathname = "";
|
|
813
|
-
if (THECLAWBAY_WEBSITE_HOSTS.has(parsed.hostname.toLowerCase())) {
|
|
814
|
-
parsed.hostname = THECLAWBAY_CANONICAL_API_HOST;
|
|
815
|
-
}
|
|
816
|
-
return trimTrailingSlash(parsed.toString());
|
|
817
|
-
}
|
|
818
|
-
catch {
|
|
819
|
-
return trimmed;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
function isPrivateHostname(hostname) {
|
|
823
|
-
const normalized = hostname.trim().toLowerCase();
|
|
824
|
-
if (!normalized)
|
|
825
|
-
return false;
|
|
826
|
-
if (normalized === "localhost" ||
|
|
827
|
-
normalized === "127.0.0.1" ||
|
|
828
|
-
normalized === "::1" ||
|
|
829
|
-
normalized === "[::1]" ||
|
|
830
|
-
normalized === "0.0.0.0") {
|
|
831
|
-
return true;
|
|
832
|
-
}
|
|
833
|
-
if (normalized.startsWith("10.") || normalized.startsWith("192.168.") || normalized.startsWith("127.")) {
|
|
834
|
-
return true;
|
|
835
|
-
}
|
|
836
|
-
const match = normalized.match(/^172\.(\d{1,3})\./);
|
|
837
|
-
if (!match)
|
|
838
|
-
return false;
|
|
839
|
-
const secondOctet = Number.parseInt(match[1] ?? "", 10);
|
|
840
|
-
return Number.isInteger(secondOctet) && secondOctet >= 16 && secondOctet <= 31;
|
|
841
|
-
}
|
|
842
|
-
function shouldPreferPublicSetupBackend(backendUrl) {
|
|
843
|
-
const trimmed = backendUrl?.trim();
|
|
844
|
-
if (!trimmed)
|
|
845
|
-
return false;
|
|
846
|
-
try {
|
|
847
|
-
const parsed = new URL(trimmed);
|
|
848
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
849
|
-
return hostname !== THECLAWBAY_CANONICAL_API_HOST && !THECLAWBAY_WEBSITE_HOSTS.has(hostname) && isPrivateHostname(hostname);
|
|
850
|
-
}
|
|
851
|
-
catch {
|
|
852
|
-
return false;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
function isTheClawBayChatgptBaseUrl(value) {
|
|
856
|
-
const trimmed = trimTrailingSlash(value.trim());
|
|
857
|
-
if (!trimmed)
|
|
858
|
-
return false;
|
|
859
|
-
try {
|
|
860
|
-
const parsed = new URL(trimmed);
|
|
861
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
862
|
-
return hostname === THECLAWBAY_CANONICAL_API_HOST || THECLAWBAY_WEBSITE_HOSTS.has(hostname);
|
|
863
|
-
}
|
|
864
|
-
catch {
|
|
865
|
-
return false;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
function openAiCompatibleProxyUrl(backendUrl) {
|
|
869
|
-
return `${publicApiOriginForBackendUrl(backendUrl)}${CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
|
|
870
|
-
}
|
|
871
|
-
function geminiCompatibleProxyBaseUrl(backendUrl) {
|
|
872
|
-
return publicApiOriginForBackendUrl(backendUrl);
|
|
873
|
-
}
|
|
874
|
-
function anthropicCompatibleProxyUrl(backendUrl) {
|
|
875
|
-
return `${publicApiOriginForBackendUrl(backendUrl)}/anthropic`;
|
|
876
|
-
}
|
|
877
|
-
function anthropicCompatibleProxyV1Url(backendUrl) {
|
|
878
|
-
return `${anthropicCompatibleProxyUrl(backendUrl)}/v1`;
|
|
879
|
-
}
|
|
880
|
-
function isTheClawBayOpenAiCompatibleBaseUrl(value) {
|
|
881
|
-
if (typeof value !== "string")
|
|
882
|
-
return false;
|
|
883
|
-
const normalized = value.trim();
|
|
884
|
-
if (!normalized)
|
|
885
|
-
return false;
|
|
886
|
-
try {
|
|
887
|
-
const parsed = new URL(normalized);
|
|
888
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
889
|
-
const pathname = trimTrailingSlash(parsed.pathname);
|
|
890
|
-
if (pathname === LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX &&
|
|
891
|
-
(THECLAWBAY_WEBSITE_HOSTS.has(hostname) ||
|
|
892
|
-
hostname === "localhost" ||
|
|
893
|
-
hostname === "127.0.0.1" ||
|
|
894
|
-
hostname === "::1")) {
|
|
895
|
-
return true;
|
|
896
|
-
}
|
|
897
|
-
return (hostname === THECLAWBAY_CANONICAL_API_HOST &&
|
|
898
|
-
pathname === CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX);
|
|
899
|
-
}
|
|
900
|
-
catch {
|
|
901
|
-
return false;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
function isTheClawBayAnthropicCompatibleBaseUrl(value) {
|
|
905
|
-
if (typeof value !== "string")
|
|
906
|
-
return false;
|
|
907
|
-
const normalized = value.trim();
|
|
908
|
-
if (!normalized)
|
|
909
|
-
return false;
|
|
910
|
-
try {
|
|
911
|
-
const parsed = new URL(normalized);
|
|
912
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
913
|
-
const pathname = trimTrailingSlash(parsed.pathname);
|
|
914
|
-
return hostname === THECLAWBAY_CANONICAL_API_HOST && pathname === "/anthropic/v1";
|
|
915
|
-
}
|
|
916
|
-
catch {
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
function uniqueStrings(values) {
|
|
921
|
-
const output = [];
|
|
922
|
-
const seen = new Set();
|
|
923
|
-
for (const value of values) {
|
|
924
|
-
const normalized = value.trim();
|
|
925
|
-
if (!normalized || seen.has(normalized))
|
|
926
|
-
continue;
|
|
927
|
-
seen.add(normalized);
|
|
928
|
-
output.push(normalized);
|
|
929
|
-
}
|
|
930
|
-
return output;
|
|
931
|
-
}
|
|
932
|
-
function xdgConfigHomeDir() {
|
|
933
|
-
const raw = process.env.XDG_CONFIG_HOME?.trim();
|
|
934
|
-
if (raw)
|
|
935
|
-
return raw;
|
|
936
|
-
return node_path_1.default.join(node_os_1.default.homedir(), ".config");
|
|
937
|
-
}
|
|
938
|
-
function configFileCandidatesForDir(dir, basenames) {
|
|
939
|
-
return basenames.map((basename) => node_path_1.default.join(dir, basename));
|
|
940
|
-
}
|
|
941
|
-
function findProjectConfigFile(params) {
|
|
942
|
-
let current = node_path_1.default.resolve(params.startDir);
|
|
943
|
-
const root = node_path_1.default.parse(current).root;
|
|
944
|
-
while (true) {
|
|
945
|
-
for (const candidate of configFileCandidatesForDir(current, params.basenames)) {
|
|
946
|
-
if ((0, node_fs_1.existsSync)(candidate))
|
|
947
|
-
return candidate;
|
|
948
|
-
}
|
|
949
|
-
if ((0, node_fs_1.existsSync)(node_path_1.default.join(current, ".git")))
|
|
950
|
-
return null;
|
|
951
|
-
if (current === root)
|
|
952
|
-
return null;
|
|
953
|
-
const parent = node_path_1.default.dirname(current);
|
|
954
|
-
if (parent === current)
|
|
955
|
-
return null;
|
|
956
|
-
current = parent;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
function configDirCandidates(appName) {
|
|
960
|
-
const home = node_os_1.default.homedir();
|
|
961
|
-
const dirs = [node_path_1.default.join(xdgConfigHomeDir(), appName)];
|
|
962
|
-
if (node_os_1.default.platform() === "darwin") {
|
|
963
|
-
dirs.push(node_path_1.default.join(home, "Library", "Application Support", appName));
|
|
964
|
-
}
|
|
965
|
-
else if (node_os_1.default.platform() === "win32") {
|
|
966
|
-
dirs.push(node_path_1.default.join(roamingAppDataDir(), appName));
|
|
967
|
-
}
|
|
968
|
-
return uniqueStrings(dirs);
|
|
969
|
-
}
|
|
970
|
-
function claudeDesktop3pConfigPath() {
|
|
971
|
-
if (node_os_1.default.platform() !== "darwin")
|
|
972
|
-
return null;
|
|
973
|
-
return node_path_1.default.join(node_os_1.default.homedir(), "Library", "Application Support", "Claude-3p", "claude_desktop_config.json");
|
|
974
|
-
}
|
|
975
|
-
function claudeDesktopLogCandidates() {
|
|
976
|
-
if (node_os_1.default.platform() !== "darwin")
|
|
977
|
-
return [];
|
|
978
|
-
const home = node_os_1.default.homedir();
|
|
979
|
-
return [
|
|
980
|
-
node_path_1.default.join(home, "Library", "Logs", "Claude-3p", "main.log"),
|
|
981
|
-
node_path_1.default.join(home, "Library", "Logs", "Claude", "main.log"),
|
|
982
|
-
];
|
|
983
|
-
}
|
|
984
|
-
function claudeDesktopAppCandidatePaths() {
|
|
985
|
-
if (node_os_1.default.platform() !== "darwin")
|
|
986
|
-
return [];
|
|
987
|
-
const home = node_os_1.default.homedir();
|
|
988
|
-
return uniqueStrings([
|
|
989
|
-
"/Applications/Claude.app",
|
|
990
|
-
node_path_1.default.join(home, "Applications", "Claude.app"),
|
|
991
|
-
]);
|
|
992
|
-
}
|
|
993
|
-
async function readFileIfExists(filePath) {
|
|
994
|
-
try {
|
|
995
|
-
return await promises_1.default.readFile(filePath, "utf8");
|
|
996
|
-
}
|
|
997
|
-
catch (error) {
|
|
998
|
-
const err = error;
|
|
999
|
-
if (err.code === "ENOENT")
|
|
1000
|
-
return null;
|
|
1001
|
-
throw error;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
async function removeFileIfExists(filePath) {
|
|
1005
|
-
try {
|
|
1006
|
-
await promises_1.default.unlink(filePath);
|
|
1007
|
-
return true;
|
|
1008
|
-
}
|
|
1009
|
-
catch (error) {
|
|
1010
|
-
const err = error;
|
|
1011
|
-
if (err.code === "ENOENT")
|
|
1012
|
-
return false;
|
|
1013
|
-
throw error;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
async function readJsonObjectFile(filePath) {
|
|
1017
|
-
const existingRaw = await readFileIfExists(filePath);
|
|
1018
|
-
if (existingRaw === null || !existingRaw.trim())
|
|
1019
|
-
return {};
|
|
1020
|
-
try {
|
|
1021
|
-
return objectRecordOr(JSON.parse(existingRaw), {});
|
|
1022
63
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
}
|
|
1027
|
-
async function writeJsonObjectFile(filePath, doc, mode) {
|
|
1028
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
1029
|
-
await promises_1.default.writeFile(filePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
1030
|
-
if (mode !== undefined)
|
|
1031
|
-
await promises_1.default.chmod(filePath, mode);
|
|
1032
|
-
}
|
|
1033
|
-
function editorHosts() {
|
|
1034
|
-
const home = node_os_1.default.homedir();
|
|
1035
|
-
if (node_os_1.default.platform() === "darwin") {
|
|
1036
|
-
const appSupport = node_path_1.default.join(home, "Library", "Application Support");
|
|
1037
|
-
return [
|
|
1038
|
-
{
|
|
1039
|
-
id: "vscode",
|
|
1040
|
-
label: "VS Code",
|
|
1041
|
-
extensionDir: node_path_1.default.join(home, ".vscode", "extensions"),
|
|
1042
|
-
userSettingsPath: node_path_1.default.join(appSupport, "Code", "User", "settings.json"),
|
|
1043
|
-
},
|
|
1044
|
-
{
|
|
1045
|
-
id: "vscode-insiders",
|
|
1046
|
-
label: "VS Code Insiders",
|
|
1047
|
-
extensionDir: node_path_1.default.join(home, ".vscode-insiders", "extensions"),
|
|
1048
|
-
userSettingsPath: node_path_1.default.join(appSupport, "Code - Insiders", "User", "settings.json"),
|
|
1049
|
-
},
|
|
1050
|
-
{
|
|
1051
|
-
id: "cursor",
|
|
1052
|
-
label: "Cursor",
|
|
1053
|
-
extensionDir: node_path_1.default.join(home, ".cursor", "extensions"),
|
|
1054
|
-
userSettingsPath: node_path_1.default.join(appSupport, "Cursor", "User", "settings.json"),
|
|
1055
|
-
},
|
|
1056
|
-
{
|
|
1057
|
-
id: "windsurf",
|
|
1058
|
-
label: "Windsurf",
|
|
1059
|
-
extensionDir: node_path_1.default.join(home, ".windsurf", "extensions"),
|
|
1060
|
-
userSettingsPath: node_path_1.default.join(appSupport, "Windsurf", "User", "settings.json"),
|
|
1061
|
-
},
|
|
1062
|
-
{
|
|
1063
|
-
id: "vscodium",
|
|
1064
|
-
label: "VSCodium",
|
|
1065
|
-
extensionDir: node_path_1.default.join(home, ".vscode-oss", "extensions"),
|
|
1066
|
-
userSettingsPath: node_path_1.default.join(appSupport, "VSCodium", "User", "settings.json"),
|
|
1067
|
-
},
|
|
1068
|
-
];
|
|
64
|
+
const lines = [];
|
|
65
|
+
if (configured.length > 0) {
|
|
66
|
+
lines.push((0, tui_1.statusLine)("ok", `Configured ${tui_1.tint.bold((0, format_1.formatSummaryList)(configured))}`));
|
|
1069
67
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
const appData = roamingAppDataDir();
|
|
1073
|
-
return [
|
|
1074
|
-
{
|
|
1075
|
-
id: "vscode",
|
|
1076
|
-
label: "VS Code",
|
|
1077
|
-
extensionDir: node_path_1.default.join(homeDir, ".vscode", "extensions"),
|
|
1078
|
-
userSettingsPath: node_path_1.default.join(appData, "Code", "User", "settings.json"),
|
|
1079
|
-
},
|
|
1080
|
-
{
|
|
1081
|
-
id: "vscode-insiders",
|
|
1082
|
-
label: "VS Code Insiders",
|
|
1083
|
-
extensionDir: node_path_1.default.join(homeDir, ".vscode-insiders", "extensions"),
|
|
1084
|
-
userSettingsPath: node_path_1.default.join(appData, "Code - Insiders", "User", "settings.json"),
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
id: "cursor",
|
|
1088
|
-
label: "Cursor",
|
|
1089
|
-
extensionDir: node_path_1.default.join(homeDir, ".cursor", "extensions"),
|
|
1090
|
-
userSettingsPath: node_path_1.default.join(appData, "Cursor", "User", "settings.json"),
|
|
1091
|
-
},
|
|
1092
|
-
{
|
|
1093
|
-
id: "windsurf",
|
|
1094
|
-
label: "Windsurf",
|
|
1095
|
-
extensionDir: node_path_1.default.join(homeDir, ".windsurf", "extensions"),
|
|
1096
|
-
userSettingsPath: node_path_1.default.join(appData, "Windsurf", "User", "settings.json"),
|
|
1097
|
-
},
|
|
1098
|
-
{
|
|
1099
|
-
id: "vscodium",
|
|
1100
|
-
label: "VSCodium",
|
|
1101
|
-
extensionDir: node_path_1.default.join(homeDir, ".vscode-oss", "extensions"),
|
|
1102
|
-
userSettingsPath: node_path_1.default.join(appData, "VSCodium", "User", "settings.json"),
|
|
1103
|
-
},
|
|
1104
|
-
];
|
|
68
|
+
else {
|
|
69
|
+
lines.push((0, tui_1.statusLine)("info", "No local tools selected."));
|
|
1105
70
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
label: "VS Code",
|
|
1110
|
-
extensionDir: node_path_1.default.join(home, ".vscode", "extensions"),
|
|
1111
|
-
userSettingsPath: node_path_1.default.join(home, ".config", "Code", "User", "settings.json"),
|
|
1112
|
-
},
|
|
1113
|
-
{
|
|
1114
|
-
id: "vscode-insiders",
|
|
1115
|
-
label: "VS Code Insiders",
|
|
1116
|
-
extensionDir: node_path_1.default.join(home, ".vscode-insiders", "extensions"),
|
|
1117
|
-
userSettingsPath: node_path_1.default.join(home, ".config", "Code - Insiders", "User", "settings.json"),
|
|
1118
|
-
},
|
|
1119
|
-
{
|
|
1120
|
-
id: "cursor",
|
|
1121
|
-
label: "Cursor",
|
|
1122
|
-
extensionDir: node_path_1.default.join(home, ".cursor", "extensions"),
|
|
1123
|
-
userSettingsPath: node_path_1.default.join(home, ".config", "Cursor", "User", "settings.json"),
|
|
1124
|
-
},
|
|
1125
|
-
{
|
|
1126
|
-
id: "windsurf",
|
|
1127
|
-
label: "Windsurf",
|
|
1128
|
-
extensionDir: node_path_1.default.join(home, ".windsurf", "extensions"),
|
|
1129
|
-
userSettingsPath: node_path_1.default.join(home, ".config", "Windsurf", "User", "settings.json"),
|
|
1130
|
-
},
|
|
1131
|
-
{
|
|
1132
|
-
id: "vscodium",
|
|
1133
|
-
label: "VSCodium",
|
|
1134
|
-
extensionDir: node_path_1.default.join(home, ".vscode-oss", "extensions"),
|
|
1135
|
-
userSettingsPath: node_path_1.default.join(home, ".config", "VSCodium", "User", "settings.json"),
|
|
1136
|
-
},
|
|
1137
|
-
];
|
|
1138
|
-
}
|
|
1139
|
-
async function detectExtensionHosts(prefixes) {
|
|
1140
|
-
const matches = [];
|
|
1141
|
-
for (const host of editorHosts()) {
|
|
1142
|
-
if (!(await pathExists(host.extensionDir)))
|
|
1143
|
-
continue;
|
|
1144
|
-
const entries = (0, node_fs_1.readdirSync)(host.extensionDir, { withFileTypes: true });
|
|
1145
|
-
const found = entries.some((entry) => {
|
|
1146
|
-
if (!entry.isDirectory())
|
|
1147
|
-
return false;
|
|
1148
|
-
const normalized = entry.name.toLowerCase();
|
|
1149
|
-
return prefixes.some((prefix) => normalized.startsWith(prefix));
|
|
1150
|
-
});
|
|
1151
|
-
if (found)
|
|
1152
|
-
matches.push(host);
|
|
71
|
+
lines.push((0, tui_1.statusLine)("ok", "Saved your The Claw Bay credential and managed machine setup."));
|
|
72
|
+
if (restartTargets.length > 0) {
|
|
73
|
+
lines.push((0, tui_1.statusLine)("warn", `Restart if already open: ${(0, format_1.formatSummaryList)(restartTargets)}`));
|
|
1153
74
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
function parseSetupClientFlags(raw) {
|
|
1157
|
-
if (!raw)
|
|
1158
|
-
return null;
|
|
1159
|
-
const parts = raw
|
|
1160
|
-
.split(",")
|
|
1161
|
-
.map((part) => part.trim().toLowerCase())
|
|
1162
|
-
.filter(Boolean);
|
|
1163
|
-
const selected = new Set();
|
|
1164
|
-
for (const part of parts) {
|
|
1165
|
-
if (SETUP_CLIENT_IDS.includes(part)) {
|
|
1166
|
-
selected.add(part);
|
|
1167
|
-
continue;
|
|
1168
|
-
}
|
|
1169
|
-
throw new Error(`unknown client "${part}". Supported values: ${SETUP_CLIENT_IDS.join(", ")}.`);
|
|
75
|
+
for (const note of params.notes) {
|
|
76
|
+
lines.push((0, tui_1.statusLine)("info", note));
|
|
1170
77
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
async function promptForSetupClients(clients) {
|
|
1174
|
-
const selected = new Set(clients.filter((client) => client.detected && client.recommended).map((client) => client.id));
|
|
1175
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1176
|
-
return selected;
|
|
78
|
+
for (const line of (0, tui_1.renderBox)({ title: "Setup complete", lines })) {
|
|
79
|
+
params.log(line);
|
|
1177
80
|
}
|
|
1178
|
-
const options = clients.map((client) => ({
|
|
1179
|
-
...client,
|
|
1180
|
-
checked: client.detected && selected.has(client.id),
|
|
1181
|
-
}));
|
|
1182
|
-
return new Promise((resolve) => {
|
|
1183
|
-
let cursor = 0;
|
|
1184
|
-
let settled = false;
|
|
1185
|
-
let hint = clients.some((client) => client.detected)
|
|
1186
|
-
? "Choose the tools you want to configure."
|
|
1187
|
-
: "No supported local clients are detected yet. Apply setup will just save your managed config and local credential env.";
|
|
1188
|
-
const stdin = process.stdin;
|
|
1189
|
-
const stdout = process.stdout;
|
|
1190
|
-
const wasRaw = stdin.isRaw;
|
|
1191
|
-
const applyIndex = options.length;
|
|
1192
|
-
const hyperlinksEnabled = supportsTerminalHyperlinks();
|
|
1193
|
-
const ansi = {
|
|
1194
|
-
reset: "\x1b[0m",
|
|
1195
|
-
bold: "\x1b[1m",
|
|
1196
|
-
dim: "\x1b[2m",
|
|
1197
|
-
inverse: "\x1b[7m",
|
|
1198
|
-
red: "\x1b[31m",
|
|
1199
|
-
green: "\x1b[32m",
|
|
1200
|
-
yellow: "\x1b[33m",
|
|
1201
|
-
gray: "\x1b[90m",
|
|
1202
|
-
};
|
|
1203
|
-
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1204
|
-
const clearScreen = () => {
|
|
1205
|
-
stdout.write("\x1b[2J\x1b[H");
|
|
1206
|
-
};
|
|
1207
|
-
const selectedCount = () => options.filter((option) => option.checked).length;
|
|
1208
|
-
const directToggleHint = options.length <= 9
|
|
1209
|
-
? `press 1-${options.length} to toggle directly`
|
|
1210
|
-
: options.length === 10
|
|
1211
|
-
? "press 1-9 or 0 to toggle directly"
|
|
1212
|
-
: "press a number key to toggle directly";
|
|
1213
|
-
const render = () => {
|
|
1214
|
-
clearScreen();
|
|
1215
|
-
stdout.write(`${paint("Choose local clients to configure", ansi.bold)}\n`);
|
|
1216
|
-
stdout.write(`${paint(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or ${directToggleHint}.`, ansi.dim, ansi.gray)}\n`);
|
|
1217
|
-
stdout.write(`${paint("Each tool name links to its official site when your terminal supports it.", ansi.dim, ansi.gray)}\n`);
|
|
1218
|
-
stdout.write(`${paint("Move to Apply setup and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
1219
|
-
for (const [index, option] of options.entries()) {
|
|
1220
|
-
const pointer = index === cursor ? paint(">", ansi.bold) : " ";
|
|
1221
|
-
const selectable = option.detected || option.installable;
|
|
1222
|
-
const mark = selectable
|
|
1223
|
-
? option.checked
|
|
1224
|
-
? paint("[x]", ansi.green, ansi.bold)
|
|
1225
|
-
: paint("[ ]", ansi.gray)
|
|
1226
|
-
: paint("[-]", ansi.red, ansi.bold);
|
|
1227
|
-
const badge = option.detected
|
|
1228
|
-
? paint("found", ansi.green)
|
|
1229
|
-
: option.installable
|
|
1230
|
-
? paint(option.checked ? "will install" : "available to install", ansi.yellow)
|
|
1231
|
-
: paint("not found", ansi.red);
|
|
1232
|
-
const label = selectable
|
|
1233
|
-
? formatSetupClientLabel(option, hyperlinksEnabled)
|
|
1234
|
-
: paint(formatSetupClientLabel(option, hyperlinksEnabled), ansi.dim, ansi.gray);
|
|
1235
|
-
stdout.write(`${pointer} ${index + 1}. ${mark} ${label} ${badge}\n`);
|
|
1236
|
-
}
|
|
1237
|
-
const applyPointer = cursor === applyIndex ? ">" : " ";
|
|
1238
|
-
const applyLabel = `Apply setup (${selectedCount()} selected)`;
|
|
1239
|
-
const applyStyled = cursor === applyIndex
|
|
1240
|
-
? paint(` ${applyLabel} `, ansi.inverse, ansi.bold, ansi.green)
|
|
1241
|
-
: paint(applyLabel, ansi.bold, ansi.green);
|
|
1242
|
-
stdout.write(`\n${applyPointer} ${applyStyled}\n`);
|
|
1243
|
-
stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
|
|
1244
|
-
};
|
|
1245
|
-
const finish = () => {
|
|
1246
|
-
if (settled)
|
|
1247
|
-
return;
|
|
1248
|
-
settled = true;
|
|
1249
|
-
stdin.off("keypress", onKeypress);
|
|
1250
|
-
if (stdin.isTTY)
|
|
1251
|
-
stdin.setRawMode(Boolean(wasRaw));
|
|
1252
|
-
stdout.write("\x1b[?25h");
|
|
1253
|
-
clearScreen();
|
|
1254
|
-
const finalSelection = new Set(options.filter((option) => option.checked).map((option) => option.id));
|
|
1255
|
-
resolve(finalSelection);
|
|
1256
|
-
};
|
|
1257
|
-
const toggleOption = (index) => {
|
|
1258
|
-
const option = options[index];
|
|
1259
|
-
if (!option)
|
|
1260
|
-
return;
|
|
1261
|
-
cursor = index;
|
|
1262
|
-
if (!option.detected && !option.installable) {
|
|
1263
|
-
hint = `${option.label} is not detected on this machine yet, so it cannot be selected.`;
|
|
1264
|
-
render();
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
option.checked = !option.checked;
|
|
1268
|
-
if (option.checked && !option.detected && option.installable) {
|
|
1269
|
-
hint = `${option.label} selected. Setup will install it first.`;
|
|
1270
|
-
}
|
|
1271
|
-
else {
|
|
1272
|
-
hint = `${option.label} ${option.checked ? "selected" : "cleared"}.`;
|
|
1273
|
-
}
|
|
1274
|
-
render();
|
|
1275
|
-
};
|
|
1276
|
-
const onKeypress = (_str, key) => {
|
|
1277
|
-
if (key.ctrl && key.name === "c") {
|
|
1278
|
-
stdout.write("\x1b[?25h");
|
|
1279
|
-
process.exit(130);
|
|
1280
|
-
}
|
|
1281
|
-
if (key.name === "up" || key.name === "k") {
|
|
1282
|
-
cursor = (cursor - 1 + options.length + 1) % (options.length + 1);
|
|
1283
|
-
render();
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (key.name === "down" || key.name === "j") {
|
|
1287
|
-
cursor = (cursor + 1) % (options.length + 1);
|
|
1288
|
-
render();
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
if (key.name === "space") {
|
|
1292
|
-
if (cursor === applyIndex) {
|
|
1293
|
-
finish();
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
toggleOption(cursor);
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
const directSelection = key.sequence === "0" && options.length >= 10
|
|
1300
|
-
? 10
|
|
1301
|
-
: /^[1-9]$/.test(key.sequence ?? "")
|
|
1302
|
-
? Number.parseInt(key.sequence ?? "", 10)
|
|
1303
|
-
: Number.NaN;
|
|
1304
|
-
if (Number.isInteger(directSelection) && directSelection >= 1 && directSelection <= options.length) {
|
|
1305
|
-
toggleOption(directSelection - 1);
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
if (key.name === "a") {
|
|
1309
|
-
const detectedOptions = options.filter((option) => option.detected);
|
|
1310
|
-
const nextValue = detectedOptions.some((option) => !option.checked);
|
|
1311
|
-
for (const option of detectedOptions)
|
|
1312
|
-
option.checked = nextValue;
|
|
1313
|
-
hint = `${nextValue ? "Selected" : "Cleared"} all detected integrations.`;
|
|
1314
|
-
render();
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1318
|
-
if (cursor === applyIndex) {
|
|
1319
|
-
finish();
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
toggleOption(cursor);
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
if (key.name === "c") {
|
|
1326
|
-
finish();
|
|
1327
|
-
}
|
|
1328
|
-
};
|
|
1329
|
-
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1330
|
-
stdin.resume();
|
|
1331
|
-
if (stdin.isTTY)
|
|
1332
|
-
stdin.setRawMode(true);
|
|
1333
|
-
stdout.write("\x1b[?25l");
|
|
1334
|
-
stdin.on("keypress", onKeypress);
|
|
1335
|
-
render();
|
|
1336
|
-
});
|
|
1337
81
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
82
|
+
class SetupCommand extends base_command_1.BaseCommand {
|
|
83
|
+
async run() {
|
|
84
|
+
await this.runSafe(async () => {
|
|
85
|
+
const { flags } = await this.parse(SetupCommand);
|
|
86
|
+
const debugOutput = flags["debug-output"];
|
|
87
|
+
if (!debugOutput && process.stdout.isTTY) {
|
|
88
|
+
this.log((0, tui_1.banner)("Connect your coding tools to The Claw Bay relay"));
|
|
89
|
+
this.log("");
|
|
1346
90
|
}
|
|
1347
|
-
}
|
|
1348
|
-
return Promise.resolve(flagSelection);
|
|
1349
|
-
}
|
|
1350
|
-
const defaults = new Set(setupClients.filter((client) => client.detected && client.recommended).map((client) => client.id));
|
|
1351
|
-
if (skipPrompt)
|
|
1352
|
-
return Promise.resolve(defaults);
|
|
1353
|
-
return promptForSetupClients(setupClients);
|
|
1354
|
-
}
|
|
1355
|
-
function installSelectedMissingClients(params) {
|
|
1356
|
-
const installed = [];
|
|
1357
|
-
for (const client of params.setupClients) {
|
|
1358
|
-
if (!params.selectedClientIds.has(client.id) || client.detected || !client.installable)
|
|
1359
|
-
continue;
|
|
1360
|
-
const plan = installPlanForSetupClient(client.id);
|
|
1361
|
-
if (!plan)
|
|
1362
|
-
continue;
|
|
1363
|
-
params.log(`Installing ${client.summaryLabel}...`);
|
|
1364
|
-
runInstallPlan(plan);
|
|
1365
|
-
installed.push(client.summaryLabel);
|
|
1366
|
-
}
|
|
1367
|
-
return installed;
|
|
1368
|
-
}
|
|
1369
|
-
async function promptForCodexConversationMigration() {
|
|
1370
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1371
|
-
return false;
|
|
1372
|
-
const rl = (0, promises_2.createInterface)({
|
|
1373
|
-
input: process.stdin,
|
|
1374
|
-
output: process.stdout,
|
|
1375
|
-
});
|
|
1376
|
-
try {
|
|
1377
|
-
const answer = await rl.question("\nConvert old Codex conversations so they stay visible under The Claw Bay? [y/N] ");
|
|
1378
|
-
const normalized = answer.trim().toLowerCase();
|
|
1379
|
-
return normalized === "y" || normalized === "yes";
|
|
1380
|
-
}
|
|
1381
|
-
finally {
|
|
1382
|
-
rl.close();
|
|
1383
|
-
process.stdin.resume();
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
async function resolveCodexConversationMigrationSelection(params) {
|
|
1387
|
-
if (!params.codexSelected)
|
|
1388
|
-
return false;
|
|
1389
|
-
if (params.flagValue !== undefined)
|
|
1390
|
-
return params.flagValue;
|
|
1391
|
-
if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
1392
|
-
return false;
|
|
1393
|
-
return promptForCodexConversationMigration();
|
|
1394
|
-
}
|
|
1395
|
-
async function resolveDeviceLabel(params) {
|
|
1396
|
-
const fallback = node_os_1.default.hostname().trim() || "This device";
|
|
1397
|
-
const preferred = (params.flagValue ?? params.managedValue ?? "").trim() || fallback;
|
|
1398
|
-
if (params.flagValue?.trim())
|
|
1399
|
-
return preferred;
|
|
1400
|
-
if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1401
|
-
return preferred;
|
|
1402
|
-
}
|
|
1403
|
-
const rl = (0, promises_2.createInterface)({
|
|
1404
|
-
input: process.stdin,
|
|
1405
|
-
output: process.stdout,
|
|
1406
|
-
});
|
|
1407
|
-
try {
|
|
1408
|
-
const answer = await rl.question(`\nName this device [${preferred}] `);
|
|
1409
|
-
return answer.trim() || preferred;
|
|
1410
|
-
}
|
|
1411
|
-
finally {
|
|
1412
|
-
rl.close();
|
|
1413
|
-
process.stdin.resume();
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
async function promptForGroupedModelSelection(params) {
|
|
1417
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY || params.models.length <= 1) {
|
|
1418
|
-
return uniqueStrings(params.defaultSelectedIds);
|
|
1419
|
-
}
|
|
1420
|
-
const sections = groupModelSections(params.models);
|
|
1421
|
-
const knownIds = new Set(params.models.map((model) => model.id));
|
|
1422
|
-
const selected = new Set(uniqueStrings(params.defaultSelectedIds).filter((modelId) => knownIds.has(modelId)));
|
|
1423
|
-
const rows = [];
|
|
1424
|
-
for (const [sectionIndex, section] of sections.entries()) {
|
|
1425
|
-
rows.push({ kind: "group", sectionIndex });
|
|
1426
|
-
for (const [modelIndex] of section.models.entries()) {
|
|
1427
|
-
rows.push({ kind: "model", sectionIndex, modelIndex });
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
rows.push({ kind: "apply" });
|
|
1431
|
-
const stdin = process.stdin;
|
|
1432
|
-
const stdout = process.stdout;
|
|
1433
|
-
const wasRaw = stdin.isRaw;
|
|
1434
|
-
const ansi = {
|
|
1435
|
-
reset: "\x1b[0m",
|
|
1436
|
-
bold: "\x1b[1m",
|
|
1437
|
-
dim: "\x1b[2m",
|
|
1438
|
-
inverse: "\x1b[7m",
|
|
1439
|
-
green: "\x1b[32m",
|
|
1440
|
-
yellow: "\x1b[33m",
|
|
1441
|
-
gray: "\x1b[90m",
|
|
1442
|
-
};
|
|
1443
|
-
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1444
|
-
const clearScreen = () => {
|
|
1445
|
-
stdout.write("\x1b[2J\x1b[H");
|
|
1446
|
-
};
|
|
1447
|
-
const rowForModel = (sectionIndex, modelIndex) => rows.findIndex((row) => row.kind === "model" && row.sectionIndex === sectionIndex && row.modelIndex === modelIndex);
|
|
1448
|
-
let settled = false;
|
|
1449
|
-
let hint = "Use ↑/↓ to move. Press Enter or Space to toggle a model or a provider header.";
|
|
1450
|
-
let cursor = Math.max(0, rows.findIndex((row) => {
|
|
1451
|
-
if (row.kind !== "model")
|
|
1452
|
-
return false;
|
|
1453
|
-
const model = sections[row.sectionIndex]?.models[row.modelIndex];
|
|
1454
|
-
return model?.id === params.defaultSelectedIds[0];
|
|
1455
|
-
}));
|
|
1456
|
-
const groupState = (sectionIndex) => {
|
|
1457
|
-
const models = sections[sectionIndex]?.models ?? [];
|
|
1458
|
-
const selectedCount = models.filter((model) => selected.has(model.id)).length;
|
|
1459
|
-
if (selectedCount === 0)
|
|
1460
|
-
return "[ ]";
|
|
1461
|
-
if (selectedCount === models.length)
|
|
1462
|
-
return "[x]";
|
|
1463
|
-
return "[-]";
|
|
1464
|
-
};
|
|
1465
|
-
const setGroupSelection = (sectionIndex, nextValue) => {
|
|
1466
|
-
for (const model of sections[sectionIndex]?.models ?? []) {
|
|
1467
|
-
if (nextValue)
|
|
1468
|
-
selected.add(model.id);
|
|
1469
|
-
else
|
|
1470
|
-
selected.delete(model.id);
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1473
|
-
const toggleCurrent = () => {
|
|
1474
|
-
const current = rows[cursor];
|
|
1475
|
-
if (!current)
|
|
1476
|
-
return;
|
|
1477
|
-
if (current.kind === "apply") {
|
|
1478
|
-
finish();
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
if (current.kind === "group") {
|
|
1482
|
-
const state = groupState(current.sectionIndex);
|
|
1483
|
-
const nextValue = state !== "[x]";
|
|
1484
|
-
setGroupSelection(current.sectionIndex, nextValue);
|
|
1485
|
-
hint = `${sections[current.sectionIndex]?.provider.label ?? "Group"} ${nextValue ? "selected" : "cleared"}.`;
|
|
1486
|
-
render();
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
const model = sections[current.sectionIndex]?.models[current.modelIndex];
|
|
1490
|
-
if (!model)
|
|
1491
|
-
return;
|
|
1492
|
-
if (selected.has(model.id))
|
|
1493
|
-
selected.delete(model.id);
|
|
1494
|
-
else
|
|
1495
|
-
selected.add(model.id);
|
|
1496
|
-
hint = `${model.name} ${selected.has(model.id) ? "selected" : "cleared"}.`;
|
|
1497
|
-
render();
|
|
1498
|
-
};
|
|
1499
|
-
const finish = () => {
|
|
1500
|
-
if (settled)
|
|
1501
|
-
return;
|
|
1502
|
-
settled = true;
|
|
1503
|
-
stdin.off("keypress", onKeypress);
|
|
1504
|
-
if (stdin.isTTY)
|
|
1505
|
-
stdin.setRawMode(Boolean(wasRaw));
|
|
1506
|
-
stdout.write("\x1b[?25h");
|
|
1507
|
-
clearScreen();
|
|
1508
|
-
resolvePromise([...selected].filter((modelId) => knownIds.has(modelId)));
|
|
1509
|
-
};
|
|
1510
|
-
const render = () => {
|
|
1511
|
-
clearScreen();
|
|
1512
|
-
stdout.write(`${paint(params.title, ansi.bold)}\n`);
|
|
1513
|
-
stdout.write(`${paint("Each provider header toggles that provider on or off. Press A to toggle all models.", ansi.dim, ansi.gray)}\n`);
|
|
1514
|
-
stdout.write(`${paint("Move to Apply and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
1515
|
-
for (const [sectionIndex, section] of sections.entries()) {
|
|
1516
|
-
const currentRow = rows[cursor];
|
|
1517
|
-
const groupCursor = currentRow?.kind === "group" && currentRow.sectionIndex === sectionIndex;
|
|
1518
|
-
const groupPointer = groupCursor ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1519
|
-
const groupMark = groupState(sectionIndex);
|
|
1520
|
-
stdout.write(`${groupPointer} ${paint(groupMark, groupMark === "[x]" ? ansi.green : ansi.gray)} ${section.provider.icon} ${paint(`${section.provider.label} — Select all`, ansi.bold)}\n`);
|
|
1521
|
-
for (const [modelIndex, model] of section.models.entries()) {
|
|
1522
|
-
const activeRow = rows[cursor];
|
|
1523
|
-
const isActive = activeRow?.kind === "model" &&
|
|
1524
|
-
activeRow.sectionIndex === sectionIndex &&
|
|
1525
|
-
activeRow.modelIndex === modelIndex;
|
|
1526
|
-
const pointer = isActive ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1527
|
-
const mark = selected.has(model.id) ? paint("[x]", ansi.green) : paint("[ ]", ansi.gray);
|
|
1528
|
-
stdout.write(`${pointer} ${mark} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}\n`);
|
|
1529
|
-
}
|
|
1530
|
-
stdout.write("\n");
|
|
1531
|
-
}
|
|
1532
|
-
const applyActive = rows[cursor]?.kind === "apply";
|
|
1533
|
-
const applyStyled = applyActive
|
|
1534
|
-
? paint(` ${params.applyLabel} (${selected.size} selected) `, ansi.inverse, ansi.bold, ansi.green)
|
|
1535
|
-
: paint(`${params.applyLabel} (${selected.size} selected)`, ansi.bold, ansi.green);
|
|
1536
|
-
stdout.write(`${applyActive ? "→" : " "} ${applyStyled}\n`);
|
|
1537
|
-
stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
|
|
1538
|
-
};
|
|
1539
|
-
const onKeypress = (_str, key) => {
|
|
1540
|
-
if (key.ctrl && key.name === "c") {
|
|
1541
|
-
stdout.write("\x1b[?25h");
|
|
1542
|
-
process.exit(130);
|
|
1543
|
-
}
|
|
1544
|
-
if (key.name === "up" || key.name === "k") {
|
|
1545
|
-
cursor = (cursor - 1 + rows.length) % rows.length;
|
|
1546
|
-
render();
|
|
1547
|
-
return;
|
|
1548
|
-
}
|
|
1549
|
-
if (key.name === "down" || key.name === "j") {
|
|
1550
|
-
cursor = (cursor + 1) % rows.length;
|
|
1551
|
-
render();
|
|
1552
|
-
return;
|
|
1553
|
-
}
|
|
1554
|
-
if (key.name === "space" || key.name === "return" || key.name === "enter") {
|
|
1555
|
-
toggleCurrent();
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
if (key.name === "a") {
|
|
1559
|
-
const nextValue = sections.some((section) => section.models.some((model) => !selected.has(model.id)));
|
|
1560
|
-
for (const [sectionIndex] of sections.entries())
|
|
1561
|
-
setGroupSelection(sectionIndex, nextValue);
|
|
1562
|
-
hint = `${nextValue ? "Selected" : "Cleared"} all available models.`;
|
|
1563
|
-
render();
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1566
|
-
if (key.name === "c") {
|
|
1567
|
-
finish();
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
let resolvePromise = () => { };
|
|
1571
|
-
const result = new Promise((resolve) => {
|
|
1572
|
-
resolvePromise = resolve;
|
|
1573
|
-
});
|
|
1574
|
-
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1575
|
-
stdin.resume();
|
|
1576
|
-
if (stdin.isTTY)
|
|
1577
|
-
stdin.setRawMode(true);
|
|
1578
|
-
stdout.write("\x1b[?25l");
|
|
1579
|
-
stdin.on("keypress", onKeypress);
|
|
1580
|
-
render();
|
|
1581
|
-
return result;
|
|
1582
|
-
}
|
|
1583
|
-
async function promptForPrimaryModel(models, defaultModel) {
|
|
1584
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY || models.length <= 1) {
|
|
1585
|
-
return defaultModel;
|
|
1586
|
-
}
|
|
1587
|
-
const sections = groupModelSections(models);
|
|
1588
|
-
const flattened = sections.flatMap((section) => section.models);
|
|
1589
|
-
const stdin = process.stdin;
|
|
1590
|
-
const stdout = process.stdout;
|
|
1591
|
-
const wasRaw = stdin.isRaw;
|
|
1592
|
-
const ansi = {
|
|
1593
|
-
reset: "\x1b[0m",
|
|
1594
|
-
bold: "\x1b[1m",
|
|
1595
|
-
dim: "\x1b[2m",
|
|
1596
|
-
green: "\x1b[32m",
|
|
1597
|
-
yellow: "\x1b[33m",
|
|
1598
|
-
gray: "\x1b[90m",
|
|
1599
|
-
};
|
|
1600
|
-
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1601
|
-
const clearScreen = () => {
|
|
1602
|
-
stdout.write("\x1b[2J\x1b[H");
|
|
1603
|
-
};
|
|
1604
|
-
let cursor = Math.max(0, flattened.findIndex((model) => model.id === defaultModel));
|
|
1605
|
-
let settled = false;
|
|
1606
|
-
let resolvePromise = () => { };
|
|
1607
|
-
const finish = (modelId) => {
|
|
1608
|
-
if (settled)
|
|
1609
|
-
return;
|
|
1610
|
-
settled = true;
|
|
1611
|
-
stdin.off("keypress", onKeypress);
|
|
1612
|
-
if (stdin.isTTY)
|
|
1613
|
-
stdin.setRawMode(Boolean(wasRaw));
|
|
1614
|
-
stdout.write("\x1b[?25h");
|
|
1615
|
-
clearScreen();
|
|
1616
|
-
resolvePromise(modelId);
|
|
1617
|
-
};
|
|
1618
|
-
const render = () => {
|
|
1619
|
-
clearScreen();
|
|
1620
|
-
stdout.write(`${paint("Choose the default model for local setup", ansi.bold)}\n`);
|
|
1621
|
-
stdout.write(`${paint("Use ↑/↓ to move and Enter to select the default model.", ansi.dim, ansi.gray)}\n\n`);
|
|
1622
|
-
let index = 0;
|
|
1623
|
-
for (const section of sections) {
|
|
1624
|
-
stdout.write(`${paint(`${section.provider.icon} ${section.provider.label}`, ansi.bold)}\n`);
|
|
1625
|
-
for (const model of section.models) {
|
|
1626
|
-
const active = index === cursor;
|
|
1627
|
-
const pointer = active ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1628
|
-
const defaultSuffix = model.id === defaultModel ? paint(" default", ansi.dim, ansi.gray) : "";
|
|
1629
|
-
stdout.write(`${pointer} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}${defaultSuffix}\n`);
|
|
1630
|
-
index += 1;
|
|
1631
|
-
}
|
|
1632
|
-
stdout.write("\n");
|
|
1633
|
-
}
|
|
1634
|
-
};
|
|
1635
|
-
const onKeypress = (_str, key) => {
|
|
1636
|
-
if (key.ctrl && key.name === "c") {
|
|
1637
|
-
stdout.write("\x1b[?25h");
|
|
1638
|
-
process.exit(130);
|
|
1639
|
-
}
|
|
1640
|
-
if (key.name === "up" || key.name === "k") {
|
|
1641
|
-
cursor = (cursor - 1 + flattened.length) % flattened.length;
|
|
1642
|
-
render();
|
|
1643
|
-
return;
|
|
1644
|
-
}
|
|
1645
|
-
if (key.name === "down" || key.name === "j") {
|
|
1646
|
-
cursor = (cursor + 1) % flattened.length;
|
|
1647
|
-
render();
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
if (key.name === "return" || key.name === "enter" || key.name === "space") {
|
|
1651
|
-
finish(flattened[cursor]?.id ?? defaultModel);
|
|
1652
|
-
}
|
|
1653
|
-
};
|
|
1654
|
-
const result = new Promise((resolve) => {
|
|
1655
|
-
resolvePromise = resolve;
|
|
1656
|
-
});
|
|
1657
|
-
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1658
|
-
stdin.resume();
|
|
1659
|
-
if (stdin.isTTY)
|
|
1660
|
-
stdin.setRawMode(true);
|
|
1661
|
-
stdout.write("\x1b[?25l");
|
|
1662
|
-
stdin.on("keypress", onKeypress);
|
|
1663
|
-
render();
|
|
1664
|
-
return result;
|
|
1665
|
-
}
|
|
1666
|
-
async function resolveConfiguredModelSelection(params) {
|
|
1667
|
-
const requestedModel = params.requestedModel?.trim();
|
|
1668
|
-
const requestedModels = params.requestedModels
|
|
1669
|
-
?.split(",")
|
|
1670
|
-
.map((entry) => entry.trim())
|
|
1671
|
-
.filter(Boolean);
|
|
1672
|
-
const availableIds = new Set(params.resolved.models.map((entry) => entry.id));
|
|
1673
|
-
if (requestedModel && availableIds.size > 0 && !availableIds.has(requestedModel)) {
|
|
1674
|
-
throw new Error(`Requested model "${requestedModel}" is not advertised by the live backend.`);
|
|
1675
|
-
}
|
|
1676
|
-
if (requestedModels?.length) {
|
|
1677
|
-
for (const modelId of requestedModels) {
|
|
1678
|
-
if (availableIds.size > 0 && !availableIds.has(modelId)) {
|
|
1679
|
-
throw new Error(`Requested model "${modelId}" is not advertised by the live backend.`);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
let selectedModels;
|
|
1684
|
-
if (requestedModels?.length) {
|
|
1685
|
-
selectedModels = params.resolved.models.filter((entry) => requestedModels.includes(entry.id));
|
|
1686
|
-
}
|
|
1687
|
-
else if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1688
|
-
selectedModels = params.resolved.models;
|
|
1689
|
-
}
|
|
1690
|
-
else if (params.resolved.models.length > 1) {
|
|
1691
|
-
const selectedIds = new Set(await promptForGroupedModelSelection({
|
|
1692
|
-
title: "Choose the OpenAI-compatible models to save into local tools",
|
|
1693
|
-
models: params.resolved.models,
|
|
1694
|
-
defaultSelectedIds: params.resolved.models.map((entry) => entry.id),
|
|
1695
|
-
applyLabel: "Apply model selection",
|
|
1696
|
-
}));
|
|
1697
|
-
selectedModels = params.resolved.models.filter((entry) => selectedIds.has(entry.id));
|
|
1698
|
-
}
|
|
1699
|
-
else {
|
|
1700
|
-
selectedModels = params.resolved.models;
|
|
1701
|
-
}
|
|
1702
|
-
const fallbackModels = selectedModels.length > 0 ? selectedModels : params.resolved.models;
|
|
1703
|
-
const selectedModelId = requestedModel && fallbackModels.some((entry) => entry.id === requestedModel)
|
|
1704
|
-
? requestedModel
|
|
1705
|
-
: params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY
|
|
1706
|
-
? fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1707
|
-
? params.resolved.model
|
|
1708
|
-
: fallbackModels[0]?.id ?? params.resolved.model
|
|
1709
|
-
: await promptForPrimaryModel(fallbackModels, fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1710
|
-
? params.resolved.model
|
|
1711
|
-
: (fallbackModels[0]?.id ?? params.resolved.model));
|
|
1712
|
-
const noteParts = [];
|
|
1713
|
-
if (requestedModels?.length) {
|
|
1714
|
-
noteParts.push(`Configured ${requestedModels.length} OpenAI-compatible models.`);
|
|
1715
|
-
}
|
|
1716
|
-
else if (selectedModels.length > 0 && selectedModels.length < params.resolved.models.length) {
|
|
1717
|
-
noteParts.push(`Configured ${selectedModels.length} OpenAI-compatible models for local tools.`);
|
|
1718
|
-
}
|
|
1719
|
-
if (selectedModelId !== params.resolved.model) {
|
|
1720
|
-
noteParts.push(`Using ${selectedModelId} as the default configured model for local tools.`);
|
|
1721
|
-
}
|
|
1722
|
-
if (availableIds.size === 0 && requestedModel && params.resolved.failure) {
|
|
1723
|
-
noteParts.push(`Could not verify requested model ${requestedModel} against the live backend (${params.resolved.failure}).`);
|
|
1724
|
-
}
|
|
1725
|
-
return {
|
|
1726
|
-
model: selectedModelId,
|
|
1727
|
-
models: fallbackModels,
|
|
1728
|
-
note: noteParts.join(" ").trim() || undefined,
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
function summarizeModelFetchFailure(detail) {
|
|
1732
|
-
const normalized = detail.replace(/\s+/g, " ").trim();
|
|
1733
|
-
if (!normalized)
|
|
1734
|
-
return "unknown error";
|
|
1735
|
-
if (normalized.length <= 140)
|
|
1736
|
-
return normalized;
|
|
1737
|
-
return `${normalized.slice(0, 137).trimEnd()}...`;
|
|
1738
|
-
}
|
|
1739
|
-
function isCredentialRejectedFailure(detail) {
|
|
1740
|
-
if (!detail)
|
|
1741
|
-
return false;
|
|
1742
|
-
const normalized = detail.toLowerCase();
|
|
1743
|
-
return (normalized.includes("http 401") ||
|
|
1744
|
-
normalized.includes("invalid api key") ||
|
|
1745
|
-
normalized.includes("missing bearer token") ||
|
|
1746
|
-
normalized.includes("device session not found") ||
|
|
1747
|
-
normalized.includes("billing access required"));
|
|
1748
|
-
}
|
|
1749
|
-
function describeCredentialRejection(authType, failure) {
|
|
1750
|
-
const detail = failure?.trim() || "credential rejected by backend";
|
|
1751
|
-
if (authType === "device-session") {
|
|
1752
|
-
return `The saved device-session credential was rejected by the backend (${detail}). Re-link this machine in the browser or run \`theclawbay logout\` before rerunning setup.`;
|
|
1753
|
-
}
|
|
1754
|
-
return `The provided API key was rejected by the backend (${detail}). Update the key and rerun setup.`;
|
|
1755
|
-
}
|
|
1756
|
-
async function fetchBackendModelIds(backendUrl, apiKey) {
|
|
1757
|
-
const url = `${openAiCompatibleProxyUrl(backendUrl)}/models`;
|
|
1758
|
-
try {
|
|
1759
|
-
const response = await fetch(url, {
|
|
1760
|
-
headers: {
|
|
1761
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1762
|
-
Accept: "application/json",
|
|
1763
|
-
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
1764
|
-
},
|
|
1765
|
-
signal: AbortSignal.timeout(10000),
|
|
1766
|
-
});
|
|
1767
|
-
if (!response.ok) {
|
|
1768
|
-
const responseText = await response.text().catch(() => "");
|
|
1769
|
-
let detail = responseText.trim();
|
|
1770
|
-
try {
|
|
1771
|
-
const parsed = JSON.parse(responseText);
|
|
1772
|
-
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
1773
|
-
detail = parsed.error.trim();
|
|
1774
|
-
}
|
|
1775
|
-
else if (typeof parsed.code === "string" && parsed.code.trim()) {
|
|
1776
|
-
detail = parsed.code.trim();
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
catch {
|
|
1780
|
-
// Keep the raw response text when the body is not JSON.
|
|
1781
|
-
}
|
|
1782
|
-
return {
|
|
1783
|
-
ids: null,
|
|
1784
|
-
failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
|
|
1785
|
-
};
|
|
1786
|
-
}
|
|
1787
|
-
const body = (await response.json());
|
|
1788
|
-
const ids = dedupeClaudeCatalogModelIds((body.data ?? [])
|
|
1789
|
-
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
1790
|
-
.filter((id) => id.length > 0));
|
|
1791
|
-
if (!ids.length) {
|
|
1792
|
-
return {
|
|
1793
|
-
ids: null,
|
|
1794
|
-
failure: "backend returned an empty model list",
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
return { ids };
|
|
1798
|
-
}
|
|
1799
|
-
catch (error) {
|
|
1800
|
-
return {
|
|
1801
|
-
ids: null,
|
|
1802
|
-
failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
|
|
1803
|
-
};
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
async function fetchClaudeModelIds(backendUrl, apiKey) {
|
|
1807
|
-
const url = `${anthropicCompatibleProxyUrl(backendUrl)}/v1/models`;
|
|
1808
|
-
try {
|
|
1809
|
-
const response = await fetch(url, {
|
|
1810
|
-
headers: {
|
|
1811
|
-
"x-api-key": apiKey,
|
|
1812
|
-
Accept: "application/json",
|
|
1813
|
-
"User-Agent": CLI_HTTP_USER_AGENT,
|
|
1814
|
-
},
|
|
1815
|
-
signal: AbortSignal.timeout(10000),
|
|
1816
|
-
});
|
|
1817
|
-
if (!response.ok) {
|
|
1818
|
-
const responseText = await response.text().catch(() => "");
|
|
1819
|
-
let detail = responseText.trim();
|
|
1820
|
-
try {
|
|
1821
|
-
const parsed = JSON.parse(responseText);
|
|
1822
|
-
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
1823
|
-
detail = parsed.error.trim();
|
|
1824
|
-
}
|
|
1825
|
-
else if (typeof parsed.code === "string" && parsed.code.trim()) {
|
|
1826
|
-
detail = parsed.code.trim();
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
catch {
|
|
1830
|
-
// Keep the raw response text when the body is not JSON.
|
|
1831
|
-
}
|
|
1832
|
-
return {
|
|
1833
|
-
ids: null,
|
|
1834
|
-
failure: `HTTP ${response.status}${detail ? `: ${summarizeModelFetchFailure(detail)}` : ""}`,
|
|
1835
|
-
};
|
|
1836
|
-
}
|
|
1837
|
-
const body = (await response.json());
|
|
1838
|
-
const ids = (body.data ?? [])
|
|
1839
|
-
.map((entry) => (typeof entry.id === "string" ? entry.id.trim() : ""))
|
|
1840
|
-
.filter((id) => id.length > 0);
|
|
1841
|
-
if (!ids.length) {
|
|
1842
|
-
return {
|
|
1843
|
-
ids: null,
|
|
1844
|
-
failure: "backend returned an empty Claude model list",
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
return { ids };
|
|
1848
|
-
}
|
|
1849
|
-
catch (error) {
|
|
1850
|
-
return {
|
|
1851
|
-
ids: null,
|
|
1852
|
-
failure: summarizeModelFetchFailure(error instanceof Error ? error.message : String(error)),
|
|
1853
|
-
};
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
function kiloStorageCandidates() {
|
|
1857
|
-
const home = node_os_1.default.homedir();
|
|
1858
|
-
const candidates = [node_path_1.default.join(home, ".config", "kilo"), node_path_1.default.join(home, ".kilo")];
|
|
1859
|
-
if (node_os_1.default.platform() === "darwin") {
|
|
1860
|
-
candidates.push(node_path_1.default.join(home, "Library", "Application Support", "Code", "User", "globalStorage", "kilocode.kilo-code"));
|
|
1861
|
-
}
|
|
1862
|
-
else if (node_os_1.default.platform() === "win32") {
|
|
1863
|
-
const appData = process.env.APPDATA;
|
|
1864
|
-
if (appData) {
|
|
1865
|
-
candidates.push(node_path_1.default.join(appData, "Code", "User", "globalStorage", "kilocode.kilo-code"));
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
else {
|
|
1869
|
-
candidates.push(node_path_1.default.join(home, ".config", "Code", "User", "globalStorage", "kilocode.kilo-code"));
|
|
1870
|
-
}
|
|
1871
|
-
return candidates;
|
|
1872
|
-
}
|
|
1873
|
-
async function detectCodexClient() {
|
|
1874
|
-
if (hasCommand("codex"))
|
|
1875
|
-
return true;
|
|
1876
|
-
const candidates = [
|
|
1877
|
-
node_path_1.default.join(paths_1.codexDir, "config.toml"),
|
|
1878
|
-
node_path_1.default.join(paths_1.codexDir, "auth.json"),
|
|
1879
|
-
node_path_1.default.join(paths_1.codexDir, "history.jsonl"),
|
|
1880
|
-
node_path_1.default.join(paths_1.codexDir, "sessions"),
|
|
1881
|
-
];
|
|
1882
|
-
for (const candidate of candidates) {
|
|
1883
|
-
if (await pathExists(candidate))
|
|
1884
|
-
return true;
|
|
1885
|
-
}
|
|
1886
|
-
return false;
|
|
1887
|
-
}
|
|
1888
|
-
async function detectGsdClient() {
|
|
1889
|
-
if (hasCommand("gsd"))
|
|
1890
|
-
return true;
|
|
1891
|
-
const candidates = [
|
|
1892
|
-
GSD_AGENT_DIR,
|
|
1893
|
-
GSD_MODELS_PATH,
|
|
1894
|
-
GSD_AUTH_PATH,
|
|
1895
|
-
GSD_SETTINGS_PATH,
|
|
1896
|
-
];
|
|
1897
|
-
for (const candidate of candidates) {
|
|
1898
|
-
if (await pathExists(candidate))
|
|
1899
|
-
return true;
|
|
1900
|
-
}
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
async function detectContinueClient() {
|
|
1904
|
-
if (hasCommand("cn") || hasCommand("continue"))
|
|
1905
|
-
return true;
|
|
1906
|
-
const candidates = [
|
|
1907
|
-
CONTINUE_CONFIG_PATH,
|
|
1908
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.json"),
|
|
1909
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".continue", "sessions"),
|
|
1910
|
-
];
|
|
1911
|
-
for (const candidate of candidates) {
|
|
1912
|
-
if (await pathExists(candidate))
|
|
1913
|
-
return true;
|
|
1914
|
-
}
|
|
1915
|
-
return (await detectExtensionHosts(["continue.continue-"])).length > 0;
|
|
1916
|
-
}
|
|
1917
|
-
async function detectClaudeCodeClient() {
|
|
1918
|
-
if (hasCommand("claude"))
|
|
1919
|
-
return true;
|
|
1920
|
-
const candidates = [
|
|
1921
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".claude"),
|
|
1922
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".claude.json"),
|
|
1923
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".config", "claude"),
|
|
1924
|
-
];
|
|
1925
|
-
for (const candidate of candidates) {
|
|
1926
|
-
if (await pathExists(candidate))
|
|
1927
|
-
return true;
|
|
1928
|
-
}
|
|
1929
|
-
return false;
|
|
1930
|
-
}
|
|
1931
|
-
async function detectClaudeClient() {
|
|
1932
|
-
if (await detectClaudeCodeClient())
|
|
1933
|
-
return true;
|
|
1934
|
-
if (claudeDesktopAppCandidatePaths().some((candidate) => (0, node_fs_1.existsSync)(candidate)))
|
|
1935
|
-
return true;
|
|
1936
|
-
const configPath = claudeDesktop3pConfigPath();
|
|
1937
|
-
if (!configPath)
|
|
1938
|
-
return false;
|
|
1939
|
-
return (await pathExists(configPath)) || (await pathExists(node_path_1.default.dirname(configPath)));
|
|
1940
|
-
}
|
|
1941
|
-
async function detectOpenCodeClient() {
|
|
1942
|
-
if (hasCommand("opencode"))
|
|
1943
|
-
return true;
|
|
1944
|
-
const candidates = [
|
|
1945
|
-
...resolveOpenCodeConfigTargets(),
|
|
1946
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".opencode.json"),
|
|
1947
|
-
];
|
|
1948
|
-
if (node_os_1.default.platform() === "darwin") {
|
|
1949
|
-
candidates.push("/Applications/OpenCode.app");
|
|
1950
|
-
candidates.push(node_path_1.default.join(node_os_1.default.homedir(), "Applications", "OpenCode.app"));
|
|
1951
|
-
}
|
|
1952
|
-
for (const candidate of candidates) {
|
|
1953
|
-
if (await pathExists(candidate))
|
|
1954
|
-
return true;
|
|
1955
|
-
}
|
|
1956
|
-
return false;
|
|
1957
|
-
}
|
|
1958
|
-
async function detectClineClient() {
|
|
1959
|
-
if (hasCommand("cline"))
|
|
1960
|
-
return true;
|
|
1961
|
-
const candidates = [
|
|
1962
|
-
CLINE_GLOBAL_STATE_PATH,
|
|
1963
|
-
CLINE_SECRETS_PATH,
|
|
1964
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".cline", "state", "taskHistory.json"),
|
|
1965
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".cline", "tasks"),
|
|
1966
|
-
];
|
|
1967
|
-
for (const candidate of candidates) {
|
|
1968
|
-
if (await pathExists(candidate))
|
|
1969
|
-
return true;
|
|
1970
|
-
}
|
|
1971
|
-
return (await detectExtensionHosts(["saoudrizwan.claude-dev-"])).length > 0;
|
|
1972
|
-
}
|
|
1973
|
-
async function detectKiloClient() {
|
|
1974
|
-
if (hasCommand("kilo"))
|
|
1975
|
-
return true;
|
|
1976
|
-
for (const candidate of kiloStorageCandidates()) {
|
|
1977
|
-
if (await pathExists(candidate))
|
|
1978
|
-
return true;
|
|
1979
|
-
}
|
|
1980
|
-
return false;
|
|
1981
|
-
}
|
|
1982
|
-
async function detectRooClient() {
|
|
1983
|
-
return (await detectExtensionHosts(["rooveterinaryinc.roo-cline-"])).length > 0;
|
|
1984
|
-
}
|
|
1985
|
-
async function detectTraeClient() {
|
|
1986
|
-
const bundlePath = traeBundlePath();
|
|
1987
|
-
if (!bundlePath)
|
|
1988
|
-
return false;
|
|
1989
|
-
return pathExists(bundlePath);
|
|
1990
|
-
}
|
|
1991
|
-
async function detectAiderClient() {
|
|
1992
|
-
if (hasCommand("aider"))
|
|
1993
|
-
return true;
|
|
1994
|
-
const candidates = [
|
|
1995
|
-
AIDER_CONFIG_PATH,
|
|
1996
|
-
node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yaml"),
|
|
1997
|
-
node_path_1.default.join(process.cwd(), ".aider.conf.yml"),
|
|
1998
|
-
node_path_1.default.join(process.cwd(), ".aider.chat.history.md"),
|
|
1999
|
-
];
|
|
2000
|
-
for (const candidate of candidates) {
|
|
2001
|
-
if (await pathExists(candidate))
|
|
2002
|
-
return true;
|
|
2003
|
-
}
|
|
2004
|
-
return false;
|
|
2005
|
-
}
|
|
2006
|
-
async function detectZoClient() {
|
|
2007
|
-
for (const candidate of zoProgramCandidates()) {
|
|
2008
|
-
if (await pathExists(candidate))
|
|
2009
|
-
return true;
|
|
2010
|
-
}
|
|
2011
|
-
for (const candidate of zoCookieDbCandidates()) {
|
|
2012
|
-
if (await pathExists(candidate))
|
|
2013
|
-
return true;
|
|
2014
|
-
}
|
|
2015
|
-
return false;
|
|
2016
|
-
}
|
|
2017
|
-
function hermesHomeDir() {
|
|
2018
|
-
const raw = process.env.HERMES_HOME?.trim();
|
|
2019
|
-
if (raw)
|
|
2020
|
-
return raw;
|
|
2021
|
-
return node_path_1.default.join(node_os_1.default.homedir(), ".hermes");
|
|
2022
|
-
}
|
|
2023
|
-
function hermesConfigPath() {
|
|
2024
|
-
return node_path_1.default.join(hermesHomeDir(), "config.yaml");
|
|
2025
|
-
}
|
|
2026
|
-
function hermesEnvPath() {
|
|
2027
|
-
return node_path_1.default.join(hermesHomeDir(), ".env");
|
|
2028
|
-
}
|
|
2029
|
-
async function detectHermesClient() {
|
|
2030
|
-
if (hasCommand("hermes"))
|
|
2031
|
-
return true;
|
|
2032
|
-
const candidates = [
|
|
2033
|
-
hermesHomeDir(),
|
|
2034
|
-
hermesConfigPath(),
|
|
2035
|
-
hermesEnvPath(),
|
|
2036
|
-
];
|
|
2037
|
-
for (const candidate of candidates) {
|
|
2038
|
-
if (await pathExists(candidate))
|
|
2039
|
-
return true;
|
|
2040
|
-
}
|
|
2041
|
-
return false;
|
|
2042
|
-
}
|
|
2043
|
-
function isOpenAiCompatibleSetupModelId(modelId) {
|
|
2044
|
-
const trimmed = modelId.trim();
|
|
2045
|
-
if (!trimmed)
|
|
2046
|
-
return false;
|
|
2047
|
-
return !trimmed.startsWith("claude-") && SUPPORTED_MODEL_IDS.includes(trimmed);
|
|
2048
|
-
}
|
|
2049
|
-
function formatDotEnvValue(value) {
|
|
2050
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
2051
|
-
}
|
|
2052
|
-
async function upsertDotEnvValue(filePath, key, value) {
|
|
2053
|
-
const existing = (await readFileIfExists(filePath)) ?? "";
|
|
2054
|
-
const lines = existing ? existing.split(/\r?\n/) : [];
|
|
2055
|
-
const assignment = `${key}=${formatDotEnvValue(value)}`;
|
|
2056
|
-
let changed = false;
|
|
2057
|
-
let replaced = false;
|
|
2058
|
-
const nextLines = lines.map((line) => {
|
|
2059
|
-
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
2060
|
-
replaced = true;
|
|
2061
|
-
if (line === assignment)
|
|
2062
|
-
return line;
|
|
2063
|
-
changed = true;
|
|
2064
|
-
return assignment;
|
|
2065
|
-
}
|
|
2066
|
-
return line;
|
|
2067
|
-
});
|
|
2068
|
-
if (!replaced) {
|
|
2069
|
-
if (nextLines.length > 0 && nextLines[nextLines.length - 1]?.trim()) {
|
|
2070
|
-
nextLines.push("");
|
|
2071
|
-
}
|
|
2072
|
-
nextLines.push(assignment);
|
|
2073
|
-
changed = true;
|
|
2074
|
-
}
|
|
2075
|
-
if (!changed)
|
|
2076
|
-
return false;
|
|
2077
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
2078
|
-
await promises_1.default.writeFile(filePath, `${nextLines.join("\n").trimEnd()}\n`, "utf8");
|
|
2079
|
-
return true;
|
|
2080
|
-
}
|
|
2081
|
-
async function writeHermesConfig(params) {
|
|
2082
|
-
const configPath = hermesConfigPath();
|
|
2083
|
-
const envPath = hermesEnvPath();
|
|
2084
|
-
const baseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
2085
|
-
const selectedModel = params.model.trim() || DEFAULT_HERMES_MODEL;
|
|
2086
|
-
const existingRaw = await readFileIfExists(configPath);
|
|
2087
|
-
const existingConfig = existingRaw?.trim()
|
|
2088
|
-
? objectRecordOr((0, yaml_1.parseDocument)(existingRaw).toJS(), {})
|
|
2089
|
-
: {};
|
|
2090
|
-
const nextConfig = { ...existingConfig };
|
|
2091
|
-
const modelConfig = objectRecordOr(existingConfig.model, {});
|
|
2092
|
-
modelConfig.provider = "custom";
|
|
2093
|
-
modelConfig.default = selectedModel;
|
|
2094
|
-
modelConfig.base_url = baseUrl;
|
|
2095
|
-
modelConfig.api_mode = "codex_responses";
|
|
2096
|
-
delete modelConfig.api_key;
|
|
2097
|
-
delete modelConfig.context_length;
|
|
2098
|
-
nextConfig.model = modelConfig;
|
|
2099
|
-
const rawCustomProviders = Array.isArray(existingConfig.custom_providers)
|
|
2100
|
-
? existingConfig.custom_providers
|
|
2101
|
-
: [];
|
|
2102
|
-
const nextCustomProviders = rawCustomProviders.filter((entry) => {
|
|
2103
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
2104
|
-
return true;
|
|
2105
|
-
const record = entry;
|
|
2106
|
-
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
2107
|
-
const providerBaseUrl = typeof record.base_url === "string" ? record.base_url.trim() : "";
|
|
2108
|
-
const keyEnv = typeof record.key_env === "string" ? record.key_env.trim() : "";
|
|
2109
|
-
if (keyEnv === HERMES_KEY_ENV_NAME)
|
|
2110
|
-
return false;
|
|
2111
|
-
if (name === HERMES_PROVIDER_NAME && providerBaseUrl === baseUrl)
|
|
2112
|
-
return false;
|
|
2113
|
-
return true;
|
|
2114
|
-
});
|
|
2115
|
-
nextCustomProviders.push({
|
|
2116
|
-
name: HERMES_PROVIDER_NAME,
|
|
2117
|
-
base_url: baseUrl,
|
|
2118
|
-
key_env: HERMES_KEY_ENV_NAME,
|
|
2119
|
-
model: selectedModel,
|
|
2120
|
-
api_mode: "codex_responses",
|
|
2121
|
-
});
|
|
2122
|
-
nextConfig.custom_providers = nextCustomProviders;
|
|
2123
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2124
|
-
await promises_1.default.writeFile(configPath, (0, yaml_1.stringify)(nextConfig), "utf8");
|
|
2125
|
-
await upsertDotEnvValue(envPath, HERMES_KEY_ENV_NAME, params.apiKey);
|
|
2126
|
-
return [configPath, envPath];
|
|
2127
|
-
}
|
|
2128
|
-
async function resolveModels(backendUrl, apiKey) {
|
|
2129
|
-
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
2130
|
-
const filteredIds = uniqueStrings((ids ?? []).filter(isOpenAiCompatibleSetupModelId));
|
|
2131
|
-
const available = new Set(filteredIds);
|
|
2132
|
-
let selected = DEFAULT_CODEX_MODEL;
|
|
2133
|
-
let note;
|
|
2134
|
-
const authRejected = isCredentialRejectedFailure(failure);
|
|
2135
|
-
for (const preferred of PREFERRED_MODELS) {
|
|
2136
|
-
if (available.has(preferred)) {
|
|
2137
|
-
selected = preferred;
|
|
2138
|
-
break;
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
if (filteredIds.length && !available.has(selected)) {
|
|
2142
|
-
selected = filteredIds[0] ?? DEFAULT_CODEX_MODEL;
|
|
2143
|
-
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
2144
|
-
}
|
|
2145
|
-
else if (!filteredIds.length) {
|
|
2146
|
-
note = failure
|
|
2147
|
-
? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
|
|
2148
|
-
: `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
2149
|
-
}
|
|
2150
|
-
const unique = [];
|
|
2151
|
-
const pushUnique = (modelId) => {
|
|
2152
|
-
if (!modelId || !isOpenAiCompatibleSetupModelId(modelId) || unique.includes(modelId))
|
|
2153
|
-
return;
|
|
2154
|
-
unique.push(modelId);
|
|
2155
|
-
};
|
|
2156
|
-
pushUnique(selected);
|
|
2157
|
-
if (filteredIds.length > 0) {
|
|
2158
|
-
for (const modelId of filteredIds)
|
|
2159
|
-
pushUnique(modelId);
|
|
2160
|
-
}
|
|
2161
|
-
else {
|
|
2162
|
-
for (const modelId of DEFAULT_MODELS)
|
|
2163
|
-
pushUnique(modelId);
|
|
2164
|
-
}
|
|
2165
|
-
return {
|
|
2166
|
-
model: selected,
|
|
2167
|
-
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) || prettyModelId(modelId) })),
|
|
2168
|
-
note,
|
|
2169
|
-
failure,
|
|
2170
|
-
authRejected,
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
|
-
async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
2174
|
-
const { ids, failure } = await fetchClaudeModelIds(backendUrl, apiKey);
|
|
2175
|
-
const authRejected = isCredentialRejectedFailure(failure);
|
|
2176
|
-
if (!ids?.length) {
|
|
2177
|
-
return {
|
|
2178
|
-
enabled: false,
|
|
2179
|
-
models: [],
|
|
2180
|
-
note: failure ? `Claude Code auto-setup skipped (${failure}).` : undefined,
|
|
2181
|
-
failure,
|
|
2182
|
-
authRejected,
|
|
2183
|
-
};
|
|
2184
|
-
}
|
|
2185
|
-
return {
|
|
2186
|
-
enabled: true,
|
|
2187
|
-
models: ids,
|
|
2188
|
-
failure,
|
|
2189
|
-
authRejected,
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
async function resolveConfiguredClaudeModels(params) {
|
|
2193
|
-
const requestedModels = params.requestedModels
|
|
2194
|
-
?.split(",")
|
|
2195
|
-
.map((entry) => entry.trim())
|
|
2196
|
-
.filter(Boolean);
|
|
2197
|
-
if (!requestedModels?.length || !params.access.enabled) {
|
|
2198
|
-
const selectedModels = !requestedModels?.length &&
|
|
2199
|
-
params.access.enabled &&
|
|
2200
|
-
!params.skipPrompt &&
|
|
2201
|
-
process.stdin.isTTY &&
|
|
2202
|
-
process.stdout.isTTY &&
|
|
2203
|
-
params.access.models.length > 1
|
|
2204
|
-
? await promptForGroupedModelSelection({
|
|
2205
|
-
title: "Choose the Claude models to save into Claude-compatible tools",
|
|
2206
|
-
models: params.access.models.map((modelId) => ({
|
|
2207
|
-
id: modelId,
|
|
2208
|
-
name: prettyModelId(modelId),
|
|
2209
|
-
})),
|
|
2210
|
-
defaultSelectedIds: params.access.models,
|
|
2211
|
-
applyLabel: "Apply Claude selection",
|
|
2212
|
-
})
|
|
2213
|
-
: params.access.models;
|
|
2214
|
-
return {
|
|
2215
|
-
...params.access,
|
|
2216
|
-
selectedModels,
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
const available = new Set(params.access.models);
|
|
2220
|
-
for (const modelId of requestedModels) {
|
|
2221
|
-
if (!available.has(modelId)) {
|
|
2222
|
-
throw new Error(`Requested Claude model "${modelId}" is not advertised by the live Claude endpoint.`);
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
return {
|
|
2226
|
-
...params.access,
|
|
2227
|
-
selectedModels: requestedModels,
|
|
2228
|
-
note: `Configured Claude-compatible clients with ${requestedModels.join(", ")}.`,
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
async function writeCodexConfig(params) {
|
|
2232
|
-
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
2233
|
-
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
2234
|
-
let existing = "";
|
|
2235
|
-
try {
|
|
2236
|
-
existing = await promises_1.default.readFile(configPath, "utf8");
|
|
2237
|
-
}
|
|
2238
|
-
catch (error) {
|
|
2239
|
-
const err = error;
|
|
2240
|
-
if (err.code !== "ENOENT")
|
|
2241
|
-
throw error;
|
|
2242
|
-
}
|
|
2243
|
-
const proxyRoot = publicApiOriginForBackendUrl(params.backendUrl);
|
|
2244
|
-
const nativeCodexBaseUrl = `${proxyRoot}${CANONICAL_CODEX_NATIVE_PROXY_SUFFIX}`;
|
|
2245
|
-
let next = await restoreManagedCodexFeatureFlags(existing);
|
|
2246
|
-
next = await ensureManagedCodexFeatureFlags(next);
|
|
2247
|
-
next = removeManagedBlock(next, MANAGED_START, MANAGED_END);
|
|
2248
|
-
next = removeProviderTable(next, DEFAULT_PROVIDER_ID);
|
|
2249
|
-
next = upsertFirstKeyLine(next, "model_provider", `"${DEFAULT_PROVIDER_ID}"`);
|
|
2250
|
-
next = upsertFirstKeyLine(next, "model", `"${params.model}"`);
|
|
2251
|
-
next = upsertFirstKeyLine(next, "openai_base_url", `"${nativeCodexBaseUrl}"`);
|
|
2252
|
-
next = removeTopLevelKeyLineIf(next, "chatgpt_base_url", isTheClawBayChatgptBaseUrl);
|
|
2253
|
-
const managedBlock = appendManagedBlock("", [
|
|
2254
|
-
MANAGED_START,
|
|
2255
|
-
`[model_providers.${DEFAULT_PROVIDER_ID}]`,
|
|
2256
|
-
'name = "The Claw Bay (OpenAI compatible)"',
|
|
2257
|
-
`base_url = "${nativeCodexBaseUrl}"`,
|
|
2258
|
-
'wire_api = "responses"',
|
|
2259
|
-
"requires_openai_auth = false",
|
|
2260
|
-
"supports_websockets = false",
|
|
2261
|
-
`experimental_bearer_token = "${params.apiKey}"`,
|
|
2262
|
-
MANAGED_END,
|
|
2263
|
-
]);
|
|
2264
|
-
next = appendManagedBlock(next, managedBlock.trimEnd().split("\n"));
|
|
2265
|
-
await promises_1.default.writeFile(configPath, next, "utf8");
|
|
2266
|
-
return configPath;
|
|
2267
|
-
}
|
|
2268
|
-
function isManagedContinueModel(model, proxyUrl) {
|
|
2269
|
-
if (typeof model !== "object" || model === null || Array.isArray(model))
|
|
2270
|
-
return false;
|
|
2271
|
-
const candidate = model;
|
|
2272
|
-
return (candidate.provider === "openai" &&
|
|
2273
|
-
candidate.name === CONTINUE_MODEL_NAME &&
|
|
2274
|
-
candidate.apiBase === proxyUrl);
|
|
2275
|
-
}
|
|
2276
|
-
async function writeContinueConfig(params) {
|
|
2277
|
-
const configPath = CONTINUE_CONFIG_PATH;
|
|
2278
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2279
|
-
const existingRaw = (await readFileIfExists(configPath)) ?? "";
|
|
2280
|
-
const proxyUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
2281
|
-
const doc = (0, yaml_1.parseDocument)(existingRaw);
|
|
2282
|
-
const docRoot = objectRecordOr(doc.toJS(), {});
|
|
2283
|
-
const models = Array.isArray(docRoot.models)
|
|
2284
|
-
? docRoot.models.filter((entry) => !isManagedContinueModel(entry, proxyUrl))
|
|
2285
|
-
: [];
|
|
2286
|
-
models.unshift({
|
|
2287
|
-
name: CONTINUE_MODEL_NAME,
|
|
2288
|
-
provider: "openai",
|
|
2289
|
-
model: params.model.trim() || DEFAULT_CONTINUE_MODEL,
|
|
2290
|
-
apiKey: params.apiKey,
|
|
2291
|
-
apiBase: proxyUrl,
|
|
2292
|
-
roles: ["chat", "edit", "apply"],
|
|
2293
|
-
});
|
|
2294
|
-
if (typeof docRoot.name !== "string" || !docRoot.name.trim()) {
|
|
2295
|
-
doc.set("name", "Local Config");
|
|
2296
|
-
}
|
|
2297
|
-
if (typeof docRoot.version !== "string" || !docRoot.version.trim()) {
|
|
2298
|
-
doc.set("version", "1.0.0");
|
|
2299
|
-
}
|
|
2300
|
-
doc.set("models", models);
|
|
2301
|
-
await promises_1.default.writeFile(configPath, doc.toString(), "utf8");
|
|
2302
|
-
return configPath;
|
|
2303
|
-
}
|
|
2304
|
-
function isManagedClineState(doc) {
|
|
2305
|
-
return (doc.planModeApiProvider === "openai" &&
|
|
2306
|
-
doc.actModeApiProvider === "openai" &&
|
|
2307
|
-
isTheClawBayOpenAiCompatibleBaseUrl(doc.openAiBaseUrl));
|
|
2308
|
-
}
|
|
2309
|
-
async function writeClineConfig(params) {
|
|
2310
|
-
const proxyUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
2311
|
-
const globalState = await readJsonObjectFile(CLINE_GLOBAL_STATE_PATH);
|
|
2312
|
-
const secrets = await readJsonObjectFile(CLINE_SECRETS_PATH);
|
|
2313
|
-
const alreadyManaged = isManagedClineState(globalState) && typeof secrets.openAiApiKey === "string";
|
|
2314
|
-
if (!alreadyManaged) {
|
|
2315
|
-
const snapshot = {
|
|
2316
|
-
globalState: {
|
|
2317
|
-
planModeApiProvider: "planModeApiProvider" in globalState ? globalState.planModeApiProvider : null,
|
|
2318
|
-
actModeApiProvider: "actModeApiProvider" in globalState ? globalState.actModeApiProvider : null,
|
|
2319
|
-
openAiBaseUrl: "openAiBaseUrl" in globalState ? globalState.openAiBaseUrl : null,
|
|
2320
|
-
planModeOpenAiModelId: "planModeOpenAiModelId" in globalState ? globalState.planModeOpenAiModelId : null,
|
|
2321
|
-
actModeOpenAiModelId: "actModeOpenAiModelId" in globalState ? globalState.actModeOpenAiModelId : null,
|
|
2322
|
-
},
|
|
2323
|
-
secrets: {
|
|
2324
|
-
openAiApiKey: typeof secrets.openAiApiKey === "string" ? secrets.openAiApiKey : null,
|
|
2325
|
-
},
|
|
2326
|
-
};
|
|
2327
|
-
await writeJsonObjectFile(CLINE_RESTORE_STATE_PATH, snapshot, 0o600);
|
|
2328
|
-
}
|
|
2329
|
-
globalState.planModeApiProvider = "openai";
|
|
2330
|
-
globalState.actModeApiProvider = "openai";
|
|
2331
|
-
globalState.openAiBaseUrl = proxyUrl;
|
|
2332
|
-
globalState.planModeOpenAiModelId = params.model.trim() || DEFAULT_CLINE_MODEL;
|
|
2333
|
-
globalState.actModeOpenAiModelId = params.model.trim() || DEFAULT_CLINE_MODEL;
|
|
2334
|
-
secrets.openAiApiKey = params.apiKey;
|
|
2335
|
-
await writeJsonObjectFile(CLINE_GLOBAL_STATE_PATH, globalState);
|
|
2336
|
-
await writeJsonObjectFile(CLINE_SECRETS_PATH, secrets, 0o600);
|
|
2337
|
-
return [CLINE_GLOBAL_STATE_PATH, CLINE_SECRETS_PATH];
|
|
2338
|
-
}
|
|
2339
|
-
function buildGsdModels(models) {
|
|
2340
|
-
const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
|
|
2341
|
-
return models
|
|
2342
|
-
.filter((model) => Boolean(model.id))
|
|
2343
|
-
.map((model) => {
|
|
2344
|
-
const pricing = supportedModelMap.get(model.id);
|
|
2345
|
-
return {
|
|
2346
|
-
id: model.id,
|
|
2347
|
-
name: model.name || model.id,
|
|
2348
|
-
reasoning: true,
|
|
2349
|
-
input: ["text", "image"],
|
|
2350
|
-
cost: {
|
|
2351
|
-
input: pricing?.inputPer1M ?? 0,
|
|
2352
|
-
output: pricing?.outputPer1M ?? 0,
|
|
2353
|
-
cacheRead: pricing?.cachedInputPer1M ?? 0,
|
|
2354
|
-
cacheWrite: pricing?.inputPer1M ?? 0,
|
|
2355
|
-
},
|
|
2356
|
-
contextWindow: modelContextLimit(model.id),
|
|
2357
|
-
maxTokens: modelOutputLimit(model.id),
|
|
2358
|
-
};
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
function normalizeGsdCredential(value) {
|
|
2362
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2363
|
-
return null;
|
|
2364
|
-
return { ...value };
|
|
2365
|
-
}
|
|
2366
|
-
async function writeGsdConfig(params) {
|
|
2367
|
-
const existingModelsRaw = await readFileIfExists(GSD_MODELS_PATH);
|
|
2368
|
-
const existingAuthRaw = await readFileIfExists(GSD_AUTH_PATH);
|
|
2369
|
-
const existingSettingsRaw = await readFileIfExists(GSD_SETTINGS_PATH);
|
|
2370
|
-
const existingSnapshotRaw = await readFileIfExists(GSD_RESTORE_STATE_PATH);
|
|
2371
|
-
let modelsDoc = {};
|
|
2372
|
-
if (existingModelsRaw?.trim()) {
|
|
2373
|
-
try {
|
|
2374
|
-
modelsDoc = objectRecordOr(JSON.parse(existingModelsRaw), {});
|
|
2375
|
-
}
|
|
2376
|
-
catch {
|
|
2377
|
-
throw new Error(`invalid JSON in GSD config: ${GSD_MODELS_PATH}`);
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
let authDoc = {};
|
|
2381
|
-
if (existingAuthRaw?.trim()) {
|
|
2382
|
-
try {
|
|
2383
|
-
authDoc = objectRecordOr(JSON.parse(existingAuthRaw), {});
|
|
2384
|
-
}
|
|
2385
|
-
catch {
|
|
2386
|
-
throw new Error(`invalid JSON in GSD auth: ${GSD_AUTH_PATH}`);
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
let settingsDoc = {};
|
|
2390
|
-
if (existingSettingsRaw?.trim()) {
|
|
2391
|
-
try {
|
|
2392
|
-
settingsDoc = objectRecordOr(JSON.parse(existingSettingsRaw), {});
|
|
2393
|
-
}
|
|
2394
|
-
catch {
|
|
2395
|
-
throw new Error(`invalid JSON in GSD settings: ${GSD_SETTINGS_PATH}`);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
if (!existingSnapshotRaw?.trim()) {
|
|
2399
|
-
const providersRoot = objectRecordOr(modelsDoc.providers, {});
|
|
2400
|
-
const snapshot = {
|
|
2401
|
-
version: 1,
|
|
2402
|
-
modelsExisted: existingModelsRaw !== null,
|
|
2403
|
-
providerConfig: objectRecordOr(providersRoot[DEFAULT_PROVIDER_ID], {}),
|
|
2404
|
-
authExisted: existingAuthRaw !== null,
|
|
2405
|
-
authEntry: normalizeGsdCredential(authDoc[DEFAULT_PROVIDER_ID]),
|
|
2406
|
-
settingsExisted: existingSettingsRaw !== null,
|
|
2407
|
-
defaultProvider: typeof settingsDoc.defaultProvider === "string" ? settingsDoc.defaultProvider : null,
|
|
2408
|
-
defaultModel: typeof settingsDoc.defaultModel === "string" ? settingsDoc.defaultModel : null,
|
|
2409
|
-
};
|
|
2410
|
-
if (Object.keys(snapshot.providerConfig ?? {}).length === 0)
|
|
2411
|
-
snapshot.providerConfig = null;
|
|
2412
|
-
await writeJsonObjectFile(GSD_RESTORE_STATE_PATH, snapshot, 0o600);
|
|
2413
|
-
}
|
|
2414
|
-
const providersRoot = objectRecordOr(modelsDoc.providers, {});
|
|
2415
|
-
providersRoot[DEFAULT_PROVIDER_ID] = {
|
|
2416
|
-
baseUrl: openAiCompatibleProxyUrl(params.backendUrl),
|
|
2417
|
-
apiKey: ENV_KEY_NAME,
|
|
2418
|
-
api: "openai-responses",
|
|
2419
|
-
models: buildGsdModels(params.models),
|
|
2420
|
-
};
|
|
2421
|
-
modelsDoc.providers = providersRoot;
|
|
2422
|
-
authDoc[DEFAULT_PROVIDER_ID] = {
|
|
2423
|
-
type: "api_key",
|
|
2424
|
-
key: params.apiKey,
|
|
2425
|
-
};
|
|
2426
|
-
settingsDoc.defaultProvider = DEFAULT_PROVIDER_ID;
|
|
2427
|
-
settingsDoc.defaultModel = params.model.trim() || DEFAULT_CODEX_MODEL;
|
|
2428
|
-
await writeJsonObjectFile(GSD_MODELS_PATH, modelsDoc);
|
|
2429
|
-
await writeJsonObjectFile(GSD_AUTH_PATH, authDoc, 0o600);
|
|
2430
|
-
await writeJsonObjectFile(GSD_SETTINGS_PATH, settingsDoc);
|
|
2431
|
-
return [GSD_MODELS_PATH, GSD_AUTH_PATH, GSD_SETTINGS_PATH];
|
|
2432
|
-
}
|
|
2433
|
-
async function writeRooConfig(params) {
|
|
2434
|
-
const hosts = await detectExtensionHosts(["rooveterinaryinc.roo-cline-"]);
|
|
2435
|
-
if (hosts.length === 0) {
|
|
2436
|
-
throw new Error("Roo Code was not detected in a supported editor profile.");
|
|
2437
|
-
}
|
|
2438
|
-
const existingSnapshotRaw = await readFileIfExists(ROO_SETTINGS_STATE_PATH);
|
|
2439
|
-
let existingSnapshot = [];
|
|
2440
|
-
if (existingSnapshotRaw?.trim()) {
|
|
2441
|
-
try {
|
|
2442
|
-
existingSnapshot = JSON.parse(existingSnapshotRaw);
|
|
2443
|
-
}
|
|
2444
|
-
catch {
|
|
2445
|
-
existingSnapshot = [];
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
const snapshotByPath = new Map(existingSnapshot.map((entry) => [entry.settingsPath, entry]));
|
|
2449
|
-
const managedPaths = [];
|
|
2450
|
-
for (const host of hosts) {
|
|
2451
|
-
const existed = await pathExists(host.userSettingsPath);
|
|
2452
|
-
const settings = await readJsonObjectFile(host.userSettingsPath);
|
|
2453
|
-
const currentValue = typeof settings["roo-cline.autoImportSettingsPath"] === "string"
|
|
2454
|
-
? settings["roo-cline.autoImportSettingsPath"]
|
|
2455
|
-
: null;
|
|
2456
|
-
if (currentValue !== ROO_IMPORT_FILE) {
|
|
2457
|
-
snapshotByPath.set(host.userSettingsPath, {
|
|
2458
|
-
settingsPath: host.userSettingsPath,
|
|
2459
|
-
existed,
|
|
2460
|
-
previousValue: currentValue,
|
|
2461
|
-
});
|
|
2462
|
-
}
|
|
2463
|
-
settings["roo-cline.autoImportSettingsPath"] = ROO_IMPORT_FILE;
|
|
2464
|
-
await writeJsonObjectFile(host.userSettingsPath, settings);
|
|
2465
|
-
managedPaths.push(host.userSettingsPath);
|
|
2466
|
-
}
|
|
2467
|
-
const providerConfigId = ROO_PROFILE_ID;
|
|
2468
|
-
const rooImport = {
|
|
2469
|
-
providerProfiles: {
|
|
2470
|
-
currentApiConfigName: ROO_PROFILE_NAME,
|
|
2471
|
-
apiConfigs: {
|
|
2472
|
-
[ROO_PROFILE_NAME]: {
|
|
2473
|
-
id: providerConfigId,
|
|
2474
|
-
apiProvider: "openai",
|
|
2475
|
-
openAiBaseUrl: openAiCompatibleProxyUrl(params.backendUrl),
|
|
2476
|
-
openAiApiKey: params.apiKey,
|
|
2477
|
-
openAiModelId: params.model.trim() || DEFAULT_ROO_MODEL,
|
|
2478
|
-
},
|
|
2479
|
-
},
|
|
2480
|
-
modeApiConfigs: Object.fromEntries(ROO_MODE_SLUGS.map((slug) => [slug, providerConfigId])),
|
|
2481
|
-
},
|
|
2482
|
-
};
|
|
2483
|
-
await writeJsonObjectFile(ROO_IMPORT_FILE, rooImport, 0o600);
|
|
2484
|
-
await writeJsonObjectFile(ROO_SETTINGS_STATE_PATH, Array.from(snapshotByPath.values()), 0o600);
|
|
2485
|
-
return [ROO_IMPORT_FILE, ...managedPaths];
|
|
2486
|
-
}
|
|
2487
|
-
async function writeAiderConfig(params) {
|
|
2488
|
-
const existing = (await readFileIfExists(AIDER_CONFIG_PATH)) ?? "";
|
|
2489
|
-
const cleaned = removeManagedBlock(existing, AIDER_MANAGED_START, AIDER_MANAGED_END);
|
|
2490
|
-
const next = appendManagedBlock(cleaned, [
|
|
2491
|
-
AIDER_MANAGED_START,
|
|
2492
|
-
`model: ${JSON.stringify(`openai/${params.model.trim() || DEFAULT_AIDER_MODEL}`)}`,
|
|
2493
|
-
`openai-api-key: ${JSON.stringify(params.apiKey)}`,
|
|
2494
|
-
`openai-api-base: ${JSON.stringify(openAiCompatibleProxyUrl(params.backendUrl))}`,
|
|
2495
|
-
"restore-chat-history: true",
|
|
2496
|
-
AIDER_MANAGED_END,
|
|
2497
|
-
]);
|
|
2498
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(AIDER_CONFIG_PATH), { recursive: true });
|
|
2499
|
-
await promises_1.default.writeFile(AIDER_CONFIG_PATH, next, "utf8");
|
|
2500
|
-
return AIDER_CONFIG_PATH;
|
|
2501
|
-
}
|
|
2502
|
-
async function persistApiKeyEnv(params) {
|
|
2503
|
-
const envDir = node_path_1.default.dirname(ENV_FILE);
|
|
2504
|
-
await promises_1.default.mkdir(envDir, { recursive: true });
|
|
2505
|
-
const envLines = [
|
|
2506
|
-
"# Generated by theclawbay setup",
|
|
2507
|
-
`export ${ENV_KEY_NAME}=${shellQuote(params.apiKey)}`,
|
|
2508
|
-
"",
|
|
2509
|
-
"# Gemini-compatible applications",
|
|
2510
|
-
`export ${GEMINI_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`,
|
|
2511
|
-
`export ${GOOGLE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`,
|
|
2512
|
-
`export ${GOOGLE_GEMINI_BASE_URL_NAME}=${shellQuote(geminiCompatibleProxyBaseUrl(params.backendUrl))}`,
|
|
2513
|
-
];
|
|
2514
|
-
if (params.claudeEnabled) {
|
|
2515
|
-
envLines.push("", "# Official Claude Code CLI", `export ${CLAUDE_ENV_API_KEY_NAME}=${shellQuote(params.apiKey)}`, `export ${CLAUDE_ENV_BASE_URL_NAME}=${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
|
|
2516
|
-
}
|
|
2517
|
-
const envContents = `${envLines.join("\n")}\n`;
|
|
2518
|
-
await promises_1.default.writeFile(ENV_FILE, envContents, "utf8");
|
|
2519
|
-
await promises_1.default.chmod(ENV_FILE, 0o600);
|
|
2520
|
-
const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
|
|
2521
|
-
const shellRcPaths = [
|
|
2522
|
-
".bashrc",
|
|
2523
|
-
".bash_profile",
|
|
2524
|
-
".bash_login",
|
|
2525
|
-
".zshrc",
|
|
2526
|
-
".zprofile",
|
|
2527
|
-
".zlogin",
|
|
2528
|
-
".profile",
|
|
2529
|
-
].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
|
|
2530
|
-
const updated = [];
|
|
2531
|
-
for (const rcPath of shellRcPaths) {
|
|
2532
|
-
let existing = "";
|
|
2533
|
-
try {
|
|
2534
|
-
existing = await promises_1.default.readFile(rcPath, "utf8");
|
|
2535
|
-
}
|
|
2536
|
-
catch (error) {
|
|
2537
|
-
const err = error;
|
|
2538
|
-
if (err.code !== "ENOENT")
|
|
2539
|
-
throw error;
|
|
2540
|
-
}
|
|
2541
|
-
const cleaned = removeManagedBlock(existing, SHELL_START, SHELL_END);
|
|
2542
|
-
const withBlock = appendManagedBlock(cleaned, [SHELL_START, sourceLine, SHELL_END]);
|
|
2543
|
-
await promises_1.default.writeFile(rcPath, withBlock, "utf8");
|
|
2544
|
-
updated.push(rcPath);
|
|
2545
|
-
}
|
|
2546
|
-
process.env[ENV_KEY_NAME] = params.apiKey;
|
|
2547
|
-
process.env[GEMINI_ENV_API_KEY_NAME] = params.apiKey;
|
|
2548
|
-
process.env[GOOGLE_ENV_API_KEY_NAME] = params.apiKey;
|
|
2549
|
-
process.env[GOOGLE_GEMINI_BASE_URL_NAME] = geminiCompatibleProxyBaseUrl(params.backendUrl);
|
|
2550
|
-
if (params.claudeEnabled) {
|
|
2551
|
-
process.env[CLAUDE_ENV_API_KEY_NAME] = params.apiKey;
|
|
2552
|
-
process.env[CLAUDE_ENV_BASE_URL_NAME] = anthropicCompatibleProxyUrl(params.backendUrl);
|
|
2553
|
-
}
|
|
2554
|
-
else {
|
|
2555
|
-
delete process.env[CLAUDE_ENV_API_KEY_NAME];
|
|
2556
|
-
delete process.env[CLAUDE_ENV_BASE_URL_NAME];
|
|
2557
|
-
}
|
|
2558
|
-
return updated;
|
|
2559
|
-
}
|
|
2560
|
-
async function persistFishEnv(params) {
|
|
2561
|
-
const fishConfPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "fish", "conf.d", "theclawbay.fish");
|
|
2562
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(fishConfPath), { recursive: true });
|
|
2563
|
-
const lines = [
|
|
2564
|
-
"# Generated by theclawbay setup",
|
|
2565
|
-
`set -gx ${ENV_KEY_NAME} ${shellQuote(params.apiKey)}`,
|
|
2566
|
-
"",
|
|
2567
|
-
"# Gemini-compatible applications",
|
|
2568
|
-
`set -gx ${GEMINI_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`,
|
|
2569
|
-
`set -gx ${GOOGLE_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`,
|
|
2570
|
-
`set -gx ${GOOGLE_GEMINI_BASE_URL_NAME} ${shellQuote(geminiCompatibleProxyBaseUrl(params.backendUrl))}`,
|
|
2571
|
-
];
|
|
2572
|
-
if (params.claudeEnabled) {
|
|
2573
|
-
lines.push("", "# Official Claude Code CLI", `set -gx ${CLAUDE_ENV_API_KEY_NAME} ${shellQuote(params.apiKey)}`, `set -gx ${CLAUDE_ENV_BASE_URL_NAME} ${shellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
|
|
2574
|
-
}
|
|
2575
|
-
await promises_1.default.writeFile(fishConfPath, `${lines.join("\n")}\n`, "utf8");
|
|
2576
|
-
return [fishConfPath];
|
|
2577
|
-
}
|
|
2578
|
-
function powerShellProfilePaths() {
|
|
2579
|
-
const candidates = new Set();
|
|
2580
|
-
const addDocumentsProfiles = (documentsDir) => {
|
|
2581
|
-
if (!documentsDir)
|
|
2582
|
-
return;
|
|
2583
|
-
candidates.add(node_path_1.default.join(documentsDir, "PowerShell", "profile.ps1"));
|
|
2584
|
-
candidates.add(node_path_1.default.join(documentsDir, "PowerShell", "Microsoft.PowerShell_profile.ps1"));
|
|
2585
|
-
candidates.add(node_path_1.default.join(documentsDir, "WindowsPowerShell", "profile.ps1"));
|
|
2586
|
-
candidates.add(node_path_1.default.join(documentsDir, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1"));
|
|
2587
|
-
};
|
|
2588
|
-
if (node_os_1.default.platform() === "win32") {
|
|
2589
|
-
addDocumentsProfiles(node_path_1.default.join(node_os_1.default.homedir(), "Documents"));
|
|
2590
|
-
addDocumentsProfiles(resolveWindowsDocumentsDirForHost());
|
|
2591
|
-
return [...candidates];
|
|
2592
|
-
}
|
|
2593
|
-
if (!isWslInteropRuntime())
|
|
2594
|
-
return [];
|
|
2595
|
-
if (!shouldManageWindowsHostProfilesFromWsl())
|
|
2596
|
-
return [];
|
|
2597
|
-
addDocumentsProfiles(resolveWindowsDocumentsDirForHost());
|
|
2598
|
-
addDocumentsProfiles(resolveWindowsPathForHost(node_path_1.default.join(resolveWindowsHomeDirForHost() ?? "", "Documents")));
|
|
2599
|
-
return [...candidates];
|
|
2600
|
-
}
|
|
2601
|
-
function isWslInteropRuntime() {
|
|
2602
|
-
return node_os_1.default.platform() === "linux" && Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
|
|
2603
|
-
}
|
|
2604
|
-
function shouldManageWindowsHostProfilesFromWsl() {
|
|
2605
|
-
const mode = (process.env.THECLAWBAY_WINDOWS_PROFILE_MODE ?? "").trim().toLowerCase();
|
|
2606
|
-
if (mode === "always")
|
|
2607
|
-
return true;
|
|
2608
|
-
if (mode === "never")
|
|
2609
|
-
return false;
|
|
2610
|
-
const home = process.env.HOME?.trim() ?? "";
|
|
2611
|
-
const user = process.env.USER?.trim() ?? "";
|
|
2612
|
-
if (!home || !user)
|
|
2613
|
-
return true;
|
|
2614
|
-
return home === node_path_1.default.posix.join("/home", user);
|
|
2615
|
-
}
|
|
2616
|
-
function readWindowsCommandStdout(command) {
|
|
2617
|
-
const shell = node_os_1.default.platform() === "win32" ? "powershell.exe" : isWslInteropRuntime() ? "powershell.exe" : null;
|
|
2618
|
-
if (!shell)
|
|
2619
|
-
return null;
|
|
2620
|
-
const result = (0, node_child_process_1.spawnSync)(shell, ["-NoProfile", "-Command", command], {
|
|
2621
|
-
encoding: "utf8",
|
|
2622
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
2623
|
-
});
|
|
2624
|
-
if (result.status !== 0)
|
|
2625
|
-
return null;
|
|
2626
|
-
const next = result.stdout.replace(/\r/g, "").trim();
|
|
2627
|
-
return next ? next : null;
|
|
2628
|
-
}
|
|
2629
|
-
function resolveWindowsPathForHost(input) {
|
|
2630
|
-
if (!input)
|
|
2631
|
-
return null;
|
|
2632
|
-
const trimmed = input.trim();
|
|
2633
|
-
if (!trimmed)
|
|
2634
|
-
return null;
|
|
2635
|
-
if (node_os_1.default.platform() === "win32")
|
|
2636
|
-
return trimmed;
|
|
2637
|
-
const match = /^([A-Za-z]):\\(.*)$/.exec(trimmed);
|
|
2638
|
-
if (!match)
|
|
2639
|
-
return null;
|
|
2640
|
-
const drive = match[1].toLowerCase();
|
|
2641
|
-
const rest = match[2].replace(/\\/g, "/");
|
|
2642
|
-
return node_path_1.default.posix.join("/mnt", drive, rest);
|
|
2643
|
-
}
|
|
2644
|
-
function resolveWindowsHomeDirForHost() {
|
|
2645
|
-
const reported = readWindowsCommandStdout("$env:USERPROFILE");
|
|
2646
|
-
if (reported)
|
|
2647
|
-
return resolveWindowsPathForHost(reported);
|
|
2648
|
-
if (node_os_1.default.platform() === "win32")
|
|
2649
|
-
return node_os_1.default.homedir();
|
|
2650
|
-
return null;
|
|
2651
|
-
}
|
|
2652
|
-
function resolveWindowsDocumentsDirForHost() {
|
|
2653
|
-
const reported = readWindowsCommandStdout("[Environment]::GetFolderPath('MyDocuments')");
|
|
2654
|
-
if (reported)
|
|
2655
|
-
return resolveWindowsPathForHost(reported);
|
|
2656
|
-
const home = resolveWindowsHomeDirForHost();
|
|
2657
|
-
if (!home)
|
|
2658
|
-
return null;
|
|
2659
|
-
return node_path_1.default.join(home, "Documents");
|
|
2660
|
-
}
|
|
2661
|
-
async function persistPowerShellEnv(params) {
|
|
2662
|
-
const updated = [];
|
|
2663
|
-
for (const profilePath of powerShellProfilePaths()) {
|
|
2664
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(profilePath), { recursive: true });
|
|
2665
|
-
const existing = (await readFileIfExists(profilePath)) ?? "";
|
|
2666
|
-
const cleaned = removeManagedBlock(existing, SHELL_START, SHELL_END);
|
|
2667
|
-
const lines = [
|
|
2668
|
-
SHELL_START,
|
|
2669
|
-
`$env:${ENV_KEY_NAME} = ${powerShellQuote(params.apiKey)}`,
|
|
2670
|
-
`$env:${GEMINI_ENV_API_KEY_NAME} = ${powerShellQuote(params.apiKey)}`,
|
|
2671
|
-
`$env:${GOOGLE_ENV_API_KEY_NAME} = ${powerShellQuote(params.apiKey)}`,
|
|
2672
|
-
`$env:${GOOGLE_GEMINI_BASE_URL_NAME} = ${powerShellQuote(geminiCompatibleProxyBaseUrl(params.backendUrl))}`,
|
|
2673
|
-
];
|
|
2674
|
-
if (params.claudeEnabled) {
|
|
2675
|
-
lines.push(`$env:${CLAUDE_ENV_API_KEY_NAME} = ${powerShellQuote(params.apiKey)}`, `$env:${CLAUDE_ENV_BASE_URL_NAME} = ${powerShellQuote(anthropicCompatibleProxyUrl(params.backendUrl))}`);
|
|
2676
|
-
}
|
|
2677
|
-
lines.push(SHELL_END);
|
|
2678
|
-
const next = appendManagedBlock(cleaned, lines);
|
|
2679
|
-
await promises_1.default.writeFile(profilePath, next, "utf8");
|
|
2680
|
-
updated.push(profilePath);
|
|
2681
|
-
}
|
|
2682
|
-
return updated;
|
|
2683
|
-
}
|
|
2684
|
-
async function persistVsCodeServerEnvSource() {
|
|
2685
|
-
const homes = [".vscode-server", ".vscode-server-insiders"];
|
|
2686
|
-
const sourceLine = `[ -f "$HOME/.config/theclawbay/env" ] && . "$HOME/.config/theclawbay/env"`;
|
|
2687
|
-
const updated = [];
|
|
2688
|
-
for (const home of homes) {
|
|
2689
|
-
const setupPath = node_path_1.default.join(node_os_1.default.homedir(), home, "server-env-setup");
|
|
2690
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(setupPath), { recursive: true });
|
|
2691
|
-
let existing = "";
|
|
2692
|
-
try {
|
|
2693
|
-
existing = await promises_1.default.readFile(setupPath, "utf8");
|
|
2694
|
-
}
|
|
2695
|
-
catch (error) {
|
|
2696
|
-
const err = error;
|
|
2697
|
-
if (err.code !== "ENOENT")
|
|
2698
|
-
throw error;
|
|
2699
|
-
}
|
|
2700
|
-
let next = existing.trimEnd();
|
|
2701
|
-
if (!next.startsWith("#!")) {
|
|
2702
|
-
next = `#!/usr/bin/env sh\n${next}`.trimEnd();
|
|
2703
|
-
}
|
|
2704
|
-
next = `${removeManagedBlock(next + "\n", SHELL_START, SHELL_END).trimEnd()}\n`;
|
|
2705
|
-
next = appendManagedBlock(next, [SHELL_START, sourceLine, SHELL_END]);
|
|
2706
|
-
await promises_1.default.writeFile(setupPath, next, "utf8");
|
|
2707
|
-
await promises_1.default.chmod(setupPath, 0o700);
|
|
2708
|
-
updated.push(setupPath);
|
|
2709
|
-
}
|
|
2710
|
-
return updated;
|
|
2711
|
-
}
|
|
2712
|
-
function terminalIntegratedEnvSettingsKey() {
|
|
2713
|
-
if (node_os_1.default.platform() === "darwin")
|
|
2714
|
-
return "terminal.integrated.env.osx";
|
|
2715
|
-
if (node_os_1.default.platform() === "win32")
|
|
2716
|
-
return "terminal.integrated.env.windows";
|
|
2717
|
-
return "terminal.integrated.env.linux";
|
|
2718
|
-
}
|
|
2719
|
-
function buildManagedEditorTerminalEnv(params) {
|
|
2720
|
-
return {
|
|
2721
|
-
[ENV_KEY_NAME]: params.apiKey,
|
|
2722
|
-
[GEMINI_ENV_API_KEY_NAME]: params.apiKey,
|
|
2723
|
-
[GOOGLE_ENV_API_KEY_NAME]: params.apiKey,
|
|
2724
|
-
[GOOGLE_GEMINI_BASE_URL_NAME]: geminiCompatibleProxyBaseUrl(params.backendUrl),
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2727
|
-
function buildManagedClaudeEditorEnv(params) {
|
|
2728
|
-
return {
|
|
2729
|
-
[CLAUDE_ENV_API_KEY_NAME]: params.apiKey,
|
|
2730
|
-
[CLAUDE_ENV_BASE_URL_NAME]: anthropicCompatibleProxyUrl(params.backendUrl),
|
|
2731
|
-
};
|
|
2732
|
-
}
|
|
2733
|
-
async function persistEditorTerminalEnvSettings(params) {
|
|
2734
|
-
const settingsKey = terminalIntegratedEnvSettingsKey();
|
|
2735
|
-
const desiredEnv = buildManagedEditorTerminalEnv(params);
|
|
2736
|
-
const existingSnapshotRaw = await readFileIfExists(EDITOR_TERMINAL_ENV_STATE_PATH);
|
|
2737
|
-
let existingSnapshot = [];
|
|
2738
|
-
if (existingSnapshotRaw?.trim()) {
|
|
2739
|
-
try {
|
|
2740
|
-
existingSnapshot = JSON.parse(existingSnapshotRaw);
|
|
2741
|
-
}
|
|
2742
|
-
catch {
|
|
2743
|
-
existingSnapshot = [];
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
const snapshotByPath = new Map(existingSnapshot.map((entry) => [entry.settingsPath, entry]));
|
|
2747
|
-
const managedPaths = [];
|
|
2748
|
-
for (const host of editorHosts()) {
|
|
2749
|
-
const settingsPath = host.userSettingsPath;
|
|
2750
|
-
const hostPresent = (await pathExists(host.extensionDir)) ||
|
|
2751
|
-
(await pathExists(settingsPath)) ||
|
|
2752
|
-
(await pathExists(node_path_1.default.dirname(settingsPath)));
|
|
2753
|
-
if (!hostPresent)
|
|
2754
|
-
continue;
|
|
2755
|
-
const existed = await pathExists(settingsPath);
|
|
2756
|
-
const settings = await readJsonObjectFile(settingsPath);
|
|
2757
|
-
const currentValue = settings[settingsKey];
|
|
2758
|
-
const currentEnv = typeof currentValue === "object" && currentValue !== null && !Array.isArray(currentValue)
|
|
2759
|
-
? { ...currentValue }
|
|
2760
|
-
: {};
|
|
2761
|
-
let changed = false;
|
|
2762
|
-
for (const [name, value] of Object.entries(desiredEnv)) {
|
|
2763
|
-
if (currentEnv[name] === value)
|
|
2764
|
-
continue;
|
|
2765
|
-
currentEnv[name] = value;
|
|
2766
|
-
changed = true;
|
|
2767
|
-
}
|
|
2768
|
-
for (const name of MANAGED_EDITOR_TERMINAL_ENV_NAMES) {
|
|
2769
|
-
if (name in desiredEnv || !(name in currentEnv))
|
|
2770
|
-
continue;
|
|
2771
|
-
delete currentEnv[name];
|
|
2772
|
-
changed = true;
|
|
2773
|
-
}
|
|
2774
|
-
if (!changed)
|
|
2775
|
-
continue;
|
|
2776
|
-
if (!snapshotByPath.has(settingsPath)) {
|
|
2777
|
-
snapshotByPath.set(settingsPath, {
|
|
2778
|
-
settingsPath,
|
|
2779
|
-
existed,
|
|
2780
|
-
hadKey: Object.prototype.hasOwnProperty.call(settings, settingsKey),
|
|
2781
|
-
previousValue: currentValue ?? null,
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
settings[settingsKey] = currentEnv;
|
|
2785
|
-
await writeJsonObjectFile(settingsPath, settings);
|
|
2786
|
-
managedPaths.push(settingsPath);
|
|
2787
|
-
}
|
|
2788
|
-
await writeJsonObjectFile(EDITOR_TERMINAL_ENV_STATE_PATH, Array.from(snapshotByPath.values()), 0o600);
|
|
2789
|
-
return managedPaths;
|
|
2790
|
-
}
|
|
2791
|
-
async function persistClaudeEditorSettings(params) {
|
|
2792
|
-
const existingSnapshotRaw = await readFileIfExists(CLAUDE_EDITOR_SETTINGS_STATE_PATH);
|
|
2793
|
-
let existingSnapshot = [];
|
|
2794
|
-
if (existingSnapshotRaw?.trim()) {
|
|
2795
|
-
try {
|
|
2796
|
-
existingSnapshot = JSON.parse(existingSnapshotRaw);
|
|
2797
|
-
}
|
|
2798
|
-
catch {
|
|
2799
|
-
existingSnapshot = [];
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
const snapshotByPath = new Map(existingSnapshot.map((entry) => [entry.settingsPath, entry]));
|
|
2803
|
-
const managedPaths = [];
|
|
2804
|
-
const desiredEnv = params.claudeEnabled
|
|
2805
|
-
? buildManagedClaudeEditorEnv({ apiKey: params.apiKey, backendUrl: params.backendUrl })
|
|
2806
|
-
: null;
|
|
2807
|
-
for (const host of editorHosts()) {
|
|
2808
|
-
const settingsPath = host.userSettingsPath;
|
|
2809
|
-
const hostPresent = (await pathExists(host.extensionDir)) ||
|
|
2810
|
-
(await pathExists(settingsPath)) ||
|
|
2811
|
-
(await pathExists(node_path_1.default.dirname(settingsPath)));
|
|
2812
|
-
if (!hostPresent)
|
|
2813
|
-
continue;
|
|
2814
|
-
const existed = await pathExists(settingsPath);
|
|
2815
|
-
const settings = await readJsonObjectFile(settingsPath);
|
|
2816
|
-
const currentEnvValue = settings[CLAUDE_CODE_EDITOR_ENV_SETTING];
|
|
2817
|
-
const currentEnv = typeof currentEnvValue === "object" && currentEnvValue !== null && !Array.isArray(currentEnvValue)
|
|
2818
|
-
? { ...currentEnvValue }
|
|
2819
|
-
: {};
|
|
2820
|
-
let changed = false;
|
|
2821
|
-
if (!snapshotByPath.has(settingsPath)) {
|
|
2822
|
-
snapshotByPath.set(settingsPath, {
|
|
2823
|
-
settingsPath,
|
|
2824
|
-
existed,
|
|
2825
|
-
envHadKey: Object.prototype.hasOwnProperty.call(settings, CLAUDE_CODE_EDITOR_ENV_SETTING),
|
|
2826
|
-
envPreviousValue: currentEnvValue ?? null,
|
|
2827
|
-
disableLoginPromptHadKey: Object.prototype.hasOwnProperty.call(settings, CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING),
|
|
2828
|
-
disableLoginPromptPreviousValue: settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING] ?? null,
|
|
2829
|
-
});
|
|
2830
|
-
}
|
|
2831
|
-
if (desiredEnv) {
|
|
2832
|
-
const nextEnv = { ...currentEnv };
|
|
2833
|
-
for (const [name, value] of Object.entries(desiredEnv)) {
|
|
2834
|
-
if (nextEnv[name] === value)
|
|
2835
|
-
continue;
|
|
2836
|
-
nextEnv[name] = value;
|
|
2837
|
-
changed = true;
|
|
2838
|
-
}
|
|
2839
|
-
for (const name of MANAGED_CLAUDE_ENV_NAMES) {
|
|
2840
|
-
if (name in desiredEnv || !(name in nextEnv))
|
|
2841
|
-
continue;
|
|
2842
|
-
delete nextEnv[name];
|
|
2843
|
-
changed = true;
|
|
2844
|
-
}
|
|
2845
|
-
settings[CLAUDE_CODE_EDITOR_ENV_SETTING] = nextEnv;
|
|
2846
|
-
if (settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING] !== true) {
|
|
2847
|
-
settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING] = true;
|
|
2848
|
-
changed = true;
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
else {
|
|
2852
|
-
if (Object.prototype.hasOwnProperty.call(settings, CLAUDE_CODE_EDITOR_ENV_SETTING)) {
|
|
2853
|
-
delete settings[CLAUDE_CODE_EDITOR_ENV_SETTING];
|
|
2854
|
-
changed = true;
|
|
2855
|
-
}
|
|
2856
|
-
if (Object.prototype.hasOwnProperty.call(settings, CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING)) {
|
|
2857
|
-
delete settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING];
|
|
2858
|
-
changed = true;
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
if (!changed)
|
|
2862
|
-
continue;
|
|
2863
|
-
await writeJsonObjectFile(settingsPath, settings);
|
|
2864
|
-
managedPaths.push(settingsPath);
|
|
2865
|
-
}
|
|
2866
|
-
await writeJsonObjectFile(CLAUDE_EDITOR_SETTINGS_STATE_PATH, Array.from(snapshotByPath.values()), 0o600);
|
|
2867
|
-
return managedPaths;
|
|
2868
|
-
}
|
|
2869
|
-
async function cleanupClaudeEditorSettings() {
|
|
2870
|
-
const snapshotRaw = await readFileIfExists(CLAUDE_EDITOR_SETTINGS_STATE_PATH);
|
|
2871
|
-
if (snapshotRaw === null || !snapshotRaw.trim())
|
|
2872
|
-
return [];
|
|
2873
|
-
let entries = [];
|
|
2874
|
-
try {
|
|
2875
|
-
entries = JSON.parse(snapshotRaw);
|
|
2876
|
-
}
|
|
2877
|
-
catch {
|
|
2878
|
-
return [];
|
|
2879
|
-
}
|
|
2880
|
-
const updated = [];
|
|
2881
|
-
for (const entry of entries) {
|
|
2882
|
-
const settings = await readJsonObjectFile(entry.settingsPath);
|
|
2883
|
-
if (!entry.envHadKey) {
|
|
2884
|
-
delete settings[CLAUDE_CODE_EDITOR_ENV_SETTING];
|
|
2885
|
-
}
|
|
2886
|
-
else {
|
|
2887
|
-
settings[CLAUDE_CODE_EDITOR_ENV_SETTING] = entry.envPreviousValue;
|
|
2888
|
-
}
|
|
2889
|
-
if (!entry.disableLoginPromptHadKey) {
|
|
2890
|
-
delete settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING];
|
|
2891
|
-
}
|
|
2892
|
-
else {
|
|
2893
|
-
settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING] = entry.disableLoginPromptPreviousValue;
|
|
2894
|
-
}
|
|
2895
|
-
if (entry.existed || Object.keys(settings).length > 0) {
|
|
2896
|
-
await writeJsonObjectFile(entry.settingsPath, settings);
|
|
2897
|
-
updated.push(entry.settingsPath);
|
|
2898
|
-
continue;
|
|
2899
|
-
}
|
|
2900
|
-
if (await removeFileIfExists(entry.settingsPath)) {
|
|
2901
|
-
updated.push(entry.settingsPath);
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
await removeFileIfExists(CLAUDE_EDITOR_SETTINGS_STATE_PATH);
|
|
2905
|
-
return updated;
|
|
2906
|
-
}
|
|
2907
|
-
async function cleanupClaudeDesktop3pConfig() {
|
|
2908
|
-
const snapshotRaw = await readFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
2909
|
-
if (snapshotRaw === null || !snapshotRaw.trim())
|
|
2910
|
-
return false;
|
|
2911
|
-
let snapshot = null;
|
|
2912
|
-
try {
|
|
2913
|
-
snapshot = JSON.parse(snapshotRaw);
|
|
2914
|
-
}
|
|
2915
|
-
catch {
|
|
2916
|
-
snapshot = null;
|
|
2917
|
-
}
|
|
2918
|
-
if (!snapshot?.configPath) {
|
|
2919
|
-
await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
2920
|
-
return false;
|
|
2921
|
-
}
|
|
2922
|
-
if (!snapshot.existed) {
|
|
2923
|
-
const removed = await removeFileIfExists(snapshot.configPath);
|
|
2924
|
-
await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
2925
|
-
return removed;
|
|
2926
|
-
}
|
|
2927
|
-
const previousRaw = snapshot.previousRaw ?? "";
|
|
2928
|
-
const existingRaw = await readFileIfExists(snapshot.configPath);
|
|
2929
|
-
if (existingRaw === previousRaw) {
|
|
2930
|
-
await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
2931
|
-
return false;
|
|
2932
|
-
}
|
|
2933
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(snapshot.configPath), { recursive: true });
|
|
2934
|
-
await promises_1.default.writeFile(snapshot.configPath, previousRaw, "utf8");
|
|
2935
|
-
await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
2936
|
-
return true;
|
|
2937
|
-
}
|
|
2938
|
-
async function cleanupLegacyClaudeEditorTerminalEnvSettings(params) {
|
|
2939
|
-
const snapshotRaw = await readFileIfExists(EDITOR_TERMINAL_ENV_STATE_PATH);
|
|
2940
|
-
if (snapshotRaw === null || !snapshotRaw.trim())
|
|
2941
|
-
return [];
|
|
2942
|
-
let entries = [];
|
|
2943
|
-
try {
|
|
2944
|
-
entries = JSON.parse(snapshotRaw);
|
|
2945
|
-
}
|
|
2946
|
-
catch {
|
|
2947
|
-
return [];
|
|
2948
|
-
}
|
|
2949
|
-
const settingsKey = terminalIntegratedEnvSettingsKey();
|
|
2950
|
-
const managedClaudeEnv = {
|
|
2951
|
-
[CLAUDE_ENV_API_KEY_NAME]: params.apiKey,
|
|
2952
|
-
[CLAUDE_ENV_BASE_URL_NAME]: anthropicCompatibleProxyUrl(params.backendUrl),
|
|
2953
|
-
};
|
|
2954
|
-
const updated = [];
|
|
2955
|
-
for (const entry of entries) {
|
|
2956
|
-
const settings = await readJsonObjectFile(entry.settingsPath);
|
|
2957
|
-
const currentValue = settings[settingsKey];
|
|
2958
|
-
if (typeof currentValue !== "object" || currentValue === null || Array.isArray(currentValue))
|
|
2959
|
-
continue;
|
|
2960
|
-
const currentEnv = { ...currentValue };
|
|
2961
|
-
const previousEnv = typeof entry.previousValue === "object" &&
|
|
2962
|
-
entry.previousValue !== null &&
|
|
2963
|
-
!Array.isArray(entry.previousValue)
|
|
2964
|
-
? { ...entry.previousValue }
|
|
2965
|
-
: {};
|
|
2966
|
-
let changed = false;
|
|
2967
|
-
for (const name of MANAGED_CLAUDE_ENV_NAMES) {
|
|
2968
|
-
const expectedManagedValue = managedClaudeEnv[name];
|
|
2969
|
-
const currentManagedValue = currentEnv[name];
|
|
2970
|
-
const currentMatchesManaged = typeof expectedManagedValue === "string" ? currentManagedValue === expectedManagedValue : false;
|
|
2971
|
-
if (!currentMatchesManaged)
|
|
2972
|
-
continue;
|
|
2973
|
-
if (Object.prototype.hasOwnProperty.call(previousEnv, name)) {
|
|
2974
|
-
currentEnv[name] = previousEnv[name];
|
|
2975
|
-
}
|
|
2976
|
-
else {
|
|
2977
|
-
delete currentEnv[name];
|
|
2978
|
-
}
|
|
2979
|
-
changed = true;
|
|
2980
|
-
}
|
|
2981
|
-
if (!changed)
|
|
2982
|
-
continue;
|
|
2983
|
-
settings[settingsKey] = currentEnv;
|
|
2984
|
-
await writeJsonObjectFile(entry.settingsPath, settings);
|
|
2985
|
-
updated.push(entry.settingsPath);
|
|
2986
|
-
}
|
|
2987
|
-
return updated;
|
|
2988
|
-
}
|
|
2989
|
-
function expandClaudeDesktopInferenceModels(modelIds) {
|
|
2990
|
-
const expanded = [];
|
|
2991
|
-
const add = (value) => {
|
|
2992
|
-
const normalized = value.trim();
|
|
2993
|
-
if (!normalized || expanded.includes(normalized))
|
|
2994
|
-
return;
|
|
2995
|
-
expanded.push(normalized);
|
|
2996
|
-
};
|
|
2997
|
-
for (const modelId of modelIds) {
|
|
2998
|
-
add(modelId);
|
|
2999
|
-
const datedMatch = modelId.match(/^(claude-[a-z]+-\d+(?:-\d+)?)-(\d{8})$/);
|
|
3000
|
-
if (datedMatch?.[1])
|
|
3001
|
-
add(datedMatch[1]);
|
|
3002
|
-
const base = datedMatch?.[1] ?? modelId;
|
|
3003
|
-
const dottedMatch = base.match(/^(claude-[a-z]+-\d+)-(\d+)$/);
|
|
3004
|
-
if (dottedMatch?.[1] && dottedMatch?.[2]) {
|
|
3005
|
-
add(`${dottedMatch[1]}.${dottedMatch[2]}`);
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
return expanded;
|
|
3009
|
-
}
|
|
3010
|
-
async function discoverClaudeDesktopDeploymentOrganizationUuid(existing) {
|
|
3011
|
-
const enterpriseConfig = objectRecordOr(existing.enterpriseConfig, {});
|
|
3012
|
-
const existingValue = enterpriseConfig.deploymentOrganizationUuid;
|
|
3013
|
-
if (typeof existingValue === "string" && existingValue.trim()) {
|
|
3014
|
-
return existingValue.trim();
|
|
3015
|
-
}
|
|
3016
|
-
const uuidPattern = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/i;
|
|
3017
|
-
const logPattern = /(deploymentOrganizationUuid|organizationUuid|organization uuid|org uuid)/i;
|
|
3018
|
-
for (const logPath of claudeDesktopLogCandidates()) {
|
|
3019
|
-
const logRaw = await readFileIfExists(logPath);
|
|
3020
|
-
if (!logRaw)
|
|
3021
|
-
continue;
|
|
3022
|
-
const tail = logRaw.slice(-200000);
|
|
3023
|
-
for (const line of tail.split(/\r?\n/).reverse()) {
|
|
3024
|
-
if (!logPattern.test(line))
|
|
3025
|
-
continue;
|
|
3026
|
-
const match = line.match(uuidPattern);
|
|
3027
|
-
if (match?.[0])
|
|
3028
|
-
return match[0];
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
return null;
|
|
3032
|
-
}
|
|
3033
|
-
async function writeClaudeDesktop3pConfig(params) {
|
|
3034
|
-
const configPath = claudeDesktop3pConfigPath();
|
|
3035
|
-
if (!configPath || params.claudeModels.length === 0)
|
|
3036
|
-
return null;
|
|
3037
|
-
const desktopPresent = claudeDesktopAppCandidatePaths().some((candidate) => (0, node_fs_1.existsSync)(candidate)) ||
|
|
3038
|
-
(await pathExists(configPath)) ||
|
|
3039
|
-
(await pathExists(node_path_1.default.dirname(configPath))) ||
|
|
3040
|
-
claudeDesktopLogCandidates().some((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
3041
|
-
if (!desktopPresent)
|
|
3042
|
-
return null;
|
|
3043
|
-
let existed = true;
|
|
3044
|
-
let existingRaw = "";
|
|
3045
|
-
try {
|
|
3046
|
-
existingRaw = await promises_1.default.readFile(configPath, "utf8");
|
|
3047
|
-
}
|
|
3048
|
-
catch (error) {
|
|
3049
|
-
const err = error;
|
|
3050
|
-
if (err.code !== "ENOENT")
|
|
3051
|
-
throw error;
|
|
3052
|
-
existed = false;
|
|
3053
|
-
}
|
|
3054
|
-
const existingSnapshotRaw = await readFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
|
|
3055
|
-
if (!existingSnapshotRaw?.trim()) {
|
|
3056
|
-
const snapshot = {
|
|
3057
|
-
version: 1,
|
|
3058
|
-
configPath,
|
|
3059
|
-
existed,
|
|
3060
|
-
previousRaw: existed ? existingRaw : null,
|
|
3061
|
-
};
|
|
3062
|
-
await writeJsonObjectFile(CLAUDE_DESKTOP_3P_STATE_PATH, snapshot, 0o600);
|
|
3063
|
-
}
|
|
3064
|
-
let doc = {};
|
|
3065
|
-
if (existingRaw.trim()) {
|
|
3066
|
-
try {
|
|
3067
|
-
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
3068
|
-
}
|
|
3069
|
-
catch {
|
|
3070
|
-
throw new Error(`invalid JSON in Claude Desktop config: ${configPath}`);
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
const enterpriseConfig = objectRecordOr(doc.enterpriseConfig, {});
|
|
3074
|
-
enterpriseConfig.inferenceProvider = "gateway";
|
|
3075
|
-
enterpriseConfig.inferenceGatewayBaseUrl = anthropicCompatibleProxyUrl(params.backendUrl);
|
|
3076
|
-
enterpriseConfig.inferenceGatewayApiKey = params.apiKey;
|
|
3077
|
-
enterpriseConfig.inferenceModels = expandClaudeDesktopInferenceModels(params.claudeModels);
|
|
3078
|
-
enterpriseConfig.isDesktopExtensionEnabled = true;
|
|
3079
|
-
enterpriseConfig.isDesktopExtensionDirectoryEnabled = true;
|
|
3080
|
-
enterpriseConfig.isDesktopExtensionSignatureRequired = false;
|
|
3081
|
-
enterpriseConfig.isLocalDevMcpEnabled = true;
|
|
3082
|
-
enterpriseConfig.isClaudeCodeForDesktopEnabled = true;
|
|
3083
|
-
const deploymentOrganizationUuid = await discoverClaudeDesktopDeploymentOrganizationUuid(doc);
|
|
3084
|
-
if (deploymentOrganizationUuid) {
|
|
3085
|
-
enterpriseConfig.deploymentOrganizationUuid = deploymentOrganizationUuid;
|
|
3086
|
-
}
|
|
3087
|
-
doc.enterpriseConfig = enterpriseConfig;
|
|
3088
|
-
doc.isUsingBuiltInNodeForMcp = true;
|
|
3089
|
-
doc.isDxtAutoUpdatesEnabled = true;
|
|
3090
|
-
await writeJsonObjectFile(configPath, doc);
|
|
3091
|
-
return configPath;
|
|
3092
|
-
}
|
|
3093
|
-
function runOpenClawConfigCommand(args) {
|
|
3094
|
-
const run = (0, node_child_process_1.spawnSync)("openclaw", args, {
|
|
3095
|
-
encoding: "utf8",
|
|
3096
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3097
|
-
});
|
|
3098
|
-
if (run.status === 0)
|
|
3099
|
-
return;
|
|
3100
|
-
const stderr = (run.stderr ?? "").trim();
|
|
3101
|
-
const stdout = (run.stdout ?? "").trim();
|
|
3102
|
-
const details = stderr || stdout || "openclaw command returned a non-zero exit status";
|
|
3103
|
-
throw new Error(`failed to update OpenClaw config: ${details}`);
|
|
3104
|
-
}
|
|
3105
|
-
function isOpenClawMissingApiKeyEnvError(error) {
|
|
3106
|
-
if (!(error instanceof Error))
|
|
3107
|
-
return false;
|
|
3108
|
-
return (error.message.includes(`Missing env var "${ENV_KEY_NAME}"`) &&
|
|
3109
|
-
error.message.includes(`models.providers.${OPENCLAW_PROVIDER_ID}.apiKey`));
|
|
3110
|
-
}
|
|
3111
|
-
function resolveOpenClawPrimaryModel(params) {
|
|
3112
|
-
const hasStableDefault = params.models.some((entry) => entry.id === DEFAULT_OPENCLAW_MODEL);
|
|
3113
|
-
const modelId = hasStableDefault ? DEFAULT_OPENCLAW_MODEL : params.model.trim() || DEFAULT_CODEX_MODEL;
|
|
3114
|
-
return `${OPENCLAW_PROVIDER_ID}/${modelId}`;
|
|
3115
|
-
}
|
|
3116
|
-
async function patchOpenClawConfigFile(params) {
|
|
3117
|
-
const provider = {
|
|
3118
|
-
baseUrl: openAiCompatibleProxyUrl(params.backendUrl),
|
|
3119
|
-
apiKey: params.apiKey,
|
|
3120
|
-
api: "openai-responses",
|
|
3121
|
-
models: buildOpenClawModels(params.models),
|
|
3122
|
-
};
|
|
3123
|
-
const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "openclaw.json");
|
|
3124
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
3125
|
-
let existingRaw = "";
|
|
3126
|
-
try {
|
|
3127
|
-
existingRaw = await promises_1.default.readFile(configPath, "utf8");
|
|
3128
|
-
}
|
|
3129
|
-
catch (error) {
|
|
3130
|
-
const err = error;
|
|
3131
|
-
if (err.code !== "ENOENT")
|
|
3132
|
-
throw error;
|
|
3133
|
-
}
|
|
3134
|
-
let doc = {};
|
|
3135
|
-
if (existingRaw.trim()) {
|
|
3136
|
-
try {
|
|
3137
|
-
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
3138
|
-
}
|
|
3139
|
-
catch {
|
|
3140
|
-
throw new Error(`invalid JSON in OpenClaw config: ${configPath}`);
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
const modelsRoot = objectRecordOr(doc.models, {});
|
|
3144
|
-
modelsRoot.mode = "merge";
|
|
3145
|
-
const providersRoot = objectRecordOr(modelsRoot.providers, {});
|
|
3146
|
-
providersRoot[OPENCLAW_PROVIDER_ID] = provider;
|
|
3147
|
-
modelsRoot.providers = providersRoot;
|
|
3148
|
-
doc.models = modelsRoot;
|
|
3149
|
-
const agentsRoot = objectRecordOr(doc.agents, {});
|
|
3150
|
-
const defaultsRoot = objectRecordOr(agentsRoot.defaults, {});
|
|
3151
|
-
const modelRoot = objectRecordOr(defaultsRoot.model, {});
|
|
3152
|
-
modelRoot.primary = resolveOpenClawPrimaryModel(params);
|
|
3153
|
-
if (typeof defaultsRoot.thinkingDefault !== "string") {
|
|
3154
|
-
defaultsRoot.thinkingDefault = "medium";
|
|
3155
|
-
}
|
|
3156
|
-
defaultsRoot.model = modelRoot;
|
|
3157
|
-
agentsRoot.defaults = defaultsRoot;
|
|
3158
|
-
doc.agents = agentsRoot;
|
|
3159
|
-
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
3160
|
-
return configPath;
|
|
3161
|
-
}
|
|
3162
|
-
async function setupOpenClaw(params) {
|
|
3163
|
-
const configPath = await patchOpenClawConfigFile(params);
|
|
3164
|
-
return { configPath };
|
|
3165
|
-
}
|
|
3166
|
-
function objectRecordOr(value, fallback) {
|
|
3167
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3168
|
-
return { ...value };
|
|
3169
|
-
}
|
|
3170
|
-
return fallback;
|
|
3171
|
-
}
|
|
3172
|
-
function modelContextLimit(modelId) {
|
|
3173
|
-
if (modelId === "gpt-5.4")
|
|
3174
|
-
return 1050000;
|
|
3175
|
-
return 272000;
|
|
3176
|
-
}
|
|
3177
|
-
function modelOutputLimit(modelId) {
|
|
3178
|
-
if (modelId === "gpt-5.4")
|
|
3179
|
-
return 128000;
|
|
3180
|
-
return 65536;
|
|
3181
|
-
}
|
|
3182
|
-
function buildOpenClawModels(models) {
|
|
3183
|
-
const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
|
|
3184
|
-
return models
|
|
3185
|
-
.filter((model) => Boolean(model.id))
|
|
3186
|
-
.map((model) => {
|
|
3187
|
-
const pricing = supportedModelMap.get(model.id);
|
|
3188
|
-
return {
|
|
3189
|
-
id: model.id,
|
|
3190
|
-
name: model.name || model.id,
|
|
3191
|
-
api: "openai-responses",
|
|
3192
|
-
reasoning: true,
|
|
3193
|
-
input: ["text", "image"],
|
|
3194
|
-
cost: {
|
|
3195
|
-
input: pricing?.inputPer1M ?? 0,
|
|
3196
|
-
output: pricing?.outputPer1M ?? 0,
|
|
3197
|
-
cacheRead: pricing?.cachedInputPer1M ?? 0,
|
|
3198
|
-
cacheWrite: pricing?.inputPer1M ?? 0,
|
|
3199
|
-
},
|
|
3200
|
-
contextWindow: modelContextLimit(model.id),
|
|
3201
|
-
maxTokens: modelOutputLimit(model.id),
|
|
3202
|
-
};
|
|
3203
|
-
});
|
|
3204
|
-
}
|
|
3205
|
-
function buildOpenCodeModelConfig(model) {
|
|
3206
|
-
return {
|
|
3207
|
-
name: model.name || model.id,
|
|
3208
|
-
reasoning: true,
|
|
3209
|
-
tool_call: true,
|
|
3210
|
-
attachment: true,
|
|
3211
|
-
modalities: {
|
|
3212
|
-
input: ["text", "image"],
|
|
3213
|
-
output: ["text"],
|
|
3214
|
-
},
|
|
3215
|
-
limit: {
|
|
3216
|
-
context: modelContextLimit(model.id),
|
|
3217
|
-
output: modelOutputLimit(model.id),
|
|
3218
|
-
},
|
|
3219
|
-
};
|
|
3220
|
-
}
|
|
3221
|
-
function buildOpenCodeModelsObject(models) {
|
|
3222
|
-
const result = {};
|
|
3223
|
-
for (const model of models) {
|
|
3224
|
-
if (!model.id)
|
|
3225
|
-
continue;
|
|
3226
|
-
result[model.id] = buildOpenCodeModelConfig(model);
|
|
3227
|
-
}
|
|
3228
|
-
return result;
|
|
3229
|
-
}
|
|
3230
|
-
function buildClaudeModelSelections(modelIds) {
|
|
3231
|
-
return uniqueStrings(modelIds)
|
|
3232
|
-
.filter(Boolean)
|
|
3233
|
-
.map((modelId) => ({
|
|
3234
|
-
id: modelId,
|
|
3235
|
-
name: prettyModelId(modelId),
|
|
3236
|
-
}));
|
|
3237
|
-
}
|
|
3238
|
-
function normalizeOpenCodeRestoreSnapshot(snapshot, fallbackConfigPath) {
|
|
3239
|
-
if (typeof snapshot !== "object" || snapshot === null || Array.isArray(snapshot))
|
|
3240
|
-
return null;
|
|
3241
|
-
const obj = snapshot;
|
|
3242
|
-
if ((obj.version === 2 || obj.version === 3) && Array.isArray(obj.targets)) {
|
|
3243
|
-
const targets = [];
|
|
3244
|
-
for (const entry of obj.targets) {
|
|
3245
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
3246
|
-
continue;
|
|
3247
|
-
const candidate = entry;
|
|
3248
|
-
const configPath = typeof candidate.configPath === "string" ? candidate.configPath.trim() : "";
|
|
3249
|
-
if (!configPath)
|
|
3250
|
-
continue;
|
|
3251
|
-
targets.push({
|
|
3252
|
-
configPath,
|
|
3253
|
-
existed: candidate.existed === true,
|
|
3254
|
-
openAiProvider: typeof candidate.openAiProvider === "object" &&
|
|
3255
|
-
candidate.openAiProvider !== null &&
|
|
3256
|
-
!Array.isArray(candidate.openAiProvider)
|
|
3257
|
-
? candidate.openAiProvider
|
|
3258
|
-
: null,
|
|
3259
|
-
anthropicProvider: typeof candidate.anthropicProvider === "object" &&
|
|
3260
|
-
candidate.anthropicProvider !== null &&
|
|
3261
|
-
!Array.isArray(candidate.anthropicProvider)
|
|
3262
|
-
? candidate.anthropicProvider
|
|
3263
|
-
: null,
|
|
3264
|
-
model: typeof candidate.model === "string" ? candidate.model : null,
|
|
3265
|
-
schema: typeof candidate.schema === "string" ? candidate.schema : null,
|
|
3266
|
-
plugin: "plugin" in candidate
|
|
3267
|
-
? Array.isArray(candidate.plugin)
|
|
3268
|
-
? candidate.plugin
|
|
3269
|
-
: null
|
|
3270
|
-
: undefined,
|
|
3271
|
-
});
|
|
3272
|
-
}
|
|
3273
|
-
return { version: 3, targets };
|
|
3274
|
-
}
|
|
3275
|
-
const looksLikeV1 = "openAiProvider" in obj || "model" in obj || "schema" in obj;
|
|
3276
|
-
if (!looksLikeV1)
|
|
3277
|
-
return null;
|
|
3278
|
-
return {
|
|
3279
|
-
version: 3,
|
|
3280
|
-
targets: [
|
|
3281
|
-
{
|
|
3282
|
-
configPath: fallbackConfigPath,
|
|
3283
|
-
existed: true,
|
|
3284
|
-
openAiProvider: typeof obj.openAiProvider === "object" && obj.openAiProvider !== null && !Array.isArray(obj.openAiProvider)
|
|
3285
|
-
? obj.openAiProvider
|
|
3286
|
-
: null,
|
|
3287
|
-
model: typeof obj.model === "string" ? obj.model : null,
|
|
3288
|
-
schema: typeof obj.schema === "string" ? obj.schema : null,
|
|
3289
|
-
},
|
|
3290
|
-
],
|
|
3291
|
-
};
|
|
3292
|
-
}
|
|
3293
|
-
async function readOpenCodeRestoreSnapshot(restoreStatePath, fallbackConfigPath) {
|
|
3294
|
-
const existingRaw = await readFileIfExists(restoreStatePath);
|
|
3295
|
-
if (existingRaw === null || !existingRaw.trim())
|
|
3296
|
-
return null;
|
|
3297
|
-
try {
|
|
3298
|
-
return normalizeOpenCodeRestoreSnapshot(JSON.parse(existingRaw), fallbackConfigPath);
|
|
3299
|
-
}
|
|
3300
|
-
catch {
|
|
3301
|
-
return null;
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
function openCodeConfigFileCandidates() {
|
|
3305
|
-
const home = node_os_1.default.homedir();
|
|
3306
|
-
const dirs = configDirCandidates("opencode");
|
|
3307
|
-
const candidates = [];
|
|
3308
|
-
for (const dir of dirs) {
|
|
3309
|
-
candidates.push(node_path_1.default.join(dir, "opencode.json"));
|
|
3310
|
-
candidates.push(node_path_1.default.join(dir, ".opencode.json"));
|
|
3311
|
-
candidates.push(node_path_1.default.join(dir, "config.json"));
|
|
3312
|
-
}
|
|
3313
|
-
candidates.push(node_path_1.default.join(home, ".opencode.json"));
|
|
3314
|
-
return uniqueStrings(candidates);
|
|
3315
|
-
}
|
|
3316
|
-
function findOpenCodeProjectConfigFile() {
|
|
3317
|
-
return findProjectConfigFile({
|
|
3318
|
-
startDir: process.cwd(),
|
|
3319
|
-
basenames: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
3320
|
-
});
|
|
3321
|
-
}
|
|
3322
|
-
function isOpenCodeAuthPlugin(value) {
|
|
3323
|
-
if (typeof value !== "string")
|
|
3324
|
-
return false;
|
|
3325
|
-
const normalized = value.trim();
|
|
3326
|
-
if (!normalized)
|
|
3327
|
-
return false;
|
|
3328
|
-
return normalized.startsWith("opencode-openai-codex-auth");
|
|
3329
|
-
}
|
|
3330
|
-
function filteredOpenCodePlugins(value) {
|
|
3331
|
-
if (!Array.isArray(value))
|
|
3332
|
-
return { next: value, changed: false };
|
|
3333
|
-
const filtered = value.filter((entry) => !isOpenCodeAuthPlugin(entry));
|
|
3334
|
-
if (filtered.length === value.length)
|
|
3335
|
-
return { next: value, changed: false };
|
|
3336
|
-
return { next: filtered, changed: true };
|
|
3337
|
-
}
|
|
3338
|
-
function kiloConfigFileCandidates() {
|
|
3339
|
-
const home = node_os_1.default.homedir();
|
|
3340
|
-
const dirs = uniqueStrings([...configDirCandidates("kilo"), node_path_1.default.join(home, ".kilo")]);
|
|
3341
|
-
const candidates = [];
|
|
3342
|
-
for (const dir of dirs) {
|
|
3343
|
-
candidates.push(node_path_1.default.join(dir, "opencode.json"));
|
|
3344
|
-
candidates.push(node_path_1.default.join(dir, "config.json"));
|
|
3345
|
-
}
|
|
3346
|
-
return uniqueStrings(candidates);
|
|
3347
|
-
}
|
|
3348
|
-
function resolveOpenCodeConfigTargets() {
|
|
3349
|
-
const candidates = openCodeConfigFileCandidates();
|
|
3350
|
-
const existing = candidates.filter((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
3351
|
-
if (existing.length)
|
|
3352
|
-
return existing;
|
|
3353
|
-
const primaryDir = configDirCandidates("opencode")[0] ?? node_path_1.default.join(xdgConfigHomeDir(), "opencode");
|
|
3354
|
-
return uniqueStrings([node_path_1.default.join(primaryDir, "opencode.json"), node_path_1.default.join(primaryDir, ".opencode.json")]);
|
|
3355
|
-
}
|
|
3356
|
-
function resolveKiloConfigTargets() {
|
|
3357
|
-
const candidates = kiloConfigFileCandidates();
|
|
3358
|
-
const existing = candidates.filter((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
3359
|
-
if (existing.length)
|
|
3360
|
-
return existing;
|
|
3361
|
-
const home = node_os_1.default.homedir();
|
|
3362
|
-
const primaryDir = uniqueStrings([...configDirCandidates("kilo"), node_path_1.default.join(home, ".kilo")])[0] ??
|
|
3363
|
-
node_path_1.default.join(xdgConfigHomeDir(), "kilo");
|
|
3364
|
-
return uniqueStrings([node_path_1.default.join(primaryDir, "opencode.json")]);
|
|
3365
|
-
}
|
|
3366
|
-
function isManagedOpenCodeProvider(provider, backendUrl) {
|
|
3367
|
-
const options = objectRecordOr(provider.options, {});
|
|
3368
|
-
if (isTheClawBayOpenAiCompatibleBaseUrl(options.baseURL))
|
|
3369
|
-
return true;
|
|
3370
|
-
return typeof options.baseURL === "string" && trimTrailingSlash(options.baseURL) === openAiCompatibleProxyUrl(backendUrl);
|
|
3371
|
-
}
|
|
3372
|
-
function isManagedOpenCodeAnthropicProvider(provider, backendUrl) {
|
|
3373
|
-
const options = objectRecordOr(provider.options, {});
|
|
3374
|
-
if (isTheClawBayAnthropicCompatibleBaseUrl(options.baseURL))
|
|
3375
|
-
return true;
|
|
3376
|
-
return typeof options.baseURL === "string" && trimTrailingSlash(options.baseURL) === anthropicCompatibleProxyV1Url(backendUrl);
|
|
3377
|
-
}
|
|
3378
|
-
function isManagedOpenCodeModel(value, supportedModelIds) {
|
|
3379
|
-
if (typeof value !== "string")
|
|
3380
|
-
return false;
|
|
3381
|
-
if (!value.startsWith(`${OPENAI_PROVIDER_ID}/`))
|
|
3382
|
-
return false;
|
|
3383
|
-
return supportedModelIds.has(value.slice(`${OPENAI_PROVIDER_ID}/`.length));
|
|
3384
|
-
}
|
|
3385
|
-
async function writeOpenCodeFamilyConfig(params) {
|
|
3386
|
-
const normalizedConfigPaths = uniqueStrings(params.configPaths);
|
|
3387
|
-
const fallbackConfigPath = normalizedConfigPaths[0] ?? node_path_1.default.join(xdgConfigHomeDir(), "opencode", "opencode.json");
|
|
3388
|
-
const restoreState = (await readOpenCodeRestoreSnapshot(params.restoreStatePath, fallbackConfigPath)) ??
|
|
3389
|
-
{
|
|
3390
|
-
version: 3,
|
|
3391
|
-
targets: [],
|
|
3392
|
-
};
|
|
3393
|
-
const supportedModelIds = new Set(params.models.map((entry) => entry.id).filter(Boolean));
|
|
3394
|
-
const restoreHasTarget = (configPath) => restoreState.targets.some((entry) => entry.configPath === configPath);
|
|
3395
|
-
let restoreStateChanged = false;
|
|
3396
|
-
const ensureRestorePluginSnapshot = (configPath, plugin) => {
|
|
3397
|
-
const target = restoreState.targets.find((entry) => entry.configPath === configPath);
|
|
3398
|
-
if (!target)
|
|
3399
|
-
return;
|
|
3400
|
-
if (Object.prototype.hasOwnProperty.call(target, "plugin"))
|
|
3401
|
-
return;
|
|
3402
|
-
target.plugin = Array.isArray(plugin) ? plugin : null;
|
|
3403
|
-
restoreStateChanged = true;
|
|
3404
|
-
};
|
|
3405
|
-
for (const configPath of normalizedConfigPaths) {
|
|
3406
|
-
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
3407
|
-
let existed = true;
|
|
3408
|
-
let existingRaw = "";
|
|
3409
|
-
try {
|
|
3410
|
-
existingRaw = await promises_1.default.readFile(configPath, "utf8");
|
|
3411
|
-
}
|
|
3412
|
-
catch (error) {
|
|
3413
|
-
const err = error;
|
|
3414
|
-
if (err.code !== "ENOENT")
|
|
3415
|
-
throw error;
|
|
3416
|
-
existed = false;
|
|
3417
|
-
}
|
|
3418
|
-
let doc = {};
|
|
3419
|
-
if (existingRaw.trim()) {
|
|
3420
|
-
try {
|
|
3421
|
-
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
3422
|
-
}
|
|
3423
|
-
catch {
|
|
3424
|
-
throw new Error(`invalid JSON in config: ${configPath}`);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
const providerRoot = objectRecordOr(doc.provider, {});
|
|
3428
|
-
const openAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
|
|
3429
|
-
const anthropicProvider = objectRecordOr(providerRoot[ANTHROPIC_PROVIDER_ID], {});
|
|
3430
|
-
const alreadyManaged = isManagedOpenCodeProvider(openAiProvider, params.backendUrl) && isManagedOpenCodeModel(doc.model, supportedModelIds);
|
|
3431
|
-
if (!alreadyManaged && !restoreHasTarget(configPath)) {
|
|
3432
|
-
restoreState.targets.push({
|
|
3433
|
-
configPath,
|
|
3434
|
-
existed,
|
|
3435
|
-
openAiProvider: Object.keys(openAiProvider).length > 0 ? openAiProvider : null,
|
|
3436
|
-
anthropicProvider: Object.keys(anthropicProvider).length > 0 ? anthropicProvider : null,
|
|
3437
|
-
model: typeof doc.model === "string" && !doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`)
|
|
3438
|
-
? doc.model
|
|
3439
|
-
: null,
|
|
3440
|
-
schema: typeof doc.$schema === "string" ? doc.$schema : null,
|
|
3441
|
-
plugin: Array.isArray(doc.plugin) ? doc.plugin : null,
|
|
3442
|
-
});
|
|
3443
|
-
restoreStateChanged = true;
|
|
3444
|
-
}
|
|
3445
|
-
else if (!alreadyManaged) {
|
|
3446
|
-
ensureRestorePluginSnapshot(configPath, doc.plugin);
|
|
3447
|
-
}
|
|
3448
|
-
const pluginFilter = filteredOpenCodePlugins(doc.plugin);
|
|
3449
|
-
if (pluginFilter.changed) {
|
|
3450
|
-
doc.plugin = pluginFilter.next;
|
|
3451
|
-
}
|
|
3452
|
-
const managedOpenAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
|
|
3453
|
-
const optionsRoot = objectRecordOr(managedOpenAiProvider.options, {});
|
|
3454
|
-
optionsRoot.baseURL = openAiCompatibleProxyUrl(params.backendUrl);
|
|
3455
|
-
optionsRoot.apiKey = params.apiKey;
|
|
3456
|
-
optionsRoot.setCacheKey = true;
|
|
3457
|
-
managedOpenAiProvider.options = optionsRoot;
|
|
3458
|
-
managedOpenAiProvider.models = buildOpenCodeModelsObject(params.models);
|
|
3459
|
-
managedOpenAiProvider.whitelist = params.models.map((entry) => entry.id).filter(Boolean);
|
|
3460
|
-
if ("blacklist" in managedOpenAiProvider) {
|
|
3461
|
-
delete managedOpenAiProvider.blacklist;
|
|
3462
|
-
}
|
|
3463
|
-
providerRoot[OPENAI_PROVIDER_ID] = managedOpenAiProvider;
|
|
3464
|
-
if ((params.claudeModels?.length ?? 0) > 0) {
|
|
3465
|
-
const claudeSelections = buildClaudeModelSelections(params.claudeModels ?? []);
|
|
3466
|
-
const managedAnthropicProvider = objectRecordOr(providerRoot[ANTHROPIC_PROVIDER_ID], {});
|
|
3467
|
-
const anthropicOptions = objectRecordOr(managedAnthropicProvider.options, {});
|
|
3468
|
-
anthropicOptions.baseURL = anthropicCompatibleProxyV1Url(params.backendUrl);
|
|
3469
|
-
anthropicOptions.apiKey = params.apiKey;
|
|
3470
|
-
anthropicOptions.setCacheKey = true;
|
|
3471
|
-
managedAnthropicProvider.options = anthropicOptions;
|
|
3472
|
-
managedAnthropicProvider.models = buildOpenCodeModelsObject(claudeSelections);
|
|
3473
|
-
managedAnthropicProvider.whitelist = claudeSelections.map((entry) => entry.id);
|
|
3474
|
-
if ("blacklist" in managedAnthropicProvider) {
|
|
3475
|
-
delete managedAnthropicProvider.blacklist;
|
|
3476
|
-
}
|
|
3477
|
-
providerRoot[ANTHROPIC_PROVIDER_ID] = managedAnthropicProvider;
|
|
3478
|
-
}
|
|
3479
|
-
else if (isManagedOpenCodeAnthropicProvider(anthropicProvider, params.backendUrl)) {
|
|
3480
|
-
delete providerRoot[ANTHROPIC_PROVIDER_ID];
|
|
3481
|
-
}
|
|
3482
|
-
if (DEFAULT_PROVIDER_ID in providerRoot) {
|
|
3483
|
-
delete providerRoot[DEFAULT_PROVIDER_ID];
|
|
3484
|
-
}
|
|
3485
|
-
doc.$schema = params.schemaUrl;
|
|
3486
|
-
doc.provider = providerRoot;
|
|
3487
|
-
doc.model = `${OPENAI_PROVIDER_ID}/${params.model}`;
|
|
3488
|
-
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
3489
|
-
}
|
|
3490
|
-
if (restoreState.targets.length > 0 && restoreStateChanged) {
|
|
3491
|
-
await writeJsonObjectFile(params.restoreStatePath, restoreState, 0o600);
|
|
3492
|
-
}
|
|
3493
|
-
return normalizedConfigPaths;
|
|
3494
|
-
}
|
|
3495
|
-
async function writeOpenCodeConfig(params) {
|
|
3496
|
-
return writeOpenCodeFamilyConfig({
|
|
3497
|
-
configPaths: resolveOpenCodeConfigTargets(),
|
|
3498
|
-
restoreStatePath: OPENCODE_RESTORE_STATE_PATH,
|
|
3499
|
-
schemaUrl: OPENCODE_CONFIG_SCHEMA_URL,
|
|
3500
|
-
...params,
|
|
3501
|
-
});
|
|
3502
|
-
}
|
|
3503
|
-
async function writeKiloConfig(params) {
|
|
3504
|
-
return writeOpenCodeFamilyConfig({
|
|
3505
|
-
configPaths: resolveKiloConfigTargets(),
|
|
3506
|
-
restoreStatePath: KILO_RESTORE_STATE_PATH,
|
|
3507
|
-
schemaUrl: KILO_CONFIG_SCHEMA_URL,
|
|
3508
|
-
...params,
|
|
3509
|
-
});
|
|
3510
|
-
}
|
|
3511
|
-
function traePatchSnippet(params) {
|
|
3512
|
-
const patchedModels = params.models.length
|
|
3513
|
-
? params.models
|
|
3514
|
-
: [{ id: params.model.trim() || DEFAULT_TRAE_MODEL, name: modelDisplayName(params.model.trim() || DEFAULT_TRAE_MODEL) }];
|
|
3515
|
-
const proxyBaseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
3516
|
-
return `async setOriginModelListMapAndCache(e,t=!0){let{userProfile:r}=this.credentialStore.getState(),i=r?.scope!==v9.BYTEDANCE,n={},o=${JSON.stringify(patchedModels)};Object.keys(e).forEach(t=>{let r=[...(e[t]||[])],s=r[0]&&"object"==typeof r[0]?r[0]:null;if([Mh.Builder,Mh.SoloCoder].includes(t)&&s){let a=o.map((e,l)=>{let o={...s};return o.id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+t+"-"+e.id,o.name=e.id,o.display_name=e.name,o.provider="theclawbay",o.icon={dark:"https://theclawbay.com/favicon.ico",light:"https://theclawbay.com/favicon.ico"},o.ak=${JSON.stringify(params.apiKey)},o.base_url=${JSON.stringify(proxyBaseUrl)},o.is_custom_base_url=!0,o.custom_model_id=${JSON.stringify(TRAE_PATCH_MARKER)}+"-"+e.id,o.selectable=!0,o.status=!0,o.is_default=0===l,o.config_source=MK.Personal,o.model_type=s.model_type??"chat_model",o.builder=s.builder??null,o.client_connect=!0,o.multimodal=!1!==s.multimodal,o.tags=Array.isArray(s.tags)?s.tags:[],o.auth_type="number"==typeof s.auth_type?s.auth_type:0,o.region=s.region??null,o.max_turns=s.max_turns??{default:50,max:50},o.context_window_size=s.context_window_size??{max:[128000],default:64000},o.features=s.features??{memory:{enable:!1},cost:{enable:!1,data:{manual_usage:0}},multimodal:{enable:!0},context_windows:{enable:!0,data:{dev_context:64000,max_context:128000,max_context_list:[128000],dev_turns:50,max_turns:50}}},o.commercial_info=s.commercial_info??{manual_usage:0,info:""},o.saas_usage=s.saas_usage??{max:0,default:null},o});n[t]=a;return}!i&&[Mh.SoloCoder,Mh.UIBuilder].includes(t)?n[t]=r?.filter(e=>e.config_source!==MK.Personal)||[]:n[t]=r}),this._modelStore.actions.setOriginModelListMap(n),t&&this._modelStorageService.storeModelListMap(n),this._logService.info("[ModelService.setOriginModelListMapAndCache] officialModels",Object.entries(n).map(([e,t])=>({scene:e,models:(t||[]).map(e=>e.display_name||e.name)})))}`;
|
|
3517
|
-
}
|
|
3518
|
-
async function patchTraeBundle(params) {
|
|
3519
|
-
const bundlePath = traeBundlePath();
|
|
3520
|
-
if (!bundlePath)
|
|
3521
|
-
throw new Error("Trae experimental support is currently Windows-only.");
|
|
3522
|
-
const existing = await promises_1.default.readFile(bundlePath, "utf8");
|
|
3523
|
-
const backupPath = traeBundleBackupPath(bundlePath);
|
|
3524
|
-
const backupExists = await pathExists(backupPath);
|
|
3525
|
-
const isPatched = existing.includes(TRAE_PATCH_MARKER);
|
|
3526
|
-
let baseSource = existing;
|
|
3527
|
-
if (!isPatched) {
|
|
3528
|
-
await promises_1.default.writeFile(backupPath, existing, "utf8");
|
|
3529
|
-
}
|
|
3530
|
-
else if (backupExists) {
|
|
3531
|
-
baseSource = await promises_1.default.readFile(backupPath, "utf8");
|
|
3532
|
-
}
|
|
3533
|
-
else {
|
|
3534
|
-
await promises_1.default.writeFile(backupPath, existing, "utf8");
|
|
3535
|
-
}
|
|
3536
|
-
const functionStart = baseSource.indexOf(TRAE_TARGET_FUNCTION_START);
|
|
3537
|
-
const functionEnd = functionStart >= 0 ? baseSource.indexOf(TRAE_TARGET_FUNCTION_END, functionStart) : -1;
|
|
3538
|
-
if (functionStart < 0 || functionEnd < 0) {
|
|
3539
|
-
throw new Error("Unsupported Trae build for experimental The Claw Bay patching.");
|
|
3540
|
-
}
|
|
3541
|
-
const next = baseSource.slice(0, functionStart) +
|
|
3542
|
-
traePatchSnippet({
|
|
3543
|
-
backendUrl: params.backendUrl,
|
|
3544
|
-
apiKey: params.apiKey,
|
|
3545
|
-
model: params.model,
|
|
3546
|
-
models: params.models,
|
|
3547
|
-
}) +
|
|
3548
|
-
baseSource.slice(functionEnd + 1);
|
|
3549
|
-
await promises_1.default.writeFile(bundlePath, next, "utf8");
|
|
3550
|
-
return bundlePath;
|
|
3551
|
-
}
|
|
3552
|
-
class SetupCommand extends base_command_1.BaseCommand {
|
|
3553
|
-
async run() {
|
|
3554
|
-
await this.runSafe(async () => {
|
|
3555
|
-
const { flags } = await this.parse(SetupCommand);
|
|
3556
|
-
const debugOutput = flags["debug-output"];
|
|
3557
91
|
let managed = null;
|
|
3558
92
|
try {
|
|
3559
93
|
managed = await (0, config_1.readManagedConfig)();
|
|
@@ -3583,12 +117,12 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3583
117
|
const managedBackendUrl = managed?.backendUrl ?? null;
|
|
3584
118
|
const backendRaw = flags.backend ??
|
|
3585
119
|
inferredBackend ??
|
|
3586
|
-
(shouldPreferPublicSetupBackend(managedBackendUrl) ? DEFAULT_BACKEND_URL : managedBackendUrl) ??
|
|
3587
|
-
DEFAULT_BACKEND_URL;
|
|
3588
|
-
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
120
|
+
((0, urls_1.shouldPreferPublicSetupBackend)(managedBackendUrl) ? urls_1.DEFAULT_BACKEND_URL : managedBackendUrl) ??
|
|
121
|
+
urls_1.DEFAULT_BACKEND_URL;
|
|
122
|
+
const backendUrl = (0, urls_1.normalizeUrl)(backendRaw, "--backend");
|
|
3589
123
|
const reusingLocalManagedCredential = !explicitApiKey &&
|
|
3590
124
|
!flags.backend &&
|
|
3591
|
-
shouldPreferPublicSetupBackend(managedBackendUrl) &&
|
|
125
|
+
(0, urls_1.shouldPreferPublicSetupBackend)(managedBackendUrl) &&
|
|
3592
126
|
authCredential.length > 0;
|
|
3593
127
|
if (reusingLocalManagedCredential) {
|
|
3594
128
|
authType = "device-session";
|
|
@@ -3597,18 +131,18 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3597
131
|
deviceLabel = null;
|
|
3598
132
|
}
|
|
3599
133
|
const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, openCodeDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected, hermesDetected] = await Promise.all([
|
|
3600
|
-
detectCodexClient(),
|
|
3601
|
-
detectClaudeClient(),
|
|
3602
|
-
detectContinueClient(),
|
|
3603
|
-
detectClineClient(),
|
|
3604
|
-
detectGsdClient(),
|
|
3605
|
-
detectOpenCodeClient(),
|
|
3606
|
-
detectKiloClient(),
|
|
3607
|
-
detectRooClient(),
|
|
3608
|
-
detectTraeClient(),
|
|
3609
|
-
detectAiderClient(),
|
|
3610
|
-
detectZoClient(),
|
|
3611
|
-
detectHermesClient(),
|
|
134
|
+
(0, codex_1.detectCodexClient)(),
|
|
135
|
+
(0, claude_1.detectClaudeClient)(),
|
|
136
|
+
(0, continue_1.detectContinueClient)(),
|
|
137
|
+
(0, cline_1.detectClineClient)(),
|
|
138
|
+
(0, gsd_1.detectGsdClient)(),
|
|
139
|
+
(0, opencode_family_1.detectOpenCodeClient)(),
|
|
140
|
+
(0, opencode_family_1.detectKiloClient)(),
|
|
141
|
+
(0, roo_1.detectRooClient)(),
|
|
142
|
+
(0, trae_1.detectTraeClient)(),
|
|
143
|
+
(0, aider_1.detectAiderClient)(),
|
|
144
|
+
(0, zo_1.detectZoClient)(),
|
|
145
|
+
(0, hermes_1.detectHermesClient)(),
|
|
3612
146
|
]);
|
|
3613
147
|
const setupClients = [
|
|
3614
148
|
{
|
|
@@ -3665,7 +199,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3665
199
|
id: "openclaw",
|
|
3666
200
|
label: "OpenClaw",
|
|
3667
201
|
summaryLabel: "OpenClaw",
|
|
3668
|
-
detected: hasCommand("openclaw"),
|
|
202
|
+
detected: (0, platform_1.hasCommand)("openclaw"),
|
|
3669
203
|
recommended: true,
|
|
3670
204
|
installable: false,
|
|
3671
205
|
icon: "🦞",
|
|
@@ -3742,17 +276,17 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3742
276
|
siteUrl: "https://hermes-agent.nousresearch.com/docs/",
|
|
3743
277
|
},
|
|
3744
278
|
];
|
|
3745
|
-
const selectedSetupClients = await resolveSetupClientSelection({
|
|
279
|
+
const selectedSetupClients = await (0, prompts_1.resolveSetupClientSelection)({
|
|
3746
280
|
setupClients,
|
|
3747
|
-
flagSelection: parseSetupClientFlags(flags.clients),
|
|
281
|
+
flagSelection: (0, prompts_1.parseSetupClientFlags)(flags.clients),
|
|
3748
282
|
skipPrompt: flags.yes,
|
|
3749
283
|
});
|
|
3750
|
-
const installedMissingClients = installSelectedMissingClients({
|
|
284
|
+
const installedMissingClients = (0, client_registry_1.installSelectedMissingClients)({
|
|
3751
285
|
setupClients,
|
|
3752
286
|
selectedClientIds: selectedSetupClients,
|
|
3753
287
|
log: (message) => this.log(message),
|
|
3754
288
|
});
|
|
3755
|
-
const migrateCodexConversations = await resolveCodexConversationMigrationSelection({
|
|
289
|
+
const migrateCodexConversations = await (0, codex_1.resolveCodexConversationMigrationSelection)({
|
|
3756
290
|
codexSelected: selectedSetupClients.has("codex"),
|
|
3757
291
|
flagValue: flags["migrate-conversations"],
|
|
3758
292
|
skipPrompt: flags.yes,
|
|
@@ -3761,7 +295,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3761
295
|
throw new Error("--migrate-conversations requires Codex to be selected for setup.");
|
|
3762
296
|
}
|
|
3763
297
|
const linkFreshDeviceSession = async () => {
|
|
3764
|
-
deviceLabel = await resolveDeviceLabel({
|
|
298
|
+
deviceLabel = await (0, backend_1.resolveDeviceLabel)({
|
|
3765
299
|
flagValue: flags["device-name"],
|
|
3766
300
|
managedValue: managed?.deviceLabel,
|
|
3767
301
|
skipPrompt: flags.yes,
|
|
@@ -3781,8 +315,8 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3781
315
|
await linkFreshDeviceSession();
|
|
3782
316
|
}
|
|
3783
317
|
else if (!explicitApiKey && managedAuthType === "device-session") {
|
|
3784
|
-
const managedProbe = await fetchBackendModelIds(backendUrl, authCredential);
|
|
3785
|
-
if (isCredentialRejectedFailure(managedProbe.failure)) {
|
|
318
|
+
const managedProbe = await (0, backend_1.fetchBackendModelIds)(backendUrl, authCredential);
|
|
319
|
+
if ((0, backend_1.isCredentialRejectedFailure)(managedProbe.failure)) {
|
|
3786
320
|
this.log(`Saved device-session credential was rejected by the backend (${managedProbe.failure}). Re-linking this machine now.`);
|
|
3787
321
|
await linkFreshDeviceSession();
|
|
3788
322
|
}
|
|
@@ -3820,12 +354,12 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3820
354
|
try {
|
|
3821
355
|
if (selectedSetupClients.size > 0) {
|
|
3822
356
|
progress.update("Resolving supported models");
|
|
3823
|
-
resolved = await resolveModels(backendUrl, authCredential);
|
|
357
|
+
resolved = await (0, backend_1.resolveModels)(backendUrl, authCredential);
|
|
3824
358
|
if (resolved.authRejected) {
|
|
3825
|
-
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
359
|
+
throw new Error((0, backend_1.describeCredentialRejection)(authType, resolved.failure));
|
|
3826
360
|
}
|
|
3827
361
|
progress.stop();
|
|
3828
|
-
const configuredModel = await resolveConfiguredModelSelection({
|
|
362
|
+
const configuredModel = await (0, prompts_1.resolveConfiguredModelSelection)({
|
|
3829
363
|
resolved,
|
|
3830
364
|
requestedModel: flags.model,
|
|
3831
365
|
requestedModels: flags.models,
|
|
@@ -3839,12 +373,12 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3839
373
|
};
|
|
3840
374
|
}
|
|
3841
375
|
progress.update("Checking Claude access");
|
|
3842
|
-
claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
|
|
376
|
+
claudeAccess = await (0, backend_1.resolveClaudeAccess)(backendUrl, authCredential);
|
|
3843
377
|
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
3844
|
-
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
378
|
+
throw new Error((0, backend_1.describeCredentialRejection)(authType, claudeAccess.failure));
|
|
3845
379
|
}
|
|
3846
380
|
progress.stop();
|
|
3847
|
-
claudeAccess = await resolveConfiguredClaudeModels({
|
|
381
|
+
claudeAccess = await (0, backend_1.resolveConfiguredClaudeModels)({
|
|
3848
382
|
access: claudeAccess,
|
|
3849
383
|
requestedModels: flags["claude-models"],
|
|
3850
384
|
skipPrompt: flags.yes,
|
|
@@ -3852,7 +386,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3852
386
|
if (flags["list-models"]) {
|
|
3853
387
|
if (resolved) {
|
|
3854
388
|
this.log("\nOpenAI-compatible models:");
|
|
3855
|
-
for (const section of groupModelSections(resolved.models)) {
|
|
389
|
+
for (const section of (0, models_1.groupModelSections)(resolved.models)) {
|
|
3856
390
|
this.log(`${section.provider.icon} ${section.provider.label}`);
|
|
3857
391
|
for (const model of section.models) {
|
|
3858
392
|
const marker = model.id === resolved.model ? " (default)" : "";
|
|
@@ -3883,69 +417,69 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3883
417
|
deviceSessionId,
|
|
3884
418
|
deviceLabel,
|
|
3885
419
|
});
|
|
3886
|
-
updatedShellFiles = await persistApiKeyEnv({
|
|
420
|
+
updatedShellFiles = await (0, env_persist_1.persistApiKeyEnv)({
|
|
3887
421
|
apiKey: authCredential,
|
|
3888
422
|
backendUrl,
|
|
3889
423
|
claudeEnabled: claudeEnvEnabled,
|
|
3890
424
|
});
|
|
3891
|
-
updatedFishFiles = await persistFishEnv({
|
|
425
|
+
updatedFishFiles = await (0, env_persist_1.persistFishEnv)({
|
|
3892
426
|
apiKey: authCredential,
|
|
3893
427
|
backendUrl,
|
|
3894
428
|
claudeEnabled: claudeEnvEnabled,
|
|
3895
429
|
});
|
|
3896
|
-
updatedPowerShellProfiles = await persistPowerShellEnv({
|
|
430
|
+
updatedPowerShellProfiles = await (0, env_persist_1.persistPowerShellEnv)({
|
|
3897
431
|
apiKey: authCredential,
|
|
3898
432
|
backendUrl,
|
|
3899
433
|
claudeEnabled: claudeEnvEnabled,
|
|
3900
434
|
});
|
|
3901
435
|
if (selectedSetupClients.has("codex") || selectedSetupClients.has("claude")) {
|
|
3902
|
-
updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
|
|
3903
|
-
updatedEditorTerminalSettings = await persistEditorTerminalEnvSettings({
|
|
436
|
+
updatedVsCodeEnvFiles = await (0, env_persist_1.persistVsCodeServerEnvSource)();
|
|
437
|
+
updatedEditorTerminalSettings = await (0, editor_env_1.persistEditorTerminalEnvSettings)({
|
|
3904
438
|
apiKey: authCredential,
|
|
3905
439
|
backendUrl,
|
|
3906
440
|
claudeEnabled: claudeEnvEnabled,
|
|
3907
441
|
});
|
|
3908
442
|
}
|
|
3909
443
|
if (selectedSetupClients.has("claude")) {
|
|
3910
|
-
updatedClaudeEditorSettings = await persistClaudeEditorSettings({
|
|
444
|
+
updatedClaudeEditorSettings = await (0, claude_1.persistClaudeEditorSettings)({
|
|
3911
445
|
apiKey: authCredential,
|
|
3912
446
|
backendUrl,
|
|
3913
447
|
claudeEnabled: claudeEnvEnabled,
|
|
3914
448
|
});
|
|
3915
449
|
if (claudeEnvEnabled) {
|
|
3916
|
-
claudeDesktop3pConfigPathManaged = await writeClaudeDesktop3pConfig({
|
|
450
|
+
claudeDesktop3pConfigPathManaged = await (0, claude_1.writeClaudeDesktop3pConfig)({
|
|
3917
451
|
backendUrl,
|
|
3918
452
|
apiKey: authCredential,
|
|
3919
453
|
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
3920
454
|
});
|
|
3921
455
|
}
|
|
3922
456
|
else {
|
|
3923
|
-
restoredClaudeDesktop3pConfig = await cleanupClaudeDesktop3pConfig();
|
|
457
|
+
restoredClaudeDesktop3pConfig = await (0, claude_1.cleanupClaudeDesktop3pConfig)();
|
|
3924
458
|
}
|
|
3925
459
|
}
|
|
3926
460
|
else {
|
|
3927
461
|
updatedEditorTerminalSettings = [
|
|
3928
462
|
...updatedEditorTerminalSettings,
|
|
3929
|
-
...await cleanupLegacyClaudeEditorTerminalEnvSettings({
|
|
463
|
+
...await (0, claude_1.cleanupLegacyClaudeEditorTerminalEnvSettings)({
|
|
3930
464
|
apiKey: authCredential,
|
|
3931
465
|
backendUrl,
|
|
3932
466
|
}),
|
|
3933
467
|
];
|
|
3934
|
-
updatedClaudeEditorSettings = await cleanupClaudeEditorSettings();
|
|
3935
|
-
restoredClaudeDesktop3pConfig = await cleanupClaudeDesktop3pConfig();
|
|
468
|
+
updatedClaudeEditorSettings = await (0, claude_1.cleanupClaudeEditorSettings)();
|
|
469
|
+
restoredClaudeDesktop3pConfig = await (0, claude_1.cleanupClaudeDesktop3pConfig)();
|
|
3936
470
|
}
|
|
3937
471
|
if (selectedSetupClients.has("codex")) {
|
|
3938
472
|
progress.update("Configuring Codex");
|
|
3939
|
-
codexConfigPath = await writeCodexConfig({
|
|
473
|
+
codexConfigPath = await (0, codex_1.writeCodexConfig)({
|
|
3940
474
|
backendUrl,
|
|
3941
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
475
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
3942
476
|
apiKey: authCredential,
|
|
3943
477
|
});
|
|
3944
478
|
if (migrateCodexConversations) {
|
|
3945
479
|
sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
|
|
3946
480
|
codexHome: paths_1.codexDir,
|
|
3947
|
-
migrationStateFile: MIGRATION_STATE_FILE,
|
|
3948
|
-
neutralizeSources: HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
|
|
481
|
+
migrationStateFile: codex_1.MIGRATION_STATE_FILE,
|
|
482
|
+
neutralizeSources: codex_1.HISTORY_PROVIDER_NEUTRALIZE_SOURCES,
|
|
3949
483
|
});
|
|
3950
484
|
}
|
|
3951
485
|
authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
|
|
@@ -3954,8 +488,8 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3954
488
|
if (migrateCodexConversations) {
|
|
3955
489
|
stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
|
|
3956
490
|
codexHome: paths_1.codexDir,
|
|
3957
|
-
targetProvider: DEFAULT_PROVIDER_ID,
|
|
3958
|
-
sourceProviders: HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
|
|
491
|
+
targetProvider: providers_1.DEFAULT_PROVIDER_ID,
|
|
492
|
+
sourceProviders: codex_1.HISTORY_PROVIDER_DB_MIGRATE_SOURCES,
|
|
3959
493
|
});
|
|
3960
494
|
}
|
|
3961
495
|
modelCacheMigration = await (0, codex_model_cache_migration_1.ensureCodexModelCacheHasGpt54)({
|
|
@@ -3964,35 +498,36 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3964
498
|
}
|
|
3965
499
|
if (selectedSetupClients.has("continue")) {
|
|
3966
500
|
progress.update("Configuring Continue");
|
|
3967
|
-
continueConfigPath = await writeContinueConfig({
|
|
501
|
+
continueConfigPath = await (0, continue_1.writeContinueConfig)({
|
|
3968
502
|
backendUrl,
|
|
3969
|
-
model: resolved?.model ?? DEFAULT_CONTINUE_MODEL,
|
|
503
|
+
model: resolved?.model ?? continue_1.DEFAULT_CONTINUE_MODEL,
|
|
504
|
+
models: resolved?.models ?? [],
|
|
3970
505
|
apiKey: authCredential,
|
|
3971
506
|
});
|
|
3972
507
|
}
|
|
3973
508
|
if (selectedSetupClients.has("cline")) {
|
|
3974
509
|
progress.update("Configuring Cline");
|
|
3975
|
-
clineConfigPaths = await writeClineConfig({
|
|
510
|
+
clineConfigPaths = await (0, cline_1.writeClineConfig)({
|
|
3976
511
|
backendUrl,
|
|
3977
|
-
model: resolved?.model ?? DEFAULT_CLINE_MODEL,
|
|
512
|
+
model: resolved?.model ?? cline_1.DEFAULT_CLINE_MODEL,
|
|
3978
513
|
apiKey: authCredential,
|
|
3979
514
|
});
|
|
3980
515
|
}
|
|
3981
516
|
if (selectedSetupClients.has("gsd")) {
|
|
3982
517
|
progress.update("Configuring GSD");
|
|
3983
|
-
gsdConfigPaths = await writeGsdConfig({
|
|
518
|
+
gsdConfigPaths = await (0, gsd_1.writeGsdConfig)({
|
|
3984
519
|
backendUrl,
|
|
3985
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
3986
|
-
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
520
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
521
|
+
models: resolved?.models ?? [{ id: models_1.DEFAULT_CODEX_MODEL, name: (0, models_1.modelDisplayName)(models_1.DEFAULT_CODEX_MODEL) }],
|
|
3987
522
|
apiKey: authCredential,
|
|
3988
523
|
});
|
|
3989
524
|
}
|
|
3990
525
|
if (selectedSetupClients.has("openclaw")) {
|
|
3991
526
|
progress.update("Configuring OpenClaw");
|
|
3992
|
-
const openClawResult = await setupOpenClaw({
|
|
527
|
+
const openClawResult = await (0, openclaw_1.setupOpenClaw)({
|
|
3993
528
|
backendUrl,
|
|
3994
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
3995
|
-
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
529
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
530
|
+
models: resolved?.models ?? [{ id: models_1.DEFAULT_CODEX_MODEL, name: (0, models_1.modelDisplayName)(models_1.DEFAULT_CODEX_MODEL) }],
|
|
3996
531
|
apiKey: authCredential,
|
|
3997
532
|
});
|
|
3998
533
|
openClawConfigPath = openClawResult.configPath;
|
|
@@ -4000,62 +535,66 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
4000
535
|
}
|
|
4001
536
|
if (selectedSetupClients.has("opencode")) {
|
|
4002
537
|
progress.update("Configuring OpenCode");
|
|
4003
|
-
openCodeConfigPaths = await writeOpenCodeConfig({
|
|
538
|
+
openCodeConfigPaths = await (0, opencode_family_1.writeOpenCodeConfig)({
|
|
4004
539
|
backendUrl,
|
|
4005
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
4006
|
-
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
540
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
541
|
+
models: resolved?.models ?? [{ id: models_1.DEFAULT_CODEX_MODEL, name: (0, models_1.modelDisplayName)(models_1.DEFAULT_CODEX_MODEL) }],
|
|
4007
542
|
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
4008
543
|
apiKey: authCredential,
|
|
4009
544
|
});
|
|
4010
545
|
}
|
|
4011
546
|
if (selectedSetupClients.has("kilo")) {
|
|
4012
547
|
progress.update("Configuring Kilo Code");
|
|
4013
|
-
kiloConfigPaths = await writeKiloConfig({
|
|
548
|
+
kiloConfigPaths = await (0, opencode_family_1.writeKiloConfig)({
|
|
4014
549
|
backendUrl,
|
|
4015
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
4016
|
-
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
550
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
551
|
+
models: resolved?.models ?? [{ id: models_1.DEFAULT_CODEX_MODEL, name: (0, models_1.modelDisplayName)(models_1.DEFAULT_CODEX_MODEL) }],
|
|
4017
552
|
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
4018
553
|
apiKey: authCredential,
|
|
4019
554
|
});
|
|
4020
555
|
}
|
|
4021
556
|
if (selectedSetupClients.has("roo")) {
|
|
4022
557
|
progress.update("Configuring Roo Code");
|
|
4023
|
-
rooConfigPaths = await writeRooConfig({
|
|
558
|
+
rooConfigPaths = await (0, roo_1.writeRooConfig)({
|
|
4024
559
|
backendUrl,
|
|
4025
|
-
model: resolved?.model ?? DEFAULT_ROO_MODEL,
|
|
560
|
+
model: resolved?.model ?? roo_1.DEFAULT_ROO_MODEL,
|
|
561
|
+
models: resolved?.models ?? [],
|
|
4026
562
|
apiKey: authCredential,
|
|
4027
563
|
});
|
|
4028
564
|
}
|
|
4029
565
|
if (selectedSetupClients.has("trae")) {
|
|
4030
566
|
progress.update("Configuring Trae");
|
|
4031
|
-
traeBundlePathPatched = await patchTraeBundle({
|
|
567
|
+
traeBundlePathPatched = await (0, trae_1.patchTraeBundle)({
|
|
4032
568
|
backendUrl,
|
|
4033
|
-
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
569
|
+
model: resolved?.model ?? models_1.DEFAULT_CODEX_MODEL,
|
|
4034
570
|
apiKey: authCredential,
|
|
4035
|
-
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
571
|
+
models: resolved?.models ?? [{ id: models_1.DEFAULT_CODEX_MODEL, name: (0, models_1.modelDisplayName)(models_1.DEFAULT_CODEX_MODEL) }],
|
|
4036
572
|
});
|
|
4037
573
|
}
|
|
4038
574
|
if (selectedSetupClients.has("aider")) {
|
|
4039
575
|
progress.update("Configuring Aider");
|
|
4040
|
-
aiderConfigPath = await writeAiderConfig({
|
|
576
|
+
aiderConfigPath = await (0, aider_1.writeAiderConfig)({
|
|
4041
577
|
backendUrl,
|
|
4042
|
-
model: resolved?.model ?? DEFAULT_AIDER_MODEL,
|
|
578
|
+
model: resolved?.model ?? aider_1.DEFAULT_AIDER_MODEL,
|
|
579
|
+
models: resolved?.models ?? [],
|
|
4043
580
|
apiKey: authCredential,
|
|
4044
581
|
});
|
|
4045
582
|
}
|
|
4046
583
|
if (selectedSetupClients.has("zo")) {
|
|
4047
584
|
progress.update("Configuring Zo");
|
|
4048
|
-
zoConfigName = await configureZoByok({
|
|
585
|
+
zoConfigName = await (0, zo_1.configureZoByok)({
|
|
4049
586
|
backendUrl,
|
|
4050
|
-
model: resolved?.model ?? DEFAULT_ZO_MODEL,
|
|
587
|
+
model: resolved?.model ?? zo_1.DEFAULT_ZO_MODEL,
|
|
588
|
+
models: resolved?.models ?? [],
|
|
4051
589
|
apiKey: authCredential,
|
|
4052
590
|
});
|
|
4053
591
|
}
|
|
4054
592
|
if (selectedSetupClients.has("hermes")) {
|
|
4055
593
|
progress.update("Configuring Hermes Agent");
|
|
4056
|
-
hermesConfigPaths = await writeHermesConfig({
|
|
594
|
+
hermesConfigPaths = await (0, hermes_1.writeHermesConfig)({
|
|
4057
595
|
backendUrl,
|
|
4058
|
-
model: resolved?.model ?? DEFAULT_HERMES_MODEL,
|
|
596
|
+
model: resolved?.model ?? hermes_1.DEFAULT_HERMES_MODEL,
|
|
597
|
+
models: resolved?.models ?? [],
|
|
4059
598
|
apiKey: authCredential,
|
|
4060
599
|
});
|
|
4061
600
|
}
|
|
@@ -4149,7 +688,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
4149
688
|
if (resolved?.note)
|
|
4150
689
|
this.log(resolved.note);
|
|
4151
690
|
if (selectedSetupClients.has("claude") && claudeAccess?.enabled) {
|
|
4152
|
-
this.log(`- Claude Code base URL: ${anthropicCompatibleProxyUrl(backendUrl)}`);
|
|
691
|
+
this.log(`- Claude Code base URL: ${(0, urls_1.anthropicCompatibleProxyUrl)(backendUrl)}`);
|
|
4153
692
|
if (claudeDesktop3pConfigPathManaged) {
|
|
4154
693
|
this.log(`- Claude Desktop 3P config: ${claudeDesktop3pConfigPathManaged}`);
|
|
4155
694
|
}
|
|
@@ -4157,7 +696,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
4157
696
|
else if (selectedSetupClients.has("claude") && claudeAccess?.note) {
|
|
4158
697
|
this.log(`- Claude Code: ${claudeAccess.note}`);
|
|
4159
698
|
}
|
|
4160
|
-
this.log(`- Local credential env: ${ENV_FILE}`);
|
|
699
|
+
this.log(`- Local credential env: ${env_persist_1.ENV_FILE}`);
|
|
4161
700
|
this.log(`- Shell profiles updated: ${updatedShellFiles.join(", ")}`);
|
|
4162
701
|
if (updatedFishFiles.length > 0) {
|
|
4163
702
|
this.log(`- Fish profiles updated: ${updatedFishFiles.join(", ")}`);
|
|
@@ -4287,7 +826,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
4287
826
|
this.log(`- OpenCode: configured (${openCodeConfigPaths.join(", ")})`);
|
|
4288
827
|
this.log("- OpenCode note: configured the OpenAI-compatible /v1 route and, when Claude access is available, the Anthropic-compatible /anthropic/v1 route.");
|
|
4289
828
|
this.log("- OpenCode note: plain OpenAI-compatible config is preferred. If you add a quota/auth plugin manually, use /api/codex-auth/v1/quota?format=legacy_codex for legacy Codex-style quota responses.");
|
|
4290
|
-
const openCodeProjectConfig = findOpenCodeProjectConfigFile();
|
|
829
|
+
const openCodeProjectConfig = (0, opencode_family_1.findOpenCodeProjectConfigFile)();
|
|
4291
830
|
const openCodeConfigEnv = process.env.OPENCODE_CONFIG?.trim() || "";
|
|
4292
831
|
if (openCodeConfigEnv) {
|
|
4293
832
|
this.log(`- OpenCode note: OPENCODE_CONFIG is set (${openCodeConfigEnv}). OpenCode may load that file and override your global config.`);
|