theclawbay 0.3.26 → 0.3.27

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # theclawbay
2
2
 
3
- CLI for connecting Codex, OpenClaw, OpenCode, Kilo, and experimental Trae to The Claw Bay.
3
+ CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and experimental Trae to The Claw Bay.
4
4
 
5
5
  ## Install
6
6
 
@@ -16,10 +16,17 @@ Get your API key from `https://theclawbay.com/dashboard`.
16
16
  theclawbay setup --api-key <apiKey>
17
17
  ```
18
18
 
19
- In an interactive terminal, setup shows one picker for Codex, OpenClaw, OpenCode, Kilo, and Windows Trae.
19
+ In an interactive terminal, setup shows one picker for Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and Windows Trae.
20
20
  Detected integrations are preselected when recommended, undetected ones stay visible but cannot be selected,
21
21
  and you can toggle items with arrow keys plus `Enter` or by pressing their number.
22
22
 
23
+ History notes:
24
+
25
+ - Continue setup only updates `~/.continue/config.yaml` and leaves `~/.continue/sessions` untouched.
26
+ - Cline setup only updates `~/.cline/data/globalState.json` and `~/.cline/data/secrets.json`; task history stays in `~/.cline/state` and `~/.cline/tasks`.
27
+ - Roo Code setup uses a managed auto-import file plus editor settings and does not touch task history. Imported Roo profiles can persist inside Roo after first launch.
28
+ - Aider setup appends a managed block to `~/.aider.conf.yml` and enables `restore-chat-history: true` without touching `.aider.chat.history.md`.
29
+
23
30
  ## Optional
24
31
 
25
32
  `theclawbay link --api-key <apiKey>`
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const promises_1 = __importDefault(require("node:fs/promises"));
7
7
  const node_os_1 = __importDefault(require("node:os"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const yaml_1 = require("yaml");
9
10
  const base_command_1 = require("../lib/base-command");
10
11
  const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
11
12
  const codex_history_migration_1 = require("../lib/codex-history-migration");
@@ -16,10 +17,19 @@ const DEFAULT_PROVIDER_ID = "theclawbay";
16
17
  const WAN_PROVIDER_ID = "theclawbay-wan";
17
18
  const MANAGED_START = "# theclawbay-managed:start";
18
19
  const MANAGED_END = "# theclawbay-managed:end";
20
+ const AIDER_MANAGED_START = "# theclawbay-aider-managed:start";
21
+ const AIDER_MANAGED_END = "# theclawbay-aider-managed:end";
19
22
  const SHELL_START = "# theclawbay-shell-managed:start";
20
23
  const SHELL_END = "# theclawbay-shell-managed:end";
21
- const ENV_FILE = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay", "env");
24
+ const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
22
25
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
26
+ const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
27
+ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
28
+ const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
29
+ const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
30
+ const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
31
+ const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
32
+ const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
23
33
  const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
24
34
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
25
35
  OPENAI_PROVIDER_ID,
@@ -27,6 +37,8 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
27
37
  DEFAULT_PROVIDER_ID,
28
38
  ]);
29
39
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID];
40
+ const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
41
+ const CONTINUE_MODEL_NAME = "The Claw Bay";
30
42
  const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
31
43
  const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
32
44
  function removeManagedBlock(source, start, end) {
@@ -125,6 +137,61 @@ function objectRecordOr(value, fallback) {
125
137
  return { ...value };
126
138
  return fallback;
127
139
  }
140
+ function isTheClawBayOpenAiCompatibleBaseUrl(value) {
141
+ return typeof value === "string" && value.trim().endsWith(THECLAWBAY_OPENAI_PROXY_SUFFIX);
142
+ }
143
+ async function readJsonObjectFile(filePath) {
144
+ const existingRaw = await readFileIfExists(filePath);
145
+ if (existingRaw === null || !existingRaw.trim())
146
+ return {};
147
+ try {
148
+ return objectRecordOr(JSON.parse(existingRaw), {});
149
+ }
150
+ catch {
151
+ return {};
152
+ }
153
+ }
154
+ async function writeJsonFile(filePath, value, mode) {
155
+ await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
156
+ await promises_1.default.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
157
+ if (mode !== undefined)
158
+ await promises_1.default.chmod(filePath, mode);
159
+ }
160
+ function roamingAppDataDir() {
161
+ if (process.env.APPDATA?.trim())
162
+ return process.env.APPDATA;
163
+ return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
164
+ }
165
+ function editorSettingsCandidates() {
166
+ const home = node_os_1.default.homedir();
167
+ if (node_os_1.default.platform() === "darwin") {
168
+ const appSupport = node_path_1.default.join(home, "Library", "Application Support");
169
+ return [
170
+ node_path_1.default.join(appSupport, "Code", "User", "settings.json"),
171
+ node_path_1.default.join(appSupport, "Code - Insiders", "User", "settings.json"),
172
+ node_path_1.default.join(appSupport, "Cursor", "User", "settings.json"),
173
+ node_path_1.default.join(appSupport, "Windsurf", "User", "settings.json"),
174
+ node_path_1.default.join(appSupport, "VSCodium", "User", "settings.json"),
175
+ ];
176
+ }
177
+ if (node_os_1.default.platform() === "win32") {
178
+ const appData = roamingAppDataDir();
179
+ return [
180
+ node_path_1.default.join(appData, "Code", "User", "settings.json"),
181
+ node_path_1.default.join(appData, "Code - Insiders", "User", "settings.json"),
182
+ node_path_1.default.join(appData, "Cursor", "User", "settings.json"),
183
+ node_path_1.default.join(appData, "Windsurf", "User", "settings.json"),
184
+ node_path_1.default.join(appData, "VSCodium", "User", "settings.json"),
185
+ ];
186
+ }
187
+ return [
188
+ node_path_1.default.join(home, ".config", "Code", "User", "settings.json"),
189
+ node_path_1.default.join(home, ".config", "Code - Insiders", "User", "settings.json"),
190
+ node_path_1.default.join(home, ".config", "Cursor", "User", "settings.json"),
191
+ node_path_1.default.join(home, ".config", "Windsurf", "User", "settings.json"),
192
+ node_path_1.default.join(home, ".config", "VSCodium", "User", "settings.json"),
193
+ ];
194
+ }
128
195
  async function cleanupCodexConfig() {
129
196
  const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
130
197
  const existing = await readFileIfExists(configPath);
@@ -136,6 +203,148 @@ async function cleanupCodexConfig() {
136
203
  next = removeTopLevelProviderSelection(next);
137
204
  return writeIfChanged(configPath, next, existing);
138
205
  }
206
+ async function cleanupContinueConfig() {
207
+ const existing = await readFileIfExists(CONTINUE_CONFIG_PATH);
208
+ if (existing === null || !existing.trim())
209
+ return false;
210
+ try {
211
+ const doc = (0, yaml_1.parseDocument)(existing);
212
+ const root = objectRecordOr(doc.toJS(), {});
213
+ const models = Array.isArray(root.models) ? root.models : [];
214
+ const filtered = models.filter((entry) => {
215
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
216
+ return true;
217
+ const candidate = entry;
218
+ return !(candidate.provider === "openai" &&
219
+ candidate.name === CONTINUE_MODEL_NAME &&
220
+ isTheClawBayOpenAiCompatibleBaseUrl(candidate.apiBase));
221
+ });
222
+ if (filtered.length === models.length)
223
+ return false;
224
+ doc.set("models", filtered);
225
+ await promises_1.default.writeFile(CONTINUE_CONFIG_PATH, doc.toString(), "utf8");
226
+ return true;
227
+ }
228
+ catch {
229
+ return false;
230
+ }
231
+ }
232
+ async function cleanupClineConfig() {
233
+ const globalState = await readJsonObjectFile(CLINE_GLOBAL_STATE_PATH);
234
+ const secrets = await readJsonObjectFile(CLINE_SECRETS_PATH);
235
+ const snapshotRaw = await readFileIfExists(CLINE_RESTORE_STATE_PATH);
236
+ let changed = false;
237
+ if (snapshotRaw?.trim()) {
238
+ try {
239
+ const snapshot = JSON.parse(snapshotRaw);
240
+ for (const [key, value] of Object.entries(snapshot.globalState ?? {})) {
241
+ if (value === null) {
242
+ if (key in globalState) {
243
+ delete globalState[key];
244
+ changed = true;
245
+ }
246
+ continue;
247
+ }
248
+ if (globalState[key] !== value) {
249
+ globalState[key] = value;
250
+ changed = true;
251
+ }
252
+ }
253
+ for (const [key, value] of Object.entries(snapshot.secrets ?? {})) {
254
+ if (value === null) {
255
+ if (key in secrets) {
256
+ delete secrets[key];
257
+ changed = true;
258
+ }
259
+ continue;
260
+ }
261
+ if (secrets[key] !== value) {
262
+ secrets[key] = value;
263
+ changed = true;
264
+ }
265
+ }
266
+ }
267
+ catch {
268
+ // Fall through to best-effort cleanup below.
269
+ }
270
+ }
271
+ else {
272
+ if (globalState.planModeApiProvider === OPENAI_PROVIDER_ID) {
273
+ delete globalState.planModeApiProvider;
274
+ changed = true;
275
+ }
276
+ if (globalState.actModeApiProvider === OPENAI_PROVIDER_ID) {
277
+ delete globalState.actModeApiProvider;
278
+ changed = true;
279
+ }
280
+ if (isTheClawBayOpenAiCompatibleBaseUrl(globalState.openAiBaseUrl)) {
281
+ delete globalState.openAiBaseUrl;
282
+ changed = true;
283
+ }
284
+ for (const key of ["planModeOpenAiModelId", "actModeOpenAiModelId"]) {
285
+ if (typeof globalState[key] === "string") {
286
+ delete globalState[key];
287
+ changed = true;
288
+ }
289
+ }
290
+ if (typeof secrets.openAiApiKey === "string") {
291
+ delete secrets.openAiApiKey;
292
+ changed = true;
293
+ }
294
+ }
295
+ if (changed) {
296
+ await writeJsonFile(CLINE_GLOBAL_STATE_PATH, globalState);
297
+ await writeJsonFile(CLINE_SECRETS_PATH, secrets, 0o600);
298
+ }
299
+ await removeFileIfExists(CLINE_RESTORE_STATE_PATH);
300
+ return changed;
301
+ }
302
+ async function cleanupRooConfig() {
303
+ const snapshotRaw = await readFileIfExists(ROO_SETTINGS_STATE_PATH);
304
+ let changed = false;
305
+ if (snapshotRaw?.trim()) {
306
+ try {
307
+ const entries = JSON.parse(snapshotRaw);
308
+ for (const entry of entries) {
309
+ const settings = await readJsonObjectFile(entry.settingsPath);
310
+ const currentValue = settings["roo-cline.autoImportSettingsPath"];
311
+ if (currentValue !== ROO_IMPORT_FILE)
312
+ continue;
313
+ if (entry.previousValue === null) {
314
+ delete settings["roo-cline.autoImportSettingsPath"];
315
+ }
316
+ else {
317
+ settings["roo-cline.autoImportSettingsPath"] = entry.previousValue;
318
+ }
319
+ await writeJsonFile(entry.settingsPath, settings);
320
+ changed = true;
321
+ }
322
+ }
323
+ catch {
324
+ // Fall back to common editor settings paths below.
325
+ }
326
+ }
327
+ if (!changed) {
328
+ for (const settingsPath of editorSettingsCandidates()) {
329
+ const settings = await readJsonObjectFile(settingsPath);
330
+ if (settings["roo-cline.autoImportSettingsPath"] !== ROO_IMPORT_FILE)
331
+ continue;
332
+ delete settings["roo-cline.autoImportSettingsPath"];
333
+ await writeJsonFile(settingsPath, settings);
334
+ changed = true;
335
+ }
336
+ }
337
+ const removedImport = await removeFileIfExists(ROO_IMPORT_FILE);
338
+ await removeFileIfExists(ROO_SETTINGS_STATE_PATH);
339
+ return changed || removedImport;
340
+ }
341
+ async function cleanupAiderConfig() {
342
+ const existing = await readFileIfExists(AIDER_CONFIG_PATH);
343
+ if (existing === null)
344
+ return false;
345
+ const next = removeManagedBlock(existing, AIDER_MANAGED_START, AIDER_MANAGED_END);
346
+ return writeIfChanged(AIDER_CONFIG_PATH, next, existing);
347
+ }
139
348
  async function cleanupShellFiles() {
140
349
  const updated = [];
141
350
  const shellRcPaths = [".bashrc", ".zshrc", ".profile"].map((name) => node_path_1.default.join(node_os_1.default.homedir(), name));
@@ -296,10 +505,14 @@ class LogoutCommand extends base_command_1.BaseCommand {
296
505
  const updatedShellFiles = await cleanupShellFiles();
297
506
  const updatedVsCodeHooks = await cleanupVsCodeHooks();
298
507
  const updatedCodexConfig = await cleanupCodexConfig();
508
+ const updatedContinueConfig = await cleanupContinueConfig();
509
+ const updatedClineConfig = await cleanupClineConfig();
299
510
  const updatedOpenClawConfig = await cleanupOpenClawConfig();
300
511
  const updatedOpenCodeConfig = await cleanupOpenCodeConfig();
301
512
  const updatedKiloConfig = await cleanupKiloConfig();
513
+ const updatedRooConfig = await cleanupRooConfig();
302
514
  const updatedTraeBundle = await cleanupTraeBundle();
515
+ const updatedAiderConfig = await cleanupAiderConfig();
303
516
  const sessionMigration = await (0, codex_history_migration_1.migrateSessionProviders)({
304
517
  codexHome: paths_1.codexDir,
305
518
  migrationStateFile: MIGRATION_STATE_FILE,
@@ -368,10 +581,14 @@ class LogoutCommand extends base_command_1.BaseCommand {
368
581
  else {
369
582
  this.log("- Codex model cache: no cleanup needed.");
370
583
  }
584
+ this.log(`- Continue config cleaned: ${updatedContinueConfig ? "yes" : "no"}`);
585
+ this.log(`- Cline config cleaned: ${updatedClineConfig ? "yes" : "no"}`);
371
586
  this.log(`- OpenClaw config cleaned: ${updatedOpenClawConfig ? "yes" : "no"}`);
372
587
  this.log(`- OpenCode config cleaned: ${updatedOpenCodeConfig ? "yes" : "no"}`);
373
588
  this.log(`- Kilo config cleaned: ${updatedKiloConfig ? "yes" : "no"}`);
589
+ this.log(`- Roo Code setup hook cleaned: ${updatedRooConfig ? "yes" : "no"}`);
374
590
  this.log(`- Trae experimental patch cleaned: ${updatedTraeBundle ? "yes" : "no"}`);
591
+ this.log(`- Aider config cleaned: ${updatedAiderConfig ? "yes" : "no"}`);
375
592
  this.log("Note: restart terminals/VS Code windows to clear already-loaded shell environment.");
376
593
  });
377
594
  }
@@ -10,6 +10,7 @@ const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_readline_1 = require("node:readline");
11
11
  const node_child_process_1 = require("node:child_process");
12
12
  const core_1 = require("@oclif/core");
13
+ const yaml_1 = require("yaml");
13
14
  const base_command_1 = require("../lib/base-command");
14
15
  const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
15
16
  const codex_history_migration_1 = require("../lib/codex-history-migration");
@@ -25,22 +26,40 @@ const CLI_HTTP_USER_AGENT = "theclawbay-cli";
25
26
  const SUPPORTED_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
26
27
  const MODEL_DISPLAY_NAMES = (0, supported_models_1.getSupportedModelDisplayNames)();
27
28
  const DEFAULT_CODEX_MODEL = SUPPORTED_MODEL_IDS[0] ?? "gpt-5.4";
29
+ const DEFAULT_CONTINUE_MODEL = DEFAULT_CODEX_MODEL;
30
+ const DEFAULT_CLINE_MODEL = DEFAULT_CODEX_MODEL;
28
31
  const DEFAULT_OPENCLAW_MODEL = DEFAULT_CODEX_MODEL;
29
32
  const DEFAULT_KILO_MODEL = DEFAULT_CODEX_MODEL;
33
+ const DEFAULT_ROO_MODEL = DEFAULT_CODEX_MODEL;
30
34
  const DEFAULT_TRAE_MODEL = DEFAULT_CODEX_MODEL;
35
+ const DEFAULT_AIDER_MODEL = DEFAULT_CODEX_MODEL;
31
36
  const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
32
37
  const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
33
38
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
34
- const ENV_FILE = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay", "env");
39
+ const ENV_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "env");
40
+ const CONTINUE_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.yaml");
41
+ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "globalState.json");
42
+ const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
43
+ const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
44
+ const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
45
+ const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
46
+ const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
35
47
  const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
36
48
  const MANAGED_START = "# theclawbay-managed:start";
37
49
  const MANAGED_END = "# theclawbay-managed:end";
50
+ const AIDER_MANAGED_START = "# theclawbay-aider-managed:start";
51
+ const AIDER_MANAGED_END = "# theclawbay-aider-managed:end";
38
52
  const SHELL_START = "# theclawbay-shell-managed:start";
39
53
  const SHELL_END = "# theclawbay-shell-managed:end";
40
54
  const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
41
55
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
42
56
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
43
- const SETUP_CLIENT_IDS = ["codex", "openclaw", "opencode", "kilo", "trae"];
57
+ const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider"];
58
+ const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
59
+ const CONTINUE_MODEL_NAME = "The Claw Bay";
60
+ const ROO_PROFILE_NAME = "The Claw Bay";
61
+ const ROO_PROFILE_ID = "theclawbay-openai-compatible";
62
+ const ROO_MODE_SLUGS = ["architect", "code", "ask", "debug", "orchestrator"];
44
63
  const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
45
64
  const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
46
65
  const TRAE_TARGET_FUNCTION_START = "async setOriginModelListMapAndCache(e,t=!0){";
@@ -175,6 +194,168 @@ function shellQuote(value) {
175
194
  function modelDisplayName(modelId) {
176
195
  return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
177
196
  }
197
+ function openAiCompatibleProxyUrl(backendUrl) {
198
+ return `${trimTrailingSlash(backendUrl)}${THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
199
+ }
200
+ function isTheClawBayOpenAiCompatibleBaseUrl(value) {
201
+ return typeof value === "string" && value.trim().endsWith(THECLAWBAY_OPENAI_PROXY_SUFFIX);
202
+ }
203
+ function roamingAppDataDir() {
204
+ if (process.env.APPDATA?.trim())
205
+ return process.env.APPDATA;
206
+ return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
207
+ }
208
+ async function readFileIfExists(filePath) {
209
+ try {
210
+ return await promises_1.default.readFile(filePath, "utf8");
211
+ }
212
+ catch (error) {
213
+ const err = error;
214
+ if (err.code === "ENOENT")
215
+ return null;
216
+ throw error;
217
+ }
218
+ }
219
+ async function readJsonObjectFile(filePath) {
220
+ const existingRaw = await readFileIfExists(filePath);
221
+ if (existingRaw === null || !existingRaw.trim())
222
+ return {};
223
+ try {
224
+ return objectRecordOr(JSON.parse(existingRaw), {});
225
+ }
226
+ catch {
227
+ throw new Error(`invalid JSON in ${filePath}`);
228
+ }
229
+ }
230
+ async function writeJsonObjectFile(filePath, doc, mode) {
231
+ await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
232
+ await promises_1.default.writeFile(filePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
233
+ if (mode !== undefined)
234
+ await promises_1.default.chmod(filePath, mode);
235
+ }
236
+ function editorHosts() {
237
+ const home = node_os_1.default.homedir();
238
+ if (node_os_1.default.platform() === "darwin") {
239
+ const appSupport = node_path_1.default.join(home, "Library", "Application Support");
240
+ return [
241
+ {
242
+ id: "vscode",
243
+ label: "VS Code",
244
+ extensionDir: node_path_1.default.join(home, ".vscode", "extensions"),
245
+ userSettingsPath: node_path_1.default.join(appSupport, "Code", "User", "settings.json"),
246
+ },
247
+ {
248
+ id: "vscode-insiders",
249
+ label: "VS Code Insiders",
250
+ extensionDir: node_path_1.default.join(home, ".vscode-insiders", "extensions"),
251
+ userSettingsPath: node_path_1.default.join(appSupport, "Code - Insiders", "User", "settings.json"),
252
+ },
253
+ {
254
+ id: "cursor",
255
+ label: "Cursor",
256
+ extensionDir: node_path_1.default.join(home, ".cursor", "extensions"),
257
+ userSettingsPath: node_path_1.default.join(appSupport, "Cursor", "User", "settings.json"),
258
+ },
259
+ {
260
+ id: "windsurf",
261
+ label: "Windsurf",
262
+ extensionDir: node_path_1.default.join(home, ".windsurf", "extensions"),
263
+ userSettingsPath: node_path_1.default.join(appSupport, "Windsurf", "User", "settings.json"),
264
+ },
265
+ {
266
+ id: "vscodium",
267
+ label: "VSCodium",
268
+ extensionDir: node_path_1.default.join(home, ".vscode-oss", "extensions"),
269
+ userSettingsPath: node_path_1.default.join(appSupport, "VSCodium", "User", "settings.json"),
270
+ },
271
+ ];
272
+ }
273
+ if (node_os_1.default.platform() === "win32") {
274
+ const homeDir = process.env.USERPROFILE?.trim() || home;
275
+ const appData = roamingAppDataDir();
276
+ return [
277
+ {
278
+ id: "vscode",
279
+ label: "VS Code",
280
+ extensionDir: node_path_1.default.join(homeDir, ".vscode", "extensions"),
281
+ userSettingsPath: node_path_1.default.join(appData, "Code", "User", "settings.json"),
282
+ },
283
+ {
284
+ id: "vscode-insiders",
285
+ label: "VS Code Insiders",
286
+ extensionDir: node_path_1.default.join(homeDir, ".vscode-insiders", "extensions"),
287
+ userSettingsPath: node_path_1.default.join(appData, "Code - Insiders", "User", "settings.json"),
288
+ },
289
+ {
290
+ id: "cursor",
291
+ label: "Cursor",
292
+ extensionDir: node_path_1.default.join(homeDir, ".cursor", "extensions"),
293
+ userSettingsPath: node_path_1.default.join(appData, "Cursor", "User", "settings.json"),
294
+ },
295
+ {
296
+ id: "windsurf",
297
+ label: "Windsurf",
298
+ extensionDir: node_path_1.default.join(homeDir, ".windsurf", "extensions"),
299
+ userSettingsPath: node_path_1.default.join(appData, "Windsurf", "User", "settings.json"),
300
+ },
301
+ {
302
+ id: "vscodium",
303
+ label: "VSCodium",
304
+ extensionDir: node_path_1.default.join(homeDir, ".vscode-oss", "extensions"),
305
+ userSettingsPath: node_path_1.default.join(appData, "VSCodium", "User", "settings.json"),
306
+ },
307
+ ];
308
+ }
309
+ return [
310
+ {
311
+ id: "vscode",
312
+ label: "VS Code",
313
+ extensionDir: node_path_1.default.join(home, ".vscode", "extensions"),
314
+ userSettingsPath: node_path_1.default.join(home, ".config", "Code", "User", "settings.json"),
315
+ },
316
+ {
317
+ id: "vscode-insiders",
318
+ label: "VS Code Insiders",
319
+ extensionDir: node_path_1.default.join(home, ".vscode-insiders", "extensions"),
320
+ userSettingsPath: node_path_1.default.join(home, ".config", "Code - Insiders", "User", "settings.json"),
321
+ },
322
+ {
323
+ id: "cursor",
324
+ label: "Cursor",
325
+ extensionDir: node_path_1.default.join(home, ".cursor", "extensions"),
326
+ userSettingsPath: node_path_1.default.join(home, ".config", "Cursor", "User", "settings.json"),
327
+ },
328
+ {
329
+ id: "windsurf",
330
+ label: "Windsurf",
331
+ extensionDir: node_path_1.default.join(home, ".windsurf", "extensions"),
332
+ userSettingsPath: node_path_1.default.join(home, ".config", "Windsurf", "User", "settings.json"),
333
+ },
334
+ {
335
+ id: "vscodium",
336
+ label: "VSCodium",
337
+ extensionDir: node_path_1.default.join(home, ".vscode-oss", "extensions"),
338
+ userSettingsPath: node_path_1.default.join(home, ".config", "VSCodium", "User", "settings.json"),
339
+ },
340
+ ];
341
+ }
342
+ async function detectExtensionHosts(prefixes) {
343
+ const matches = [];
344
+ for (const host of editorHosts()) {
345
+ if (!(await pathExists(host.extensionDir)))
346
+ continue;
347
+ const entries = (0, node_fs_1.readdirSync)(host.extensionDir, { withFileTypes: true });
348
+ const found = entries.some((entry) => {
349
+ if (!entry.isDirectory())
350
+ return false;
351
+ const normalized = entry.name.toLowerCase();
352
+ return prefixes.some((prefix) => normalized.startsWith(prefix));
353
+ });
354
+ if (found)
355
+ matches.push(host);
356
+ }
357
+ return matches;
358
+ }
178
359
  function parseSetupClientFlags(raw) {
179
360
  if (!raw)
180
361
  return null;
@@ -386,6 +567,35 @@ async function detectCodexClient() {
386
567
  }
387
568
  return false;
388
569
  }
570
+ async function detectContinueClient() {
571
+ if (hasCommand("cn") || hasCommand("continue"))
572
+ return true;
573
+ const candidates = [
574
+ CONTINUE_CONFIG_PATH,
575
+ node_path_1.default.join(node_os_1.default.homedir(), ".continue", "config.json"),
576
+ node_path_1.default.join(node_os_1.default.homedir(), ".continue", "sessions"),
577
+ ];
578
+ for (const candidate of candidates) {
579
+ if (await pathExists(candidate))
580
+ return true;
581
+ }
582
+ return (await detectExtensionHosts(["continue.continue-"])).length > 0;
583
+ }
584
+ async function detectClineClient() {
585
+ if (hasCommand("cline"))
586
+ return true;
587
+ const candidates = [
588
+ CLINE_GLOBAL_STATE_PATH,
589
+ CLINE_SECRETS_PATH,
590
+ node_path_1.default.join(node_os_1.default.homedir(), ".cline", "state", "taskHistory.json"),
591
+ node_path_1.default.join(node_os_1.default.homedir(), ".cline", "tasks"),
592
+ ];
593
+ for (const candidate of candidates) {
594
+ if (await pathExists(candidate))
595
+ return true;
596
+ }
597
+ return (await detectExtensionHosts(["saoudrizwan.claude-dev-"])).length > 0;
598
+ }
389
599
  async function detectKiloClient() {
390
600
  if (hasCommand("kilo"))
391
601
  return true;
@@ -395,12 +605,30 @@ async function detectKiloClient() {
395
605
  }
396
606
  return false;
397
607
  }
608
+ async function detectRooClient() {
609
+ return (await detectExtensionHosts(["rooveterinaryinc.roo-cline-"])).length > 0;
610
+ }
398
611
  async function detectTraeClient() {
399
612
  const bundlePath = traeBundlePath();
400
613
  if (!bundlePath)
401
614
  return false;
402
615
  return pathExists(bundlePath);
403
616
  }
617
+ async function detectAiderClient() {
618
+ if (hasCommand("aider"))
619
+ return true;
620
+ const candidates = [
621
+ AIDER_CONFIG_PATH,
622
+ node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yaml"),
623
+ node_path_1.default.join(process.cwd(), ".aider.conf.yml"),
624
+ node_path_1.default.join(process.cwd(), ".aider.chat.history.md"),
625
+ ];
626
+ for (const candidate of candidates) {
627
+ if (await pathExists(candidate))
628
+ return true;
629
+ }
630
+ return false;
631
+ }
404
632
  async function resolveModels(backendUrl, apiKey) {
405
633
  const ids = await fetchBackendModelIds(backendUrl, apiKey);
406
634
  const available = new Set(ids ?? []);
@@ -468,6 +696,146 @@ async function writeCodexConfig(params) {
468
696
  await promises_1.default.writeFile(configPath, next, "utf8");
469
697
  return configPath;
470
698
  }
699
+ function isManagedContinueModel(model, proxyUrl) {
700
+ if (typeof model !== "object" || model === null || Array.isArray(model))
701
+ return false;
702
+ const candidate = model;
703
+ return (candidate.provider === "openai" &&
704
+ candidate.name === CONTINUE_MODEL_NAME &&
705
+ candidate.apiBase === proxyUrl);
706
+ }
707
+ async function writeContinueConfig(params) {
708
+ const configPath = CONTINUE_CONFIG_PATH;
709
+ await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
710
+ const existingRaw = (await readFileIfExists(configPath)) ?? "";
711
+ const proxyUrl = openAiCompatibleProxyUrl(params.backendUrl);
712
+ const doc = (0, yaml_1.parseDocument)(existingRaw);
713
+ const docRoot = objectRecordOr(doc.toJS(), {});
714
+ const models = Array.isArray(docRoot.models)
715
+ ? docRoot.models.filter((entry) => !isManagedContinueModel(entry, proxyUrl))
716
+ : [];
717
+ models.unshift({
718
+ name: CONTINUE_MODEL_NAME,
719
+ provider: "openai",
720
+ model: params.model.trim() || DEFAULT_CONTINUE_MODEL,
721
+ apiKey: params.apiKey,
722
+ apiBase: proxyUrl,
723
+ roles: ["chat", "edit", "apply"],
724
+ });
725
+ if (typeof docRoot.name !== "string" || !docRoot.name.trim()) {
726
+ doc.set("name", "Local Config");
727
+ }
728
+ if (typeof docRoot.version !== "string" || !docRoot.version.trim()) {
729
+ doc.set("version", "1.0.0");
730
+ }
731
+ doc.set("models", models);
732
+ await promises_1.default.writeFile(configPath, doc.toString(), "utf8");
733
+ return configPath;
734
+ }
735
+ function isManagedClineState(doc) {
736
+ return (doc.planModeApiProvider === "openai" &&
737
+ doc.actModeApiProvider === "openai" &&
738
+ isTheClawBayOpenAiCompatibleBaseUrl(doc.openAiBaseUrl));
739
+ }
740
+ async function writeClineConfig(params) {
741
+ const proxyUrl = openAiCompatibleProxyUrl(params.backendUrl);
742
+ const globalState = await readJsonObjectFile(CLINE_GLOBAL_STATE_PATH);
743
+ const secrets = await readJsonObjectFile(CLINE_SECRETS_PATH);
744
+ const alreadyManaged = isManagedClineState(globalState) && typeof secrets.openAiApiKey === "string";
745
+ if (!alreadyManaged) {
746
+ const snapshot = {
747
+ globalState: {
748
+ planModeApiProvider: "planModeApiProvider" in globalState ? globalState.planModeApiProvider : null,
749
+ actModeApiProvider: "actModeApiProvider" in globalState ? globalState.actModeApiProvider : null,
750
+ openAiBaseUrl: "openAiBaseUrl" in globalState ? globalState.openAiBaseUrl : null,
751
+ planModeOpenAiModelId: "planModeOpenAiModelId" in globalState ? globalState.planModeOpenAiModelId : null,
752
+ actModeOpenAiModelId: "actModeOpenAiModelId" in globalState ? globalState.actModeOpenAiModelId : null,
753
+ },
754
+ secrets: {
755
+ openAiApiKey: typeof secrets.openAiApiKey === "string" ? secrets.openAiApiKey : null,
756
+ },
757
+ };
758
+ await writeJsonObjectFile(CLINE_RESTORE_STATE_PATH, snapshot, 0o600);
759
+ }
760
+ globalState.planModeApiProvider = "openai";
761
+ globalState.actModeApiProvider = "openai";
762
+ globalState.openAiBaseUrl = proxyUrl;
763
+ globalState.planModeOpenAiModelId = params.model.trim() || DEFAULT_CLINE_MODEL;
764
+ globalState.actModeOpenAiModelId = params.model.trim() || DEFAULT_CLINE_MODEL;
765
+ secrets.openAiApiKey = params.apiKey;
766
+ await writeJsonObjectFile(CLINE_GLOBAL_STATE_PATH, globalState);
767
+ await writeJsonObjectFile(CLINE_SECRETS_PATH, secrets, 0o600);
768
+ return [CLINE_GLOBAL_STATE_PATH, CLINE_SECRETS_PATH];
769
+ }
770
+ async function writeRooConfig(params) {
771
+ const hosts = await detectExtensionHosts(["rooveterinaryinc.roo-cline-"]);
772
+ if (hosts.length === 0) {
773
+ throw new Error("Roo Code was not detected in a supported editor profile.");
774
+ }
775
+ const existingSnapshotRaw = await readFileIfExists(ROO_SETTINGS_STATE_PATH);
776
+ let existingSnapshot = [];
777
+ if (existingSnapshotRaw?.trim()) {
778
+ try {
779
+ existingSnapshot = JSON.parse(existingSnapshotRaw);
780
+ }
781
+ catch {
782
+ existingSnapshot = [];
783
+ }
784
+ }
785
+ const snapshotByPath = new Map(existingSnapshot.map((entry) => [entry.settingsPath, entry]));
786
+ const managedPaths = [];
787
+ for (const host of hosts) {
788
+ const existed = await pathExists(host.userSettingsPath);
789
+ const settings = await readJsonObjectFile(host.userSettingsPath);
790
+ const currentValue = typeof settings["roo-cline.autoImportSettingsPath"] === "string"
791
+ ? settings["roo-cline.autoImportSettingsPath"]
792
+ : null;
793
+ if (currentValue !== ROO_IMPORT_FILE) {
794
+ snapshotByPath.set(host.userSettingsPath, {
795
+ settingsPath: host.userSettingsPath,
796
+ existed,
797
+ previousValue: currentValue,
798
+ });
799
+ }
800
+ settings["roo-cline.autoImportSettingsPath"] = ROO_IMPORT_FILE;
801
+ await writeJsonObjectFile(host.userSettingsPath, settings);
802
+ managedPaths.push(host.userSettingsPath);
803
+ }
804
+ const providerConfigId = ROO_PROFILE_ID;
805
+ const rooImport = {
806
+ providerProfiles: {
807
+ currentApiConfigName: ROO_PROFILE_NAME,
808
+ apiConfigs: {
809
+ [ROO_PROFILE_NAME]: {
810
+ id: providerConfigId,
811
+ apiProvider: "openai",
812
+ openAiBaseUrl: openAiCompatibleProxyUrl(params.backendUrl),
813
+ openAiApiKey: params.apiKey,
814
+ openAiModelId: params.model.trim() || DEFAULT_ROO_MODEL,
815
+ },
816
+ },
817
+ modeApiConfigs: Object.fromEntries(ROO_MODE_SLUGS.map((slug) => [slug, providerConfigId])),
818
+ },
819
+ };
820
+ await writeJsonObjectFile(ROO_IMPORT_FILE, rooImport, 0o600);
821
+ await writeJsonObjectFile(ROO_SETTINGS_STATE_PATH, Array.from(snapshotByPath.values()), 0o600);
822
+ return [ROO_IMPORT_FILE, ...managedPaths];
823
+ }
824
+ async function writeAiderConfig(params) {
825
+ const existing = (await readFileIfExists(AIDER_CONFIG_PATH)) ?? "";
826
+ const cleaned = removeManagedBlock(existing, AIDER_MANAGED_START, AIDER_MANAGED_END);
827
+ const next = appendManagedBlock(cleaned, [
828
+ AIDER_MANAGED_START,
829
+ `model: ${JSON.stringify(`openai/${params.model.trim() || DEFAULT_AIDER_MODEL}`)}`,
830
+ `openai-api-key: ${JSON.stringify(params.apiKey)}`,
831
+ `openai-api-base: ${JSON.stringify(openAiCompatibleProxyUrl(params.backendUrl))}`,
832
+ "restore-chat-history: true",
833
+ AIDER_MANAGED_END,
834
+ ]);
835
+ await promises_1.default.mkdir(node_path_1.default.dirname(AIDER_CONFIG_PATH), { recursive: true });
836
+ await promises_1.default.writeFile(AIDER_CONFIG_PATH, next, "utf8");
837
+ return AIDER_CONFIG_PATH;
838
+ }
471
839
  async function persistApiKeyEnv(apiKey) {
472
840
  const envDir = node_path_1.default.dirname(ENV_FILE);
473
841
  await promises_1.default.mkdir(envDir, { recursive: true });
@@ -743,10 +1111,14 @@ class SetupCommand extends base_command_1.BaseCommand {
743
1111
  throw new Error('API key is required. Run "theclawbay setup --api-key <key>".');
744
1112
  const backendRaw = flags.backend ?? (0, api_key_1.tryInferBackendUrlFromApiKey)(apiKey) ?? managed?.backendUrl ?? DEFAULT_BACKEND_URL;
745
1113
  const backendUrl = normalizeUrl(backendRaw, "--backend");
746
- const [codexDetected, kiloDetected, traeDetected] = await Promise.all([
1114
+ const [codexDetected, continueDetected, clineDetected, kiloDetected, rooDetected, traeDetected, aiderDetected] = await Promise.all([
747
1115
  detectCodexClient(),
1116
+ detectContinueClient(),
1117
+ detectClineClient(),
748
1118
  detectKiloClient(),
1119
+ detectRooClient(),
749
1120
  detectTraeClient(),
1121
+ detectAiderClient(),
750
1122
  ]);
751
1123
  const setupClients = [
752
1124
  {
@@ -755,6 +1127,18 @@ class SetupCommand extends base_command_1.BaseCommand {
755
1127
  detected: codexDetected,
756
1128
  recommended: true,
757
1129
  },
1130
+ {
1131
+ id: "continue",
1132
+ label: "Continue",
1133
+ detected: continueDetected,
1134
+ recommended: true,
1135
+ },
1136
+ {
1137
+ id: "cline",
1138
+ label: "Cline",
1139
+ detected: clineDetected,
1140
+ recommended: true,
1141
+ },
758
1142
  {
759
1143
  id: "openclaw",
760
1144
  label: "OpenClaw",
@@ -773,12 +1157,24 @@ class SetupCommand extends base_command_1.BaseCommand {
773
1157
  detected: kiloDetected,
774
1158
  recommended: true,
775
1159
  },
1160
+ {
1161
+ id: "roo",
1162
+ label: "Roo Code",
1163
+ detected: rooDetected,
1164
+ recommended: true,
1165
+ },
776
1166
  {
777
1167
  id: "trae",
778
1168
  label: "Trae (experimental)",
779
1169
  detected: traeDetected,
780
1170
  recommended: false,
781
1171
  },
1172
+ {
1173
+ id: "aider",
1174
+ label: "Aider",
1175
+ detected: aiderDetected,
1176
+ recommended: true,
1177
+ },
782
1178
  ];
783
1179
  const selectedSetupClients = await resolveSetupClientSelection({
784
1180
  setupClients,
@@ -797,11 +1193,15 @@ class SetupCommand extends base_command_1.BaseCommand {
797
1193
  let authSeed = null;
798
1194
  let stateDbMigration = null;
799
1195
  let modelCacheMigration = null;
1196
+ let continueConfigPath = null;
1197
+ let clineConfigPaths = [];
800
1198
  let openClawConfigPath = null;
801
1199
  let openClawCliWarning = null;
802
1200
  let openCodePath = null;
803
1201
  let kiloConfigPath = null;
1202
+ let rooConfigPaths = [];
804
1203
  let traeBundlePathPatched = null;
1204
+ let aiderConfigPath = null;
805
1205
  if (selectedSetupClients.has("codex")) {
806
1206
  codexConfigPath = await writeCodexConfig({ backendUrl, model: resolved?.model ?? DEFAULT_CODEX_MODEL, apiKey });
807
1207
  updatedVsCodeEnvFiles = await persistVsCodeServerEnvSource();
@@ -823,6 +1223,20 @@ class SetupCommand extends base_command_1.BaseCommand {
823
1223
  codexHome: paths_1.codexDir,
824
1224
  });
825
1225
  }
1226
+ if (selectedSetupClients.has("continue")) {
1227
+ continueConfigPath = await writeContinueConfig({
1228
+ backendUrl,
1229
+ model: resolved?.model ?? DEFAULT_CONTINUE_MODEL,
1230
+ apiKey,
1231
+ });
1232
+ }
1233
+ if (selectedSetupClients.has("cline")) {
1234
+ clineConfigPaths = await writeClineConfig({
1235
+ backendUrl,
1236
+ model: resolved?.model ?? DEFAULT_CLINE_MODEL,
1237
+ apiKey,
1238
+ });
1239
+ }
826
1240
  if (selectedSetupClients.has("openclaw")) {
827
1241
  const openClawResult = await setupOpenClaw({
828
1242
  backendUrl,
@@ -849,6 +1263,13 @@ class SetupCommand extends base_command_1.BaseCommand {
849
1263
  apiKey,
850
1264
  });
851
1265
  }
1266
+ if (selectedSetupClients.has("roo")) {
1267
+ rooConfigPaths = await writeRooConfig({
1268
+ backendUrl,
1269
+ model: resolved?.model ?? DEFAULT_ROO_MODEL,
1270
+ apiKey,
1271
+ });
1272
+ }
852
1273
  if (selectedSetupClients.has("trae")) {
853
1274
  traeBundlePathPatched = await patchTraeBundle({
854
1275
  backendUrl,
@@ -857,6 +1278,13 @@ class SetupCommand extends base_command_1.BaseCommand {
857
1278
  models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
858
1279
  });
859
1280
  }
1281
+ if (selectedSetupClients.has("aider")) {
1282
+ aiderConfigPath = await writeAiderConfig({
1283
+ backendUrl,
1284
+ model: resolved?.model ?? DEFAULT_AIDER_MODEL,
1285
+ apiKey,
1286
+ });
1287
+ }
860
1288
  this.log("Setup complete");
861
1289
  this.log(`- Managed config: ${paths_1.managedConfigPath}`);
862
1290
  this.log(`- Backend: ${backendUrl}`);
@@ -919,6 +1347,26 @@ class SetupCommand extends base_command_1.BaseCommand {
919
1347
  else {
920
1348
  this.log("- Codex: not detected (skipped)");
921
1349
  }
1350
+ if (selectedSetupClients.has("continue")) {
1351
+ this.log(`- Continue: configured (${continueConfigPath})`);
1352
+ this.log("- Continue history: left ~/.continue/sessions untouched.");
1353
+ }
1354
+ else if (continueDetected) {
1355
+ this.log("- Continue: detected but skipped");
1356
+ }
1357
+ else {
1358
+ this.log("- Continue: not detected (skipped)");
1359
+ }
1360
+ if (selectedSetupClients.has("cline")) {
1361
+ this.log(`- Cline: configured (${clineConfigPaths.join(", ")})`);
1362
+ this.log("- Cline history: left ~/.cline/state and ~/.cline/tasks untouched.");
1363
+ }
1364
+ else if (clineDetected) {
1365
+ this.log("- Cline: detected but skipped");
1366
+ }
1367
+ else {
1368
+ this.log("- Cline: not detected (skipped)");
1369
+ }
922
1370
  const openClawDetected = setupClients.find((client) => client.id === "openclaw")?.detected ?? false;
923
1371
  if (selectedSetupClients.has("openclaw")) {
924
1372
  this.log(`- OpenClaw: configured (${openClawConfigPath})`);
@@ -950,6 +1398,17 @@ class SetupCommand extends base_command_1.BaseCommand {
950
1398
  else {
951
1399
  this.log("- Kilo Code: not detected (skipped)");
952
1400
  }
1401
+ if (selectedSetupClients.has("roo")) {
1402
+ this.log(`- Roo Code: configured (${rooConfigPaths.join(", ")})`);
1403
+ this.log("- Roo Code history: left existing task history untouched.");
1404
+ this.log("- Roo Code note: imported profiles can persist inside Roo after first launch even after logout.");
1405
+ }
1406
+ else if (rooDetected) {
1407
+ this.log("- Roo Code: detected but skipped");
1408
+ }
1409
+ else {
1410
+ this.log("- Roo Code: not detected (skipped)");
1411
+ }
953
1412
  if (selectedSetupClients.has("trae")) {
954
1413
  this.log(`- Trae: experimental bundle patch installed (${traeBundlePathPatched})`);
955
1414
  this.log(`- Trae: replaced Builder and SoloCoder model lists with ${resolved?.models.length ?? 1} TheClawBay models.`);
@@ -961,6 +1420,16 @@ class SetupCommand extends base_command_1.BaseCommand {
961
1420
  else {
962
1421
  this.log("- Trae: not detected (skipped)");
963
1422
  }
1423
+ if (selectedSetupClients.has("aider")) {
1424
+ this.log(`- Aider: configured (${aiderConfigPath})`);
1425
+ this.log("- Aider history: restore-chat-history enabled; existing .aider chat files were left untouched.");
1426
+ }
1427
+ else if (aiderDetected) {
1428
+ this.log("- Aider: detected but skipped");
1429
+ }
1430
+ else {
1431
+ this.log("- Aider: not detected (skipped)");
1432
+ }
964
1433
  if (authSeed?.action === "seeded") {
965
1434
  this.log("- Codex login state: seeded local API-key auth for Codex UI.");
966
1435
  }
@@ -979,11 +1448,11 @@ class SetupCommand extends base_command_1.BaseCommand {
979
1448
  else if (authSeed) {
980
1449
  this.log("- Codex login state: no change.");
981
1450
  }
982
- this.log("Next: restart terminal/VS Code window only if you rely on the The Claw Bay API key in your own scripts.");
1451
+ this.log("Next: restart Continue, Cline, Roo Code, or Trae only if they were already open during setup.");
983
1452
  });
984
1453
  }
985
1454
  }
986
- SetupCommand.description = "One-time setup: configure Codex and any detected OpenClaw, OpenCode, Kilo, or experimental Trae installs to use The Claw Bay";
1455
+ SetupCommand.description = "One-time setup: configure Codex plus any detected Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Trae, or Aider installs to use The Claw Bay";
987
1456
  SetupCommand.flags = {
988
1457
  backend: core_1.Flags.string({
989
1458
  required: false,
@@ -996,7 +1465,7 @@ SetupCommand.flags = {
996
1465
  }),
997
1466
  clients: core_1.Flags.string({
998
1467
  required: false,
999
- description: "Detected local clients to configure: codex, openclaw, opencode, kilo, trae",
1468
+ description: "Detected local clients to configure: codex, continue, cline, openclaw, opencode, kilo, roo, trae, aider",
1000
1469
  }),
1001
1470
  yes: core_1.Flags.boolean({
1002
1471
  required: false,
@@ -1,4 +1,6 @@
1
1
  export declare const codexDir: string;
2
+ export declare const theclawbayConfigDir: string;
3
+ export declare const theclawbayStateDir: string;
2
4
  export declare const managedConfigPath: string;
3
5
  export declare const updateCheckStatePath: string;
4
6
  export declare const legacyManagedConfigPathClayBay: string;
@@ -3,10 +3,12 @@ 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
- exports.legacyManagedConfigPathCodexAuth = exports.legacyManagedConfigPathClayBay = exports.updateCheckStatePath = exports.managedConfigPath = exports.codexDir = void 0;
6
+ exports.legacyManagedConfigPathCodexAuth = exports.legacyManagedConfigPathClayBay = exports.updateCheckStatePath = exports.managedConfigPath = exports.theclawbayStateDir = exports.theclawbayConfigDir = exports.codexDir = void 0;
7
7
  const node_os_1 = __importDefault(require("node:os"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  exports.codexDir = node_path_1.default.join(node_os_1.default.homedir(), ".codex");
10
+ exports.theclawbayConfigDir = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay");
11
+ exports.theclawbayStateDir = node_path_1.default.join(exports.theclawbayConfigDir, "state");
10
12
  exports.managedConfigPath = node_path_1.default.join(exports.codexDir, "theclawbay.managed.json");
11
13
  exports.updateCheckStatePath = node_path_1.default.join(exports.codexDir, "theclawbay.update-check.json");
12
14
  exports.legacyManagedConfigPathClayBay = node_path_1.default.join(exports.codexDir, "theclaybay.managed.json");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.26",
4
- "description": "CLI for connecting Codex, OpenClaw, OpenCode, Kilo, and experimental Trae to The Claw Bay.",
3
+ "version": "0.3.27",
4
+ "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and experimental Trae to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "theclawbay": "dist/index.js"
@@ -32,15 +32,20 @@
32
32
  "cli",
33
33
  "setup",
34
34
  "api-key",
35
+ "continue",
36
+ "cline",
35
37
  "openclaw",
36
38
  "opencode",
37
39
  "kilo",
40
+ "roo",
41
+ "aider",
38
42
  "trae"
39
43
  ],
40
44
  "preferGlobal": true,
41
45
  "dependencies": {
42
46
  "@oclif/core": "^4.8.0",
43
- "tslib": "^2.8.1"
47
+ "tslib": "^2.8.1",
48
+ "yaml": "^2.8.2"
44
49
  },
45
50
  "devDependencies": {
46
51
  "@types/node": "^20.17.0",