theclawbay 0.3.77 → 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 +14 -1
- package/dist/commands/backup.d.ts +10 -0
- package/dist/commands/backup.js +51 -0
- package/dist/commands/setup.d.ts +4 -0
- package/dist/commands/setup.js +142 -2
- 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 +1 -1
- 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, Hermes Agent, 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
|
|
|
@@ -19,6 +19,7 @@ theclawbay setup --api-key <apiKey>
|
|
|
19
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/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");
|
|
@@ -1228,6 +1229,62 @@ async function resolveDeviceLabel(params) {
|
|
|
1228
1229
|
rl.close();
|
|
1229
1230
|
}
|
|
1230
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
|
+
}
|
|
1231
1288
|
function summarizeModelFetchFailure(detail) {
|
|
1232
1289
|
const normalized = detail.replace(/\s+/g, " ").trim();
|
|
1233
1290
|
if (!normalized)
|
|
@@ -1678,6 +1735,29 @@ async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
|
1678
1735
|
authRejected,
|
|
1679
1736
|
};
|
|
1680
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
|
+
}
|
|
1681
1761
|
async function writeCodexConfig(params) {
|
|
1682
1762
|
const configPath = node_path_1.default.join(paths_1.codexDir, "config.toml");
|
|
1683
1763
|
await promises_1.default.mkdir(paths_1.codexDir, { recursive: true });
|
|
@@ -3176,6 +3256,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3176
3256
|
const progress = this.createProgressHandle(!debugOutput);
|
|
3177
3257
|
let resolved = null;
|
|
3178
3258
|
let claudeAccess = null;
|
|
3259
|
+
let backupSummary = null;
|
|
3179
3260
|
let updatedShellFiles = [];
|
|
3180
3261
|
let updatedFishFiles = [];
|
|
3181
3262
|
let updatedPowerShellProfiles = [];
|
|
@@ -3208,14 +3289,48 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3208
3289
|
if (resolved.authRejected) {
|
|
3209
3290
|
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
3210
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
|
+
};
|
|
3211
3302
|
}
|
|
3212
3303
|
progress.update("Checking Claude access");
|
|
3213
3304
|
claudeAccess = await resolveClaudeAccess(backendUrl, authCredential);
|
|
3214
3305
|
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
3215
3306
|
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
3216
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
|
+
}
|
|
3217
3328
|
const claudeSelected = selectedSetupClients.has("claude");
|
|
3218
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
|
+
}
|
|
3219
3334
|
progress.update("Saving shared machine config");
|
|
3220
3335
|
await (0, config_1.writeManagedConfig)({
|
|
3221
3336
|
backendUrl,
|
|
@@ -3257,7 +3372,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3257
3372
|
claudeDesktop3pConfigPathManaged = await writeClaudeDesktop3pConfig({
|
|
3258
3373
|
backendUrl,
|
|
3259
3374
|
apiKey: authCredential,
|
|
3260
|
-
claudeModels: claudeAccess?.enabled ? claudeAccess.models : [],
|
|
3375
|
+
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
3261
3376
|
});
|
|
3262
3377
|
}
|
|
3263
3378
|
else {
|
|
@@ -3345,7 +3460,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3345
3460
|
backendUrl,
|
|
3346
3461
|
model: resolved?.model ?? DEFAULT_CODEX_MODEL,
|
|
3347
3462
|
models: resolved?.models ?? [{ id: DEFAULT_CODEX_MODEL, name: modelDisplayName(DEFAULT_CODEX_MODEL) }],
|
|
3348
|
-
claudeModels: claudeAccess?.enabled ? claudeAccess.models : [],
|
|
3463
|
+
claudeModels: claudeAccess?.enabled ? (claudeAccess.selectedModels ?? claudeAccess.models) : [],
|
|
3349
3464
|
apiKey: authCredential,
|
|
3350
3465
|
});
|
|
3351
3466
|
}
|
|
@@ -3413,6 +3528,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3413
3528
|
const summaryNotes = new Set();
|
|
3414
3529
|
if (resolved?.note)
|
|
3415
3530
|
summaryNotes.add(resolved.note);
|
|
3531
|
+
if (backupSummary) {
|
|
3532
|
+
summaryNotes.add(`Created a local backup snapshot (${backupSummary.id}) before changing client configs.`);
|
|
3533
|
+
}
|
|
3416
3534
|
if (selectedSetupClients.has("claude") && claudeAccess?.enabled) {
|
|
3417
3535
|
summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY.");
|
|
3418
3536
|
if (claudeDesktop3pConfigPathManaged) {
|
|
@@ -3467,6 +3585,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3467
3585
|
this.log(`- Managed config: ${paths_1.managedConfigPath}`);
|
|
3468
3586
|
this.log(`- Backend: ${backendUrl}`);
|
|
3469
3587
|
this.log(`- Auth mode: ${authType}`);
|
|
3588
|
+
if (backupSummary) {
|
|
3589
|
+
this.log(`- Backup: ${backupSummary.id} (${backupSummary.directory})`);
|
|
3590
|
+
}
|
|
3470
3591
|
if (authType === "device-session") {
|
|
3471
3592
|
this.log(`- Device session id: ${deviceSessionId ?? "n/a"}`);
|
|
3472
3593
|
this.log(`- Device label: ${deviceLabel ?? "n/a"}`);
|
|
@@ -3724,6 +3845,25 @@ SetupCommand.flags = {
|
|
|
3724
3845
|
required: false,
|
|
3725
3846
|
description: "Detected local clients to configure: codex, claude, continue, cline, gsd, openclaw, opencode, kilo, roo, trae, aider, zo, hermes",
|
|
3726
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",
|
|
3866
|
+
}),
|
|
3727
3867
|
yes: core_1.Flags.boolean({
|
|
3728
3868
|
required: false,
|
|
3729
3869
|
char: "y",
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theclawbay",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.78",
|
|
4
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": {
|
|
@@ -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
|
]
|