claudish 1.4.1 → 1.6.0
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/index.js +270 -107
- package/package.json +3 -2
- package/scripts/extract-models.ts +207 -0
package/dist/index.js
CHANGED
|
@@ -1,67 +1,163 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
|
+
|
|
31
|
+
// src/config.ts
|
|
32
|
+
var exports_config = {};
|
|
33
|
+
__export(exports_config, {
|
|
34
|
+
OPENROUTER_HEADERS: () => OPENROUTER_HEADERS,
|
|
35
|
+
OPENROUTER_API_URL: () => OPENROUTER_API_URL,
|
|
36
|
+
MODEL_INFO: () => MODEL_INFO,
|
|
37
|
+
ENV: () => ENV,
|
|
38
|
+
DEFAULT_PORT_RANGE: () => DEFAULT_PORT_RANGE,
|
|
39
|
+
DEFAULT_MODEL: () => DEFAULT_MODEL
|
|
40
|
+
});
|
|
41
|
+
var DEFAULT_MODEL = "x-ai/grok-code-fast-1", DEFAULT_PORT_RANGE, MODEL_INFO, ENV, OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS;
|
|
42
|
+
var init_config = __esm(() => {
|
|
43
|
+
DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
|
|
44
|
+
MODEL_INFO = {
|
|
45
|
+
"x-ai/grok-code-fast-1": {
|
|
46
|
+
name: "Ultra-fast coding",
|
|
47
|
+
description: "Ultra-fast coding",
|
|
48
|
+
priority: 1,
|
|
49
|
+
provider: "xAI"
|
|
50
|
+
},
|
|
51
|
+
"minimax/minimax-m2": {
|
|
52
|
+
name: "Compact high-efficiency",
|
|
53
|
+
description: "Compact high-efficiency",
|
|
54
|
+
priority: 2,
|
|
55
|
+
provider: "MiniMax"
|
|
56
|
+
},
|
|
57
|
+
"z-ai/glm-4.6": {
|
|
58
|
+
name: "Enhanced coding capabilities",
|
|
59
|
+
description: "Enhanced coding capabilities",
|
|
60
|
+
priority: 3,
|
|
61
|
+
provider: "Zhipu AI"
|
|
62
|
+
},
|
|
63
|
+
"openai/gpt-5.1-codex": {
|
|
64
|
+
name: "Specialized software engineering",
|
|
65
|
+
description: "Specialized software engineering",
|
|
66
|
+
priority: 4,
|
|
67
|
+
provider: "OpenAI"
|
|
68
|
+
},
|
|
69
|
+
"google/gemini-2.5-flash": {
|
|
70
|
+
name: "Advanced reasoning with built-in thinking",
|
|
71
|
+
description: "Advanced reasoning with built-in thinking",
|
|
72
|
+
priority: 5,
|
|
73
|
+
provider: "Google"
|
|
74
|
+
},
|
|
75
|
+
"google/gemini-2.5-pro": {
|
|
76
|
+
name: "State-of-the-art reasoning",
|
|
77
|
+
description: "State-of-the-art reasoning",
|
|
78
|
+
priority: 6,
|
|
79
|
+
provider: "Google"
|
|
80
|
+
},
|
|
81
|
+
"qwen/qwen3-vl-235b-a22b-instruct": {
|
|
82
|
+
name: "Multimodal vision-language",
|
|
83
|
+
description: "Multimodal vision-language",
|
|
84
|
+
priority: 7,
|
|
85
|
+
provider: "Alibaba"
|
|
86
|
+
},
|
|
87
|
+
"google/gemini-2.0-flash-001": {
|
|
88
|
+
name: "Faster TTFT, multimodal",
|
|
89
|
+
description: "Faster TTFT, multimodal",
|
|
90
|
+
priority: 8,
|
|
91
|
+
provider: "Google"
|
|
92
|
+
},
|
|
93
|
+
"google/gemini-2.5-flash-lite": {
|
|
94
|
+
name: "Ultra-low latency",
|
|
95
|
+
description: "Ultra-low latency",
|
|
96
|
+
priority: 9,
|
|
97
|
+
provider: "Google"
|
|
98
|
+
},
|
|
99
|
+
"deepseek/deepseek-chat-v3-0324": {
|
|
100
|
+
name: "685B parameter MoE",
|
|
101
|
+
description: "685B parameter MoE",
|
|
102
|
+
priority: 10,
|
|
103
|
+
provider: "DeepSeek"
|
|
104
|
+
},
|
|
105
|
+
"openai/gpt-4o-mini": {
|
|
106
|
+
name: "Compact multimodal",
|
|
107
|
+
description: "Compact multimodal",
|
|
108
|
+
priority: 11,
|
|
109
|
+
provider: "OpenAI"
|
|
110
|
+
},
|
|
111
|
+
custom: {
|
|
112
|
+
name: "Custom Model",
|
|
113
|
+
description: "Enter any OpenRouter model ID manually",
|
|
114
|
+
priority: 999,
|
|
115
|
+
provider: "Custom"
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
ENV = {
|
|
119
|
+
OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
|
|
120
|
+
CLAUDISH_MODEL: "CLAUDISH_MODEL",
|
|
121
|
+
CLAUDISH_PORT: "CLAUDISH_PORT",
|
|
122
|
+
CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME",
|
|
123
|
+
ANTHROPIC_MODEL: "ANTHROPIC_MODEL",
|
|
124
|
+
ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL"
|
|
125
|
+
};
|
|
126
|
+
OPENROUTER_HEADERS = {
|
|
127
|
+
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
128
|
+
"X-Title": "Claudish - OpenRouter Proxy"
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// src/types.ts
|
|
133
|
+
var exports_types = {};
|
|
134
|
+
__export(exports_types, {
|
|
135
|
+
OPENROUTER_MODELS: () => OPENROUTER_MODELS
|
|
136
|
+
});
|
|
137
|
+
var OPENROUTER_MODELS;
|
|
138
|
+
var init_types = __esm(() => {
|
|
139
|
+
OPENROUTER_MODELS = [
|
|
140
|
+
"x-ai/grok-code-fast-1",
|
|
141
|
+
"minimax/minimax-m2",
|
|
142
|
+
"z-ai/glm-4.6",
|
|
143
|
+
"openai/gpt-5.1-codex",
|
|
144
|
+
"google/gemini-2.5-flash",
|
|
145
|
+
"google/gemini-2.5-pro",
|
|
146
|
+
"qwen/qwen3-vl-235b-a22b-instruct",
|
|
147
|
+
"google/gemini-2.0-flash-001",
|
|
148
|
+
"google/gemini-2.5-flash-lite",
|
|
149
|
+
"deepseek/deepseek-chat-v3-0324",
|
|
150
|
+
"openai/gpt-4o-mini",
|
|
151
|
+
"custom"
|
|
152
|
+
];
|
|
153
|
+
});
|
|
2
154
|
|
|
3
155
|
// src/claude-runner.ts
|
|
156
|
+
init_config();
|
|
4
157
|
import { spawn } from "node:child_process";
|
|
5
158
|
import { writeFileSync, unlinkSync } from "node:fs";
|
|
6
159
|
import { tmpdir } from "node:os";
|
|
7
160
|
import { join } from "node:path";
|
|
8
|
-
|
|
9
|
-
// src/config.ts
|
|
10
|
-
var DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
|
|
11
|
-
var MODEL_INFO = {
|
|
12
|
-
"x-ai/grok-code-fast-1": {
|
|
13
|
-
name: "Grok Code Fast",
|
|
14
|
-
description: "xAI's fast coding model",
|
|
15
|
-
priority: 1,
|
|
16
|
-
provider: "xAI"
|
|
17
|
-
},
|
|
18
|
-
"openai/gpt-5-codex": {
|
|
19
|
-
name: "GPT-5 Codex",
|
|
20
|
-
description: "OpenAI's advanced coding model",
|
|
21
|
-
priority: 2,
|
|
22
|
-
provider: "OpenAI"
|
|
23
|
-
},
|
|
24
|
-
"minimax/minimax-m2": {
|
|
25
|
-
name: "MiniMax M2",
|
|
26
|
-
description: "MiniMax's high-performance model",
|
|
27
|
-
priority: 3,
|
|
28
|
-
provider: "MiniMax"
|
|
29
|
-
},
|
|
30
|
-
"z-ai/glm-4.6": {
|
|
31
|
-
name: "GLM-4.6",
|
|
32
|
-
description: "Advanced language model",
|
|
33
|
-
priority: 4,
|
|
34
|
-
provider: "Zhipu AI"
|
|
35
|
-
},
|
|
36
|
-
"qwen/qwen3-vl-235b-a22b-instruct": {
|
|
37
|
-
name: "Qwen3 VL 235B",
|
|
38
|
-
description: "Alibaba's vision-language model",
|
|
39
|
-
priority: 5,
|
|
40
|
-
provider: "Alibaba"
|
|
41
|
-
},
|
|
42
|
-
"anthropic/claude-sonnet-4.5": {
|
|
43
|
-
name: "Claude Sonnet 4.5",
|
|
44
|
-
description: "Anthropic's Claude (for comparison)",
|
|
45
|
-
priority: 6,
|
|
46
|
-
provider: "Anthropic"
|
|
47
|
-
},
|
|
48
|
-
custom: {
|
|
49
|
-
name: "Custom Model",
|
|
50
|
-
description: "Enter any OpenRouter model ID manually",
|
|
51
|
-
priority: 999,
|
|
52
|
-
provider: "Custom"
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
var ENV = {
|
|
56
|
-
OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
|
|
57
|
-
CLAUDISH_MODEL: "CLAUDISH_MODEL",
|
|
58
|
-
CLAUDISH_PORT: "CLAUDISH_PORT",
|
|
59
|
-
CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME",
|
|
60
|
-
ANTHROPIC_MODEL: "ANTHROPIC_MODEL",
|
|
61
|
-
ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL"
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// src/claude-runner.ts
|
|
65
161
|
function createTempSettingsFile(modelDisplay, port) {
|
|
66
162
|
const tempDir = tmpdir();
|
|
67
163
|
const timestamp = Date.now();
|
|
@@ -194,16 +290,73 @@ async function checkClaudeInstalled() {
|
|
|
194
290
|
}
|
|
195
291
|
}
|
|
196
292
|
|
|
197
|
-
// src/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
293
|
+
// src/cli.ts
|
|
294
|
+
init_config();
|
|
295
|
+
|
|
296
|
+
// src/model-loader.ts
|
|
297
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
298
|
+
import { join as join2, dirname } from "node:path";
|
|
299
|
+
import { fileURLToPath } from "node:url";
|
|
300
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
301
|
+
var __dirname2 = dirname(__filename2);
|
|
302
|
+
var _cachedModelInfo = null;
|
|
303
|
+
var _cachedModelIds = null;
|
|
304
|
+
function loadModelInfo() {
|
|
305
|
+
if (_cachedModelInfo) {
|
|
306
|
+
return _cachedModelInfo;
|
|
307
|
+
}
|
|
308
|
+
const jsonPath = join2(__dirname2, "../recommended-models.json");
|
|
309
|
+
if (existsSync(jsonPath)) {
|
|
310
|
+
try {
|
|
311
|
+
const jsonContent = readFileSync(jsonPath, "utf-8");
|
|
312
|
+
const data = JSON.parse(jsonContent);
|
|
313
|
+
const modelInfo = {};
|
|
314
|
+
for (const model of data.models) {
|
|
315
|
+
modelInfo[model.id] = {
|
|
316
|
+
name: model.name,
|
|
317
|
+
description: model.description,
|
|
318
|
+
priority: model.priority,
|
|
319
|
+
provider: model.provider
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
modelInfo.custom = {
|
|
323
|
+
name: "Custom Model",
|
|
324
|
+
description: "Enter any OpenRouter model ID manually",
|
|
325
|
+
priority: 999,
|
|
326
|
+
provider: "Custom"
|
|
327
|
+
};
|
|
328
|
+
_cachedModelInfo = modelInfo;
|
|
329
|
+
return modelInfo;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.warn("⚠️ Failed to load recommended-models.json, falling back to build-time config");
|
|
332
|
+
console.warn(` Error: ${error}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const { MODEL_INFO: MODEL_INFO2 } = (init_config(), __toCommonJS(exports_config));
|
|
336
|
+
_cachedModelInfo = MODEL_INFO2;
|
|
337
|
+
return MODEL_INFO2;
|
|
338
|
+
}
|
|
339
|
+
function getAvailableModels() {
|
|
340
|
+
if (_cachedModelIds) {
|
|
341
|
+
return _cachedModelIds;
|
|
342
|
+
}
|
|
343
|
+
const jsonPath = join2(__dirname2, "../recommended-models.json");
|
|
344
|
+
if (existsSync(jsonPath)) {
|
|
345
|
+
try {
|
|
346
|
+
const jsonContent = readFileSync(jsonPath, "utf-8");
|
|
347
|
+
const data = JSON.parse(jsonContent);
|
|
348
|
+
const modelIds = data.models.sort((a, b) => a.priority - b.priority).map((m) => m.id);
|
|
349
|
+
const result = [...modelIds, "custom"];
|
|
350
|
+
_cachedModelIds = result;
|
|
351
|
+
return result;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.warn("⚠️ Failed to load model list from JSON, falling back to build-time config");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const { OPENROUTER_MODELS: OPENROUTER_MODELS2 } = (init_types(), __toCommonJS(exports_types));
|
|
357
|
+
_cachedModelIds = [...OPENROUTER_MODELS2];
|
|
358
|
+
return [...OPENROUTER_MODELS2];
|
|
359
|
+
}
|
|
207
360
|
|
|
208
361
|
// src/cli.ts
|
|
209
362
|
function parseArgs(args) {
|
|
@@ -447,8 +600,10 @@ function printAvailableModels() {
|
|
|
447
600
|
console.log(`
|
|
448
601
|
Available OpenRouter Models (in priority order):
|
|
449
602
|
`);
|
|
450
|
-
|
|
451
|
-
|
|
603
|
+
const models = getAvailableModels();
|
|
604
|
+
const modelInfo = loadModelInfo();
|
|
605
|
+
for (const model of models) {
|
|
606
|
+
const info = modelInfo[model];
|
|
452
607
|
console.log(` ${model}`);
|
|
453
608
|
console.log(` ${info.name} - ${info.description}`);
|
|
454
609
|
console.log("");
|
|
@@ -459,6 +614,9 @@ Available OpenRouter Models (in priority order):
|
|
|
459
614
|
`);
|
|
460
615
|
}
|
|
461
616
|
|
|
617
|
+
// src/index.ts
|
|
618
|
+
init_config();
|
|
619
|
+
|
|
462
620
|
// src/simple-selector.ts
|
|
463
621
|
import { createInterface } from "readline";
|
|
464
622
|
async function promptForApiKey() {
|
|
@@ -511,12 +669,14 @@ async function promptForApiKey() {
|
|
|
511
669
|
});
|
|
512
670
|
}
|
|
513
671
|
async function selectModelInteractively() {
|
|
672
|
+
const models = getAvailableModels();
|
|
673
|
+
const modelInfo = loadModelInfo();
|
|
514
674
|
return new Promise((resolve) => {
|
|
515
675
|
console.log(`
|
|
516
676
|
\x1B[1m\x1B[36mSelect an OpenRouter model:\x1B[0m
|
|
517
677
|
`);
|
|
518
|
-
|
|
519
|
-
const info =
|
|
678
|
+
models.forEach((model, index) => {
|
|
679
|
+
const info = modelInfo[model];
|
|
520
680
|
const displayName = info ? info.name : model;
|
|
521
681
|
const description = info ? info.description : "Custom model entry";
|
|
522
682
|
const provider = info ? info.provider : "";
|
|
@@ -528,7 +688,7 @@ async function selectModelInteractively() {
|
|
|
528
688
|
}
|
|
529
689
|
console.log("");
|
|
530
690
|
});
|
|
531
|
-
console.log(`\x1B[2mEnter number (1-${
|
|
691
|
+
console.log(`\x1B[2mEnter number (1-${models.length}) or 'q' to quit:\x1B[0m`);
|
|
532
692
|
const rl = createInterface({
|
|
533
693
|
input: process.stdin,
|
|
534
694
|
output: process.stdout,
|
|
@@ -542,11 +702,11 @@ async function selectModelInteractively() {
|
|
|
542
702
|
process.exit(0);
|
|
543
703
|
}
|
|
544
704
|
const selection = parseInt(trimmed, 10);
|
|
545
|
-
if (isNaN(selection) || selection < 1 || selection >
|
|
546
|
-
console.log(`\x1B[31mInvalid selection. Please enter 1-${
|
|
705
|
+
if (isNaN(selection) || selection < 1 || selection > models.length) {
|
|
706
|
+
console.log(`\x1B[31mInvalid selection. Please enter 1-${models.length}\x1B[0m`);
|
|
547
707
|
return;
|
|
548
708
|
}
|
|
549
|
-
const model =
|
|
709
|
+
const model = models[selection - 1];
|
|
550
710
|
if (model === "custom") {
|
|
551
711
|
rl.close();
|
|
552
712
|
console.log(`
|
|
@@ -603,8 +763,8 @@ async function selectModelInteractively() {
|
|
|
603
763
|
}
|
|
604
764
|
|
|
605
765
|
// src/logger.ts
|
|
606
|
-
import { writeFileSync as writeFileSync2, appendFile, existsSync, mkdirSync } from "fs";
|
|
607
|
-
import { join as
|
|
766
|
+
import { writeFileSync as writeFileSync2, appendFile, existsSync as existsSync2, mkdirSync } from "fs";
|
|
767
|
+
import { join as join3 } from "path";
|
|
608
768
|
var logFilePath = null;
|
|
609
769
|
var logLevel = "info";
|
|
610
770
|
var logBuffer = [];
|
|
@@ -649,12 +809,12 @@ function initLogger(debugMode, level = "info") {
|
|
|
649
809
|
return;
|
|
650
810
|
}
|
|
651
811
|
logLevel = level;
|
|
652
|
-
const logsDir =
|
|
653
|
-
if (!
|
|
812
|
+
const logsDir = join3(process.cwd(), "logs");
|
|
813
|
+
if (!existsSync2(logsDir)) {
|
|
654
814
|
mkdirSync(logsDir, { recursive: true });
|
|
655
815
|
}
|
|
656
816
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
|
|
657
|
-
logFilePath =
|
|
817
|
+
logFilePath = join3(logsDir, `claudish_${timestamp}.log`);
|
|
658
818
|
writeFileSync2(logFilePath, `Claudish Debug Log - ${new Date().toISOString()}
|
|
659
819
|
Log Level: ${level}
|
|
660
820
|
${"=".repeat(80)}
|
|
@@ -745,7 +905,7 @@ async function isPortAvailable(port) {
|
|
|
745
905
|
});
|
|
746
906
|
}
|
|
747
907
|
|
|
748
|
-
// node_modules/hono/dist/compose.js
|
|
908
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/compose.js
|
|
749
909
|
var compose = (middleware, onError, onNotFound) => {
|
|
750
910
|
return (context, next) => {
|
|
751
911
|
let index = -1;
|
|
@@ -789,10 +949,10 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
789
949
|
};
|
|
790
950
|
};
|
|
791
951
|
|
|
792
|
-
// node_modules/hono/dist/request/constants.js
|
|
952
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/request/constants.js
|
|
793
953
|
var GET_MATCH_RESULT = Symbol();
|
|
794
954
|
|
|
795
|
-
// node_modules/hono/dist/utils/body.js
|
|
955
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/utils/body.js
|
|
796
956
|
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
797
957
|
const { all = false, dot = false } = options;
|
|
798
958
|
const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
|
|
@@ -860,7 +1020,7 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
860
1020
|
});
|
|
861
1021
|
};
|
|
862
1022
|
|
|
863
|
-
// node_modules/hono/dist/utils/url.js
|
|
1023
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/utils/url.js
|
|
864
1024
|
var splitPath = (path) => {
|
|
865
1025
|
const paths = path.split("/");
|
|
866
1026
|
if (paths[0] === "") {
|
|
@@ -992,9 +1152,12 @@ var _decodeURI = (value) => {
|
|
|
992
1152
|
var _getQueryParam = (url, key, multiple) => {
|
|
993
1153
|
let encoded;
|
|
994
1154
|
if (!multiple && key && !/[%+]/.test(key)) {
|
|
995
|
-
let keyIndex2 = url.indexOf(
|
|
1155
|
+
let keyIndex2 = url.indexOf("?", 8);
|
|
996
1156
|
if (keyIndex2 === -1) {
|
|
997
|
-
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (!url.startsWith(key, keyIndex2 + 1)) {
|
|
1160
|
+
keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
|
|
998
1161
|
}
|
|
999
1162
|
while (keyIndex2 !== -1) {
|
|
1000
1163
|
const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
|
|
@@ -1055,7 +1218,7 @@ var getQueryParams = (url, key) => {
|
|
|
1055
1218
|
};
|
|
1056
1219
|
var decodeURIComponent_ = decodeURIComponent;
|
|
1057
1220
|
|
|
1058
|
-
// node_modules/hono/dist/request.js
|
|
1221
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/request.js
|
|
1059
1222
|
var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
|
|
1060
1223
|
var HonoRequest = class {
|
|
1061
1224
|
raw;
|
|
@@ -1166,7 +1329,7 @@ var HonoRequest = class {
|
|
|
1166
1329
|
}
|
|
1167
1330
|
};
|
|
1168
1331
|
|
|
1169
|
-
// node_modules/hono/dist/utils/html.js
|
|
1332
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/utils/html.js
|
|
1170
1333
|
var HtmlEscapedCallbackPhase = {
|
|
1171
1334
|
Stringify: 1,
|
|
1172
1335
|
BeforeStream: 2,
|
|
@@ -1204,7 +1367,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
|
|
|
1204
1367
|
}
|
|
1205
1368
|
};
|
|
1206
1369
|
|
|
1207
|
-
// node_modules/hono/dist/context.js
|
|
1370
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/context.js
|
|
1208
1371
|
var TEXT_PLAIN = "text/plain; charset=UTF-8";
|
|
1209
1372
|
var setDefaultContentType = (contentType, headers) => {
|
|
1210
1373
|
return {
|
|
@@ -1370,7 +1533,7 @@ var Context = class {
|
|
|
1370
1533
|
};
|
|
1371
1534
|
};
|
|
1372
1535
|
|
|
1373
|
-
// node_modules/hono/dist/router.js
|
|
1536
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router.js
|
|
1374
1537
|
var METHOD_NAME_ALL = "ALL";
|
|
1375
1538
|
var METHOD_NAME_ALL_LOWERCASE = "all";
|
|
1376
1539
|
var METHODS = ["get", "post", "put", "delete", "options", "patch"];
|
|
@@ -1378,10 +1541,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
|
|
|
1378
1541
|
var UnsupportedPathError = class extends Error {
|
|
1379
1542
|
};
|
|
1380
1543
|
|
|
1381
|
-
// node_modules/hono/dist/utils/constants.js
|
|
1544
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/utils/constants.js
|
|
1382
1545
|
var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
|
|
1383
1546
|
|
|
1384
|
-
// node_modules/hono/dist/hono-base.js
|
|
1547
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/hono-base.js
|
|
1385
1548
|
var notFoundHandler = (c) => {
|
|
1386
1549
|
return c.text("404 Not Found", 404);
|
|
1387
1550
|
};
|
|
@@ -1600,7 +1763,7 @@ var Hono = class {
|
|
|
1600
1763
|
};
|
|
1601
1764
|
};
|
|
1602
1765
|
|
|
1603
|
-
// node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
1766
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
1604
1767
|
var emptyParam = [];
|
|
1605
1768
|
function match(method, path) {
|
|
1606
1769
|
const matchers = this.buildAllMatchers();
|
|
@@ -1621,7 +1784,7 @@ function match(method, path) {
|
|
|
1621
1784
|
return match2(method, path);
|
|
1622
1785
|
}
|
|
1623
1786
|
|
|
1624
|
-
// node_modules/hono/dist/router/reg-exp-router/node.js
|
|
1787
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
1625
1788
|
var LABEL_REG_EXP_STR = "[^/]+";
|
|
1626
1789
|
var ONLY_WILDCARD_REG_EXP_STR = ".*";
|
|
1627
1790
|
var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
|
|
@@ -1725,7 +1888,7 @@ var Node = class {
|
|
|
1725
1888
|
}
|
|
1726
1889
|
};
|
|
1727
1890
|
|
|
1728
|
-
// node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
1891
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
1729
1892
|
var Trie = class {
|
|
1730
1893
|
#context = { varIndex: 0 };
|
|
1731
1894
|
#root = new Node;
|
|
@@ -1781,7 +1944,7 @@ var Trie = class {
|
|
|
1781
1944
|
}
|
|
1782
1945
|
};
|
|
1783
1946
|
|
|
1784
|
-
// node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1947
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1785
1948
|
var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
|
|
1786
1949
|
var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
|
|
1787
1950
|
function buildWildcardRegExp(path) {
|
|
@@ -1946,7 +2109,7 @@ var RegExpRouter = class {
|
|
|
1946
2109
|
}
|
|
1947
2110
|
};
|
|
1948
2111
|
|
|
1949
|
-
// node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
2112
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
1950
2113
|
var PreparedRegExpRouter = class {
|
|
1951
2114
|
name = "PreparedRegExpRouter";
|
|
1952
2115
|
#matchers;
|
|
@@ -2018,7 +2181,7 @@ var PreparedRegExpRouter = class {
|
|
|
2018
2181
|
match = match;
|
|
2019
2182
|
};
|
|
2020
2183
|
|
|
2021
|
-
// node_modules/hono/dist/router/smart-router/router.js
|
|
2184
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/smart-router/router.js
|
|
2022
2185
|
var SmartRouter = class {
|
|
2023
2186
|
name = "SmartRouter";
|
|
2024
2187
|
#routers = [];
|
|
@@ -2073,7 +2236,7 @@ var SmartRouter = class {
|
|
|
2073
2236
|
}
|
|
2074
2237
|
};
|
|
2075
2238
|
|
|
2076
|
-
// node_modules/hono/dist/router/trie-router/node.js
|
|
2239
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/trie-router/node.js
|
|
2077
2240
|
var emptyParams = /* @__PURE__ */ Object.create(null);
|
|
2078
2241
|
var Node2 = class {
|
|
2079
2242
|
#methods;
|
|
@@ -2227,7 +2390,7 @@ var Node2 = class {
|
|
|
2227
2390
|
}
|
|
2228
2391
|
};
|
|
2229
2392
|
|
|
2230
|
-
// node_modules/hono/dist/router/trie-router/router.js
|
|
2393
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/router/trie-router/router.js
|
|
2231
2394
|
var TrieRouter = class {
|
|
2232
2395
|
name = "TrieRouter";
|
|
2233
2396
|
#node;
|
|
@@ -2249,7 +2412,7 @@ var TrieRouter = class {
|
|
|
2249
2412
|
}
|
|
2250
2413
|
};
|
|
2251
2414
|
|
|
2252
|
-
// node_modules/hono/dist/hono.js
|
|
2415
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/hono.js
|
|
2253
2416
|
var Hono2 = class extends Hono {
|
|
2254
2417
|
constructor(options = {}) {
|
|
2255
2418
|
super(options);
|
|
@@ -2259,7 +2422,7 @@ var Hono2 = class extends Hono {
|
|
|
2259
2422
|
}
|
|
2260
2423
|
};
|
|
2261
2424
|
|
|
2262
|
-
// node_modules/hono/dist/middleware/cors/index.js
|
|
2425
|
+
// node_modules/.pnpm/hono@4.10.6/node_modules/hono/dist/middleware/cors/index.js
|
|
2263
2426
|
var cors = (options) => {
|
|
2264
2427
|
const defaults = {
|
|
2265
2428
|
origin: "*",
|
|
@@ -2344,7 +2507,7 @@ var cors = (options) => {
|
|
|
2344
2507
|
};
|
|
2345
2508
|
};
|
|
2346
2509
|
|
|
2347
|
-
// node_modules/@hono/node-server/dist/index.mjs
|
|
2510
|
+
// node_modules/.pnpm/@hono+node-server@1.19.6_hono@4.10.6/node_modules/@hono/node-server/dist/index.mjs
|
|
2348
2511
|
import { createServer as createServerHTTP } from "http";
|
|
2349
2512
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
2350
2513
|
import { Http2ServerRequest } from "http2";
|
|
@@ -3081,8 +3244,8 @@ class AdapterManager {
|
|
|
3081
3244
|
|
|
3082
3245
|
// src/proxy-server.ts
|
|
3083
3246
|
async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey) {
|
|
3084
|
-
const
|
|
3085
|
-
const
|
|
3247
|
+
const OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions";
|
|
3248
|
+
const OPENROUTER_HEADERS2 = {
|
|
3086
3249
|
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
3087
3250
|
"X-Title": "Claudish - OpenRouter Proxy"
|
|
3088
3251
|
};
|
|
@@ -3458,9 +3621,9 @@ IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JS
|
|
|
3458
3621
|
const headers = {
|
|
3459
3622
|
"Content-Type": "application/json",
|
|
3460
3623
|
Authorization: `Bearer ${openrouterApiKey}`,
|
|
3461
|
-
...
|
|
3624
|
+
...OPENROUTER_HEADERS2
|
|
3462
3625
|
};
|
|
3463
|
-
const openrouterResponse = await fetch(
|
|
3626
|
+
const openrouterResponse = await fetch(OPENROUTER_API_URL2, {
|
|
3464
3627
|
method: "POST",
|
|
3465
3628
|
headers,
|
|
3466
3629
|
body: JSON.stringify(openrouterPayload)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "CLI tool to run Claude Code with any OpenRouter model (Grok, GPT-5, MiniMax, etc.) via local Anthropic API-compatible proxy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"dev:grok": "bun run src/index.ts --interactive --model x-ai/grok-code-fast-1",
|
|
13
13
|
"dev:grok:debug": "bun run src/index.ts --interactive --debug --log-level info --model x-ai/grok-code-fast-1",
|
|
14
14
|
"dev:info": "bun run src/index.ts --interactive --monitor",
|
|
15
|
-
"
|
|
15
|
+
"extract-models": "bun run scripts/extract-models.ts",
|
|
16
|
+
"build": "bun run extract-models && bun build src/index.ts --outdir dist --target node && chmod +x dist/index.js",
|
|
16
17
|
"link": "npm link",
|
|
17
18
|
"unlink": "npm unlink -g claudish",
|
|
18
19
|
"install-global": "bun run build && npm link",
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract model information from shared/recommended-models.md
|
|
5
|
+
* and generate TypeScript types for use in Claudish
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
interface ModelInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
priority: number;
|
|
15
|
+
provider: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ExtractedModels {
|
|
19
|
+
[key: string]: ModelInfo;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractModels(markdownContent: string): ExtractedModels {
|
|
23
|
+
const models: ExtractedModels = {};
|
|
24
|
+
let priority = 1;
|
|
25
|
+
|
|
26
|
+
// Extract from Quick Reference section (lines 11-30)
|
|
27
|
+
const quickRefMatch = markdownContent.match(
|
|
28
|
+
/## Quick Reference - Model IDs Only\n\n([\s\S]*?)\n---/,
|
|
29
|
+
);
|
|
30
|
+
if (!quickRefMatch) {
|
|
31
|
+
throw new Error("Could not find Quick Reference section");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const quickRef = quickRefMatch[1];
|
|
35
|
+
const lines = quickRef.split("\n");
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
// Match pattern: - `model-id` - Description (may contain commas), $price/1M, contextK/M [⭐]
|
|
39
|
+
// Use non-greedy match and look for $ to find the price section
|
|
40
|
+
const match = line.match(
|
|
41
|
+
/^- `([^`]+)` - (.+?), (\$[\d.]+\/1M), ([\dKM]+)(?: ⭐)?$/,
|
|
42
|
+
);
|
|
43
|
+
if (match) {
|
|
44
|
+
const [, modelId, description] = match;
|
|
45
|
+
|
|
46
|
+
// Determine provider from model ID
|
|
47
|
+
let provider = "Unknown";
|
|
48
|
+
if (modelId.startsWith("x-ai/")) provider = "xAI";
|
|
49
|
+
else if (modelId.startsWith("minimax/")) provider = "MiniMax";
|
|
50
|
+
else if (modelId.startsWith("z-ai/")) provider = "Zhipu AI";
|
|
51
|
+
else if (modelId.startsWith("openai/")) provider = "OpenAI";
|
|
52
|
+
else if (modelId.startsWith("google/")) provider = "Google";
|
|
53
|
+
else if (modelId.startsWith("qwen/")) provider = "Alibaba";
|
|
54
|
+
else if (modelId.startsWith("deepseek/")) provider = "DeepSeek";
|
|
55
|
+
else if (modelId.startsWith("anthropic/")) provider = "Anthropic";
|
|
56
|
+
|
|
57
|
+
// Extract short name from description
|
|
58
|
+
const name = description.trim();
|
|
59
|
+
|
|
60
|
+
models[modelId] = {
|
|
61
|
+
name,
|
|
62
|
+
description: description.trim(),
|
|
63
|
+
priority: priority++,
|
|
64
|
+
provider,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add custom option
|
|
70
|
+
models.custom = {
|
|
71
|
+
name: "Custom Model",
|
|
72
|
+
description: "Enter any OpenRouter model ID manually",
|
|
73
|
+
priority: 999,
|
|
74
|
+
provider: "Custom",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return models;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generateTypeScript(models: ExtractedModels): string {
|
|
81
|
+
const modelIds = Object.keys(models)
|
|
82
|
+
.filter((id) => id !== "custom")
|
|
83
|
+
.map((id) => ` | "${id}"`)
|
|
84
|
+
.join("\n");
|
|
85
|
+
|
|
86
|
+
const modelInfo = Object.entries(models)
|
|
87
|
+
.map(([id, info]) => {
|
|
88
|
+
return ` "${id}": {
|
|
89
|
+
name: "${info.name}",
|
|
90
|
+
description: "${info.description}",
|
|
91
|
+
priority: ${info.priority},
|
|
92
|
+
provider: "${info.provider}",
|
|
93
|
+
}`;
|
|
94
|
+
})
|
|
95
|
+
.join(",\n");
|
|
96
|
+
|
|
97
|
+
return `// AUTO-GENERATED from shared/recommended-models.md
|
|
98
|
+
// DO NOT EDIT MANUALLY - Run 'bun run extract-models' to regenerate
|
|
99
|
+
|
|
100
|
+
import type { OpenRouterModel } from "./types.js";
|
|
101
|
+
|
|
102
|
+
export const DEFAULT_MODEL: OpenRouterModel = "x-ai/grok-code-fast-1";
|
|
103
|
+
export const DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
|
|
104
|
+
|
|
105
|
+
// Model metadata for validation and display
|
|
106
|
+
export const MODEL_INFO: Record<
|
|
107
|
+
OpenRouterModel,
|
|
108
|
+
{ name: string; description: string; priority: number; provider: string }
|
|
109
|
+
> = {
|
|
110
|
+
${modelInfo},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Environment variable names
|
|
114
|
+
export const ENV = {
|
|
115
|
+
OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
|
|
116
|
+
CLAUDISH_MODEL: "CLAUDISH_MODEL",
|
|
117
|
+
CLAUDISH_PORT: "CLAUDISH_PORT",
|
|
118
|
+
CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME", // Set by claudish to show active model in status line
|
|
119
|
+
ANTHROPIC_MODEL: "ANTHROPIC_MODEL", // Claude Code standard env var for model selection
|
|
120
|
+
ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL", // Claude Code standard env var for fast model
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
// OpenRouter API Configuration
|
|
124
|
+
export const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
125
|
+
export const OPENROUTER_HEADERS = {
|
|
126
|
+
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
127
|
+
"X-Title": "Claudish - OpenRouter Proxy",
|
|
128
|
+
} as const;
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function generateTypes(models: ExtractedModels): string {
|
|
133
|
+
const modelIds = Object.keys(models)
|
|
134
|
+
.filter((id) => id !== "custom")
|
|
135
|
+
.map((id) => ` "${id}"`)
|
|
136
|
+
.join(",\n");
|
|
137
|
+
|
|
138
|
+
return `// AUTO-GENERATED from shared/recommended-models.md
|
|
139
|
+
// DO NOT EDIT MANUALLY - Run 'bun run extract-models' to regenerate
|
|
140
|
+
|
|
141
|
+
// OpenRouter Models - Top Recommended for Development (Priority Order)
|
|
142
|
+
export const OPENROUTER_MODELS = [
|
|
143
|
+
${modelIds},
|
|
144
|
+
"custom",
|
|
145
|
+
] as const;
|
|
146
|
+
|
|
147
|
+
export type OpenRouterModel = (typeof OPENROUTER_MODELS)[number];
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Main execution
|
|
152
|
+
try {
|
|
153
|
+
const sharedModelsPath = join(
|
|
154
|
+
import.meta.dir,
|
|
155
|
+
"../../../shared/recommended-models.md",
|
|
156
|
+
);
|
|
157
|
+
const configPath = join(import.meta.dir, "../src/config.ts");
|
|
158
|
+
const typesPath = join(import.meta.dir, "../src/types.ts");
|
|
159
|
+
|
|
160
|
+
console.log("📖 Reading shared/recommended-models.md...");
|
|
161
|
+
const markdownContent = readFileSync(sharedModelsPath, "utf-8");
|
|
162
|
+
|
|
163
|
+
console.log("🔍 Extracting model information...");
|
|
164
|
+
const models = extractModels(markdownContent);
|
|
165
|
+
|
|
166
|
+
console.log(`✅ Found ${Object.keys(models).length - 1} models + custom option`);
|
|
167
|
+
|
|
168
|
+
console.log("📝 Generating config.ts...");
|
|
169
|
+
const configCode = generateTypeScript(models);
|
|
170
|
+
writeFileSync(configPath, configCode);
|
|
171
|
+
|
|
172
|
+
console.log("📝 Generating types.ts...");
|
|
173
|
+
const typesCode = generateTypes(models);
|
|
174
|
+
const existingTypes = readFileSync(typesPath, "utf-8");
|
|
175
|
+
|
|
176
|
+
// Replace OPENROUTER_MODELS array and OpenRouterModel type, keep other types
|
|
177
|
+
// Handle both auto-generated and manual versions
|
|
178
|
+
let updatedTypes = existingTypes;
|
|
179
|
+
|
|
180
|
+
// Try to replace auto-generated section first
|
|
181
|
+
if (existingTypes.includes("// AUTO-GENERATED")) {
|
|
182
|
+
updatedTypes = existingTypes.replace(
|
|
183
|
+
/\/\/ AUTO-GENERATED[\s\S]*?export type OpenRouterModel = \(typeof OPENROUTER_MODELS\)\[number\];/,
|
|
184
|
+
typesCode.trim(),
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
// First time - replace manual OPENROUTER_MODELS section
|
|
188
|
+
updatedTypes = existingTypes.replace(
|
|
189
|
+
/\/\/ OpenRouter Models[\s\S]*?export type OpenRouterModel = \(typeof OPENROUTER_MODELS\)\[number\];/,
|
|
190
|
+
typesCode.trim(),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
writeFileSync(typesPath, updatedTypes);
|
|
195
|
+
|
|
196
|
+
console.log("✅ Successfully generated TypeScript files");
|
|
197
|
+
console.log("");
|
|
198
|
+
console.log("Models:");
|
|
199
|
+
for (const [id, info] of Object.entries(models)) {
|
|
200
|
+
if (id !== "custom") {
|
|
201
|
+
console.log(` • ${id} - ${info.name} (${info.provider})`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error("❌ Error:", error);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|