theclawbay 0.3.78 → 0.3.80
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/dist/commands/setup.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default class SetupCommand extends BaseCommand {
|
|
|
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
9
|
model: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
models: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
11
|
"claude-models": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
12
|
"list-models": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
backup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -672,8 +672,115 @@ function shellQuote(value) {
|
|
|
672
672
|
function powerShellQuote(value) {
|
|
673
673
|
return `'${value.replace(/'/g, "''")}'`;
|
|
674
674
|
}
|
|
675
|
+
function shellCommandForCurrentPlatform(command) {
|
|
676
|
+
if (node_os_1.default.platform() === "win32") {
|
|
677
|
+
return { executable: "cmd.exe", args: ["/c", command] };
|
|
678
|
+
}
|
|
679
|
+
return { executable: "bash", args: ["-lc", command] };
|
|
680
|
+
}
|
|
681
|
+
function installPlanForSetupClient(clientId) {
|
|
682
|
+
if (clientId === "codex") {
|
|
683
|
+
return {
|
|
684
|
+
summary: "Install Codex CLI",
|
|
685
|
+
command: "npm install -g @openai/codex",
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (clientId === "claude") {
|
|
689
|
+
return {
|
|
690
|
+
summary: "Install Claude Code CLI",
|
|
691
|
+
command: "npm install -g @anthropic-ai/claude-code",
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (clientId === "opencode") {
|
|
695
|
+
return {
|
|
696
|
+
summary: "Install OpenCode CLI",
|
|
697
|
+
command: "npm install -g opencode-ai",
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (clientId === "aider") {
|
|
701
|
+
return {
|
|
702
|
+
summary: "Install Aider",
|
|
703
|
+
command: node_os_1.default.platform() === "win32"
|
|
704
|
+
? "py -m pip install --user --upgrade aider-chat"
|
|
705
|
+
: "python3 -m pip install --user --upgrade aider-chat",
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
function isSetupClientInstallable(clientId) {
|
|
711
|
+
return installPlanForSetupClient(clientId) !== null;
|
|
712
|
+
}
|
|
713
|
+
function runInstallPlan(plan) {
|
|
714
|
+
const { executable, args } = shellCommandForCurrentPlatform(plan.command);
|
|
715
|
+
const run = (0, node_child_process_1.spawnSync)(executable, args, {
|
|
716
|
+
stdio: "inherit",
|
|
717
|
+
env: process.env,
|
|
718
|
+
});
|
|
719
|
+
if (run.status === 0)
|
|
720
|
+
return;
|
|
721
|
+
throw new Error(`${plan.summary} failed.`);
|
|
722
|
+
}
|
|
675
723
|
function modelDisplayName(modelId) {
|
|
676
|
-
return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
|
|
724
|
+
return MODEL_DISPLAY_NAMES[modelId] ?? prettyModelId(modelId);
|
|
725
|
+
}
|
|
726
|
+
function supportedModelProviderMap() {
|
|
727
|
+
return new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model.provider]));
|
|
728
|
+
}
|
|
729
|
+
function groupForModelId(modelId) {
|
|
730
|
+
const knownProvider = supportedModelProviderMap().get(modelId);
|
|
731
|
+
if (knownProvider === "openai") {
|
|
732
|
+
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
733
|
+
}
|
|
734
|
+
if (knownProvider === "google") {
|
|
735
|
+
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
736
|
+
}
|
|
737
|
+
if (knownProvider === "deepseek") {
|
|
738
|
+
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
739
|
+
}
|
|
740
|
+
if (modelId.startsWith("claude-")) {
|
|
741
|
+
return { id: "anthropic", label: "Anthropic Claude", icon: "✳", order: 3 };
|
|
742
|
+
}
|
|
743
|
+
if (modelId.startsWith("gemini-")) {
|
|
744
|
+
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
745
|
+
}
|
|
746
|
+
if (modelId.startsWith("deepseek-")) {
|
|
747
|
+
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
748
|
+
}
|
|
749
|
+
if (modelId.startsWith("gpt-") || modelId.startsWith("codex-")) {
|
|
750
|
+
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
751
|
+
}
|
|
752
|
+
return { id: "other", label: "Other models", icon: "•", order: 9 };
|
|
753
|
+
}
|
|
754
|
+
function groupModelOptions(models) {
|
|
755
|
+
return models.map((model) => ({
|
|
756
|
+
...model,
|
|
757
|
+
provider: groupForModelId(model.id),
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
760
|
+
function groupModelSections(models) {
|
|
761
|
+
const sections = new Map();
|
|
762
|
+
for (const model of groupModelOptions(models)) {
|
|
763
|
+
const existing = sections.get(model.provider.id);
|
|
764
|
+
if (existing) {
|
|
765
|
+
existing.models.push(model);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
sections.set(model.provider.id, { provider: model.provider, models: [model] });
|
|
769
|
+
}
|
|
770
|
+
return [...sections.values()]
|
|
771
|
+
.sort((left, right) => left.provider.order - right.provider.order || left.provider.label.localeCompare(right.provider.label))
|
|
772
|
+
.map((section) => ({
|
|
773
|
+
provider: section.provider,
|
|
774
|
+
models: [...section.models].sort((left, right) => left.name.localeCompare(right.name)),
|
|
775
|
+
}));
|
|
776
|
+
}
|
|
777
|
+
function prettyModelId(modelId) {
|
|
778
|
+
return modelId
|
|
779
|
+
.replace(/^claude-/, "Claude ")
|
|
780
|
+
.replace(/^gemini-/, "Gemini ")
|
|
781
|
+
.replace(/^deepseek-/, "DeepSeek ")
|
|
782
|
+
.replace(/-/g, " ")
|
|
783
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
677
784
|
}
|
|
678
785
|
function publicApiOriginForBackendUrl(backendUrl) {
|
|
679
786
|
const trimmed = trimTrailingSlash(backendUrl);
|
|
@@ -691,6 +798,39 @@ function publicApiOriginForBackendUrl(backendUrl) {
|
|
|
691
798
|
return trimmed;
|
|
692
799
|
}
|
|
693
800
|
}
|
|
801
|
+
function isPrivateHostname(hostname) {
|
|
802
|
+
const normalized = hostname.trim().toLowerCase();
|
|
803
|
+
if (!normalized)
|
|
804
|
+
return false;
|
|
805
|
+
if (normalized === "localhost" ||
|
|
806
|
+
normalized === "127.0.0.1" ||
|
|
807
|
+
normalized === "::1" ||
|
|
808
|
+
normalized === "[::1]" ||
|
|
809
|
+
normalized === "0.0.0.0") {
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
if (normalized.startsWith("10.") || normalized.startsWith("192.168.") || normalized.startsWith("127.")) {
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
const match = normalized.match(/^172\.(\d{1,3})\./);
|
|
816
|
+
if (!match)
|
|
817
|
+
return false;
|
|
818
|
+
const secondOctet = Number.parseInt(match[1] ?? "", 10);
|
|
819
|
+
return Number.isInteger(secondOctet) && secondOctet >= 16 && secondOctet <= 31;
|
|
820
|
+
}
|
|
821
|
+
function shouldPreferPublicSetupBackend(backendUrl) {
|
|
822
|
+
const trimmed = backendUrl?.trim();
|
|
823
|
+
if (!trimmed)
|
|
824
|
+
return false;
|
|
825
|
+
try {
|
|
826
|
+
const parsed = new URL(trimmed);
|
|
827
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
828
|
+
return hostname !== THECLAWBAY_CANONICAL_API_HOST && !THECLAWBAY_WEBSITE_HOSTS.has(hostname) && isPrivateHostname(hostname);
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
694
834
|
function isTheClawBayChatgptBaseUrl(value) {
|
|
695
835
|
const trimmed = trimTrailingSlash(value.trim());
|
|
696
836
|
if (!trimmed)
|
|
@@ -1057,17 +1197,18 @@ async function promptForSetupClients(clients) {
|
|
|
1057
1197
|
stdout.write(`${paint("Move to Apply setup and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
1058
1198
|
for (const [index, option] of options.entries()) {
|
|
1059
1199
|
const pointer = index === cursor ? paint(">", ansi.bold) : " ";
|
|
1060
|
-
const
|
|
1200
|
+
const selectable = option.detected || option.installable;
|
|
1201
|
+
const mark = selectable
|
|
1061
1202
|
? option.checked
|
|
1062
1203
|
? paint("[x]", ansi.green, ansi.bold)
|
|
1063
1204
|
: paint("[ ]", ansi.gray)
|
|
1064
1205
|
: paint("[-]", ansi.red, ansi.bold);
|
|
1065
1206
|
const badge = option.detected
|
|
1066
|
-
?
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const label =
|
|
1207
|
+
? paint("found", ansi.green)
|
|
1208
|
+
: option.installable
|
|
1209
|
+
? paint(option.checked ? "will install" : "available to install", ansi.yellow)
|
|
1210
|
+
: paint("not found", ansi.red);
|
|
1211
|
+
const label = selectable
|
|
1071
1212
|
? formatSetupClientLabel(option, hyperlinksEnabled)
|
|
1072
1213
|
: paint(formatSetupClientLabel(option, hyperlinksEnabled), ansi.dim, ansi.gray);
|
|
1073
1214
|
stdout.write(`${pointer} ${index + 1}. ${mark} ${label} ${badge}\n`);
|
|
@@ -1097,13 +1238,18 @@ async function promptForSetupClients(clients) {
|
|
|
1097
1238
|
if (!option)
|
|
1098
1239
|
return;
|
|
1099
1240
|
cursor = index;
|
|
1100
|
-
if (!option.detected) {
|
|
1241
|
+
if (!option.detected && !option.installable) {
|
|
1101
1242
|
hint = `${option.label} is not detected on this machine yet, so it cannot be selected.`;
|
|
1102
1243
|
render();
|
|
1103
1244
|
return;
|
|
1104
1245
|
}
|
|
1105
1246
|
option.checked = !option.checked;
|
|
1106
|
-
|
|
1247
|
+
if (option.checked && !option.detected && option.installable) {
|
|
1248
|
+
hint = `${option.label} selected. Setup will install it first.`;
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
hint = `${option.label} ${option.checked ? "selected" : "cleared"}.`;
|
|
1252
|
+
}
|
|
1107
1253
|
render();
|
|
1108
1254
|
};
|
|
1109
1255
|
const onKeypress = (_str, key) => {
|
|
@@ -1173,7 +1319,7 @@ function resolveSetupClientSelection(params) {
|
|
|
1173
1319
|
for (const client of setupClients) {
|
|
1174
1320
|
if (!flagSelection.has(client.id))
|
|
1175
1321
|
continue;
|
|
1176
|
-
if (!client.detected) {
|
|
1322
|
+
if (!client.detected && !client.installable) {
|
|
1177
1323
|
throw new Error(`${client.label} is not detected on this machine, so it cannot be selected yet.`);
|
|
1178
1324
|
}
|
|
1179
1325
|
}
|
|
@@ -1184,6 +1330,20 @@ function resolveSetupClientSelection(params) {
|
|
|
1184
1330
|
return Promise.resolve(defaults);
|
|
1185
1331
|
return promptForSetupClients(setupClients);
|
|
1186
1332
|
}
|
|
1333
|
+
function installSelectedMissingClients(params) {
|
|
1334
|
+
const installed = [];
|
|
1335
|
+
for (const client of params.setupClients) {
|
|
1336
|
+
if (!params.selectedClientIds.has(client.id) || client.detected || !client.installable)
|
|
1337
|
+
continue;
|
|
1338
|
+
const plan = installPlanForSetupClient(client.id);
|
|
1339
|
+
if (!plan)
|
|
1340
|
+
continue;
|
|
1341
|
+
params.log(`Installing ${client.summaryLabel}...`);
|
|
1342
|
+
runInstallPlan(plan);
|
|
1343
|
+
installed.push(client.summaryLabel);
|
|
1344
|
+
}
|
|
1345
|
+
return installed;
|
|
1346
|
+
}
|
|
1187
1347
|
async function promptForCodexConversationMigration() {
|
|
1188
1348
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1189
1349
|
return false;
|
|
@@ -1229,60 +1389,317 @@ async function resolveDeviceLabel(params) {
|
|
|
1229
1389
|
rl.close();
|
|
1230
1390
|
}
|
|
1231
1391
|
}
|
|
1392
|
+
async function promptForGroupedModelSelection(params) {
|
|
1393
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || params.models.length <= 1) {
|
|
1394
|
+
return uniqueStrings(params.defaultSelectedIds);
|
|
1395
|
+
}
|
|
1396
|
+
const sections = groupModelSections(params.models);
|
|
1397
|
+
const knownIds = new Set(params.models.map((model) => model.id));
|
|
1398
|
+
const selected = new Set(uniqueStrings(params.defaultSelectedIds).filter((modelId) => knownIds.has(modelId)));
|
|
1399
|
+
const rows = [];
|
|
1400
|
+
for (const [sectionIndex, section] of sections.entries()) {
|
|
1401
|
+
rows.push({ kind: "group", sectionIndex });
|
|
1402
|
+
for (const [modelIndex] of section.models.entries()) {
|
|
1403
|
+
rows.push({ kind: "model", sectionIndex, modelIndex });
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
rows.push({ kind: "apply" });
|
|
1407
|
+
const stdin = process.stdin;
|
|
1408
|
+
const stdout = process.stdout;
|
|
1409
|
+
const wasRaw = stdin.isRaw;
|
|
1410
|
+
const ansi = {
|
|
1411
|
+
reset: "\x1b[0m",
|
|
1412
|
+
bold: "\x1b[1m",
|
|
1413
|
+
dim: "\x1b[2m",
|
|
1414
|
+
inverse: "\x1b[7m",
|
|
1415
|
+
green: "\x1b[32m",
|
|
1416
|
+
yellow: "\x1b[33m",
|
|
1417
|
+
gray: "\x1b[90m",
|
|
1418
|
+
};
|
|
1419
|
+
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1420
|
+
const clearScreen = () => {
|
|
1421
|
+
stdout.write("\x1b[2J\x1b[H");
|
|
1422
|
+
};
|
|
1423
|
+
const rowForModel = (sectionIndex, modelIndex) => rows.findIndex((row) => row.kind === "model" && row.sectionIndex === sectionIndex && row.modelIndex === modelIndex);
|
|
1424
|
+
let settled = false;
|
|
1425
|
+
let hint = "Use ↑/↓ to move. Press Enter or Space to toggle a model or a provider header.";
|
|
1426
|
+
let cursor = Math.max(0, rows.findIndex((row) => {
|
|
1427
|
+
if (row.kind !== "model")
|
|
1428
|
+
return false;
|
|
1429
|
+
const model = sections[row.sectionIndex]?.models[row.modelIndex];
|
|
1430
|
+
return model?.id === params.defaultSelectedIds[0];
|
|
1431
|
+
}));
|
|
1432
|
+
const groupState = (sectionIndex) => {
|
|
1433
|
+
const models = sections[sectionIndex]?.models ?? [];
|
|
1434
|
+
const selectedCount = models.filter((model) => selected.has(model.id)).length;
|
|
1435
|
+
if (selectedCount === 0)
|
|
1436
|
+
return "[ ]";
|
|
1437
|
+
if (selectedCount === models.length)
|
|
1438
|
+
return "[x]";
|
|
1439
|
+
return "[-]";
|
|
1440
|
+
};
|
|
1441
|
+
const setGroupSelection = (sectionIndex, nextValue) => {
|
|
1442
|
+
for (const model of sections[sectionIndex]?.models ?? []) {
|
|
1443
|
+
if (nextValue)
|
|
1444
|
+
selected.add(model.id);
|
|
1445
|
+
else
|
|
1446
|
+
selected.delete(model.id);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
const toggleCurrent = () => {
|
|
1450
|
+
const current = rows[cursor];
|
|
1451
|
+
if (!current)
|
|
1452
|
+
return;
|
|
1453
|
+
if (current.kind === "apply") {
|
|
1454
|
+
finish();
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (current.kind === "group") {
|
|
1458
|
+
const state = groupState(current.sectionIndex);
|
|
1459
|
+
const nextValue = state !== "[x]";
|
|
1460
|
+
setGroupSelection(current.sectionIndex, nextValue);
|
|
1461
|
+
hint = `${sections[current.sectionIndex]?.provider.label ?? "Group"} ${nextValue ? "selected" : "cleared"}.`;
|
|
1462
|
+
render();
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const model = sections[current.sectionIndex]?.models[current.modelIndex];
|
|
1466
|
+
if (!model)
|
|
1467
|
+
return;
|
|
1468
|
+
if (selected.has(model.id))
|
|
1469
|
+
selected.delete(model.id);
|
|
1470
|
+
else
|
|
1471
|
+
selected.add(model.id);
|
|
1472
|
+
hint = `${model.name} ${selected.has(model.id) ? "selected" : "cleared"}.`;
|
|
1473
|
+
render();
|
|
1474
|
+
};
|
|
1475
|
+
const finish = () => {
|
|
1476
|
+
if (settled)
|
|
1477
|
+
return;
|
|
1478
|
+
settled = true;
|
|
1479
|
+
stdin.off("keypress", onKeypress);
|
|
1480
|
+
if (stdin.isTTY)
|
|
1481
|
+
stdin.setRawMode(Boolean(wasRaw));
|
|
1482
|
+
stdout.write("\x1b[?25h");
|
|
1483
|
+
clearScreen();
|
|
1484
|
+
resolvePromise([...selected].filter((modelId) => knownIds.has(modelId)));
|
|
1485
|
+
};
|
|
1486
|
+
const render = () => {
|
|
1487
|
+
clearScreen();
|
|
1488
|
+
stdout.write(`${paint(params.title, ansi.bold)}\n`);
|
|
1489
|
+
stdout.write(`${paint("Each provider header toggles that provider on or off. Press A to toggle all models.", ansi.dim, ansi.gray)}\n`);
|
|
1490
|
+
stdout.write(`${paint("Move to Apply and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
1491
|
+
for (const [sectionIndex, section] of sections.entries()) {
|
|
1492
|
+
const currentRow = rows[cursor];
|
|
1493
|
+
const groupCursor = currentRow?.kind === "group" && currentRow.sectionIndex === sectionIndex;
|
|
1494
|
+
const groupPointer = groupCursor ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1495
|
+
const groupMark = groupState(sectionIndex);
|
|
1496
|
+
stdout.write(`${groupPointer} ${paint(groupMark, groupMark === "[x]" ? ansi.green : ansi.gray)} ${section.provider.icon} ${paint(`${section.provider.label} — Select all`, ansi.bold)}\n`);
|
|
1497
|
+
for (const [modelIndex, model] of section.models.entries()) {
|
|
1498
|
+
const activeRow = rows[cursor];
|
|
1499
|
+
const isActive = activeRow?.kind === "model" &&
|
|
1500
|
+
activeRow.sectionIndex === sectionIndex &&
|
|
1501
|
+
activeRow.modelIndex === modelIndex;
|
|
1502
|
+
const pointer = isActive ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1503
|
+
const mark = selected.has(model.id) ? paint("[x]", ansi.green) : paint("[ ]", ansi.gray);
|
|
1504
|
+
stdout.write(`${pointer} ${mark} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}\n`);
|
|
1505
|
+
}
|
|
1506
|
+
stdout.write("\n");
|
|
1507
|
+
}
|
|
1508
|
+
const applyActive = rows[cursor]?.kind === "apply";
|
|
1509
|
+
const applyStyled = applyActive
|
|
1510
|
+
? paint(` ${params.applyLabel} (${selected.size} selected) `, ansi.inverse, ansi.bold, ansi.green)
|
|
1511
|
+
: paint(`${params.applyLabel} (${selected.size} selected)`, ansi.bold, ansi.green);
|
|
1512
|
+
stdout.write(`${applyActive ? "→" : " "} ${applyStyled}\n`);
|
|
1513
|
+
stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
|
|
1514
|
+
};
|
|
1515
|
+
const onKeypress = (_str, key) => {
|
|
1516
|
+
if (key.ctrl && key.name === "c") {
|
|
1517
|
+
stdout.write("\x1b[?25h");
|
|
1518
|
+
process.exit(130);
|
|
1519
|
+
}
|
|
1520
|
+
if (key.name === "up" || key.name === "k") {
|
|
1521
|
+
cursor = (cursor - 1 + rows.length) % rows.length;
|
|
1522
|
+
render();
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (key.name === "down" || key.name === "j") {
|
|
1526
|
+
cursor = (cursor + 1) % rows.length;
|
|
1527
|
+
render();
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
if (key.name === "space" || key.name === "return" || key.name === "enter") {
|
|
1531
|
+
toggleCurrent();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (key.name === "a") {
|
|
1535
|
+
const nextValue = sections.some((section) => section.models.some((model) => !selected.has(model.id)));
|
|
1536
|
+
for (const [sectionIndex] of sections.entries())
|
|
1537
|
+
setGroupSelection(sectionIndex, nextValue);
|
|
1538
|
+
hint = `${nextValue ? "Selected" : "Cleared"} all available models.`;
|
|
1539
|
+
render();
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (key.name === "c") {
|
|
1543
|
+
finish();
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
let resolvePromise = () => { };
|
|
1547
|
+
const result = new Promise((resolve) => {
|
|
1548
|
+
resolvePromise = resolve;
|
|
1549
|
+
});
|
|
1550
|
+
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1551
|
+
if (stdin.isTTY)
|
|
1552
|
+
stdin.setRawMode(true);
|
|
1553
|
+
stdout.write("\x1b[?25l");
|
|
1554
|
+
stdin.on("keypress", onKeypress);
|
|
1555
|
+
render();
|
|
1556
|
+
return result;
|
|
1557
|
+
}
|
|
1232
1558
|
async function promptForPrimaryModel(models, defaultModel) {
|
|
1233
1559
|
if (!process.stdin.isTTY || !process.stdout.isTTY || models.length <= 1) {
|
|
1234
1560
|
return defaultModel;
|
|
1235
1561
|
}
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1562
|
+
const sections = groupModelSections(models);
|
|
1563
|
+
const flattened = sections.flatMap((section) => section.models);
|
|
1564
|
+
const stdin = process.stdin;
|
|
1565
|
+
const stdout = process.stdout;
|
|
1566
|
+
const wasRaw = stdin.isRaw;
|
|
1567
|
+
const ansi = {
|
|
1568
|
+
reset: "\x1b[0m",
|
|
1569
|
+
bold: "\x1b[1m",
|
|
1570
|
+
dim: "\x1b[2m",
|
|
1571
|
+
green: "\x1b[32m",
|
|
1572
|
+
yellow: "\x1b[33m",
|
|
1573
|
+
gray: "\x1b[90m",
|
|
1574
|
+
};
|
|
1575
|
+
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1576
|
+
const clearScreen = () => {
|
|
1577
|
+
stdout.write("\x1b[2J\x1b[H");
|
|
1578
|
+
};
|
|
1579
|
+
let cursor = Math.max(0, flattened.findIndex((model) => model.id === defaultModel));
|
|
1580
|
+
let settled = false;
|
|
1581
|
+
let resolvePromise = () => { };
|
|
1582
|
+
const finish = (modelId) => {
|
|
1583
|
+
if (settled)
|
|
1584
|
+
return;
|
|
1585
|
+
settled = true;
|
|
1586
|
+
stdin.off("keypress", onKeypress);
|
|
1587
|
+
if (stdin.isTTY)
|
|
1588
|
+
stdin.setRawMode(Boolean(wasRaw));
|
|
1589
|
+
stdout.write("\x1b[?25h");
|
|
1590
|
+
clearScreen();
|
|
1591
|
+
resolvePromise(modelId);
|
|
1592
|
+
};
|
|
1593
|
+
const render = () => {
|
|
1594
|
+
clearScreen();
|
|
1595
|
+
stdout.write(`${paint("Choose the default model for local setup", ansi.bold)}\n`);
|
|
1596
|
+
stdout.write(`${paint("Use ↑/↓ to move and Enter to select the default model.", ansi.dim, ansi.gray)}\n\n`);
|
|
1597
|
+
let index = 0;
|
|
1598
|
+
for (const section of sections) {
|
|
1599
|
+
stdout.write(`${paint(`${section.provider.icon} ${section.provider.label}`, ansi.bold)}\n`);
|
|
1600
|
+
for (const model of section.models) {
|
|
1601
|
+
const active = index === cursor;
|
|
1602
|
+
const pointer = active ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1603
|
+
const defaultSuffix = model.id === defaultModel ? paint(" default", ansi.dim, ansi.gray) : "";
|
|
1604
|
+
stdout.write(`${pointer} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}${defaultSuffix}\n`);
|
|
1605
|
+
index += 1;
|
|
1606
|
+
}
|
|
1607
|
+
stdout.write("\n");
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
const onKeypress = (_str, key) => {
|
|
1611
|
+
if (key.ctrl && key.name === "c") {
|
|
1612
|
+
stdout.write("\x1b[?25h");
|
|
1613
|
+
process.exit(130);
|
|
1614
|
+
}
|
|
1615
|
+
if (key.name === "up" || key.name === "k") {
|
|
1616
|
+
cursor = (cursor - 1 + flattened.length) % flattened.length;
|
|
1617
|
+
render();
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
if (key.name === "down" || key.name === "j") {
|
|
1621
|
+
cursor = (cursor + 1) % flattened.length;
|
|
1622
|
+
render();
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
if (key.name === "return" || key.name === "enter" || key.name === "space") {
|
|
1626
|
+
finish(flattened[cursor]?.id ?? defaultModel);
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
const result = new Promise((resolve) => {
|
|
1630
|
+
resolvePromise = resolve;
|
|
1239
1631
|
});
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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
|
-
}
|
|
1632
|
+
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1633
|
+
if (stdin.isTTY)
|
|
1634
|
+
stdin.setRawMode(true);
|
|
1635
|
+
stdout.write("\x1b[?25l");
|
|
1636
|
+
stdin.on("keypress", onKeypress);
|
|
1637
|
+
render();
|
|
1638
|
+
return result;
|
|
1261
1639
|
}
|
|
1262
1640
|
async function resolveConfiguredModelSelection(params) {
|
|
1263
1641
|
const requestedModel = params.requestedModel?.trim();
|
|
1642
|
+
const requestedModels = params.requestedModels
|
|
1643
|
+
?.split(",")
|
|
1644
|
+
.map((entry) => entry.trim())
|
|
1645
|
+
.filter(Boolean);
|
|
1264
1646
|
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
|
-
}
|
|
1647
|
+
if (requestedModel && availableIds.size > 0 && !availableIds.has(requestedModel)) {
|
|
1274
1648
|
throw new Error(`Requested model "${requestedModel}" is not advertised by the live backend.`);
|
|
1275
1649
|
}
|
|
1276
|
-
if (
|
|
1277
|
-
|
|
1650
|
+
if (requestedModels?.length) {
|
|
1651
|
+
for (const modelId of requestedModels) {
|
|
1652
|
+
if (availableIds.size > 0 && !availableIds.has(modelId)) {
|
|
1653
|
+
throw new Error(`Requested model "${modelId}" is not advertised by the live backend.`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
let selectedModels;
|
|
1658
|
+
if (requestedModels?.length) {
|
|
1659
|
+
selectedModels = params.resolved.models.filter((entry) => requestedModels.includes(entry.id));
|
|
1660
|
+
}
|
|
1661
|
+
else if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1662
|
+
selectedModels = params.resolved.models;
|
|
1278
1663
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1664
|
+
else if (params.resolved.models.length > 1) {
|
|
1665
|
+
const selectedIds = new Set(await promptForGroupedModelSelection({
|
|
1666
|
+
title: "Choose the OpenAI-compatible models to save into local tools",
|
|
1667
|
+
models: params.resolved.models,
|
|
1668
|
+
defaultSelectedIds: params.resolved.models.map((entry) => entry.id),
|
|
1669
|
+
applyLabel: "Apply model selection",
|
|
1670
|
+
}));
|
|
1671
|
+
selectedModels = params.resolved.models.filter((entry) => selectedIds.has(entry.id));
|
|
1672
|
+
}
|
|
1673
|
+
else {
|
|
1674
|
+
selectedModels = params.resolved.models;
|
|
1675
|
+
}
|
|
1676
|
+
const fallbackModels = selectedModels.length > 0 ? selectedModels : params.resolved.models;
|
|
1677
|
+
const selectedModelId = requestedModel && fallbackModels.some((entry) => entry.id === requestedModel)
|
|
1678
|
+
? requestedModel
|
|
1679
|
+
: params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY
|
|
1680
|
+
? fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1681
|
+
? params.resolved.model
|
|
1682
|
+
: fallbackModels[0]?.id ?? params.resolved.model
|
|
1683
|
+
: await promptForPrimaryModel(fallbackModels, fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1684
|
+
? params.resolved.model
|
|
1685
|
+
: (fallbackModels[0]?.id ?? params.resolved.model));
|
|
1686
|
+
const noteParts = [];
|
|
1687
|
+
if (requestedModels?.length) {
|
|
1688
|
+
noteParts.push(`Configured ${requestedModels.length} OpenAI-compatible models.`);
|
|
1689
|
+
}
|
|
1690
|
+
else if (selectedModels.length > 0 && selectedModels.length < params.resolved.models.length) {
|
|
1691
|
+
noteParts.push(`Configured ${selectedModels.length} OpenAI-compatible models for local tools.`);
|
|
1692
|
+
}
|
|
1693
|
+
if (selectedModelId !== params.resolved.model) {
|
|
1694
|
+
noteParts.push(`Using ${selectedModelId} as the default configured model for local tools.`);
|
|
1695
|
+
}
|
|
1696
|
+
if (availableIds.size === 0 && requestedModel && params.resolved.failure) {
|
|
1697
|
+
noteParts.push(`Could not verify requested model ${requestedModel} against the live backend (${params.resolved.failure}).`);
|
|
1282
1698
|
}
|
|
1283
1699
|
return {
|
|
1284
|
-
model:
|
|
1285
|
-
|
|
1700
|
+
model: selectedModelId,
|
|
1701
|
+
models: fallbackModels,
|
|
1702
|
+
note: noteParts.join(" ").trim() || undefined,
|
|
1286
1703
|
};
|
|
1287
1704
|
}
|
|
1288
1705
|
function summarizeModelFetchFailure(detail) {
|
|
@@ -1597,6 +2014,12 @@ async function detectHermesClient() {
|
|
|
1597
2014
|
}
|
|
1598
2015
|
return false;
|
|
1599
2016
|
}
|
|
2017
|
+
function isOpenAiCompatibleSetupModelId(modelId) {
|
|
2018
|
+
const trimmed = modelId.trim();
|
|
2019
|
+
if (!trimmed)
|
|
2020
|
+
return false;
|
|
2021
|
+
return !trimmed.startsWith("claude-");
|
|
2022
|
+
}
|
|
1600
2023
|
function formatDotEnvValue(value) {
|
|
1601
2024
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1602
2025
|
}
|
|
@@ -1678,7 +2101,8 @@ async function writeHermesConfig(params) {
|
|
|
1678
2101
|
}
|
|
1679
2102
|
async function resolveModels(backendUrl, apiKey) {
|
|
1680
2103
|
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1681
|
-
const
|
|
2104
|
+
const filteredIds = (ids ?? []).filter(isOpenAiCompatibleSetupModelId);
|
|
2105
|
+
const available = new Set(filteredIds);
|
|
1682
2106
|
let selected = DEFAULT_CODEX_MODEL;
|
|
1683
2107
|
let note;
|
|
1684
2108
|
const authRejected = isCredentialRejectedFailure(failure);
|
|
@@ -1688,29 +2112,29 @@ async function resolveModels(backendUrl, apiKey) {
|
|
|
1688
2112
|
break;
|
|
1689
2113
|
}
|
|
1690
2114
|
}
|
|
1691
|
-
if (
|
|
1692
|
-
selected =
|
|
2115
|
+
if (filteredIds.length && !available.has(selected)) {
|
|
2116
|
+
selected = filteredIds[0] ?? DEFAULT_CODEX_MODEL;
|
|
1693
2117
|
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
1694
2118
|
}
|
|
1695
|
-
else if (!
|
|
2119
|
+
else if (!filteredIds.length) {
|
|
1696
2120
|
note = failure
|
|
1697
2121
|
? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
|
|
1698
2122
|
: `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
1699
2123
|
}
|
|
1700
2124
|
const unique = [];
|
|
1701
2125
|
const pushUnique = (modelId) => {
|
|
1702
|
-
if (!modelId || unique.includes(modelId))
|
|
2126
|
+
if (!modelId || !isOpenAiCompatibleSetupModelId(modelId) || unique.includes(modelId))
|
|
1703
2127
|
return;
|
|
1704
2128
|
unique.push(modelId);
|
|
1705
2129
|
};
|
|
1706
2130
|
pushUnique(selected);
|
|
1707
2131
|
for (const modelId of DEFAULT_MODELS)
|
|
1708
2132
|
pushUnique(modelId);
|
|
1709
|
-
for (const modelId of
|
|
2133
|
+
for (const modelId of filteredIds)
|
|
1710
2134
|
pushUnique(modelId);
|
|
1711
2135
|
return {
|
|
1712
2136
|
model: selected,
|
|
1713
|
-
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) })),
|
|
2137
|
+
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) || prettyModelId(modelId) })),
|
|
1714
2138
|
note,
|
|
1715
2139
|
failure,
|
|
1716
2140
|
authRejected,
|
|
@@ -1735,15 +2159,31 @@ async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
|
1735
2159
|
authRejected,
|
|
1736
2160
|
};
|
|
1737
2161
|
}
|
|
1738
|
-
function resolveConfiguredClaudeModels(params) {
|
|
2162
|
+
async function resolveConfiguredClaudeModels(params) {
|
|
1739
2163
|
const requestedModels = params.requestedModels
|
|
1740
2164
|
?.split(",")
|
|
1741
2165
|
.map((entry) => entry.trim())
|
|
1742
2166
|
.filter(Boolean);
|
|
1743
2167
|
if (!requestedModels?.length || !params.access.enabled) {
|
|
2168
|
+
const selectedModels = !requestedModels?.length &&
|
|
2169
|
+
params.access.enabled &&
|
|
2170
|
+
!params.skipPrompt &&
|
|
2171
|
+
process.stdin.isTTY &&
|
|
2172
|
+
process.stdout.isTTY &&
|
|
2173
|
+
params.access.models.length > 1
|
|
2174
|
+
? await promptForGroupedModelSelection({
|
|
2175
|
+
title: "Choose the Claude models to save into Claude-compatible tools",
|
|
2176
|
+
models: params.access.models.map((modelId) => ({
|
|
2177
|
+
id: modelId,
|
|
2178
|
+
name: prettyModelId(modelId),
|
|
2179
|
+
})),
|
|
2180
|
+
defaultSelectedIds: params.access.models,
|
|
2181
|
+
applyLabel: "Apply Claude selection",
|
|
2182
|
+
})
|
|
2183
|
+
: params.access.models;
|
|
1744
2184
|
return {
|
|
1745
2185
|
...params.access,
|
|
1746
|
-
selectedModels
|
|
2186
|
+
selectedModels,
|
|
1747
2187
|
};
|
|
1748
2188
|
}
|
|
1749
2189
|
const available = new Set(params.access.models);
|
|
@@ -3073,13 +3513,25 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3073
3513
|
let deviceLabel = managedAuthType === "device-session"
|
|
3074
3514
|
? managed?.deviceLabel ?? null
|
|
3075
3515
|
: null;
|
|
3516
|
+
const inferredBackend = explicitApiKey || managedAuthType === "api-key"
|
|
3517
|
+
? (0, api_key_1.tryInferBackendUrlFromApiKey)(explicitApiKey || managedCredential)
|
|
3518
|
+
: null;
|
|
3519
|
+
const managedBackendUrl = managed?.backendUrl ?? null;
|
|
3076
3520
|
const backendRaw = flags.backend ??
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
: null) ??
|
|
3080
|
-
managed?.backendUrl ??
|
|
3521
|
+
inferredBackend ??
|
|
3522
|
+
(shouldPreferPublicSetupBackend(managedBackendUrl) ? DEFAULT_BACKEND_URL : managedBackendUrl) ??
|
|
3081
3523
|
DEFAULT_BACKEND_URL;
|
|
3082
3524
|
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
3525
|
+
const reusingLocalManagedCredential = !explicitApiKey &&
|
|
3526
|
+
!flags.backend &&
|
|
3527
|
+
shouldPreferPublicSetupBackend(managedBackendUrl) &&
|
|
3528
|
+
authCredential.length > 0;
|
|
3529
|
+
if (reusingLocalManagedCredential) {
|
|
3530
|
+
authType = "device-session";
|
|
3531
|
+
authCredential = "";
|
|
3532
|
+
deviceSessionId = null;
|
|
3533
|
+
deviceLabel = null;
|
|
3534
|
+
}
|
|
3083
3535
|
const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, openCodeDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected, hermesDetected] = await Promise.all([
|
|
3084
3536
|
detectCodexClient(),
|
|
3085
3537
|
detectClaudeClient(),
|
|
@@ -3101,6 +3553,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3101
3553
|
summaryLabel: "Codex",
|
|
3102
3554
|
detected: codexDetected,
|
|
3103
3555
|
recommended: true,
|
|
3556
|
+
installable: true,
|
|
3104
3557
|
icon: "◎",
|
|
3105
3558
|
siteUrl: "https://openai.com/codex",
|
|
3106
3559
|
},
|
|
@@ -3110,6 +3563,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3110
3563
|
summaryLabel: "Claude",
|
|
3111
3564
|
detected: claudeDetected,
|
|
3112
3565
|
recommended: true,
|
|
3566
|
+
installable: true,
|
|
3113
3567
|
icon: "✳",
|
|
3114
3568
|
siteUrl: "https://code.claude.com",
|
|
3115
3569
|
},
|
|
@@ -3119,6 +3573,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3119
3573
|
summaryLabel: "Continue",
|
|
3120
3574
|
detected: continueDetected,
|
|
3121
3575
|
recommended: true,
|
|
3576
|
+
installable: false,
|
|
3122
3577
|
icon: "▶",
|
|
3123
3578
|
siteUrl: "https://continue.dev",
|
|
3124
3579
|
},
|
|
@@ -3128,6 +3583,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3128
3583
|
summaryLabel: "Cline",
|
|
3129
3584
|
detected: clineDetected,
|
|
3130
3585
|
recommended: true,
|
|
3586
|
+
installable: false,
|
|
3131
3587
|
icon: "◈",
|
|
3132
3588
|
siteUrl: "https://cline.bot",
|
|
3133
3589
|
},
|
|
@@ -3137,6 +3593,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3137
3593
|
summaryLabel: "GSD",
|
|
3138
3594
|
detected: gsdDetected,
|
|
3139
3595
|
recommended: true,
|
|
3596
|
+
installable: false,
|
|
3140
3597
|
icon: "▣",
|
|
3141
3598
|
siteUrl: "https://github.com/gsd-build/gsd-2",
|
|
3142
3599
|
},
|
|
@@ -3146,6 +3603,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3146
3603
|
summaryLabel: "OpenClaw",
|
|
3147
3604
|
detected: hasCommand("openclaw"),
|
|
3148
3605
|
recommended: true,
|
|
3606
|
+
installable: false,
|
|
3149
3607
|
icon: "🦞",
|
|
3150
3608
|
siteUrl: "https://openclaw.ai",
|
|
3151
3609
|
},
|
|
@@ -3155,6 +3613,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3155
3613
|
summaryLabel: "OpenCode",
|
|
3156
3614
|
detected: openCodeDetected,
|
|
3157
3615
|
recommended: true,
|
|
3616
|
+
installable: true,
|
|
3158
3617
|
icon: "⌘",
|
|
3159
3618
|
siteUrl: "https://opencode.ai",
|
|
3160
3619
|
},
|
|
@@ -3164,6 +3623,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3164
3623
|
summaryLabel: "Kilo Code",
|
|
3165
3624
|
detected: kiloDetected,
|
|
3166
3625
|
recommended: true,
|
|
3626
|
+
installable: false,
|
|
3167
3627
|
icon: "⚡",
|
|
3168
3628
|
siteUrl: "https://kilo.ai",
|
|
3169
3629
|
},
|
|
@@ -3173,6 +3633,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3173
3633
|
summaryLabel: "Roo Code",
|
|
3174
3634
|
detected: rooDetected,
|
|
3175
3635
|
recommended: true,
|
|
3636
|
+
installable: false,
|
|
3176
3637
|
icon: "🦘",
|
|
3177
3638
|
siteUrl: "https://roocode.com",
|
|
3178
3639
|
},
|
|
@@ -3182,6 +3643,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3182
3643
|
summaryLabel: "Trae",
|
|
3183
3644
|
detected: traeDetected,
|
|
3184
3645
|
recommended: false,
|
|
3646
|
+
installable: false,
|
|
3185
3647
|
icon: "△",
|
|
3186
3648
|
siteUrl: "https://www.trae.ai",
|
|
3187
3649
|
},
|
|
@@ -3191,6 +3653,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3191
3653
|
summaryLabel: "Aider",
|
|
3192
3654
|
detected: aiderDetected,
|
|
3193
3655
|
recommended: true,
|
|
3656
|
+
installable: true,
|
|
3194
3657
|
icon: "✦",
|
|
3195
3658
|
siteUrl: "https://aider.chat",
|
|
3196
3659
|
},
|
|
@@ -3200,6 +3663,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3200
3663
|
summaryLabel: "Zo",
|
|
3201
3664
|
detected: zoDetected,
|
|
3202
3665
|
recommended: false,
|
|
3666
|
+
installable: false,
|
|
3203
3667
|
icon: "◉",
|
|
3204
3668
|
siteUrl: "https://www.zo.computer",
|
|
3205
3669
|
},
|
|
@@ -3209,6 +3673,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3209
3673
|
summaryLabel: "Hermes",
|
|
3210
3674
|
detected: hermesDetected,
|
|
3211
3675
|
recommended: true,
|
|
3676
|
+
installable: false,
|
|
3212
3677
|
icon: "☿",
|
|
3213
3678
|
siteUrl: "https://hermes-agent.nousresearch.com/docs/",
|
|
3214
3679
|
},
|
|
@@ -3218,6 +3683,11 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3218
3683
|
flagSelection: parseSetupClientFlags(flags.clients),
|
|
3219
3684
|
skipPrompt: flags.yes,
|
|
3220
3685
|
});
|
|
3686
|
+
const installedMissingClients = installSelectedMissingClients({
|
|
3687
|
+
setupClients,
|
|
3688
|
+
selectedClientIds: selectedSetupClients,
|
|
3689
|
+
log: (message) => this.log(message),
|
|
3690
|
+
});
|
|
3221
3691
|
const migrateCodexConversations = await resolveCodexConversationMigrationSelection({
|
|
3222
3692
|
codexSelected: selectedSetupClients.has("codex"),
|
|
3223
3693
|
flagValue: flags["migrate-conversations"],
|
|
@@ -3270,6 +3740,7 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3270
3740
|
let authSeedCleanup = null;
|
|
3271
3741
|
let stateDbMigration = null;
|
|
3272
3742
|
let modelCacheMigration = null;
|
|
3743
|
+
let installedClientsSummary = installedMissingClients;
|
|
3273
3744
|
let continueConfigPath = null;
|
|
3274
3745
|
let clineConfigPaths = [];
|
|
3275
3746
|
let gsdConfigPaths = [];
|
|
@@ -3289,14 +3760,17 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3289
3760
|
if (resolved.authRejected) {
|
|
3290
3761
|
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
3291
3762
|
}
|
|
3763
|
+
progress.stop();
|
|
3292
3764
|
const configuredModel = await resolveConfiguredModelSelection({
|
|
3293
3765
|
resolved,
|
|
3294
3766
|
requestedModel: flags.model,
|
|
3767
|
+
requestedModels: flags.models,
|
|
3295
3768
|
skipPrompt: flags.yes,
|
|
3296
3769
|
});
|
|
3297
3770
|
resolved = {
|
|
3298
3771
|
...resolved,
|
|
3299
3772
|
model: configuredModel.model,
|
|
3773
|
+
models: configuredModel.models,
|
|
3300
3774
|
note: [resolved.note, configuredModel.note].filter(Boolean).join(" ").trim() || undefined,
|
|
3301
3775
|
};
|
|
3302
3776
|
}
|
|
@@ -3305,23 +3779,28 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3305
3779
|
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
3306
3780
|
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
3307
3781
|
}
|
|
3308
|
-
|
|
3782
|
+
progress.stop();
|
|
3783
|
+
claudeAccess = await resolveConfiguredClaudeModels({
|
|
3309
3784
|
access: claudeAccess,
|
|
3310
3785
|
requestedModels: flags["claude-models"],
|
|
3786
|
+
skipPrompt: flags.yes,
|
|
3311
3787
|
});
|
|
3312
3788
|
if (flags["list-models"]) {
|
|
3313
3789
|
if (resolved) {
|
|
3314
3790
|
this.log("\nOpenAI-compatible models:");
|
|
3315
|
-
for (const
|
|
3316
|
-
|
|
3317
|
-
|
|
3791
|
+
for (const section of groupModelSections(resolved.models)) {
|
|
3792
|
+
this.log(`${section.provider.icon} ${section.provider.label}`);
|
|
3793
|
+
for (const model of section.models) {
|
|
3794
|
+
const marker = model.id === resolved.model ? " (default)" : "";
|
|
3795
|
+
this.log(` - ${model.id}${marker}`);
|
|
3796
|
+
}
|
|
3318
3797
|
}
|
|
3319
3798
|
}
|
|
3320
3799
|
if (claudeAccess.models.length > 0) {
|
|
3321
3800
|
this.log("\nClaude models:");
|
|
3322
3801
|
for (const modelId of claudeAccess.models) {
|
|
3323
3802
|
const marker = claudeAccess.selectedModels?.includes(modelId) ? " (selected)" : "";
|
|
3324
|
-
this.log(
|
|
3803
|
+
this.log(` - ${modelId}${marker}`);
|
|
3325
3804
|
}
|
|
3326
3805
|
}
|
|
3327
3806
|
}
|
|
@@ -3531,6 +4010,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3531
4010
|
if (backupSummary) {
|
|
3532
4011
|
summaryNotes.add(`Created a local backup snapshot (${backupSummary.id}) before changing client configs.`);
|
|
3533
4012
|
}
|
|
4013
|
+
if (installedClientsSummary.length > 0) {
|
|
4014
|
+
summaryNotes.add(`Installed missing clients: ${installedClientsSummary.join(", ")}.`);
|
|
4015
|
+
}
|
|
3534
4016
|
if (selectedSetupClients.has("claude") && claudeAccess?.enabled) {
|
|
3535
4017
|
summaryNotes.add("Claude Code: exported ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY.");
|
|
3536
4018
|
if (claudeDesktop3pConfigPathManaged) {
|
|
@@ -3585,6 +4067,9 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3585
4067
|
this.log(`- Managed config: ${paths_1.managedConfigPath}`);
|
|
3586
4068
|
this.log(`- Backend: ${backendUrl}`);
|
|
3587
4069
|
this.log(`- Auth mode: ${authType}`);
|
|
4070
|
+
if (installedClientsSummary.length > 0) {
|
|
4071
|
+
this.log(`- Installed missing clients: ${installedClientsSummary.join(", ")}`);
|
|
4072
|
+
}
|
|
3588
4073
|
if (backupSummary) {
|
|
3589
4074
|
this.log(`- Backup: ${backupSummary.id} (${backupSummary.directory})`);
|
|
3590
4075
|
}
|
|
@@ -3849,6 +4334,10 @@ SetupCommand.flags = {
|
|
|
3849
4334
|
required: false,
|
|
3850
4335
|
description: "Default OpenAI-compatible model to configure for local tools",
|
|
3851
4336
|
}),
|
|
4337
|
+
models: core_1.Flags.string({
|
|
4338
|
+
required: false,
|
|
4339
|
+
description: "Comma-separated OpenAI-compatible model ids to save into local tools",
|
|
4340
|
+
}),
|
|
3852
4341
|
"claude-models": core_1.Flags.string({
|
|
3853
4342
|
required: false,
|
|
3854
4343
|
description: "Comma-separated Claude model ids to configure for Claude-compatible tools",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type SupportedModelTone = "coral" | "sea" | "ink";
|
|
2
|
+
export type SupportedModelProvider = "openai" | "google" | "deepseek";
|
|
2
3
|
export type SupportedModelConfig = {
|
|
3
4
|
id: string;
|
|
4
5
|
label: string;
|
|
5
6
|
note: string;
|
|
6
7
|
tone: SupportedModelTone;
|
|
8
|
+
provider: SupportedModelProvider;
|
|
7
9
|
inputPer1M: number;
|
|
8
10
|
cachedInputPer1M: number;
|
|
9
11
|
outputPer1M: number;
|
|
@@ -25,6 +25,7 @@ function parseSupportedModels(raw) {
|
|
|
25
25
|
const label = typeof record.label === "string" ? record.label.trim() : "";
|
|
26
26
|
const note = typeof record.note === "string" ? record.note.trim() : "";
|
|
27
27
|
const tone = record.tone;
|
|
28
|
+
const provider = record.provider;
|
|
28
29
|
const inputPer1M = typeof record.inputPer1M === "number" ? record.inputPer1M : NaN;
|
|
29
30
|
const cachedInputPer1M = typeof record.cachedInputPer1M === "number" ? record.cachedInputPer1M : NaN;
|
|
30
31
|
const outputPer1M = typeof record.outputPer1M === "number" ? record.outputPer1M : NaN;
|
|
@@ -37,6 +38,9 @@ function parseSupportedModels(raw) {
|
|
|
37
38
|
if (tone !== "coral" && tone !== "sea" && tone !== "ink") {
|
|
38
39
|
throw new Error(`unsupported tone for model ${id}`);
|
|
39
40
|
}
|
|
41
|
+
if (provider !== "openai" && provider !== "google" && provider !== "deepseek") {
|
|
42
|
+
throw new Error(`unsupported provider for model ${id}`);
|
|
43
|
+
}
|
|
40
44
|
if (!Number.isFinite(inputPer1M) || !Number.isFinite(cachedInputPer1M) || !Number.isFinite(outputPer1M)) {
|
|
41
45
|
throw new Error(`unsupported pricing fields for model ${id}`);
|
|
42
46
|
}
|
|
@@ -45,6 +49,7 @@ function parseSupportedModels(raw) {
|
|
|
45
49
|
label,
|
|
46
50
|
note,
|
|
47
51
|
tone,
|
|
52
|
+
provider,
|
|
48
53
|
inputPer1M,
|
|
49
54
|
cachedInputPer1M,
|
|
50
55
|
outputPer1M,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theclawbay",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.80",
|
|
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": {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"label": "GPT-5.5",
|
|
5
5
|
"note": "Frontier model for complex coding, research, and real-world work.",
|
|
6
6
|
"tone": "coral",
|
|
7
|
+
"provider": "openai",
|
|
7
8
|
"inputPer1M": 5.0,
|
|
8
9
|
"cachedInputPer1M": 0.5,
|
|
9
10
|
"outputPer1M": 30.0
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
"label": "GPT-5.4",
|
|
14
15
|
"note": "Frontier coding model with the widest headroom.",
|
|
15
16
|
"tone": "coral",
|
|
17
|
+
"provider": "openai",
|
|
16
18
|
"inputPer1M": 2.5,
|
|
17
19
|
"cachedInputPer1M": 0.25,
|
|
18
20
|
"outputPer1M": 15.0
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
"label": "GPT-5.4 Mini",
|
|
23
25
|
"note": "Smaller GPT-5.4 variant for quick iterations.",
|
|
24
26
|
"tone": "sea",
|
|
27
|
+
"provider": "openai",
|
|
25
28
|
"inputPer1M": 1.25,
|
|
26
29
|
"cachedInputPer1M": 0.125,
|
|
27
30
|
"outputPer1M": 10.0
|
|
@@ -31,42 +34,17 @@
|
|
|
31
34
|
"label": "GPT Image 2",
|
|
32
35
|
"note": "OpenAI-compatible image generation endpoint backed by codex-lb.",
|
|
33
36
|
"tone": "coral",
|
|
37
|
+
"provider": "openai",
|
|
34
38
|
"inputPer1M": 5.0,
|
|
35
39
|
"cachedInputPer1M": 2.0,
|
|
36
40
|
"outputPer1M": 30.0
|
|
37
41
|
},
|
|
38
|
-
{
|
|
39
|
-
"id": "gpt-image-1.5",
|
|
40
|
-
"label": "GPT Image 1.5",
|
|
41
|
-
"note": "Native image generation model for direct image outputs.",
|
|
42
|
-
"tone": "coral",
|
|
43
|
-
"inputPer1M": 8.0,
|
|
44
|
-
"cachedInputPer1M": 2.0,
|
|
45
|
-
"outputPer1M": 32.0
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"id": "gpt-5.1-codex-max",
|
|
49
|
-
"label": "GPT-5.1 Codex Max",
|
|
50
|
-
"note": "Higher-throughput option for longer coding sessions.",
|
|
51
|
-
"tone": "coral",
|
|
52
|
-
"inputPer1M": 1.25,
|
|
53
|
-
"cachedInputPer1M": 0.125,
|
|
54
|
-
"outputPer1M": 10.0
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"id": "gpt-5.1-codex-mini",
|
|
58
|
-
"label": "GPT-5.1 Codex Mini",
|
|
59
|
-
"note": "Lower-cost Codex path for quick iterations.",
|
|
60
|
-
"tone": "sea",
|
|
61
|
-
"inputPer1M": 0.25,
|
|
62
|
-
"cachedInputPer1M": 0.025,
|
|
63
|
-
"outputPer1M": 2.0
|
|
64
|
-
},
|
|
65
42
|
{
|
|
66
43
|
"id": "gemini-2.5-pro",
|
|
67
44
|
"label": "Gemini 2.5 Pro",
|
|
68
45
|
"note": "Google's flagship coding and reasoning model through CliRelay.",
|
|
69
46
|
"tone": "coral",
|
|
47
|
+
"provider": "google",
|
|
70
48
|
"inputPer1M": 1.25,
|
|
71
49
|
"cachedInputPer1M": 0.125,
|
|
72
50
|
"outputPer1M": 10.0
|
|
@@ -76,6 +54,7 @@
|
|
|
76
54
|
"label": "Gemini 2.5 Flash",
|
|
77
55
|
"note": "Balanced Gemini path for fast multimodal and agentic work.",
|
|
78
56
|
"tone": "ink",
|
|
57
|
+
"provider": "google",
|
|
79
58
|
"inputPer1M": 0.3,
|
|
80
59
|
"cachedInputPer1M": 0.03,
|
|
81
60
|
"outputPer1M": 2.5
|
|
@@ -85,6 +64,7 @@
|
|
|
85
64
|
"label": "Gemini 2.5 Flash Lite",
|
|
86
65
|
"note": "Lowest-cost Gemini path for lightweight coding and tooling loops.",
|
|
87
66
|
"tone": "sea",
|
|
67
|
+
"provider": "google",
|
|
88
68
|
"inputPer1M": 0.1,
|
|
89
69
|
"cachedInputPer1M": 0.01,
|
|
90
70
|
"outputPer1M": 0.4
|
|
@@ -94,6 +74,7 @@
|
|
|
94
74
|
"label": "Gemini 3 Pro Preview",
|
|
95
75
|
"note": "Preview Gemini model for heavier multimodal and coding sessions.",
|
|
96
76
|
"tone": "coral",
|
|
77
|
+
"provider": "google",
|
|
97
78
|
"inputPer1M": 2.0,
|
|
98
79
|
"cachedInputPer1M": 0.2,
|
|
99
80
|
"outputPer1M": 12.0
|
|
@@ -103,6 +84,7 @@
|
|
|
103
84
|
"label": "Gemini 3.1 Pro Preview",
|
|
104
85
|
"note": "Newest Gemini Pro preview with stronger agentic and coding behavior.",
|
|
105
86
|
"tone": "coral",
|
|
87
|
+
"provider": "google",
|
|
106
88
|
"inputPer1M": 2.0,
|
|
107
89
|
"cachedInputPer1M": 0.2,
|
|
108
90
|
"outputPer1M": 12.0
|
|
@@ -112,6 +94,7 @@
|
|
|
112
94
|
"label": "Gemini 3 Flash Preview",
|
|
113
95
|
"note": "Faster Gemini preview tuned for responsive high-volume tasks.",
|
|
114
96
|
"tone": "ink",
|
|
97
|
+
"provider": "google",
|
|
115
98
|
"inputPer1M": 0.5,
|
|
116
99
|
"cachedInputPer1M": 0.05,
|
|
117
100
|
"outputPer1M": 3.0
|
|
@@ -121,6 +104,7 @@
|
|
|
121
104
|
"label": "DeepSeek V4 Flash",
|
|
122
105
|
"note": "Lowest-cost DeepSeek path with 1M context through CliRelay.",
|
|
123
106
|
"tone": "sea",
|
|
107
|
+
"provider": "deepseek",
|
|
124
108
|
"inputPer1M": 0.14,
|
|
125
109
|
"cachedInputPer1M": 0.0028,
|
|
126
110
|
"outputPer1M": 0.28,
|
|
@@ -131,6 +115,7 @@
|
|
|
131
115
|
"label": "DeepSeek V4 Pro",
|
|
132
116
|
"note": "Flagship DeepSeek reasoning path with 1M context through CliRelay.",
|
|
133
117
|
"tone": "coral",
|
|
118
|
+
"provider": "deepseek",
|
|
134
119
|
"inputPer1M": 0.435,
|
|
135
120
|
"cachedInputPer1M": 0.003625,
|
|
136
121
|
"outputPer1M": 0.87,
|