theclawbay 0.3.32 → 0.3.33

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
@@ -18,6 +18,7 @@ theclawbay setup --api-key <apiKey>
18
18
 
19
19
  In an interactive terminal, setup shows one picker for Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and Windows Trae.
20
20
  Each row includes a terminal-friendly icon plus the tool's official site, and you can toggle items with arrow keys plus `Enter` or by pressing their number.
21
+ Re-running setup also migrates legacy OpenCode and Kilo patches to the current reasoning-capable provider path and refreshes OpenClaw model metadata.
21
22
 
22
23
  ## Optional
23
24
 
@@ -13,6 +13,7 @@ const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
13
13
  const codex_history_migration_1 = require("../lib/codex-history-migration");
14
14
  const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
15
15
  const paths_1 = require("../lib/config/paths");
16
+ const supported_models_1 = require("../lib/supported-models");
16
17
  const OPENAI_PROVIDER_ID = "openai";
17
18
  const DEFAULT_PROVIDER_ID = "theclawbay";
18
19
  const WAN_PROVIDER_ID = "theclawbay-wan";
@@ -29,6 +30,8 @@ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homed
29
30
  const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
30
31
  const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
31
32
  const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
33
+ const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
34
+ const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
32
35
  const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
33
36
  const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
34
37
  const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
@@ -39,6 +42,7 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
39
42
  ]);
40
43
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID];
41
44
  const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
45
+ const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
42
46
  const CONTINUE_MODEL_NAME = "The Claw Bay";
43
47
  const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
44
48
  const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
@@ -179,6 +183,18 @@ async function writeJsonFile(filePath, value, mode) {
179
183
  if (mode !== undefined)
180
184
  await promises_1.default.chmod(filePath, mode);
181
185
  }
186
+ function isManagedOpenCodeModel(value) {
187
+ if (typeof value !== "string")
188
+ return false;
189
+ if (!value.startsWith(`${OPENAI_PROVIDER_ID}/`))
190
+ return false;
191
+ return SUPPORTED_MODEL_IDS.has(value.slice(`${OPENAI_PROVIDER_ID}/`.length));
192
+ }
193
+ function isManagedOpenCodeProvider(value) {
194
+ const provider = objectRecordOr(value, {});
195
+ const options = objectRecordOr(provider.options, {});
196
+ return isTheClawBayOpenAiCompatibleBaseUrl(options.baseURL);
197
+ }
182
198
  function roamingAppDataDir() {
183
199
  if (process.env.APPDATA?.trim())
184
200
  return process.env.APPDATA;
@@ -433,11 +449,11 @@ async function cleanupOpenClawConfig() {
433
449
  await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
434
450
  return true;
435
451
  }
436
- async function cleanupOpenCodeConfig() {
437
- const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
438
- const existingRaw = await readFileIfExists(configPath);
439
- if (existingRaw === null || !existingRaw.trim())
440
- return false;
452
+ async function cleanupOpenCodeFamilyConfig(params) {
453
+ const existingRaw = await readFileIfExists(params.configPath);
454
+ if (existingRaw === null || !existingRaw.trim()) {
455
+ return removeFileIfExists(params.restoreStatePath);
456
+ }
441
457
  let doc;
442
458
  try {
443
459
  doc = objectRecordOr(JSON.parse(existingRaw), {});
@@ -445,56 +461,76 @@ async function cleanupOpenCodeConfig() {
445
461
  catch {
446
462
  return false;
447
463
  }
464
+ const snapshotRaw = await readFileIfExists(params.restoreStatePath);
448
465
  let changed = false;
449
466
  const providerRoot = objectRecordOr(doc.provider, {});
450
- for (const id of [DEFAULT_PROVIDER_ID]) {
451
- if (id in providerRoot) {
452
- delete providerRoot[id];
453
- changed = true;
454
- }
455
- }
456
- doc.provider = providerRoot;
457
- const model = doc.model;
458
- if (typeof model === "string" && model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
459
- delete doc.model;
467
+ if (DEFAULT_PROVIDER_ID in providerRoot) {
468
+ delete providerRoot[DEFAULT_PROVIDER_ID];
460
469
  changed = true;
461
470
  }
462
- if (!changed)
463
- return false;
464
- await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
465
- return true;
466
- }
467
- async function cleanupKiloConfig() {
468
- const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json");
469
- const existingRaw = await readFileIfExists(configPath);
470
- if (existingRaw === null || !existingRaw.trim())
471
- return false;
472
- let doc;
473
- try {
474
- doc = objectRecordOr(JSON.parse(existingRaw), {});
475
- }
476
- catch {
477
- return false;
478
- }
479
- let changed = false;
480
- const providerRoot = objectRecordOr(doc.provider, {});
481
- for (const id of [DEFAULT_PROVIDER_ID]) {
482
- if (id in providerRoot) {
483
- delete providerRoot[id];
471
+ if (snapshotRaw?.trim()) {
472
+ try {
473
+ const snapshot = JSON.parse(snapshotRaw);
474
+ if (snapshot.openAiProvider) {
475
+ providerRoot[OPENAI_PROVIDER_ID] = snapshot.openAiProvider;
476
+ }
477
+ else if (OPENAI_PROVIDER_ID in providerRoot) {
478
+ delete providerRoot[OPENAI_PROVIDER_ID];
479
+ }
480
+ doc.provider = providerRoot;
481
+ if (snapshot.model === null) {
482
+ if (isManagedOpenCodeModel(doc.model) || typeof doc.model === "string" && doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
483
+ delete doc.model;
484
+ }
485
+ }
486
+ else {
487
+ doc.model = snapshot.model;
488
+ }
489
+ if (snapshot.schema === null) {
490
+ if (typeof doc.$schema === "string")
491
+ delete doc.$schema;
492
+ }
493
+ else {
494
+ doc.$schema = snapshot.schema;
495
+ }
484
496
  changed = true;
497
+ await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
498
+ await removeFileIfExists(params.restoreStatePath);
499
+ return changed;
500
+ }
501
+ catch {
502
+ // Fall through to best-effort cleanup below.
485
503
  }
486
504
  }
487
505
  doc.provider = providerRoot;
488
506
  const model = doc.model;
489
- if (typeof model === "string" && model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
507
+ if (typeof model === "string" && (model.startsWith(`${DEFAULT_PROVIDER_ID}/`) || isManagedOpenCodeModel(model))) {
490
508
  delete doc.model;
491
509
  changed = true;
492
510
  }
511
+ if (isManagedOpenCodeProvider(providerRoot[OPENAI_PROVIDER_ID])) {
512
+ delete providerRoot[OPENAI_PROVIDER_ID];
513
+ doc.provider = providerRoot;
514
+ changed = true;
515
+ }
493
516
  if (!changed)
494
517
  return false;
495
- await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
518
+ await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
519
+ await removeFileIfExists(params.restoreStatePath);
496
520
  return true;
497
521
  }
522
+ async function cleanupOpenCodeConfig() {
523
+ return cleanupOpenCodeFamilyConfig({
524
+ configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json"),
525
+ restoreStatePath: OPENCODE_RESTORE_STATE_PATH,
526
+ });
527
+ }
528
+ async function cleanupKiloConfig() {
529
+ return cleanupOpenCodeFamilyConfig({
530
+ configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json"),
531
+ restoreStatePath: KILO_RESTORE_STATE_PATH,
532
+ });
533
+ }
498
534
  async function cleanupTraeBundle() {
499
535
  const bundlePath = traeBundlePath();
500
536
  if (!bundlePath)
@@ -42,6 +42,8 @@ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homed
42
42
  const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
43
43
  const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
44
44
  const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
45
+ const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
46
+ const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
45
47
  const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
46
48
  const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
47
49
  const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
@@ -56,6 +58,9 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan",
56
58
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
57
59
  const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider"];
58
60
  const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
61
+ const OPENAI_PROVIDER_ID = "openai";
62
+ const OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
63
+ const KILO_CONFIG_SCHEMA_URL = "https://kilo.ai/config.json";
59
64
  const CONTINUE_MODEL_NAME = "The Claw Bay";
60
65
  const ROO_PROFILE_NAME = "The Claw Bay";
61
66
  const ROO_PROFILE_ID = "theclawbay-openai-compatible";
@@ -1018,7 +1023,7 @@ async function patchOpenClawConfigFile(params) {
1018
1023
  baseUrl: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
1019
1024
  apiKey: params.apiKey,
1020
1025
  api: "openai-responses",
1021
- models: params.models,
1026
+ models: buildOpenClawModels(params.models),
1022
1027
  };
1023
1028
  const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "openclaw.json");
1024
1029
  await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
@@ -1050,6 +1055,9 @@ async function patchOpenClawConfigFile(params) {
1050
1055
  const defaultsRoot = objectRecordOr(agentsRoot.defaults, {});
1051
1056
  const modelRoot = objectRecordOr(defaultsRoot.model, {});
1052
1057
  modelRoot.primary = resolveOpenClawPrimaryModel(params);
1058
+ if (typeof defaultsRoot.thinkingDefault !== "string") {
1059
+ defaultsRoot.thinkingDefault = "medium";
1060
+ }
1053
1061
  defaultsRoot.model = modelRoot;
1054
1062
  agentsRoot.defaults = defaultsRoot;
1055
1063
  doc.agents = agentsRoot;
@@ -1066,21 +1074,57 @@ function objectRecordOr(value, fallback) {
1066
1074
  }
1067
1075
  return fallback;
1068
1076
  }
1069
- function openCodeModelsObject(models) {
1077
+ function modelContextLimit(modelId) {
1078
+ if (modelId === "gpt-5.4")
1079
+ return 1050000;
1080
+ return 272000;
1081
+ }
1082
+ function modelOutputLimit(modelId) {
1083
+ if (modelId === "gpt-5.4")
1084
+ return 128000;
1085
+ return 65536;
1086
+ }
1087
+ function buildOpenCodeModelConfig(model) {
1088
+ return {
1089
+ name: model.name || model.id,
1090
+ reasoning: true,
1091
+ tool_call: true,
1092
+ attachment: true,
1093
+ modalities: {
1094
+ input: ["text", "image"],
1095
+ output: ["text"],
1096
+ },
1097
+ limit: {
1098
+ context: modelContextLimit(model.id),
1099
+ output: modelOutputLimit(model.id),
1100
+ },
1101
+ };
1102
+ }
1103
+ function buildOpenCodeModelsObject(models) {
1070
1104
  const result = {};
1071
1105
  for (const model of models) {
1072
1106
  if (!model.id)
1073
1107
  continue;
1074
- result[model.id] = { name: model.name || model.id };
1108
+ result[model.id] = buildOpenCodeModelConfig(model);
1075
1109
  }
1076
1110
  return result;
1077
1111
  }
1078
- async function writeOpenCodeConfig(params) {
1079
- const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
1080
- await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
1112
+ function isManagedOpenCodeProvider(provider) {
1113
+ const options = objectRecordOr(provider.options, {});
1114
+ return isTheClawBayOpenAiCompatibleBaseUrl(options.baseURL);
1115
+ }
1116
+ function isManagedOpenCodeModel(value, supportedModelIds) {
1117
+ if (typeof value !== "string")
1118
+ return false;
1119
+ if (!value.startsWith(`${OPENAI_PROVIDER_ID}/`))
1120
+ return false;
1121
+ return supportedModelIds.has(value.slice(`${OPENAI_PROVIDER_ID}/`.length));
1122
+ }
1123
+ async function writeOpenCodeFamilyConfig(params) {
1124
+ await promises_1.default.mkdir(node_path_1.default.dirname(params.configPath), { recursive: true });
1081
1125
  let existingRaw = "";
1082
1126
  try {
1083
- existingRaw = await promises_1.default.readFile(configPath, "utf8");
1127
+ existingRaw = await promises_1.default.readFile(params.configPath, "utf8");
1084
1128
  }
1085
1129
  catch (error) {
1086
1130
  const err = error;
@@ -1093,60 +1137,81 @@ async function writeOpenCodeConfig(params) {
1093
1137
  doc = objectRecordOr(JSON.parse(existingRaw), {});
1094
1138
  }
1095
1139
  catch {
1096
- throw new Error(`invalid JSON in OpenCode config: ${configPath}`);
1140
+ throw new Error(`invalid JSON in config: ${params.configPath}`);
1097
1141
  }
1098
1142
  }
1099
1143
  const providerRoot = objectRecordOr(doc.provider, {});
1100
- providerRoot[DEFAULT_PROVIDER_ID] = {
1101
- npm: "@ai-sdk/openai-compatible",
1102
- name: DEFAULT_PROVIDER_ID,
1103
- options: {
1104
- baseURL: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
1105
- apiKey: params.apiKey,
1106
- },
1107
- models: openCodeModelsObject(params.models),
1108
- };
1144
+ const openAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
1145
+ const supportedModelIds = new Set(params.models.map((entry) => entry.id).filter(Boolean));
1146
+ const alreadyManaged = isManagedOpenCodeProvider(openAiProvider) && isManagedOpenCodeModel(doc.model, supportedModelIds);
1147
+ if (!alreadyManaged) {
1148
+ const snapshot = {
1149
+ openAiProvider: Object.keys(openAiProvider).length > 0 ? openAiProvider : null,
1150
+ model: typeof doc.model === "string" && !doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`)
1151
+ ? doc.model
1152
+ : null,
1153
+ schema: typeof doc.$schema === "string" ? doc.$schema : null,
1154
+ };
1155
+ await writeJsonObjectFile(params.restoreStatePath, snapshot, 0o600);
1156
+ }
1157
+ const managedOpenAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
1158
+ const optionsRoot = objectRecordOr(managedOpenAiProvider.options, {});
1159
+ optionsRoot.baseURL = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`;
1160
+ optionsRoot.apiKey = params.apiKey;
1161
+ managedOpenAiProvider.options = optionsRoot;
1162
+ managedOpenAiProvider.models = buildOpenCodeModelsObject(params.models);
1163
+ managedOpenAiProvider.whitelist = params.models.map((entry) => entry.id).filter(Boolean);
1164
+ if ("blacklist" in managedOpenAiProvider) {
1165
+ delete managedOpenAiProvider.blacklist;
1166
+ }
1167
+ providerRoot[OPENAI_PROVIDER_ID] = managedOpenAiProvider;
1168
+ if (DEFAULT_PROVIDER_ID in providerRoot) {
1169
+ delete providerRoot[DEFAULT_PROVIDER_ID];
1170
+ }
1171
+ doc.$schema = params.schemaUrl;
1109
1172
  doc.provider = providerRoot;
1110
- doc.model = `${DEFAULT_PROVIDER_ID}/${params.model}`;
1111
- await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
1112
- return configPath;
1173
+ doc.model = `${OPENAI_PROVIDER_ID}/${params.model}`;
1174
+ await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
1175
+ return params.configPath;
1176
+ }
1177
+ function buildOpenClawModels(models) {
1178
+ const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
1179
+ return models
1180
+ .filter((model) => Boolean(model.id))
1181
+ .map((model) => {
1182
+ const pricing = supportedModelMap.get(model.id);
1183
+ return {
1184
+ id: model.id,
1185
+ name: model.name || model.id,
1186
+ api: "openai-responses",
1187
+ reasoning: true,
1188
+ input: ["text", "image"],
1189
+ cost: {
1190
+ input: pricing?.inputPer1M ?? 0,
1191
+ output: pricing?.outputPer1M ?? 0,
1192
+ cacheRead: pricing?.cachedInputPer1M ?? 0,
1193
+ cacheWrite: pricing?.inputPer1M ?? 0,
1194
+ },
1195
+ contextWindow: modelContextLimit(model.id),
1196
+ maxTokens: modelOutputLimit(model.id),
1197
+ };
1198
+ });
1199
+ }
1200
+ async function writeOpenCodeConfig(params) {
1201
+ return writeOpenCodeFamilyConfig({
1202
+ configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json"),
1203
+ restoreStatePath: OPENCODE_RESTORE_STATE_PATH,
1204
+ schemaUrl: OPENCODE_CONFIG_SCHEMA_URL,
1205
+ ...params,
1206
+ });
1113
1207
  }
1114
1208
  async function writeKiloConfig(params) {
1115
- const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json");
1116
- await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
1117
- let existingRaw = "";
1118
- try {
1119
- existingRaw = await promises_1.default.readFile(configPath, "utf8");
1120
- }
1121
- catch (error) {
1122
- const err = error;
1123
- if (err.code !== "ENOENT")
1124
- throw error;
1125
- }
1126
- let doc = {};
1127
- if (existingRaw.trim()) {
1128
- try {
1129
- doc = objectRecordOr(JSON.parse(existingRaw), {});
1130
- }
1131
- catch {
1132
- throw new Error(`invalid JSON in Kilo config: ${configPath}`);
1133
- }
1134
- }
1135
- const providerRoot = objectRecordOr(doc.provider, {});
1136
- providerRoot[DEFAULT_PROVIDER_ID] = {
1137
- npm: "@ai-sdk/openai-compatible",
1138
- name: DEFAULT_PROVIDER_ID,
1139
- options: {
1140
- baseURL: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
1141
- apiKey: params.apiKey,
1142
- },
1143
- models: openCodeModelsObject(params.models),
1144
- };
1145
- doc.$schema = "https://kilo.ai/config.json";
1146
- doc.provider = providerRoot;
1147
- doc.model = `${DEFAULT_PROVIDER_ID}/${params.model || DEFAULT_KILO_MODEL}`;
1148
- await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
1149
- return configPath;
1209
+ return writeOpenCodeFamilyConfig({
1210
+ configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json"),
1211
+ restoreStatePath: KILO_RESTORE_STATE_PATH,
1212
+ schemaUrl: KILO_CONFIG_SCHEMA_URL,
1213
+ ...params,
1214
+ });
1150
1215
  }
1151
1216
  function traePatchSnippet(params) {
1152
1217
  const patchedModels = params.models.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.32",
3
+ "version": "0.3.33",
4
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": {