220 lines
6.9 KiB
TypeScript
220 lines
6.9 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
/**
|
|
* Extract model information from shared/recommended-models.md
|
|
* and generate TypeScript types for use in Claudish
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
interface ModelInfo {
|
|
name: string;
|
|
description: string;
|
|
priority: number;
|
|
provider: string;
|
|
}
|
|
|
|
interface ExtractedModels {
|
|
[key: string]: ModelInfo;
|
|
}
|
|
|
|
function extractModels(markdownContent: string): ExtractedModels {
|
|
const models: ExtractedModels = {};
|
|
let priority = 1;
|
|
|
|
// Extract from Quick Reference section (lines 11-30)
|
|
const quickRefMatch = markdownContent.match(
|
|
/## Quick Reference - Model IDs Only\n\n([\s\S]*?)\n---/,
|
|
);
|
|
if (!quickRefMatch) {
|
|
throw new Error("Could not find Quick Reference section");
|
|
}
|
|
|
|
const quickRef = quickRefMatch[1];
|
|
const lines = quickRef.split("\n");
|
|
|
|
for (const line of lines) {
|
|
// Match pattern: - `model-id` - Description (may contain commas), $price/1M or FREE, contextK/M [⭐]
|
|
// Use non-greedy match and look for $ or FREE to find the price section
|
|
const match = line.match(
|
|
/^- `([^`]+)` - (.+?), (?:\$[\d.]+\/1M|FREE), ([\dKM]+)(?: ⭐)?$/,
|
|
);
|
|
if (match) {
|
|
const [, modelId, description] = match;
|
|
|
|
// Determine provider from model ID
|
|
let provider = "Unknown";
|
|
if (modelId.startsWith("x-ai/")) provider = "xAI";
|
|
else if (modelId.startsWith("minimax/")) provider = "MiniMax";
|
|
else if (modelId.startsWith("z-ai/")) provider = "Zhipu AI";
|
|
else if (modelId.startsWith("openai/")) provider = "OpenAI";
|
|
else if (modelId.startsWith("google/")) provider = "Google";
|
|
else if (modelId.startsWith("qwen/")) provider = "Alibaba";
|
|
else if (modelId.startsWith("deepseek/")) provider = "DeepSeek";
|
|
else if (modelId.startsWith("tngtech/")) provider = "TNG Tech";
|
|
else if (modelId.startsWith("openrouter/")) provider = "OpenRouter";
|
|
else if (modelId.startsWith("anthropic/")) provider = "Anthropic";
|
|
|
|
// Extract short name from description
|
|
const name = description.trim();
|
|
|
|
models[modelId] = {
|
|
name,
|
|
description: description.trim(),
|
|
priority: priority++,
|
|
provider,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Add custom option
|
|
models.custom = {
|
|
name: "Custom Model",
|
|
description: "Enter any OpenRouter model ID manually",
|
|
priority: 999,
|
|
provider: "Custom",
|
|
};
|
|
|
|
return models;
|
|
}
|
|
|
|
function generateTypeScript(models: ExtractedModels): string {
|
|
const modelIds = Object.keys(models)
|
|
.filter((id) => id !== "custom")
|
|
.map((id) => ` | "${id}"`)
|
|
.join("\n");
|
|
|
|
const modelInfo = Object.entries(models)
|
|
.map(([id, info]) => {
|
|
return ` "${id}": {
|
|
name: "${info.name}",
|
|
description: "${info.description}",
|
|
priority: ${info.priority},
|
|
provider: "${info.provider}",
|
|
}`;
|
|
})
|
|
.join(",\n");
|
|
|
|
return `// AUTO-GENERATED from shared/recommended-models.md
|
|
// DO NOT EDIT MANUALLY - Run 'bun run extract-models' to regenerate
|
|
|
|
import type { OpenRouterModel } from "./types.js";
|
|
|
|
export const DEFAULT_MODEL: OpenRouterModel = "x-ai/grok-code-fast-1";
|
|
export const DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
|
|
|
|
// Model metadata for validation and display
|
|
export const MODEL_INFO: Record<
|
|
OpenRouterModel,
|
|
{ name: string; description: string; priority: number; provider: string }
|
|
> = {
|
|
${modelInfo},
|
|
};
|
|
|
|
// Environment variable names
|
|
export const ENV = {
|
|
OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
|
|
CLAUDISH_MODEL: "CLAUDISH_MODEL",
|
|
CLAUDISH_PORT: "CLAUDISH_PORT",
|
|
CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME", // Set by claudish to show active model in status line
|
|
ANTHROPIC_MODEL: "ANTHROPIC_MODEL", // Claude Code standard env var for model selection
|
|
ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL", // Claude Code standard env var for fast model
|
|
// Claudish model mapping overrides (highest priority)
|
|
CLAUDISH_MODEL_OPUS: "CLAUDISH_MODEL_OPUS",
|
|
CLAUDISH_MODEL_SONNET: "CLAUDISH_MODEL_SONNET",
|
|
CLAUDISH_MODEL_HAIKU: "CLAUDISH_MODEL_HAIKU",
|
|
CLAUDISH_MODEL_SUBAGENT: "CLAUDISH_MODEL_SUBAGENT",
|
|
// Claude Code standard model configuration (fallback if CLAUDISH_* not set)
|
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
ANTHROPIC_DEFAULT_SONNET_MODEL: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
|
|
} as const;
|
|
|
|
// OpenRouter API Configuration
|
|
export const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
export const OPENROUTER_HEADERS = {
|
|
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
"X-Title": "Claudish - OpenRouter Proxy",
|
|
} as const;
|
|
`;
|
|
}
|
|
|
|
function generateTypes(models: ExtractedModels): string {
|
|
const modelIds = Object.keys(models)
|
|
.filter((id) => id !== "custom")
|
|
.map((id) => ` "${id}"`)
|
|
.join(",\n");
|
|
|
|
return `// AUTO-GENERATED from shared/recommended-models.md
|
|
// DO NOT EDIT MANUALLY - Run 'bun run extract-models' to regenerate
|
|
|
|
// OpenRouter Models - Top Recommended for Development (Priority Order)
|
|
export const OPENROUTER_MODELS = [
|
|
${modelIds},
|
|
"custom",
|
|
] as const;
|
|
|
|
export type OpenRouterModel = (typeof OPENROUTER_MODELS)[number];
|
|
`;
|
|
}
|
|
|
|
// Main execution
|
|
try {
|
|
const sharedModelsPath = join(
|
|
import.meta.dir,
|
|
"../../../shared/recommended-models.md",
|
|
);
|
|
const configPath = join(import.meta.dir, "../src/config.ts");
|
|
const typesPath = join(import.meta.dir, "../src/types.ts");
|
|
|
|
console.log("📖 Reading shared/recommended-models.md...");
|
|
const markdownContent = readFileSync(sharedModelsPath, "utf-8");
|
|
|
|
console.log("🔍 Extracting model information...");
|
|
const models = extractModels(markdownContent);
|
|
|
|
console.log(`✅ Found ${Object.keys(models).length - 1} models + custom option`);
|
|
|
|
console.log("📝 Generating config.ts...");
|
|
const configCode = generateTypeScript(models);
|
|
writeFileSync(configPath, configCode);
|
|
|
|
console.log("📝 Generating types.ts...");
|
|
const typesCode = generateTypes(models);
|
|
const existingTypes = readFileSync(typesPath, "utf-8");
|
|
|
|
// Replace OPENROUTER_MODELS array and OpenRouterModel type, keep other types
|
|
// Handle both auto-generated and manual versions
|
|
let updatedTypes = existingTypes;
|
|
|
|
// Try to replace auto-generated section first
|
|
if (existingTypes.includes("// AUTO-GENERATED")) {
|
|
updatedTypes = existingTypes.replace(
|
|
/\/\/ AUTO-GENERATED[\s\S]*?export type OpenRouterModel = \(typeof OPENROUTER_MODELS\)\[number\];/,
|
|
typesCode.trim(),
|
|
);
|
|
} else {
|
|
// First time - replace manual OPENROUTER_MODELS section
|
|
updatedTypes = existingTypes.replace(
|
|
/\/\/ OpenRouter Models[\s\S]*?export type OpenRouterModel = \(typeof OPENROUTER_MODELS\)\[number\];/,
|
|
typesCode.trim(),
|
|
);
|
|
}
|
|
|
|
writeFileSync(typesPath, updatedTypes);
|
|
|
|
console.log("✅ Successfully generated TypeScript files");
|
|
console.log("");
|
|
console.log("Models:");
|
|
for (const [id, info] of Object.entries(models)) {
|
|
if (id !== "custom") {
|
|
console.log(` • ${id} - ${info.name} (${info.provider})`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ Error:", error);
|
|
process.exit(1);
|
|
}
|