theclawbay 0.3.76 → 0.3.78
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 +15 -2
- package/dist/commands/backup.d.ts +10 -0
- package/dist/commands/backup.js +51 -0
- package/dist/commands/logout.js +117 -0
- package/dist/commands/setup.d.ts +4 -0
- package/dist/commands/setup.js +285 -5
- package/dist/lib/backups.d.ts +30 -0
- package/dist/lib/backups.js +286 -0
- package/dist/lib/codex-model-cache-migration.js +5 -1
- package/dist/lib/config/paths.d.ts +1 -0
- package/dist/lib/config/paths.js +2 -1
- package/dist/lib/supported-models.d.ts +1 -0
- package/dist/lib/supported-models.js +4 -0
- package/package.json +4 -3
- package/theclawbay-supported-models.json +20 -36
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, experimental Trae, and experimental Zo to The Claw Bay.
|
|
3
|
+
CLI for connecting Codex, Claude Code, Hermes Agent, Gemini-compatible apps, 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,9 +16,10 @@ 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, Windows Trae, and Zo.
|
|
19
|
+
In an interactive terminal, setup shows one picker for Codex, Hermes Agent, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, Windows Trae, and Zo.
|
|
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 `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
|
+
Setup also creates a local backup before changing config files, and you can choose the default model with `--model <id>` or print the live backend model list with `--list-models`.
|
|
22
23
|
|
|
23
24
|
## Optional
|
|
24
25
|
|
|
@@ -33,3 +34,15 @@ Removes The Claw Bay-managed local config from this machine.
|
|
|
33
34
|
`theclawbay usage`
|
|
34
35
|
|
|
35
36
|
Shows your current shared 5-hour and weekly usage windows.
|
|
37
|
+
|
|
38
|
+
`theclawbay backup`
|
|
39
|
+
|
|
40
|
+
Creates a timestamped local backup of the managed config and supported client config paths.
|
|
41
|
+
|
|
42
|
+
`theclawbay backup --list`
|
|
43
|
+
|
|
44
|
+
Shows the saved backups with their date/time and captured target counts.
|
|
45
|
+
|
|
46
|
+
`theclawbay backup --restore <backupId>`
|
|
47
|
+
|
|
48
|
+
Restores a saved backup snapshot.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from "../lib/base-command";
|
|
2
|
+
export default class BackupCommand extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
list: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
restore: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../lib/base-command");
|
|
5
|
+
const backups_1 = require("../lib/backups");
|
|
6
|
+
class BackupCommand extends base_command_1.BaseCommand {
|
|
7
|
+
async run() {
|
|
8
|
+
await this.runSafe(async () => {
|
|
9
|
+
const { flags } = await this.parse(BackupCommand);
|
|
10
|
+
if (flags.list && flags.restore) {
|
|
11
|
+
throw new Error("--list and --restore cannot be used together.");
|
|
12
|
+
}
|
|
13
|
+
if (flags.list) {
|
|
14
|
+
const backups = await (0, backups_1.listBackups)();
|
|
15
|
+
if (backups.length === 0) {
|
|
16
|
+
this.log("No backups found yet.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.log("Available backups:");
|
|
20
|
+
for (const backup of backups) {
|
|
21
|
+
this.log(`- ${backup.id} ${(0, backups_1.formatBackupTimestamp)(backup.createdAt)} ${backup.capturedTargets}/${backup.totalTargets} targets ${backup.reason}`);
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (flags.restore) {
|
|
26
|
+
const restored = await (0, backups_1.restoreBackup)(flags.restore);
|
|
27
|
+
this.log(`Restored backup ${restored.id} from ${(0, backups_1.formatBackupTimestamp)(restored.createdAt)} (${restored.capturedTargets}/${restored.totalTargets} targets).`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const backup = await (0, backups_1.createBackup)(flags.reason?.trim() || "manual backup");
|
|
31
|
+
this.log(`Created backup ${backup.id} at ${(0, backups_1.formatBackupTimestamp)(backup.createdAt)} (${backup.capturedTargets}/${backup.totalTargets} targets).`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
BackupCommand.description = "Create, list, and restore local The Claw Bay client configuration backups";
|
|
36
|
+
BackupCommand.flags = {
|
|
37
|
+
list: core_1.Flags.boolean({
|
|
38
|
+
required: false,
|
|
39
|
+
default: false,
|
|
40
|
+
description: "List available backups instead of creating a new one",
|
|
41
|
+
}),
|
|
42
|
+
restore: core_1.Flags.string({
|
|
43
|
+
required: false,
|
|
44
|
+
description: "Restore a backup by id",
|
|
45
|
+
}),
|
|
46
|
+
reason: core_1.Flags.string({
|
|
47
|
+
required: false,
|
|
48
|
+
description: "Optional note to record with a newly created backup",
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
exports.default = BackupCommand;
|
package/dist/commands/logout.js
CHANGED
|
@@ -71,6 +71,8 @@ const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
|
|
|
71
71
|
const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
|
|
72
72
|
const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
|
|
73
73
|
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
74
|
+
const HERMES_PROVIDER_NAME = "The Claw Bay";
|
|
75
|
+
const HERMES_KEY_ENV_NAME = "HERMES_THECLAWBAY_API_KEY";
|
|
74
76
|
const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
|
|
75
77
|
const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
|
|
76
78
|
const MANAGED_EDITOR_TERMINAL_ENV_NAMES = [
|
|
@@ -272,6 +274,36 @@ async function readFileIfExists(filePath) {
|
|
|
272
274
|
throw error;
|
|
273
275
|
}
|
|
274
276
|
}
|
|
277
|
+
function hermesHomeDir() {
|
|
278
|
+
const raw = process.env.HERMES_HOME?.trim();
|
|
279
|
+
if (raw)
|
|
280
|
+
return raw;
|
|
281
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".hermes");
|
|
282
|
+
}
|
|
283
|
+
function hermesConfigPath() {
|
|
284
|
+
return node_path_1.default.join(hermesHomeDir(), "config.yaml");
|
|
285
|
+
}
|
|
286
|
+
function hermesEnvPath() {
|
|
287
|
+
return node_path_1.default.join(hermesHomeDir(), ".env");
|
|
288
|
+
}
|
|
289
|
+
async function removeDotEnvValue(filePath, key) {
|
|
290
|
+
const existing = await readFileIfExists(filePath);
|
|
291
|
+
if (existing === null)
|
|
292
|
+
return false;
|
|
293
|
+
const nextLines = existing
|
|
294
|
+
.split(/\r?\n/)
|
|
295
|
+
.filter((line) => !new RegExp(`^\\s*${key}\\s*=`).test(line));
|
|
296
|
+
const next = `${nextLines.join("\n").trimEnd()}\n`;
|
|
297
|
+
const normalizedExisting = `${existing.trimEnd()}\n`;
|
|
298
|
+
if (next === normalizedExisting)
|
|
299
|
+
return false;
|
|
300
|
+
if (!next.trim()) {
|
|
301
|
+
return removeFileIfExists(filePath);
|
|
302
|
+
}
|
|
303
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
304
|
+
await promises_1.default.writeFile(filePath, next, "utf8");
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
275
307
|
function localAppDataDir() {
|
|
276
308
|
if (process.env.LOCALAPPDATA?.trim())
|
|
277
309
|
return process.env.LOCALAPPDATA;
|
|
@@ -1308,6 +1340,85 @@ async function cleanupKiloConfig() {
|
|
|
1308
1340
|
restoreStatePath: KILO_RESTORE_STATE_PATH,
|
|
1309
1341
|
});
|
|
1310
1342
|
}
|
|
1343
|
+
async function cleanupHermesConfig() {
|
|
1344
|
+
const configPath = hermesConfigPath();
|
|
1345
|
+
const envPath = hermesEnvPath();
|
|
1346
|
+
const existingRaw = await readFileIfExists(configPath);
|
|
1347
|
+
let configChanged = false;
|
|
1348
|
+
let removedManagedProvider = false;
|
|
1349
|
+
const removedProviderBaseUrls = new Set();
|
|
1350
|
+
if (existingRaw !== null && existingRaw.trim()) {
|
|
1351
|
+
try {
|
|
1352
|
+
const existingConfig = objectRecordOr((0, yaml_1.parseDocument)(existingRaw).toJS(), {});
|
|
1353
|
+
const nextConfig = { ...existingConfig };
|
|
1354
|
+
const rawCustomProviders = Array.isArray(existingConfig.custom_providers)
|
|
1355
|
+
? existingConfig.custom_providers
|
|
1356
|
+
: [];
|
|
1357
|
+
const nextCustomProviders = rawCustomProviders.filter((entry) => {
|
|
1358
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
1359
|
+
return true;
|
|
1360
|
+
const record = entry;
|
|
1361
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
1362
|
+
const providerBaseUrl = typeof record.base_url === "string" ? record.base_url.trim().replace(/\/+$/g, "") : "";
|
|
1363
|
+
const keyEnv = typeof record.key_env === "string" ? record.key_env.trim() : "";
|
|
1364
|
+
if (keyEnv === HERMES_KEY_ENV_NAME) {
|
|
1365
|
+
removedManagedProvider = true;
|
|
1366
|
+
if (providerBaseUrl)
|
|
1367
|
+
removedProviderBaseUrls.add(providerBaseUrl);
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
if (name === HERMES_PROVIDER_NAME && isTheClawBayOpenAiCompatibleBaseUrl(providerBaseUrl)) {
|
|
1371
|
+
removedManagedProvider = true;
|
|
1372
|
+
if (providerBaseUrl)
|
|
1373
|
+
removedProviderBaseUrls.add(providerBaseUrl);
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
return true;
|
|
1377
|
+
});
|
|
1378
|
+
if (nextCustomProviders.length !== rawCustomProviders.length) {
|
|
1379
|
+
configChanged = true;
|
|
1380
|
+
}
|
|
1381
|
+
if (nextCustomProviders.length > 0) {
|
|
1382
|
+
nextConfig.custom_providers = nextCustomProviders;
|
|
1383
|
+
}
|
|
1384
|
+
else if ("custom_providers" in nextConfig) {
|
|
1385
|
+
delete nextConfig.custom_providers;
|
|
1386
|
+
configChanged = true;
|
|
1387
|
+
}
|
|
1388
|
+
const modelConfig = objectRecordOr(existingConfig.model, {});
|
|
1389
|
+
const provider = typeof modelConfig.provider === "string" ? modelConfig.provider.trim() : "";
|
|
1390
|
+
const baseUrl = typeof modelConfig.base_url === "string" ? modelConfig.base_url.trim().replace(/\/+$/g, "") : "";
|
|
1391
|
+
const apiMode = typeof modelConfig.api_mode === "string" ? modelConfig.api_mode.trim() : "";
|
|
1392
|
+
const shouldRemoveManagedModel = provider === "custom" &&
|
|
1393
|
+
(isTheClawBayOpenAiCompatibleBaseUrl(baseUrl) ||
|
|
1394
|
+
(removedManagedProvider && apiMode === "codex_responses" && removedProviderBaseUrls.has(baseUrl)));
|
|
1395
|
+
if (shouldRemoveManagedModel) {
|
|
1396
|
+
for (const key of ["provider", "default", "base_url", "api_mode", "api_key", "context_length"]) {
|
|
1397
|
+
if (key in modelConfig) {
|
|
1398
|
+
delete modelConfig[key];
|
|
1399
|
+
configChanged = true;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (Object.keys(modelConfig).length > 0) {
|
|
1403
|
+
nextConfig.model = modelConfig;
|
|
1404
|
+
}
|
|
1405
|
+
else if ("model" in nextConfig) {
|
|
1406
|
+
delete nextConfig.model;
|
|
1407
|
+
configChanged = true;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (configChanged) {
|
|
1411
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
1412
|
+
await promises_1.default.writeFile(configPath, (0, yaml_1.stringify)(nextConfig), "utf8");
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
catch {
|
|
1416
|
+
// Leave invalid user config untouched; still remove our dedicated env key below.
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const envChanged = await removeDotEnvValue(envPath, HERMES_KEY_ENV_NAME);
|
|
1420
|
+
return configChanged || envChanged;
|
|
1421
|
+
}
|
|
1311
1422
|
async function cleanupTraeBundle() {
|
|
1312
1423
|
const bundlePath = traeBundlePath();
|
|
1313
1424
|
if (!bundlePath)
|
|
@@ -1359,6 +1470,7 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
1359
1470
|
let updatedOpenCodeConfig = false;
|
|
1360
1471
|
let updatedKiloConfig = false;
|
|
1361
1472
|
let updatedRooConfig = false;
|
|
1473
|
+
let updatedHermesConfig = false;
|
|
1362
1474
|
let updatedTraeBundle = false;
|
|
1363
1475
|
let updatedAiderConfig = false;
|
|
1364
1476
|
let sessionMigration;
|
|
@@ -1436,6 +1548,8 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
1436
1548
|
updatedKiloConfig = await cleanupKiloConfig();
|
|
1437
1549
|
progress.update("Reverting Roo Code");
|
|
1438
1550
|
updatedRooConfig = await cleanupRooConfig();
|
|
1551
|
+
progress.update("Reverting Hermes");
|
|
1552
|
+
updatedHermesConfig = await cleanupHermesConfig();
|
|
1439
1553
|
progress.update("Reverting Trae");
|
|
1440
1554
|
updatedTraeBundle = await cleanupTraeBundle();
|
|
1441
1555
|
progress.update("Reverting Aider");
|
|
@@ -1491,6 +1605,8 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
1491
1605
|
revertedTargets.push("Kilo Code");
|
|
1492
1606
|
if (updatedRooConfig)
|
|
1493
1607
|
revertedTargets.push("Roo Code");
|
|
1608
|
+
if (updatedHermesConfig)
|
|
1609
|
+
revertedTargets.push("Hermes");
|
|
1494
1610
|
if (updatedTraeBundle)
|
|
1495
1611
|
revertedTargets.push("Trae");
|
|
1496
1612
|
if (updatedAiderConfig)
|
|
@@ -1595,6 +1711,7 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
1595
1711
|
this.log(`- OpenCode config cleaned: ${updatedOpenCodeConfig ? "yes" : "no"}`);
|
|
1596
1712
|
this.log(`- Kilo config cleaned: ${updatedKiloConfig ? "yes" : "no"}`);
|
|
1597
1713
|
this.log(`- Roo Code setup hook cleaned: ${updatedRooConfig ? "yes" : "no"}`);
|
|
1714
|
+
this.log(`- Hermes config cleaned: ${updatedHermesConfig ? "yes" : "no"}`);
|
|
1598
1715
|
this.log(`- Trae experimental patch cleaned: ${updatedTraeBundle ? "yes" : "no"}`);
|
|
1599
1716
|
this.log(`- Aider config cleaned: ${updatedAiderConfig ? "yes" : "no"}`);
|
|
1600
1717
|
this.log("Note: restart terminals/VS Code windows to clear already-loaded shell environment.");
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export default class SetupCommand extends BaseCommand {
|
|
|
6
6
|
"api-key": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
7
|
"device-name": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
8
|
clients: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
model: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
"claude-models": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
"list-models": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
backup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
13
|
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
14
|
"migrate-conversations": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
15
|
"debug-output": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -13,6 +13,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
13
13
|
const core_1 = require("@oclif/core");
|
|
14
14
|
const yaml_1 = require("yaml");
|
|
15
15
|
const base_command_1 = require("../lib/base-command");
|
|
16
|
+
const backups_1 = require("../lib/backups");
|
|
16
17
|
const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
17
18
|
const codex_history_migration_1 = require("../lib/codex-history-migration");
|
|
18
19
|
const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
|
|
@@ -36,6 +37,7 @@ const DEFAULT_ROO_MODEL = DEFAULT_CODEX_MODEL;
|
|
|
36
37
|
const DEFAULT_TRAE_MODEL = DEFAULT_CODEX_MODEL;
|
|
37
38
|
const DEFAULT_AIDER_MODEL = DEFAULT_CODEX_MODEL;
|
|
38
39
|
const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
|
|
40
|
+
const DEFAULT_HERMES_MODEL = DEFAULT_CODEX_MODEL;
|
|
39
41
|
const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
40
42
|
const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
|
|
41
43
|
const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
|
|
@@ -92,7 +94,7 @@ const SHELL_END = "# theclawbay-shell-managed:end";
|
|
|
92
94
|
const OPENCLAW_PROVIDER_ID = DEFAULT_PROVIDER_ID;
|
|
93
95
|
const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan", DEFAULT_PROVIDER_ID]);
|
|
94
96
|
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", DEFAULT_PROVIDER_ID, "theclawbay-wan"];
|
|
95
|
-
const SETUP_CLIENT_IDS = ["codex", "claude", "continue", "cline", "gsd", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo"];
|
|
97
|
+
const SETUP_CLIENT_IDS = ["codex", "claude", "continue", "cline", "gsd", "openclaw", "opencode", "kilo", "roo", "trae", "aider", "zo", "hermes"];
|
|
96
98
|
const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
97
99
|
const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
|
|
98
100
|
const CANONICAL_CODEX_NATIVE_PROXY_SUFFIX = "/backend-api/codex";
|
|
@@ -113,6 +115,8 @@ const ZO_CONFIG_NAME_PREFIX = "The Claw Bay";
|
|
|
113
115
|
const ZO_COOKIE_HOST = ".zo.computer";
|
|
114
116
|
const ZO_ACCESS_TOKEN_COOKIE = "access_token";
|
|
115
117
|
const ZO_API_BASE_URL = "https://api.zo.computer";
|
|
118
|
+
const HERMES_PROVIDER_NAME = "The Claw Bay";
|
|
119
|
+
const HERMES_KEY_ENV_NAME = "HERMES_THECLAWBAY_API_KEY";
|
|
116
120
|
function trimTrailingSlash(value) {
|
|
117
121
|
return value.replace(/\/+$/g, "");
|
|
118
122
|
}
|
|
@@ -1225,6 +1229,62 @@ async function resolveDeviceLabel(params) {
|
|
|
1225
1229
|
rl.close();
|
|
1226
1230
|
}
|
|
1227
1231
|
}
|
|
1232
|
+
async function promptForPrimaryModel(models, defaultModel) {
|
|
1233
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || models.length <= 1) {
|
|
1234
|
+
return defaultModel;
|
|
1235
|
+
}
|
|
1236
|
+
const rl = (0, promises_2.createInterface)({
|
|
1237
|
+
input: process.stdin,
|
|
1238
|
+
output: process.stdout,
|
|
1239
|
+
});
|
|
1240
|
+
try {
|
|
1241
|
+
const lines = models.map((model, index) => {
|
|
1242
|
+
const defaultSuffix = model.id === defaultModel ? " (default)" : "";
|
|
1243
|
+
return ` ${index + 1}. ${model.name} — ${model.id}${defaultSuffix}`;
|
|
1244
|
+
});
|
|
1245
|
+
const answer = await rl.question(`\nChoose the default OpenAI-compatible model for local setup:\n${lines.join("\n")}\nSelect a number or press Enter for ${defaultModel}: `);
|
|
1246
|
+
const trimmed = answer.trim();
|
|
1247
|
+
if (!trimmed)
|
|
1248
|
+
return defaultModel;
|
|
1249
|
+
const asNumber = Number.parseInt(trimmed, 10);
|
|
1250
|
+
if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= models.length) {
|
|
1251
|
+
return models[asNumber - 1]?.id ?? defaultModel;
|
|
1252
|
+
}
|
|
1253
|
+
const directMatch = models.find((model) => model.id === trimmed);
|
|
1254
|
+
if (directMatch)
|
|
1255
|
+
return directMatch.id;
|
|
1256
|
+
throw new Error(`Unknown model selection "${trimmed}".`);
|
|
1257
|
+
}
|
|
1258
|
+
finally {
|
|
1259
|
+
rl.close();
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
async function resolveConfiguredModelSelection(params) {
|
|
1263
|
+
const requestedModel = params.requestedModel?.trim();
|
|
1264
|
+
const availableIds = new Set(params.resolved.models.map((entry) => entry.id));
|
|
1265
|
+
if (requestedModel) {
|
|
1266
|
+
if (availableIds.size === 0 || availableIds.has(requestedModel)) {
|
|
1267
|
+
return {
|
|
1268
|
+
model: requestedModel,
|
|
1269
|
+
note: availableIds.size === 0 && params.resolved.failure
|
|
1270
|
+
? `Could not verify requested model ${requestedModel} against the live backend (${params.resolved.failure}).`
|
|
1271
|
+
: undefined,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
throw new Error(`Requested model "${requestedModel}" is not advertised by the live backend.`);
|
|
1275
|
+
}
|
|
1276
|
+
if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1277
|
+
return { model: params.resolved.model };
|
|
1278
|
+
}
|
|
1279
|
+
const selected = await promptForPrimaryModel(params.resolved.models, params.resolved.model);
|
|
1280
|
+
if (selected === params.resolved.model) {
|
|
1281
|
+
return { model: selected };
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
model: selected,
|
|
1285
|
+
note: `Using ${selected} as the default configured model for local tools.`,
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1228
1288
|
function summarizeModelFetchFailure(detail) {
|
|
1229
1289
|
const normalized = detail.replace(/\s+/g, " ").trim();
|
|
1230
1290
|
if (!normalized)
|
|
@@ -1511,6 +1571,111 @@ async function detectZoClient() {
|
|
|
1511
1571
|
}
|
|
1512
1572
|
return false;
|
|
1513
1573
|
}
|
|
1574
|
+
function hermesHomeDir() {
|
|
1575
|
+
const raw = process.env.HERMES_HOME?.trim();
|
|
1576
|
+
if (raw)
|
|
1577
|
+
return raw;
|
|
1578
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".hermes");
|
|
1579
|
+
}
|
|
1580
|
+
function hermesConfigPath() {
|
|
1581
|
+
return node_path_1.default.join(hermesHomeDir(), "config.yaml");
|
|
1582
|
+
}
|
|
1583
|
+
function hermesEnvPath() {
|
|
1584
|
+
return node_path_1.default.join(hermesHomeDir(), ".env");
|
|
1585
|
+
}
|
|
1586
|
+
async function detectHermesClient() {
|
|
1587
|
+
if (hasCommand("hermes"))
|
|
1588
|
+
return true;
|
|
1589
|
+
const candidates = [
|
|
1590
|
+
hermesHomeDir(),
|
|
1591
|
+
hermesConfigPath(),
|
|
1592
|
+
hermesEnvPath(),
|
|
1593
|
+
];
|
|
1594
|
+
for (const candidate of candidates) {
|
|
1595
|
+
if (await pathExists(candidate))
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
function formatDotEnvValue(value) {
|
|
1601
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1602
|
+
}
|
|
1603
|
+
async function upsertDotEnvValue(filePath, key, value) {
|
|
1604
|
+
const existing = (await readFileIfExists(filePath)) ?? "";
|
|
1605
|
+
const lines = existing ? existing.split(/\r?\n/) : [];
|
|
1606
|
+
const assignment = `${key}=${formatDotEnvValue(value)}`;
|
|
1607
|
+
let changed = false;
|
|
1608
|
+
let replaced = false;
|
|
1609
|
+
const nextLines = lines.map((line) => {
|
|
1610
|
+
if (new RegExp(`^\\s*${key}\\s*=`).test(line)) {
|
|
1611
|
+
replaced = true;
|
|
1612
|
+
if (line === assignment)
|
|
1613
|
+
return line;
|
|
1614
|
+
changed = true;
|
|
1615
|
+
return assignment;
|
|
1616
|
+
}
|
|
1617
|
+
return line;
|
|
1618
|
+
});
|
|
1619
|
+
if (!replaced) {
|
|
1620
|
+
if (nextLines.length > 0 && nextLines[nextLines.length - 1]?.trim()) {
|
|
1621
|
+
nextLines.push("");
|
|
1622
|
+
}
|
|
1623
|
+
nextLines.push(assignment);
|
|
1624
|
+
changed = true;
|
|
1625
|
+
}
|
|
1626
|
+
if (!changed)
|
|
1627
|
+
return false;
|
|
1628
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
1629
|
+
await promises_1.default.writeFile(filePath, `${nextLines.join("\n").trimEnd()}\n`, "utf8");
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
async function writeHermesConfig(params) {
|
|
1633
|
+
const configPath = hermesConfigPath();
|
|
1634
|
+
const envPath = hermesEnvPath();
|
|
1635
|
+
const baseUrl = openAiCompatibleProxyUrl(params.backendUrl);
|
|
1636
|
+
const selectedModel = params.model.trim() || DEFAULT_HERMES_MODEL;
|
|
1637
|
+
const existingRaw = await readFileIfExists(configPath);
|
|
1638
|
+
const existingConfig = existingRaw?.trim()
|
|
1639
|
+
? objectRecordOr((0, yaml_1.parseDocument)(existingRaw).toJS(), {})
|
|
1640
|
+
: {};
|
|
1641
|
+
const nextConfig = { ...existingConfig };
|
|
1642
|
+
const modelConfig = objectRecordOr(existingConfig.model, {});
|
|
1643
|
+
modelConfig.provider = "custom";
|
|
1644
|
+
modelConfig.default = selectedModel;
|
|
1645
|
+
modelConfig.base_url = baseUrl;
|
|
1646
|
+
modelConfig.api_mode = "codex_responses";
|
|
1647
|
+
delete modelConfig.api_key;
|
|
1648
|
+
delete modelConfig.context_length;
|
|
1649
|
+
nextConfig.model = modelConfig;
|
|
1650
|
+
const rawCustomProviders = Array.isArray(existingConfig.custom_providers)
|
|
1651
|
+
? existingConfig.custom_providers
|
|
1652
|
+
: [];
|
|
1653
|
+
const nextCustomProviders = rawCustomProviders.filter((entry) => {
|
|
1654
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
1655
|
+
return true;
|
|
1656
|
+
const record = entry;
|
|
1657
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
1658
|
+
const providerBaseUrl = typeof record.base_url === "string" ? record.base_url.trim() : "";
|
|
1659
|
+
const keyEnv = typeof record.key_env === "string" ? record.key_env.trim() : "";
|
|
1660
|
+
if (keyEnv === HERMES_KEY_ENV_NAME)
|
|
1661
|
+
return false;
|
|
1662
|
+
if (name === HERMES_PROVIDER_NAME && providerBaseUrl === baseUrl)
|
|
1663
|
+
return false;
|
|
1664
|
+
return true;
|
|
1665
|
+
});
|
|
1666
|
+
nextCustomProviders.push({
|
|
1667
|
+
name: HERMES_PROVIDER_NAME,
|
|
1668
|
+
base_url: baseUrl,
|
|
1669
|
+
key_env: HERMES_KEY_ENV_NAME,
|
|
1670
|
+
model: selectedModel,
|
|
1671
|
+
api_mode: "codex_responses",
|
|
1672
|
+
});
|
|
1673
|
+
nextConfig.custom_providers = nextCustomProviders;
|
|
1674
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
1675
|
+
await promises_1.default.writeFile(configPath, (0, yaml_1.stringify)(nextConfig), "utf8");
|
|
1676
|
+
await upsertDotEnvValue(envPath, HERMES_KEY_ENV_NAME, params.apiKey);
|
|
1677
|
+
return [configPath, envPath];
|
|
1678
|
+
}
|
|
1514
1679
|
async function resolveModels(backendUrl, apiKey) {
|
|
1515
1680
|
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1516
1681
|
const available = new Set(ids ?? []);
|
|
@@ -1570,6 +1735,29 @@ async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
|
1570
1735
|
authRejected,
|
|
1571
1736
|
};
|
|
1572
1737
|
}
|
|
1738
|
+
function resolveConfiguredClaudeModels(params) {
|
|
1739
|
+
const requestedModels = params.requestedModels
|
|
1740
|
+
?.split(",")
|
|
1741
|
+
.map((entry) => entry.trim())
|
|
1742
|
+
.filter(Boolean);
|
|
1743
|
+
if (!requestedModels?.length || !params.access.enabled) {
|
|
1744
|
+
return {
|
|
1745
|
+
...params.access,
|
|
1746
|
+
selectedModels: params.access.models,
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
const available = new Set(params.access.models);
|
|
1750
|
+
for (const modelId of requestedModels) {
|
|
1751
|
+
if (!available.has(modelId)) {
|
|
1752
|
+
throw new Error(`Requested Claude model "${modelId}" is not advertised by the live Claude endpoint.`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return {
|
|
1756
|
+
...params.access,
|
|
1757
|
+
selectedModels: requestedModels,
|
|
1758
|
+
note: `Configured Claude-compatible clients with ${requestedModels.join(", ")}.`,
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1573
1761
|
async function writeCodexConfig(params) {
|
|
1574
1762
|
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
1575
1763
|
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
@@ -2892,7 +3080,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2892
3080
|
managed?.backendUrl ??
|
|
2893
3081
|
DEFAULT_BACKEND_URL;
|
|
2894
3082
|
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
2895
|
-
const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, openCodeDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected] = await Promise.all([
|
|
3083
|
+
const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, openCodeDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected, hermesDetected] = await Promise.all([
|
|
2896
3084
|
detectCodexClient(),
|
|
2897
3085
|
detectClaudeClient(),
|
|
2898
3086
|
detectContinueClient(),
|
|
@@ -2904,6 +3092,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
2904
3092
|
detectTraeClient(),
|
|
2905
3093
|
detectAiderClient(),
|
|
2906
3094
|
detectZoClient(),
|
|
3095
|
+
detectHermesClient(),
|
|
2907
3096
|
]);
|
|
2908
3097
|
const setupClients = [
|
|
2909
3098
|
{
|
|
@@ -3014,6 +3203,15 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3014
3203
|
icon: "◉",
|
|
3015
3204
|
siteUrl: "https://www.zo.computer",
|
|
3016
3205
|
},
|
|
3206
|
+
{
|
|
3207
|
+
id: "hermes",
|
|
3208
|
+
label: "Hermes Agent",
|
|
3209
|
+
summaryLabel: "Hermes",
|
|
3210
|
+
detected: hermesDetected,
|
|
3211
|
+
recommended: true,
|
|
3212
|
+
icon: "☿",
|
|
3213
|
+
siteUrl: "https://hermes-agent.nousresearch.com/docs/",
|
|
3214
|
+
},
|
|
3017
3215
|
];
|
|
3018
3216
|
const selectedSetupClients = await resolveSetupClientSelection({
|
|
3019
3217
|
setupClients,
|
|
@@ -3058,6 +3256,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3058
3256
|
const progress = this.createProgressHandle(!debugOutput);
|
|
3059
3257
|
let resolved = null;
|
|
3060
3258
|
let claudeAccess = null;
|
|
3259
|
+
let backupSummary = null;
|
|
3061
3260
|
let updatedShellFiles = [];
|
|
3062
3261
|
let updatedFishFiles = [];
|
|
3063
3262
|
let updatedPowerShellProfiles = [];
|
|
@@ -3082,6 +3281,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3082
3281
|
let traeBundlePathPatched = null;
|
|
3083
3282
|
let aiderConfigPath = null;
|
|
3084
3283
|
let zoConfigName = null;
|
|
3284
|
+
let hermesConfigPaths = [];
|
|
3085
3285
|
try {
|
|
3086
3286
|
if (selectedSetupClients.size > 0) {
|
|
3087
3287
|
progress.update("Resolving supported models");
|
|
@@ -3089,14 +3289,48 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3089
3289
|
if (resolved.authRejected) {
|
|
3090
3290
|
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
3091
3291
|
}
|
|
3292
|
+
const configuredModel = await resolveConfiguredModelSelection({
|
|
3293
|
+
resolved,
|
|
3294
|
+
requestedModel: flags.model,
|
|
3295
|
+
skipPrompt: flags.yes,
|
|
3296
|
+
});
|
|
3297
|
+
resolved = {
|
|
3298
|
+
...resolved,
|
|
3299
|
+
model: configuredModel.model,
|
|
3300
|
+
note: [resolved.note, configuredModel.note].filter(Boolean).join(" ").trim() || undefined,
|
|
3301
|
+
};
|
|
3092
3302
|
}
|
|
3093
3303
|
progress.update("Checking Claude access");
|
|
3094
3304
|
claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
|
|
3095
3305
|
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
3096
3306
|
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
3097
3307
|
}
|
|
3308
|
+
claudeAccess = resolveConfiguredClaudeModels({
|
|
3309
|
+
access: claudeAccess,
|
|
3310
|
+
requestedModels: flags["claude-models"],
|
|
3311
|
+
});
|
|
3312
|
+
if (flags["list-models"]) {
|
|
3313
|
+
if (resolved) {
|
|
3314
|
+
this.log("\nOpenAI-compatible models:");
|
|
3315
|
+
for (const model of resolved.models) {
|
|
3316
|
+
const marker = model.id === resolved.model ? " (default)" : "";
|
|
3317
|
+
this.log(`- ${model.id}${marker}`);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
if (claudeAccess.models.length > 0) {
|
|
3321
|
+
this.log("\nClaude models:");
|
|
3322
|
+
for (const modelId of claudeAccess.models) {
|
|
3323
|
+
const marker = claudeAccess.selectedModels?.includes(modelId) ? " (selected)" : "";
|
|
3324
|
+
this.log(`- ${modelId}${marker}`);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3098
3328
|
const claudeSelected = selectedSetupClients.has("claude");
|
|
3099
3329
|
const claudeEnvEnabled = claudeSelected && claudeAccess.enabled;
|
|
3330
|
+
if (flags.backup) {
|
|
3331
|
+
progress.update("Creating local config backup");
|
|
3332
|
+
backupSummary = await (0, backups_1.createBackup)("pre-setup backup");
|
|
3333
|
+
}
|
|
3100
3334
|
progress.update("Saving shared machine config");
|
|
3101
3335
|
await (0, config_1.writeManagedConfig)({
|
|
3102
3336
|
backendUrl,
|
|
@@ -3138,7 +3372,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3138
3372
|
claudeDesktop3pConfigPathManaged = await writeClaudeDesktop3pConfig({
|
|
3139
3373
|
backendUrl,
|
|
3140
3374
|
apiKey: authCredential,
|
|
3141
|
-
claudeModels: claudeAccess?.enabled ? claudeAccess.models : [],
|
|
3375
|
+
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
3142
3376
|
});
|
|
3143
3377
|
}
|
|
3144
3378
|
else {
|
|
@@ -3226,7 +3460,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3226
3460
|
backendUrl,
|
|
3227
3461
|
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
3228
3462
|
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
3229
|
-
claudeModels: claudeAccess?.enabled ? claudeAccess.models : [],
|
|
3463
|
+
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
3230
3464
|
apiKey: authCredential,
|
|
3231
3465
|
});
|
|
3232
3466
|
}
|
|
@@ -3272,6 +3506,14 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3272
3506
|
apiKey: authCredential,
|
|
3273
3507
|
});
|
|
3274
3508
|
}
|
|
3509
|
+
if (selectedSetupClients.has("hermes")) {
|
|
3510
|
+
progress.update("Configuring Hermes Agent");
|
|
3511
|
+
hermesConfigPaths = await writeHermesConfig({
|
|
3512
|
+
backendUrl,
|
|
3513
|
+
model: resolved?.model ?? DEFAULT_HERMES_MODEL,
|
|
3514
|
+
apiKey: authCredential,
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3275
3517
|
}
|
|
3276
3518
|
catch (error) {
|
|
3277
3519
|
progress.fail("Setup failed");
|
|
@@ -3286,6 +3528,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3286
3528
|
const summaryNotes = new Set();
|
|
3287
3529
|
if (resolved?.note)
|
|
3288
3530
|
summaryNotes.add(resolved.note);
|
|
3531
|
+
if (backupSummary) {
|
|
3532
|
+
summaryNotes.add(`Created a local backup snapshot (${backupSummary.id}) before changing client configs.`);
|
|
3533
|
+
}
|
|
3289
3534
|
if (selectedSetupClients.has("claude") && claudeAccess?.enabled) {
|
|
3290
3535
|
summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY.");
|
|
3291
3536
|
if (claudeDesktop3pConfigPathManaged) {
|
|
@@ -3302,6 +3547,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3302
3547
|
if (selectedSetupClients.has("zo")) {
|
|
3303
3548
|
summaryNotes.add("Zo uses an experimental account-level BYOK API integration. Keep Zo signed in while running setup.");
|
|
3304
3549
|
}
|
|
3550
|
+
if (selectedSetupClients.has("hermes")) {
|
|
3551
|
+
summaryNotes.add("Hermes Agent: added a named custom provider that uses HERMES_THECLAWBAY_API_KEY, so Hermes can keep its custom-endpoint routing without depending on OPENAI_BASE_URL.");
|
|
3552
|
+
}
|
|
3305
3553
|
if (openClawCliWarning)
|
|
3306
3554
|
summaryNotes.add(openClawCliWarning);
|
|
3307
3555
|
if ((stateDbMigration?.failed.length ?? 0) > 0) {
|
|
@@ -3337,6 +3585,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3337
3585
|
this.log(`- Managed config: ${paths_1.managedConfigPath}`);
|
|
3338
3586
|
this.log(`- Backend: ${backendUrl}`);
|
|
3339
3587
|
this.log(`- Auth mode: ${authType}`);
|
|
3588
|
+
if (backupSummary) {
|
|
3589
|
+
this.log(`- Backup: ${backupSummary.id} (${backupSummary.directory})`);
|
|
3590
|
+
}
|
|
3340
3591
|
if (authType === "device-session") {
|
|
3341
3592
|
this.log(`- Device session id: ${deviceSessionId ?? "n/a"}`);
|
|
3342
3593
|
this.log(`- Device label: ${deviceLabel ?? "n/a"}`);
|
|
@@ -3551,6 +3802,16 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3551
3802
|
else {
|
|
3552
3803
|
this.log("- Zo: not detected (skipped)");
|
|
3553
3804
|
}
|
|
3805
|
+
if (selectedSetupClients.has("hermes")) {
|
|
3806
|
+
this.log(`- Hermes Agent: configured (${hermesConfigPaths.join(", ")})`);
|
|
3807
|
+
this.log("- Hermes Agent note: saved a named custom provider plus HERMES_THECLAWBAY_API_KEY so Hermes can reuse its custom-provider credential-pool path without relying on OPENAI_BASE_URL.");
|
|
3808
|
+
}
|
|
3809
|
+
else if (hermesDetected) {
|
|
3810
|
+
this.log("- Hermes Agent: detected but skipped");
|
|
3811
|
+
}
|
|
3812
|
+
else {
|
|
3813
|
+
this.log("- Hermes Agent: not detected (skipped)");
|
|
3814
|
+
}
|
|
3554
3815
|
if (selectedSetupClients.has("claude")) {
|
|
3555
3816
|
this.log(`- Claude Code: ${claudeAccess?.enabled ? "configured via shared env" : "selected, but no Claude access was detected for this credential"}`);
|
|
3556
3817
|
}
|
|
@@ -3582,7 +3843,26 @@ SetupCommand.flags = {
|
|
|
3582
3843
|
}),
|
|
3583
3844
|
clients: core_1.Flags.string({
|
|
3584
3845
|
required: false,
|
|
3585
|
-
description: "Detected local clients to configure: codex, claude, continue, cline, gsd, openclaw, opencode, kilo, roo, trae, aider, zo",
|
|
3846
|
+
description: "Detected local clients to configure: codex, claude, continue, cline, gsd, openclaw, opencode, kilo, roo, trae, aider, zo, hermes",
|
|
3847
|
+
}),
|
|
3848
|
+
model: core_1.Flags.string({
|
|
3849
|
+
required: false,
|
|
3850
|
+
description: "Default OpenAI-compatible model to configure for local tools",
|
|
3851
|
+
}),
|
|
3852
|
+
"claude-models": core_1.Flags.string({
|
|
3853
|
+
required: false,
|
|
3854
|
+
description: "Comma-separated Claude model ids to configure for Claude-compatible tools",
|
|
3855
|
+
}),
|
|
3856
|
+
"list-models": core_1.Flags.boolean({
|
|
3857
|
+
required: false,
|
|
3858
|
+
default: false,
|
|
3859
|
+
description: "Print the live OpenAI-compatible and Claude model ids discovered during setup",
|
|
3860
|
+
}),
|
|
3861
|
+
backup: core_1.Flags.boolean({
|
|
3862
|
+
required: false,
|
|
3863
|
+
allowNo: true,
|
|
3864
|
+
default: true,
|
|
3865
|
+
description: "Create a local backup before changing client configs",
|
|
3586
3866
|
}),
|
|
3587
3867
|
yes: core_1.Flags.boolean({
|
|
3588
3868
|
required: false,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type BackupTargetKind = "file" | "directory";
|
|
2
|
+
export type BackupTargetManifestEntry = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
targetPath: string;
|
|
6
|
+
kind: BackupTargetKind;
|
|
7
|
+
existed: boolean;
|
|
8
|
+
snapshotRelativePath: string | null;
|
|
9
|
+
};
|
|
10
|
+
export type BackupManifest = {
|
|
11
|
+
version: 1;
|
|
12
|
+
id: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
hostname: string;
|
|
16
|
+
platform: NodeJS.Platform;
|
|
17
|
+
targets: BackupTargetManifestEntry[];
|
|
18
|
+
};
|
|
19
|
+
export type BackupSummary = {
|
|
20
|
+
id: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
totalTargets: number;
|
|
24
|
+
capturedTargets: number;
|
|
25
|
+
directory: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function formatBackupTimestamp(isoString: string): string;
|
|
28
|
+
export declare function createBackup(reason: string): Promise<BackupSummary>;
|
|
29
|
+
export declare function listBackups(): Promise<BackupSummary[]>;
|
|
30
|
+
export declare function restoreBackup(id: string): Promise<BackupSummary>;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatBackupTimestamp = formatBackupTimestamp;
|
|
7
|
+
exports.createBackup = createBackup;
|
|
8
|
+
exports.listBackups = listBackups;
|
|
9
|
+
exports.restoreBackup = restoreBackup;
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
13
|
+
const paths_1 = require("./config/paths");
|
|
14
|
+
function homePath(...parts) {
|
|
15
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ...parts);
|
|
16
|
+
}
|
|
17
|
+
function shellRcTargets() {
|
|
18
|
+
return [
|
|
19
|
+
[".bashrc", "Bash profile"],
|
|
20
|
+
[".bash_profile", "Bash login profile"],
|
|
21
|
+
[".bash_login", "Alternate bash login profile"],
|
|
22
|
+
[".zshrc", "Zsh profile"],
|
|
23
|
+
[".zprofile", "Zsh login profile"],
|
|
24
|
+
[".zlogin", "Zsh post-login profile"],
|
|
25
|
+
[".profile", "POSIX shell profile"],
|
|
26
|
+
].map(([fileName, label]) => ({
|
|
27
|
+
id: `shell-${fileName.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase()}`,
|
|
28
|
+
label,
|
|
29
|
+
targetPath: homePath(fileName),
|
|
30
|
+
kind: "file",
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function editorSettingsTargets() {
|
|
34
|
+
const home = node_os_1.default.homedir();
|
|
35
|
+
const targets = [];
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
const appSupport = homePath("Library", "Application Support");
|
|
38
|
+
targets.push([node_path_1.default.join(appSupport, "Code", "User", "settings.json"), "VS Code settings"], [node_path_1.default.join(appSupport, "Code - Insiders", "User", "settings.json"), "VS Code Insiders settings"], [node_path_1.default.join(appSupport, "Cursor", "User", "settings.json"), "Cursor settings"], [node_path_1.default.join(appSupport, "Windsurf", "User", "settings.json"), "Windsurf settings"], [node_path_1.default.join(appSupport, "VSCodium", "User", "settings.json"), "VSCodium settings"], [node_path_1.default.join(appSupport, "Claude-3p", "claude_desktop_config.json"), "Claude Desktop 3P config"]);
|
|
39
|
+
}
|
|
40
|
+
else if (process.platform === "win32") {
|
|
41
|
+
const appData = process.env.APPDATA?.trim() || node_path_1.default.join(home, "AppData", "Roaming");
|
|
42
|
+
targets.push([node_path_1.default.join(appData, "Code", "User", "settings.json"), "VS Code settings"], [node_path_1.default.join(appData, "Code - Insiders", "User", "settings.json"), "VS Code Insiders settings"], [node_path_1.default.join(appData, "Cursor", "User", "settings.json"), "Cursor settings"], [node_path_1.default.join(appData, "Windsurf", "User", "settings.json"), "Windsurf settings"], [node_path_1.default.join(appData, "VSCodium", "User", "settings.json"), "VSCodium settings"]);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
targets.push([homePath(".config", "Code", "User", "settings.json"), "VS Code settings"], [homePath(".config", "Code - Insiders", "User", "settings.json"), "VS Code Insiders settings"], [homePath(".config", "Cursor", "User", "settings.json"), "Cursor settings"], [homePath(".config", "Windsurf", "User", "settings.json"), "Windsurf settings"], [homePath(".config", "VSCodium", "User", "settings.json"), "VSCodium settings"]);
|
|
46
|
+
}
|
|
47
|
+
return targets.map(([targetPath, label]) => ({
|
|
48
|
+
id: `editor-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`,
|
|
49
|
+
label,
|
|
50
|
+
targetPath,
|
|
51
|
+
kind: "file",
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
function backupTargets() {
|
|
55
|
+
const targets = [
|
|
56
|
+
{
|
|
57
|
+
id: "theclawbay-config",
|
|
58
|
+
label: "The Claw Bay config directory",
|
|
59
|
+
targetPath: paths_1.theclawbayConfigDir,
|
|
60
|
+
kind: "directory",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "codex-home",
|
|
64
|
+
label: "Codex home directory",
|
|
65
|
+
targetPath: paths_1.codexDir,
|
|
66
|
+
kind: "directory",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "continue-home",
|
|
70
|
+
label: "Continue config directory",
|
|
71
|
+
targetPath: homePath(".continue"),
|
|
72
|
+
kind: "directory",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "cline-home",
|
|
76
|
+
label: "Cline config directory",
|
|
77
|
+
targetPath: homePath(".cline"),
|
|
78
|
+
kind: "directory",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "gsd-home",
|
|
82
|
+
label: "GSD agent directory",
|
|
83
|
+
targetPath: homePath(".gsd"),
|
|
84
|
+
kind: "directory",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "openclaw-home",
|
|
88
|
+
label: "OpenClaw config directory",
|
|
89
|
+
targetPath: homePath(".openclaw"),
|
|
90
|
+
kind: "directory",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "opencode-config-dir",
|
|
94
|
+
label: "OpenCode config directory",
|
|
95
|
+
targetPath: homePath(".config", "opencode"),
|
|
96
|
+
kind: "directory",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "opencode-home-file",
|
|
100
|
+
label: "OpenCode home config file",
|
|
101
|
+
targetPath: homePath(".opencode.json"),
|
|
102
|
+
kind: "file",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "kilo-config-dir",
|
|
106
|
+
label: "Kilo config directory",
|
|
107
|
+
targetPath: homePath(".config", "kilo"),
|
|
108
|
+
kind: "directory",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "kilo-home-dir",
|
|
112
|
+
label: "Legacy Kilo home directory",
|
|
113
|
+
targetPath: homePath(".kilo"),
|
|
114
|
+
kind: "directory",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "aider-config",
|
|
118
|
+
label: "Aider config file",
|
|
119
|
+
targetPath: homePath(".aider.conf.yml"),
|
|
120
|
+
kind: "file",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "fish-config",
|
|
124
|
+
label: "Fish The Claw Bay env file",
|
|
125
|
+
targetPath: homePath(".config", "fish", "conf.d", "theclawbay.fish"),
|
|
126
|
+
kind: "file",
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
const deduped = new Map();
|
|
130
|
+
for (const target of [...targets, ...shellRcTargets(), ...editorSettingsTargets()]) {
|
|
131
|
+
deduped.set(target.targetPath, target);
|
|
132
|
+
}
|
|
133
|
+
return [...deduped.values()];
|
|
134
|
+
}
|
|
135
|
+
async function pathExists(targetPath) {
|
|
136
|
+
try {
|
|
137
|
+
await promises_1.default.lstat(targetPath);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const err = error;
|
|
142
|
+
if (err.code === "ENOENT")
|
|
143
|
+
return false;
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function ensureCleanDirectory(targetPath) {
|
|
148
|
+
await promises_1.default.rm(targetPath, { recursive: true, force: true });
|
|
149
|
+
await promises_1.default.mkdir(targetPath, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
async function writeManifest(directory, manifest) {
|
|
152
|
+
await promises_1.default.writeFile(node_path_1.default.join(directory, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
153
|
+
}
|
|
154
|
+
function backupIdFromDate(date) {
|
|
155
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
156
|
+
return [
|
|
157
|
+
date.getFullYear(),
|
|
158
|
+
pad(date.getMonth() + 1),
|
|
159
|
+
pad(date.getDate()),
|
|
160
|
+
"-",
|
|
161
|
+
pad(date.getHours()),
|
|
162
|
+
pad(date.getMinutes()),
|
|
163
|
+
pad(date.getSeconds()),
|
|
164
|
+
].join("");
|
|
165
|
+
}
|
|
166
|
+
function formatBackupTimestamp(isoString) {
|
|
167
|
+
const date = new Date(isoString);
|
|
168
|
+
if (Number.isNaN(date.getTime()))
|
|
169
|
+
return isoString;
|
|
170
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
171
|
+
dateStyle: "medium",
|
|
172
|
+
timeStyle: "short",
|
|
173
|
+
}).format(date);
|
|
174
|
+
}
|
|
175
|
+
async function createBackup(reason) {
|
|
176
|
+
const now = new Date();
|
|
177
|
+
const id = backupIdFromDate(now);
|
|
178
|
+
const backupDirectory = node_path_1.default.join(paths_1.theclawbayBackupDir, id);
|
|
179
|
+
const snapshotRoot = node_path_1.default.join(backupDirectory, "files");
|
|
180
|
+
await ensureCleanDirectory(snapshotRoot);
|
|
181
|
+
const manifestTargets = [];
|
|
182
|
+
for (const target of backupTargets()) {
|
|
183
|
+
const existed = await pathExists(target.targetPath);
|
|
184
|
+
const snapshotRelativePath = existed ? node_path_1.default.join("files", target.id) : null;
|
|
185
|
+
if (existed && snapshotRelativePath) {
|
|
186
|
+
const snapshotPath = node_path_1.default.join(backupDirectory, snapshotRelativePath);
|
|
187
|
+
await promises_1.default.cp(target.targetPath, snapshotPath, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
manifestTargets.push({
|
|
190
|
+
id: target.id,
|
|
191
|
+
label: target.label,
|
|
192
|
+
targetPath: target.targetPath,
|
|
193
|
+
kind: target.kind,
|
|
194
|
+
existed,
|
|
195
|
+
snapshotRelativePath,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const manifest = {
|
|
199
|
+
version: 1,
|
|
200
|
+
id,
|
|
201
|
+
createdAt: now.toISOString(),
|
|
202
|
+
reason,
|
|
203
|
+
hostname: node_os_1.default.hostname(),
|
|
204
|
+
platform: process.platform,
|
|
205
|
+
targets: manifestTargets,
|
|
206
|
+
};
|
|
207
|
+
await writeManifest(backupDirectory, manifest);
|
|
208
|
+
return {
|
|
209
|
+
id,
|
|
210
|
+
createdAt: manifest.createdAt,
|
|
211
|
+
reason,
|
|
212
|
+
totalTargets: manifestTargets.length,
|
|
213
|
+
capturedTargets: manifestTargets.filter((target) => target.existed).length,
|
|
214
|
+
directory: backupDirectory,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function readManifest(directory) {
|
|
218
|
+
try {
|
|
219
|
+
const raw = await promises_1.default.readFile(node_path_1.default.join(directory, "manifest.json"), "utf8");
|
|
220
|
+
return JSON.parse(raw);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const err = error;
|
|
224
|
+
if (err.code === "ENOENT")
|
|
225
|
+
return null;
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function listBackups() {
|
|
230
|
+
try {
|
|
231
|
+
const entries = await promises_1.default.readdir(paths_1.theclawbayBackupDir, { withFileTypes: true });
|
|
232
|
+
const manifests = await Promise.all(entries
|
|
233
|
+
.filter((entry) => entry.isDirectory())
|
|
234
|
+
.map(async (entry) => {
|
|
235
|
+
const directory = node_path_1.default.join(paths_1.theclawbayBackupDir, entry.name);
|
|
236
|
+
const manifest = await readManifest(directory);
|
|
237
|
+
if (!manifest)
|
|
238
|
+
return null;
|
|
239
|
+
return {
|
|
240
|
+
id: manifest.id,
|
|
241
|
+
createdAt: manifest.createdAt,
|
|
242
|
+
reason: manifest.reason,
|
|
243
|
+
totalTargets: manifest.targets.length,
|
|
244
|
+
capturedTargets: manifest.targets.filter((target) => target.existed).length,
|
|
245
|
+
directory,
|
|
246
|
+
};
|
|
247
|
+
}));
|
|
248
|
+
return manifests
|
|
249
|
+
.filter((entry) => entry !== null)
|
|
250
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
const err = error;
|
|
254
|
+
if (err.code === "ENOENT")
|
|
255
|
+
return [];
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function restoreBackup(id) {
|
|
260
|
+
const trimmed = id.trim();
|
|
261
|
+
if (!trimmed)
|
|
262
|
+
throw new Error("backup id is required for restore.");
|
|
263
|
+
const backupDirectory = node_path_1.default.join(paths_1.theclawbayBackupDir, trimmed);
|
|
264
|
+
const manifest = await readManifest(backupDirectory);
|
|
265
|
+
if (!manifest) {
|
|
266
|
+
throw new Error(`Backup "${trimmed}" was not found.`);
|
|
267
|
+
}
|
|
268
|
+
for (const target of manifest.targets) {
|
|
269
|
+
if (!target.existed || !target.snapshotRelativePath) {
|
|
270
|
+
await promises_1.default.rm(target.targetPath, { recursive: true, force: true });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const snapshotPath = node_path_1.default.join(backupDirectory, target.snapshotRelativePath);
|
|
274
|
+
await promises_1.default.rm(target.targetPath, { recursive: true, force: true });
|
|
275
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(target.targetPath), { recursive: true });
|
|
276
|
+
await promises_1.default.cp(snapshotPath, target.targetPath, { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
id: manifest.id,
|
|
280
|
+
createdAt: manifest.createdAt,
|
|
281
|
+
reason: manifest.reason,
|
|
282
|
+
totalTargets: manifest.targets.length,
|
|
283
|
+
capturedTargets: manifest.targets.filter((target) => target.existed).length,
|
|
284
|
+
directory: backupDirectory,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -213,7 +213,11 @@ function normalizeSeedModel(template, modelId) {
|
|
|
213
213
|
seed.supports_parallel_tool_calls = true;
|
|
214
214
|
}
|
|
215
215
|
if (typeof seed.context_window !== "number") {
|
|
216
|
-
|
|
216
|
+
const configuredContextWindow = SUPPORTED_MODEL_CONFIG.get(modelId)?.contextWindow;
|
|
217
|
+
seed.context_window =
|
|
218
|
+
typeof configuredContextWindow === "number" && Number.isFinite(configuredContextWindow)
|
|
219
|
+
? configuredContextWindow
|
|
220
|
+
: 272000;
|
|
217
221
|
}
|
|
218
222
|
if (modelId === "gpt-5.5" && typeof seed.minimal_client_version !== "string") {
|
|
219
223
|
seed.minimal_client_version = "0.124.0";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const codexDir: string;
|
|
2
2
|
export declare const theclawbayConfigDir: string;
|
|
3
3
|
export declare const theclawbayStateDir: string;
|
|
4
|
+
export declare const theclawbayBackupDir: string;
|
|
4
5
|
export declare const managedConfigPath: string;
|
|
5
6
|
export declare const updateCheckStatePath: string;
|
|
6
7
|
export declare const legacyManagedConfigPathClayBay: string;
|
package/dist/lib/config/paths.js
CHANGED
|
@@ -3,12 +3,13 @@ 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.theclawbayStateDir = exports.theclawbayConfigDir = exports.codexDir = void 0;
|
|
6
|
+
exports.legacyManagedConfigPathCodexAuth = exports.legacyManagedConfigPathClayBay = exports.updateCheckStatePath = exports.managedConfigPath = exports.theclawbayBackupDir = 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
10
|
exports.theclawbayConfigDir = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay");
|
|
11
11
|
exports.theclawbayStateDir = node_path_1.default.join(exports.theclawbayConfigDir, "state");
|
|
12
|
+
exports.theclawbayBackupDir = node_path_1.default.join(node_os_1.default.homedir(), ".config", "theclawbay-backups");
|
|
12
13
|
exports.managedConfigPath = node_path_1.default.join(exports.codexDir, "theclawbay.managed.json");
|
|
13
14
|
exports.updateCheckStatePath = node_path_1.default.join(exports.codexDir, "theclawbay.update-check.json");
|
|
14
15
|
exports.legacyManagedConfigPathClayBay = node_path_1.default.join(exports.codexDir, "theclaybay.managed.json");
|
|
@@ -7,6 +7,7 @@ export type SupportedModelConfig = {
|
|
|
7
7
|
inputPer1M: number;
|
|
8
8
|
cachedInputPer1M: number;
|
|
9
9
|
outputPer1M: number;
|
|
10
|
+
contextWindow?: number | null;
|
|
10
11
|
};
|
|
11
12
|
export declare function getSupportedModels(): SupportedModelConfig[];
|
|
12
13
|
export declare function getSupportedModelIds(): string[];
|
|
@@ -28,6 +28,9 @@ function parseSupportedModels(raw) {
|
|
|
28
28
|
const inputPer1M = typeof record.inputPer1M === "number" ? record.inputPer1M : NaN;
|
|
29
29
|
const cachedInputPer1M = typeof record.cachedInputPer1M === "number" ? record.cachedInputPer1M : NaN;
|
|
30
30
|
const outputPer1M = typeof record.outputPer1M === "number" ? record.outputPer1M : NaN;
|
|
31
|
+
const contextWindow = typeof record.contextWindow === "number" && Number.isFinite(record.contextWindow) && record.contextWindow > 0
|
|
32
|
+
? Math.round(record.contextWindow)
|
|
33
|
+
: null;
|
|
31
34
|
if (!id || !label || !note) {
|
|
32
35
|
throw new Error("supported-models config is missing required text fields");
|
|
33
36
|
}
|
|
@@ -45,6 +48,7 @@ function parseSupportedModels(raw) {
|
|
|
45
48
|
inputPer1M,
|
|
46
49
|
cachedInputPer1M,
|
|
47
50
|
outputPer1M,
|
|
51
|
+
contextWindow,
|
|
48
52
|
};
|
|
49
53
|
});
|
|
50
54
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theclawbay",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "CLI for connecting Codex, Gemini-compatible apps, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
|
|
3
|
+
"version": "0.3.78",
|
|
4
|
+
"description": "CLI for connecting Codex, Hermes Agent, Gemini-compatible apps, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "
|
|
8
|
+
"url": "https://github.com/RCRTCBHAL900/TheClawBay"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
11
|
"theclawbay": "dist/index.js"
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"api-key",
|
|
41
41
|
"continue",
|
|
42
42
|
"cline",
|
|
43
|
+
"hermes",
|
|
43
44
|
"gsd",
|
|
44
45
|
"openclaw",
|
|
45
46
|
"opencode",
|
|
@@ -44,42 +44,6 @@
|
|
|
44
44
|
"cachedInputPer1M": 2.0,
|
|
45
45
|
"outputPer1M": 32.0
|
|
46
46
|
},
|
|
47
|
-
{
|
|
48
|
-
"id": "gpt-5.3-codex",
|
|
49
|
-
"label": "GPT-5.3 Codex",
|
|
50
|
-
"note": "Strong daily-driver Codex model for heavier work.",
|
|
51
|
-
"tone": "ink",
|
|
52
|
-
"inputPer1M": 1.75,
|
|
53
|
-
"cachedInputPer1M": 0.175,
|
|
54
|
-
"outputPer1M": 14.0
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"id": "codex-auto-review",
|
|
58
|
-
"label": "Codex Auto Review",
|
|
59
|
-
"note": "Compatibility alias for Codex IDE auto-review flows.",
|
|
60
|
-
"tone": "ink",
|
|
61
|
-
"inputPer1M": 1.75,
|
|
62
|
-
"cachedInputPer1M": 0.175,
|
|
63
|
-
"outputPer1M": 14.0
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "gpt-5.2-codex",
|
|
67
|
-
"label": "GPT-5.2 Codex",
|
|
68
|
-
"note": "Stable compatibility option for older Codex flows.",
|
|
69
|
-
"tone": "sea",
|
|
70
|
-
"inputPer1M": 1.75,
|
|
71
|
-
"cachedInputPer1M": 0.175,
|
|
72
|
-
"outputPer1M": 14.0
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"id": "gpt-5.2",
|
|
76
|
-
"label": "GPT-5.2",
|
|
77
|
-
"note": "Balanced GPT-5 path when you want a non-Codex option.",
|
|
78
|
-
"tone": "ink",
|
|
79
|
-
"inputPer1M": 1.75,
|
|
80
|
-
"cachedInputPer1M": 0.175,
|
|
81
|
-
"outputPer1M": 14.0
|
|
82
|
-
},
|
|
83
47
|
{
|
|
84
48
|
"id": "gpt-5.1-codex-max",
|
|
85
49
|
"label": "GPT-5.1 Codex Max",
|
|
@@ -151,5 +115,25 @@
|
|
|
151
115
|
"inputPer1M": 0.5,
|
|
152
116
|
"cachedInputPer1M": 0.05,
|
|
153
117
|
"outputPer1M": 3.0
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "deepseek-v4-flash",
|
|
121
|
+
"label": "DeepSeek V4 Flash",
|
|
122
|
+
"note": "Lowest-cost DeepSeek path with 1M context through CliRelay.",
|
|
123
|
+
"tone": "sea",
|
|
124
|
+
"inputPer1M": 0.14,
|
|
125
|
+
"cachedInputPer1M": 0.0028,
|
|
126
|
+
"outputPer1M": 0.28,
|
|
127
|
+
"contextWindow": 1000000
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "deepseek-v4-pro",
|
|
131
|
+
"label": "DeepSeek V4 Pro",
|
|
132
|
+
"note": "Flagship DeepSeek reasoning path with 1M context through CliRelay.",
|
|
133
|
+
"tone": "coral",
|
|
134
|
+
"inputPer1M": 0.435,
|
|
135
|
+
"cachedInputPer1M": 0.003625,
|
|
136
|
+
"outputPer1M": 0.87,
|
|
137
|
+
"contextWindow": 1000000
|
|
154
138
|
}
|
|
155
139
|
]
|