theclawbay 0.3.36 → 0.3.38

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, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and experimental Trae to The Claw Bay.
3
+ CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.
4
4
 
5
5
  ## Install
6
6
 
@@ -16,8 +16,8 @@ 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, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and Windows Trae.
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.
19
+ In an interactive terminal, setup shows one picker for Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, Windows Trae, and Zo.
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 `1-9` and `0`.
21
21
  Re-running setup also migrates legacy OpenCode and Kilo patches to the current reasoning-capable provider path and refreshes OpenClaw model metadata.
22
22
 
23
23
  ## Optional
@@ -20,7 +20,7 @@ class LinkCommand extends base_command_1.BaseCommand {
20
20
  });
21
21
  this.log(`Linked. Managed config written to ${paths_1.managedConfigPath}`);
22
22
  this.log(`Backend: ${backendUrl}`);
23
- this.log('Run "theclawbay setup" to configure Codex and any detected OpenClaw, OpenCode, Kilo, or experimental Trae installs on this machine.');
23
+ this.log('Run "theclawbay setup" to configure Codex and any detected OpenClaw, OpenCode, Kilo, experimental Trae, or experimental Zo installs on this machine.');
24
24
  });
25
25
  }
26
26
  }
@@ -12,8 +12,8 @@ const base_command_1 = require("../lib/base-command");
12
12
  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
- const paths_1 = require("../lib/config/paths");
16
15
  const supported_models_1 = require("../lib/supported-models");
16
+ const paths_1 = require("../lib/config/paths");
17
17
  const OPENAI_PROVIDER_ID = "openai";
18
18
  const DEFAULT_PROVIDER_ID = "theclawbay";
19
19
  const WAN_PROVIDER_ID = "theclawbay-wan";
@@ -516,7 +516,9 @@ function normalizeOpenCodeRestoreSnapshot(snapshot, fallbackConfigPath) {
516
516
  targets.push({
517
517
  configPath,
518
518
  existed: candidate.existed === true,
519
- openAiProvider: typeof candidate.openAiProvider === "object" && candidate.openAiProvider !== null && !Array.isArray(candidate.openAiProvider)
519
+ openAiProvider: typeof candidate.openAiProvider === "object" &&
520
+ candidate.openAiProvider !== null &&
521
+ !Array.isArray(candidate.openAiProvider)
520
522
  ? candidate.openAiProvider
521
523
  : null,
522
524
  model: typeof candidate.model === "string" ? candidate.model : null,
@@ -544,7 +546,6 @@ function normalizeOpenCodeRestoreSnapshot(snapshot, fallbackConfigPath) {
544
546
  : null,
545
547
  model: typeof obj.model === "string" ? obj.model : null,
546
548
  schema: typeof obj.schema === "string" ? obj.schema : null,
547
- plugin: undefined,
548
549
  },
549
550
  ],
550
551
  };
@@ -607,8 +608,7 @@ async function cleanupOpenCodeFamilyConfigs(params) {
607
608
  }
608
609
  doc.provider = providerRoot;
609
610
  if (target.model === null) {
610
- if (isManagedOpenCodeModel(doc.model) ||
611
- (typeof doc.model === "string" && doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`))) {
611
+ if (isManagedOpenCodeModel(doc.model) || (typeof doc.model === "string" && doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`))) {
612
612
  delete doc.model;
613
613
  changed = true;
614
614
  }
@@ -33,6 +33,7 @@ const DEFAULT_KILO_MODEL = DEFAULT_CODEX_MODEL;
33
33
  const DEFAULT_ROO_MODEL = DEFAULT_CODEX_MODEL;
34
34
  const DEFAULT_TRAE_MODEL = DEFAULT_CODEX_MODEL;
35
35
  const DEFAULT_AIDER_MODEL = DEFAULT_CODEX_MODEL;
36
+ const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
36
37
  const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
37
38
  const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
38
39
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
@@ -56,11 +57,8 @@ const SHELL_END = "# theclawbay-shell-managed:end";
56
57
  const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
57
58
  const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
58
59
  const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
59
- const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider"];
60
+ const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
60
61
  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";
64
62
  const CONTINUE_MODEL_NAME = "The Claw Bay";
65
63
  const ROO_PROFILE_NAME = "The Claw Bay";
66
64
  const ROO_PROFILE_ID = "theclawbay-openai-compatible";
@@ -69,6 +67,13 @@ const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
69
67
  const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
70
68
  const TRAE_TARGET_FUNCTION_START = "async setOriginModelListMapAndCache(e,t=!0){";
71
69
  const TRAE_TARGET_FUNCTION_END = "}async refreshModelListConfig";
70
+ const OPENAI_PROVIDER_ID = "openai";
71
+ const OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
72
+ const KILO_CONFIG_SCHEMA_URL = "https://kilo.ai/config.json";
73
+ const ZO_CONFIG_NAME_PREFIX = "The Claw Bay";
74
+ const ZO_COOKIE_HOST = ".zo.computer";
75
+ const ZO_ACCESS_TOKEN_COOKIE = "access_token";
76
+ const ZO_API_BASE_URL = "https://api.zo.computer";
72
77
  function trimTrailingSlash(value) {
73
78
  return value.replace(/\/+$/g, "");
74
79
  }
@@ -137,7 +142,7 @@ function logSetupCompactSummary(params) {
137
142
  .filter((client) => params.selectedSetupClients.has(client.id))
138
143
  .map((client) => client.summaryLabel);
139
144
  const restartTargets = params.setupClients
140
- .filter((client) => params.selectedSetupClients.has(client.id) && ["continue", "cline", "roo", "trae"].includes(client.id))
145
+ .filter((client) => params.selectedSetupClients.has(client.id) && ["continue", "cline", "roo", "trae", "zo"].includes(client.id))
141
146
  .map((client) => client.summaryLabel);
142
147
  if (configured.length > 0) {
143
148
  params.log(`Configured: ${formatSummaryList(configured)}`);
@@ -158,6 +163,171 @@ function localAppDataDir() {
158
163
  return process.env.LOCALAPPDATA;
159
164
  return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Local");
160
165
  }
166
+ function roamingAppDataDir() {
167
+ if (process.env.APPDATA?.trim())
168
+ return process.env.APPDATA;
169
+ return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
170
+ }
171
+ function zoCookieDbCandidates() {
172
+ const home = node_os_1.default.homedir();
173
+ const candidates = new Set();
174
+ switch (node_os_1.default.platform()) {
175
+ case "win32":
176
+ candidates.add(node_path_1.default.join(roamingAppDataDir(), "Zo", "Network", "Cookies"));
177
+ break;
178
+ case "darwin":
179
+ candidates.add(node_path_1.default.join(home, "Library", "Application Support", "Zo", "Network", "Cookies"));
180
+ break;
181
+ default:
182
+ candidates.add(node_path_1.default.join(home, ".config", "Zo", "Network", "Cookies"));
183
+ break;
184
+ }
185
+ return Array.from(candidates);
186
+ }
187
+ function zoProgramCandidates() {
188
+ const candidates = new Set();
189
+ if (process.env.THECLAWBAY_ZO_PATH?.trim()) {
190
+ candidates.add(process.env.THECLAWBAY_ZO_PATH.trim());
191
+ }
192
+ if (node_os_1.default.platform() === "win32") {
193
+ candidates.add(node_path_1.default.join(localAppDataDir(), "Programs", "Zo"));
194
+ for (const envKey of ["ProgramFiles", "ProgramFiles(x86)"]) {
195
+ const root = process.env[envKey]?.trim();
196
+ if (root)
197
+ candidates.add(node_path_1.default.join(root, "Zo"));
198
+ }
199
+ }
200
+ else if (node_os_1.default.platform() === "darwin") {
201
+ candidates.add("/Applications/Zo.app");
202
+ }
203
+ return Array.from(candidates);
204
+ }
205
+ function pythonCommandCandidates() {
206
+ if (node_os_1.default.platform() === "win32") {
207
+ return [
208
+ { command: "py", baseArgs: ["-3"] },
209
+ { command: "python", baseArgs: [] },
210
+ { command: "python3", baseArgs: [] },
211
+ ];
212
+ }
213
+ return [
214
+ { command: "python3", baseArgs: [] },
215
+ { command: "python", baseArgs: [] },
216
+ ];
217
+ }
218
+ function readZoCookieValue(cookieName) {
219
+ const dbPath = zoCookieDbCandidates().find((candidate) => (0, node_fs_1.existsSync)(candidate));
220
+ if (!dbPath)
221
+ return null;
222
+ const pythonScript = [
223
+ "import json, sqlite3, sys",
224
+ "db_path, host_key, cookie_name = sys.argv[1:4]",
225
+ 'conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)',
226
+ "row = conn.execute(",
227
+ ' "select value from cookies where host_key=? and name=? order by length(value) desc limit 1",',
228
+ " (host_key, cookie_name),",
229
+ ").fetchone()",
230
+ 'print(json.dumps({"value": row[0] if row else None}))',
231
+ ].join("\n");
232
+ for (const candidate of pythonCommandCandidates()) {
233
+ const result = (0, node_child_process_1.spawnSync)(candidate.command, [...candidate.baseArgs, "-c", pythonScript, dbPath, ZO_COOKIE_HOST, cookieName], {
234
+ encoding: "utf8",
235
+ stdio: ["ignore", "pipe", "pipe"],
236
+ });
237
+ if (result.status !== 0 || !result.stdout.trim())
238
+ continue;
239
+ try {
240
+ const parsed = JSON.parse(result.stdout);
241
+ if (typeof parsed.value === "string" && parsed.value.trim()) {
242
+ return parsed.value.trim();
243
+ }
244
+ }
245
+ catch {
246
+ continue;
247
+ }
248
+ }
249
+ if (hasCommand("sqlite3")) {
250
+ const result = (0, node_child_process_1.spawnSync)("sqlite3", [
251
+ dbPath,
252
+ `select value from cookies where host_key='${ZO_COOKIE_HOST}' and name='${cookieName}' order by length(value) desc limit 1;`,
253
+ ], {
254
+ encoding: "utf8",
255
+ stdio: ["ignore", "pipe", "pipe"],
256
+ });
257
+ if (result.status === 0 && result.stdout.trim()) {
258
+ return result.stdout.trim();
259
+ }
260
+ }
261
+ return null;
262
+ }
263
+ function parseZoByokConfig(value) {
264
+ if (!value || typeof value !== "object")
265
+ return null;
266
+ return value;
267
+ }
268
+ async function zoApiRequest(params) {
269
+ const response = await fetch(`${ZO_API_BASE_URL}${params.pathname}`, {
270
+ method: params.method ?? "GET",
271
+ headers: {
272
+ Authorization: `Bearer ${params.token}`,
273
+ Accept: "application/json",
274
+ "User-Agent": CLI_HTTP_USER_AGENT,
275
+ ...(params.body ? { "Content-Type": "application/json" } : {}),
276
+ },
277
+ body: params.body ? JSON.stringify(params.body) : undefined,
278
+ signal: AbortSignal.timeout(10000),
279
+ });
280
+ const responseText = await response.text();
281
+ if (!response.ok) {
282
+ const detail = responseText ? ` ${responseText.slice(0, 400)}` : "";
283
+ throw new Error(`Zo API ${params.method ?? "GET"} ${params.pathname} failed with ${response.status}.${detail}`.trim());
284
+ }
285
+ return (responseText ? JSON.parse(responseText) : null);
286
+ }
287
+ async function configureZoByok(params) {
288
+ const accessToken = readZoCookieValue(ZO_ACCESS_TOKEN_COOKIE);
289
+ if (!accessToken) {
290
+ throw new Error("Zo was detected, but no active Zo desktop session token could be read. Open Zo, sign in, and rerun setup.");
291
+ }
292
+ const desiredName = `${ZO_CONFIG_NAME_PREFIX} ${modelDisplayName(params.model)}`;
293
+ const desiredBaseUrl = `${trimTrailingSlash(params.backendUrl)}${THECLAWBAY_OPENAI_PROXY_SUFFIX}`;
294
+ const body = {
295
+ provider: "openai-style",
296
+ format: "openai",
297
+ name: desiredName,
298
+ base_url: desiredBaseUrl,
299
+ api_key: params.apiKey,
300
+ model_id: params.model,
301
+ supports_images: false,
302
+ };
303
+ const existing = await zoApiRequest({
304
+ token: accessToken,
305
+ pathname: "/byok/",
306
+ });
307
+ const existingConfigs = (Array.isArray(existing) ? existing : []).map(parseZoByokConfig).filter(Boolean);
308
+ const matched = existingConfigs.find((entry) => {
309
+ const name = typeof entry.name === "string" ? entry.name : "";
310
+ const baseUrl = typeof entry.base_url === "string" ? entry.base_url : "";
311
+ const modelId = typeof entry.model_id === "string" ? entry.model_id : "";
312
+ return name === desiredName || (baseUrl === desiredBaseUrl && modelId === params.model);
313
+ });
314
+ if (matched && typeof matched.id === "string" && matched.id) {
315
+ await zoApiRequest({
316
+ token: accessToken,
317
+ pathname: `/byok/${matched.id}`,
318
+ method: "PATCH",
319
+ body,
320
+ });
321
+ return desiredName;
322
+ }
323
+ await zoApiRequest({
324
+ token: accessToken,
325
+ pathname: "/byok/",
326
+ method: "POST",
327
+ body,
328
+ });
329
+ return desiredName;
330
+ }
161
331
  const TRAE_BUNDLE_RELATIVE_PATH = node_path_1.default.join("resources", "app", "node_modules", "@byted-icube", "ai-modules-chat", "dist", "index.js");
162
332
  function appendTraeBundlePath(basePath) {
163
333
  if (basePath.toLowerCase().endsWith(node_path_1.default.normalize(TRAE_BUNDLE_RELATIVE_PATH).toLowerCase())) {
@@ -301,9 +471,8 @@ function findProjectConfigFile(params) {
301
471
  if ((0, node_fs_1.existsSync)(candidate))
302
472
  return candidate;
303
473
  }
304
- if ((0, node_fs_1.existsSync)(node_path_1.default.join(current, ".git"))) {
474
+ if ((0, node_fs_1.existsSync)(node_path_1.default.join(current, ".git")))
305
475
  return null;
306
- }
307
476
  if (current === root)
308
477
  return null;
309
478
  const parent = node_path_1.default.dirname(current);
@@ -323,11 +492,6 @@ function configDirCandidates(appName) {
323
492
  }
324
493
  return uniqueStrings(dirs);
325
494
  }
326
- function roamingAppDataDir() {
327
- if (process.env.APPDATA?.trim())
328
- return process.env.APPDATA;
329
- return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
330
- }
331
495
  async function readFileIfExists(filePath) {
332
496
  try {
333
497
  return await promises_1.default.readFile(filePath, "utf8");
@@ -531,10 +695,15 @@ async function promptForSetupClients(clients) {
531
695
  stdout.write("\x1b[2J\x1b[H");
532
696
  };
533
697
  const selectedCount = () => options.filter((option) => option.checked).length;
698
+ const directToggleHint = options.length <= 9
699
+ ? `press 1-${options.length} to toggle directly`
700
+ : options.length === 10
701
+ ? "press 1-9 or 0 to toggle directly"
702
+ : "press a number key to toggle directly";
534
703
  const render = () => {
535
704
  clearScreen();
536
705
  stdout.write(`${paint("Choose local clients to configure", ansi.bold)}\n`);
537
- stdout.write(`${paint(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or press 1-${options.length} to toggle directly.`, ansi.dim, ansi.gray)}\n`);
706
+ stdout.write(`${paint(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or ${directToggleHint}.`, ansi.dim, ansi.gray)}\n`);
538
707
  stdout.write(`${paint("Each tool name links to its official site when your terminal supports it.", ansi.dim, ansi.gray)}\n`);
539
708
  stdout.write(`${paint("Move to Apply setup and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
540
709
  for (const [index, option] of options.entries()) {
@@ -612,7 +781,11 @@ async function promptForSetupClients(clients) {
612
781
  toggleOption(cursor);
613
782
  return;
614
783
  }
615
- const directSelection = Number.parseInt(key.sequence ?? "", 10);
784
+ const directSelection = key.sequence === "0" && options.length >= 10
785
+ ? 10
786
+ : /^[1-9]$/.test(key.sequence ?? "")
787
+ ? Number.parseInt(key.sequence ?? "", 10)
788
+ : Number.NaN;
616
789
  if (Number.isInteger(directSelection) && directSelection >= 1 && directSelection <= options.length) {
617
790
  toggleOption(directSelection - 1);
618
791
  return;
@@ -780,6 +953,17 @@ async function detectAiderClient() {
780
953
  }
781
954
  return false;
782
955
  }
956
+ async function detectZoClient() {
957
+ for (const candidate of zoProgramCandidates()) {
958
+ if (await pathExists(candidate))
959
+ return true;
960
+ }
961
+ for (const candidate of zoCookieDbCandidates()) {
962
+ if (await pathExists(candidate))
963
+ return true;
964
+ }
965
+ return false;
966
+ }
783
967
  async function resolveModels(backendUrl, apiKey) {
784
968
  const ids = await fetchBackendModelIds(backendUrl, apiKey);
785
969
  const available = new Set(ids ?? []);
@@ -1135,6 +1319,29 @@ function modelOutputLimit(modelId) {
1135
1319
  return 128000;
1136
1320
  return 65536;
1137
1321
  }
1322
+ function buildOpenClawModels(models) {
1323
+ const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
1324
+ return models
1325
+ .filter((model) => Boolean(model.id))
1326
+ .map((model) => {
1327
+ const pricing = supportedModelMap.get(model.id);
1328
+ return {
1329
+ id: model.id,
1330
+ name: model.name || model.id,
1331
+ api: "openai-responses",
1332
+ reasoning: true,
1333
+ input: ["text", "image"],
1334
+ cost: {
1335
+ input: pricing?.inputPer1M ?? 0,
1336
+ output: pricing?.outputPer1M ?? 0,
1337
+ cacheRead: pricing?.cachedInputPer1M ?? 0,
1338
+ cacheWrite: pricing?.inputPer1M ?? 0,
1339
+ },
1340
+ contextWindow: modelContextLimit(model.id),
1341
+ maxTokens: modelOutputLimit(model.id),
1342
+ };
1343
+ });
1344
+ }
1138
1345
  function buildOpenCodeModelConfig(model) {
1139
1346
  return {
1140
1347
  name: model.name || model.id,
@@ -1176,11 +1383,18 @@ function normalizeOpenCodeRestoreSnapshot(snapshot, fallbackConfigPath) {
1176
1383
  targets.push({
1177
1384
  configPath,
1178
1385
  existed: candidate.existed === true,
1179
- openAiProvider: typeof candidate.openAiProvider === "object" && candidate.openAiProvider !== null && !Array.isArray(candidate.openAiProvider)
1386
+ openAiProvider: typeof candidate.openAiProvider === "object" &&
1387
+ candidate.openAiProvider !== null &&
1388
+ !Array.isArray(candidate.openAiProvider)
1180
1389
  ? candidate.openAiProvider
1181
1390
  : null,
1182
1391
  model: typeof candidate.model === "string" ? candidate.model : null,
1183
1392
  schema: typeof candidate.schema === "string" ? candidate.schema : null,
1393
+ plugin: "plugin" in candidate
1394
+ ? Array.isArray(candidate.plugin)
1395
+ ? candidate.plugin
1396
+ : null
1397
+ : undefined,
1184
1398
  });
1185
1399
  }
1186
1400
  return { version: 2, targets };
@@ -1272,7 +1486,8 @@ function resolveKiloConfigTargets() {
1272
1486
  if (existing.length)
1273
1487
  return existing;
1274
1488
  const home = node_os_1.default.homedir();
1275
- const primaryDir = uniqueStrings([...configDirCandidates("kilo"), node_path_1.default.join(home, ".kilo")])[0] ?? node_path_1.default.join(xdgConfigHomeDir(), "kilo");
1489
+ const primaryDir = uniqueStrings([...configDirCandidates("kilo"), node_path_1.default.join(home, ".kilo")])[0] ??
1490
+ node_path_1.default.join(xdgConfigHomeDir(), "kilo");
1276
1491
  return uniqueStrings([node_path_1.default.join(primaryDir, "opencode.json")]);
1277
1492
  }
1278
1493
  function isManagedOpenCodeProvider(provider) {
@@ -1375,29 +1590,6 @@ async function writeOpenCodeFamilyConfig(params) {
1375
1590
  }
1376
1591
  return normalizedConfigPaths;
1377
1592
  }
1378
- function buildOpenClawModels(models) {
1379
- const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
1380
- return models
1381
- .filter((model) => Boolean(model.id))
1382
- .map((model) => {
1383
- const pricing = supportedModelMap.get(model.id);
1384
- return {
1385
- id: model.id,
1386
- name: model.name || model.id,
1387
- api: "openai-responses",
1388
- reasoning: true,
1389
- input: ["text", "image"],
1390
- cost: {
1391
- input: pricing?.inputPer1M ?? 0,
1392
- output: pricing?.outputPer1M ?? 0,
1393
- cacheRead: pricing?.cachedInputPer1M ?? 0,
1394
- cacheWrite: pricing?.inputPer1M ?? 0,
1395
- },
1396
- contextWindow: modelContextLimit(model.id),
1397
- maxTokens: modelOutputLimit(model.id),
1398
- };
1399
- });
1400
- }
1401
1593
  async function writeOpenCodeConfig(params) {
1402
1594
  return writeOpenCodeFamilyConfig({
1403
1595
  configPaths: resolveOpenCodeConfigTargets(),
@@ -1473,7 +1665,7 @@ class SetupCommand extends base_command_1.BaseCommand {
1473
1665
  throw new Error('API key is required. Run "theclawbay setup --api-key <key>".');
1474
1666
  const backendRaw = flags.backend ?? (0, api_key_1.tryInferBackendUrlFromApiKey)(apiKey) ?? managed?.backendUrl ?? DEFAULT_BACKEND_URL;
1475
1667
  const backendUrl = normalizeUrl(backendRaw, "--backend");
1476
- const [codexDetected, continueDetected, clineDetected, kiloDetected, rooDetected, traeDetected, aiderDetected] = await Promise.all([
1668
+ const [codexDetected, continueDetected, clineDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected] = await Promise.all([
1477
1669
  detectCodexClient(),
1478
1670
  detectContinueClient(),
1479
1671
  detectClineClient(),
@@ -1481,6 +1673,7 @@ class SetupCommand extends base_command_1.BaseCommand {
1481
1673
  detectRooClient(),
1482
1674
  detectTraeClient(),
1483
1675
  detectAiderClient(),
1676
+ detectZoClient(),
1484
1677
  ]);
1485
1678
  const setupClients = [
1486
1679
  {
@@ -1564,6 +1757,15 @@ class SetupCommand extends base_command_1.BaseCommand {
1564
1757
  icon: "✦",
1565
1758
  siteUrl: "https://aider.chat",
1566
1759
  },
1760
+ {
1761
+ id: "zo",
1762
+ label: "Zo (experimental)",
1763
+ summaryLabel: "Zo",
1764
+ detected: zoDetected,
1765
+ recommended: false,
1766
+ icon: "◉",
1767
+ siteUrl: "https://www.zo.computer",
1768
+ },
1567
1769
  ];
1568
1770
  const selectedSetupClients = await resolveSetupClientSelection({
1569
1771
  setupClients,
@@ -1588,6 +1790,7 @@ class SetupCommand extends base_command_1.BaseCommand {
1588
1790
  let rooConfigPaths = [];
1589
1791
  let traeBundlePathPatched = null;
1590
1792
  let aiderConfigPath = null;
1793
+ let zoConfigName = null;
1591
1794
  try {
1592
1795
  if (selectedSetupClients.size > 0) {
1593
1796
  progress.update("Resolving supported models");
@@ -1688,6 +1891,14 @@ class SetupCommand extends base_command_1.BaseCommand {
1688
1891
  apiKey,
1689
1892
  });
1690
1893
  }
1894
+ if (selectedSetupClients.has("zo")) {
1895
+ progress.update("Configuring Zo");
1896
+ zoConfigName = await configureZoByok({
1897
+ backendUrl,
1898
+ model: resolved?.model ?? DEFAULT_ZO_MODEL,
1899
+ apiKey,
1900
+ });
1901
+ }
1691
1902
  }
1692
1903
  catch (error) {
1693
1904
  progress.fail("Setup failed");
@@ -1705,6 +1916,9 @@ class SetupCommand extends base_command_1.BaseCommand {
1705
1916
  if (selectedSetupClients.has("trae")) {
1706
1917
  summaryNotes.add("Trae uses an experimental patch. Restart Trae and pick a The Claw Bay model to test it.");
1707
1918
  }
1919
+ if (selectedSetupClients.has("zo")) {
1920
+ summaryNotes.add("Zo uses an experimental account-level BYOK API integration. Keep Zo signed in while running setup.");
1921
+ }
1708
1922
  if (openClawCliWarning)
1709
1923
  summaryNotes.add(openClawCliWarning);
1710
1924
  if ((stateDbMigration?.failed.length ?? 0) > 0) {
@@ -1877,6 +2091,16 @@ class SetupCommand extends base_command_1.BaseCommand {
1877
2091
  else {
1878
2092
  this.log("- Aider: not detected (skipped)");
1879
2093
  }
2094
+ if (selectedSetupClients.has("zo")) {
2095
+ this.log(`- Zo: configured account-level BYOK model (${zoConfigName})`);
2096
+ this.log("- Zo: used the desktop session token from the local Zo app cookie store.");
2097
+ }
2098
+ else if (zoDetected) {
2099
+ this.log("- Zo: detected but skipped");
2100
+ }
2101
+ else {
2102
+ this.log("- Zo: not detected (skipped)");
2103
+ }
1880
2104
  if (authSeed?.action === "seeded") {
1881
2105
  this.log("- Codex login state: seeded local API-key auth for Codex UI.");
1882
2106
  }
@@ -1895,11 +2119,11 @@ class SetupCommand extends base_command_1.BaseCommand {
1895
2119
  else if (authSeed) {
1896
2120
  this.log("- Codex login state: no change.");
1897
2121
  }
1898
- this.log("Next: restart Continue, Cline, Roo Code, or Trae only if they were already open during setup.");
2122
+ this.log("Next: restart Continue, Cline, Roo Code, Trae, or Zo only if they were already open during setup.");
1899
2123
  });
1900
2124
  }
1901
2125
  }
1902
- 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";
2126
+ SetupCommand.description = "One-time setup: configure Codex plus any detected Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Trae, Aider, or Zo installs to use The Claw Bay";
1903
2127
  SetupCommand.flags = {
1904
2128
  backend: core_1.Flags.string({
1905
2129
  required: false,
@@ -1912,7 +2136,7 @@ SetupCommand.flags = {
1912
2136
  }),
1913
2137
  clients: core_1.Flags.string({
1914
2138
  required: false,
1915
- description: "Detected local clients to configure: codex, continue, cline, openclaw, opencode, kilo, roo, trae, aider",
2139
+ description: "Detected local clients to configure: codex, continue, cline, openclaw, opencode, kilo, roo, trae, aider, zo",
1916
2140
  }),
1917
2141
  yes: core_1.Flags.boolean({
1918
2142
  required: false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.36",
4
- "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and experimental Trae to The Claw Bay.",
3
+ "version": "0.3.38",
4
+ "description": "CLI for connecting Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "theclawbay": "dist/index.js"
@@ -39,7 +39,8 @@
39
39
  "kilo",
40
40
  "roo",
41
41
  "aider",
42
- "trae"
42
+ "trae",
43
+ "zo"
43
44
  ],
44
45
  "preferGlobal": true,
45
46
  "dependencies": {