move llms to core package

This commit is contained in:
musistudio
2025-12-28 22:41:56 +08:00
parent bd55450b1d
commit 60a1f94878
24 changed files with 742 additions and 220 deletions

View File

@@ -8,7 +8,7 @@ Claude Code Router is a tool that routes Claude Code requests to different LLM p
- **cli** (`@musistudio/claude-code-router-cli`): Command-line tool providing the `ccr` command - **cli** (`@musistudio/claude-code-router-cli`): Command-line tool providing the `ccr` command
- **server** (`@musistudio/claude-code-router-server`): Core server handling API routing and transformations - **server** (`@musistudio/claude-code-router-server`): Core server handling API routing and transformations
- **shared** (`@musistudio/claude-code-router-shared`): Shared constants and utilities - **shared** (`@musistudio/claude-code-router-shared`): Shared constants, utilities, and preset management
- **ui** (`@musistudio/claude-code-router-ui`): Web management interface (React + Vite) - **ui** (`@musistudio/claude-code-router-ui`): Web management interface (React + Vite)
## Build Commands ## Build Commands
@@ -127,11 +127,22 @@ ccr restart # Restart server
ccr status # Show status ccr status # Show status
ccr code # Execute claude command ccr code # Execute claude command
ccr model # Interactive model selection and configuration ccr model # Interactive model selection and configuration
ccr preset # Manage presets (export, install, list, info, delete)
ccr activate # Output shell environment variables (for integration) ccr activate # Output shell environment variables (for integration)
ccr ui # Open Web UI ccr ui # Open Web UI
ccr statusline # Integrated statusline (reads JSON from stdin) ccr statusline # Integrated statusline (reads JSON from stdin)
``` ```
### Preset Commands
```bash
ccr preset export <name> # Export current configuration as a preset
ccr preset install <source> # Install a preset from file, URL, or name
ccr preset list # List all installed presets
ccr preset info <name> # Show preset information
ccr preset delete <name> # Delete a preset
```
## Subagent Routing ## Subagent Routing
Use special tags in subagent prompts to specify models: Use special tags in subagent prompts to specify models:
@@ -140,6 +151,83 @@ Use special tags in subagent prompts to specify models:
Please help me analyze this code... Please help me analyze this code...
``` ```
## Preset System
The preset system allows users to save, share, and reuse configurations easily.
### Preset Structure
Presets are stored in `~/.claude-code-router/presets/<preset-name>/manifest.json`
Each preset contains:
- **Metadata**: name, version, description, author, keywords, etc.
- **Configuration**: Providers, Router, transformers, and other settings
- **Dynamic Schema** (optional): Input fields for collecting required information during installation
- **Required Inputs** (optional): Fields that need to be filled during installation (e.g., API keys)
### Core Functions
Located in `packages/shared/src/preset/`:
- **export.ts**: Export current configuration as a preset (.ccrsets file)
- `exportPreset(presetName, config, options)`: Creates ZIP archive with manifest.json
- Automatically sanitizes sensitive data (api_key fields become `{{field}}` placeholders)
- **install.ts**: Install and manage presets
- `installPreset(preset, config, options)`: Install preset to config
- `loadPreset(source)`: Load preset from file, URL, or directory
- `listPresets()`: List all installed presets
- `isPresetInstalled(presetName)`: Check if preset is installed
- `validatePreset(preset)`: Validate preset structure
- **merge.ts**: Merge preset configuration with existing config
- Handles conflicts using different strategies (ask, overwrite, merge, skip)
- **sensitiveFields.ts**: Identify and sanitize sensitive fields
- Detects api_key, password, secret fields automatically
- Creates `requiredInputs` array for installation prompts
### Preset File Format
**manifest.json** (in ZIP archive or extracted directory):
```json
{
"name": "my-preset",
"version": "1.0.0",
"description": "My configuration",
"author": "Author Name",
"keywords": ["openai", "production"],
"Providers": [...],
"Router": {...},
"schema": [
{
"id": "apiKey",
"type": "password",
"label": "OpenAI API Key",
"prompt": "Enter your OpenAI API key"
}
],
"requiredInputs": [
{
"field": "Providers[0].api_key",
"placeholder": "Enter your API key"
}
]
}
```
### CLI Integration
The CLI layer (`packages/cli/src/utils/preset/`) handles:
- User interaction and prompts
- File operations
- Display formatting
Key files:
- `commands.ts`: Command handlers for `ccr preset` subcommands
- `export.ts`: CLI wrapper for export functionality
- `install.ts`: CLI wrapper for install functionality
## Dependencies ## Dependencies
``` ```

View File

@@ -253,7 +253,46 @@ This command provides an interactive interface to:
The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually. The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually.
### 6. Activate Command (Environment Variables Setup) ### 6. Presets Management
Presets allow you to save, share, and reuse configurations easily. You can export your current configuration as a preset and install presets from files or URLs.
```shell
# Export current configuration as a preset
ccr preset export my-preset
# Export with metadata
ccr preset export my-preset --description "My OpenAI config" --author "Your Name" --tags "openai,production"
# Install a preset from file, URL, or registry
ccr preset install my-preset.ccrsets
ccr preset install https://example.com/preset.ccrsets
# List all installed presets
ccr preset list
# Show preset information
ccr preset info my-preset
# Delete a preset
ccr preset delete my-preset
```
**Preset Features:**
- **Export**: Save your current configuration as a `.ccrsets` file (ZIP archive with manifest.json)
- **Install**: Install presets from local files, URLs, or the preset registry
- **Sensitive Data Handling**: API keys and other sensitive data are automatically sanitized during export (marked as `{{field}}` placeholders)
- **Dynamic Configuration**: Presets can include input schemas for collecting required information during installation
- **Version Control**: Each preset includes version metadata for tracking updates
**Preset File Structure:**
```
~/.claude-code-router/presets/
├── my-preset/
│ └── manifest.json # Contains configuration and metadata
```
### 7. Activate Command (Environment Variables Setup)
The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK. The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK.

View File

@@ -203,7 +203,71 @@ ccr ui
![UI](/blog/images/ui.png) ![UI](/blog/images/ui.png)
### 5. Activate 命令(环境变量设置) ### 5. CLI 模型管理
对于偏好终端工作流的用户,可以使用交互式 CLI 模型选择器:
```shell
ccr model
```
该命令提供交互式界面来:
- 查看当前配置
- 查看所有配置的模型default、background、think、longContext、webSearch、image
- 切换模型:快速更改每个路由器类型使用的模型
- 添加新模型:向现有提供商添加模型
- 创建新提供商:设置完整的提供商配置,包括:
- 提供商名称和 API 端点
- API 密钥
- 可用模型
- Transformer 配置,支持:
- 多个转换器openrouter、deepseek、gemini 等)
- Transformer 选项(例如,带自定义限制的 maxtoken
- 特定于提供商的路由例如OpenRouter 提供商偏好)
CLI 工具验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂的设置变得容易,无需手动编辑 JSON 文件。
### 6. 预设管理
预设允许您轻松保存、共享和重用配置。您可以将当前配置导出为预设,并从文件或 URL 安装预设。
```shell
# 将当前配置导出为预设
ccr preset export my-preset
# 使用元数据导出
ccr preset export my-preset --description "我的 OpenAI 配置" --author "您的名字" --tags "openai,生产环境"
# 从文件、URL 或注册表安装预设
ccr preset install my-preset.ccrsets
ccr preset install https://example.com/preset.ccrsets
# 列出所有已安装的预设
ccr preset list
# 显示预设信息
ccr preset info my-preset
# 删除预设
ccr preset delete my-preset
```
**预设功能:**
- **导出**:将当前配置保存为 `.ccrsets` 文件(包含 manifest.json 的 ZIP 存档)
- **安装**从本地文件、URL 或预设注册表安装预设
- **敏感数据处理**:导出期间自动清理 API 密钥和其他敏感数据(标记为 `{{field}}` 占位符)
- **动态配置**:预设可以包含输入架构,用于在安装期间收集所需信息
- **版本控制**:每个预设包含版本元数据,用于跟踪更新
**预设文件结构:**
```
~/.claude-code-router/presets/
├── my-preset/
│ └── manifest.json # 包含配置和元数据
```
### 7. Activate 命令(环境变量设置)
`activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。 `activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。

View File

@@ -5,6 +5,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "node scripts/build.js", "build": "node scripts/build.js",
"build:core": "node scripts/build-core.js",
"build:shared": "node scripts/build-shared.js",
"build:cli": "pnpm --filter @CCR/cli build", "build:cli": "pnpm --filter @CCR/cli build",
"build:server": "pnpm --filter @CCR/server build", "build:server": "pnpm --filter @CCR/server build",
"build:ui": "pnpm --filter @CCR/ui build", "build:ui": "pnpm --filter @CCR/ui build",

View File

@@ -12,10 +12,12 @@ import { activateCommand } from "./utils/activateCommand";
import { readConfigFile } from "./utils"; import { readConfigFile } from "./utils";
import { version } from "../package.json"; import { version } from "../package.json";
import { spawn, exec } from "child_process"; import { spawn, exec } from "child_process";
import { PID_FILE, REFERENCE_COUNT_FILE } from "@CCR/shared"; import {PID_FILE, readPresetFile, REFERENCE_COUNT_FILE} from "@CCR/shared";
import fs, { existsSync, readFileSync } from "fs"; import fs, { existsSync, readFileSync } from "fs";
import { join } from "path"; import { join } from "path";
import { parseStatusLineData, StatusLineInput } from "./utils/statusline"; import { parseStatusLineData, StatusLineInput } from "./utils/statusline";
import {handlePresetCommand} from "./utils/preset";
const command = process.argv[2]; const command = process.argv[2];
@@ -95,7 +97,6 @@ async function main() {
// 如果命令不是已知命令,检查是否是 preset // 如果命令不是已知命令,检查是否是 preset
if (command && !KNOWN_COMMANDS.includes(command)) { if (command && !KNOWN_COMMANDS.includes(command)) {
const { readPresetFile } = await import("./utils");
const presetData: any = await readPresetFile(command); const presetData: any = await readPresetFile(command);
if (presetData) { if (presetData) {
@@ -248,7 +249,6 @@ async function main() {
await runModelSelector(); await runModelSelector();
break; break;
case "preset": case "preset":
const { handlePresetCommand } = await import("./utils/preset");
await handlePresetCommand(process.argv.slice(3)); await handlePresetCommand(process.argv.slice(3));
break; break;
case "activate": case "activate":

View File

@@ -105,20 +105,11 @@ export async function applyPresetCli(
console.log(`${BOLDCYAN}Validating preset...${RESET} ${GREEN}${RESET}`); console.log(`${BOLDCYAN}Validating preset...${RESET} ${GREEN}${RESET}`);
// 检查是否已经配置过通过检查manifest中是否已有敏感信息 // 检查是否需要配置
const presetDir = getPresetDir(presetName); if (preset.schema && preset.schema.length > 0) {
console.log(`\n${BOLDCYAN}Configuration required:${RESET} ${preset.schema.length} field(s)\n`);
try { } else {
const existingManifest = await readManifestFromDir(presetDir); console.log(`\n${DIM}No configuration required for this preset${RESET}\n`);
// 检查是否已经配置了敏感信息例如api_key
const hasSecrets = existingManifest.Providers?.some((p: any) => p.api_key && p.api_key !== '');
if (hasSecrets) {
console.log(`\n${GREEN}${RESET} Preset already configured`);
console.log(`${DIM}You can use this preset with: ccr ${presetName}${RESET}\n`);
return;
}
} catch {
// manifest不存在继续配置流程
} }
// 收集用户输入 // 收集用户输入

View File

@@ -14,8 +14,13 @@ import {
getDefaultValue, getDefaultValue,
sortFieldsByDependencies, sortFieldsByDependencies,
getAffectedFields, getAffectedFields,
} from '@musistudio/claude-code-router-shared'; } from '@CCR/shared';
import { input, confirm, select, password } from '@inquirer/prompts'; import input from '@inquirer/input';
import confirm from '@inquirer/confirm';
import select from '@inquirer/select';
import password from '@inquirer/password';
import checkbox from '@inquirer/checkbox';
import editor from '@inquirer/editor';
// ANSI 颜色代码 // ANSI 颜色代码
export const COLORS = { export const COLORS = {
@@ -183,7 +188,6 @@ async function promptField(
} }
// @inquirer/prompts 没有多选,使用 checkbox // @inquirer/prompts 没有多选,使用 checkbox
const { checkbox } = await import('@inquirer/prompts');
return await checkbox({ return await checkbox({
message, message,
choices: options.map(opt => ({ choices: options.map(opt => ({
@@ -195,7 +199,6 @@ async function promptField(
} }
case InputType.EDITOR: { case InputType.EDITOR: {
const { editor } = await import('@inquirer/prompts');
return await editor({ return await editor({
message, message,
default: field.defaultValue, default: field.defaultValue,

View File

@@ -19,7 +19,7 @@
"dependencies": { "dependencies": {
"@fastify/multipart": "^9.0.0", "@fastify/multipart": "^9.0.0",
"@fastify/static": "^8.2.0", "@fastify/static": "^8.2.0",
"@musistudio/llms": "^1.0.51", "@musistudio/llms": "workspace:*",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"json5": "^2.2.3", "json5": "^2.2.3",

View File

@@ -1,4 +1,4 @@
import { existsSync, writeFileSync, unlinkSync } from "fs"; import { existsSync } from "fs";
import { writeFile } from "fs/promises"; import { writeFile } from "fs/promises";
import { homedir } from "os"; import { homedir } from "os";
import { join } from "path"; import { join } from "path";
@@ -6,7 +6,7 @@ import { initConfig, initDir } from "./utils";
import { createServer } from "./server"; import { createServer } from "./server";
import { router } from "./utils/router"; import { router } from "./utils/router";
import { apiKeyAuth } from "./middleware/auth"; import { apiKeyAuth } from "./middleware/auth";
import { CONFIG_FILE, HOME_DIR } from "@CCR/shared"; import {CONFIG_FILE, HOME_DIR, listPresets} from "@CCR/shared";
import { createStream } from 'rotating-file-stream'; import { createStream } from 'rotating-file-stream';
import { sessionUsageCache } from "./utils/cache"; import { sessionUsageCache } from "./utils/cache";
import {SSEParserTransform} from "./utils/SSEParser.transform"; import {SSEParserTransform} from "./utils/SSEParser.transform";
@@ -16,7 +16,6 @@ import JSON5 from "json5";
import { IAgent, ITool } from "./agents/type"; import { IAgent, ITool } from "./agents/type";
import agentsManager from "./agents"; import agentsManager from "./agents";
import { EventEmitter } from "node:events"; import { EventEmitter } from "node:events";
import {spawn} from "child_process";
const event = new EventEmitter() const event = new EventEmitter()
@@ -121,6 +120,8 @@ async function getServer(options: RunOptions = {}) {
} }
} }
const presets = await listPresets();
const serverInstance = await createServer({ const serverInstance = await createServer({
jsonPath: CONFIG_FILE, jsonPath: CONFIG_FILE,
initialConfig: { initialConfig: {
@@ -137,6 +138,11 @@ async function getServer(options: RunOptions = {}) {
logger: loggerConfig, logger: loggerConfig,
}); });
presets.forEach(preset => {
console.log(preset.name, preset.config);
serverInstance.registerNamespace(preset.name, preset.config);
})
// Add async preHandler hook for authentication // Add async preHandler hook for authentication
serverInstance.addHook("preHandler", async (req: any, reply: any) => { serverInstance.addHook("preHandler", async (req: any, reply: any) => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {

View File

@@ -10,8 +10,6 @@ import {
readManifestFromDir, readManifestFromDir,
manifestToPresetFile, manifestToPresetFile,
extractPreset, extractPreset,
validatePreset,
loadPreset,
saveManifest, saveManifest,
isPresetInstalled, isPresetInstalled,
downloadPresetToTemp, downloadPresetToTemp,
@@ -20,16 +18,15 @@ import {
type PresetFile, type PresetFile,
type ManifestFile, type ManifestFile,
type PresetMetadata, type PresetMetadata,
MergeStrategy
} from "@CCR/shared"; } from "@CCR/shared";
import fastifyMultipart from "@fastify/multipart";
import AdmZip from "adm-zip";
export const createServer = async (config: any): Promise<any> => { export const createServer = async (config: any): Promise<any> => {
const server = new Server(config); const server = new Server(config);
const app = server.app; const app = server.app;
// Register multipart plugin for file uploads (dynamic import) app.register(fastifyMultipart, {
const fastifyMultipart = await import('@fastify/multipart');
app.register(fastifyMultipart.default, {
limits: { limits: {
fileSize: 50 * 1024 * 1024, // 50MB fileSize: 50 * 1024 * 1024, // 50MB
}, },
@@ -171,8 +168,6 @@ export const createServer = async (config: any): Promise<any> => {
} }
}); });
// ========== Preset 相关 API ==========
// 获取预设列表 // 获取预设列表
app.get("/api/presets", async (req: any, reply: any) => { app.get("/api/presets", async (req: any, reply: any) => {
try { try {
@@ -435,8 +430,13 @@ export const createServer = async (config: any): Promise<any> => {
} }
// 解析 GitHub 仓库 URL // 解析 GitHub 仓库 URL
// 支持格式: https://github.com/owner/repo 或 https://github.com/owner/repo.git // 支持格式:
const githubRepoMatch = repo.match(/github\.com[:/]([^/]+)\/([^/.]+)/); // - owner/repo (简短格式,来自市场)
// - github.com/owner/repo
// - https://github.com/owner/repo
// - https://github.com/owner/repo.git
// - git@github.com:owner/repo.git
const githubRepoMatch = repo.match(/(?:github\.com[:/]|^)([^/]+)\/([^/\s#]+?)(?:\.git)?$/);
if (!githubRepoMatch) { if (!githubRepoMatch) {
reply.status(400).send({ error: "Invalid GitHub repository URL" }); reply.status(400).send({ error: "Invalid GitHub repository URL" });
return; return;
@@ -484,7 +484,6 @@ export const createServer = async (config: any): Promise<any> => {
// 辅助函数:从 ZIP 加载预设 // 辅助函数:从 ZIP 加载预设
async function loadPresetFromZip(zipFile: string): Promise<PresetFile> { async function loadPresetFromZip(zipFile: string): Promise<PresetFile> {
const AdmZip = (await import('adm-zip')).default;
const zip = new AdmZip(zipFile); const zip = new AdmZip(zipFile);
// 首先尝试在根目录查找 manifest.json // 首先尝试在根目录查找 manifest.json

View File

@@ -8,3 +8,4 @@ export * from './preset/install';
export * from './preset/export'; export * from './preset/export';
export * from './preset/readPreset'; export * from './preset/readPreset';
export * from './preset/schema'; export * from './preset/schema';

View File

@@ -4,12 +4,11 @@
*/ */
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as fsSync from 'fs';
import * as path from 'path'; import * as path from 'path';
import JSON5 from 'json5'; import JSON5 from 'json5';
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
import { PresetFile, MergeStrategy, RequiredInput, ManifestFile } from './types'; import { PresetFile, MergeStrategy, RequiredInput, ManifestFile, PresetInfo } from './types';
import { HOME_DIR } from '../constants'; import { HOME_DIR, PRESETS_DIR } from '../constants';
/** /**
* 获取预设目录的完整路径 * 获取预设目录的完整路径
@@ -48,6 +47,58 @@ export async function extractPreset(sourceZip: string, targetDir: string): Promi
// 解压文件 // 解压文件
const zip = new AdmZip(sourceZip); const zip = new AdmZip(sourceZip);
const entries = zip.getEntries();
// 检测是否有单一的根目录GitHub ZIP 文件通常有这个特征)
if (entries.length > 0) {
// 获取所有顶层目录
const rootDirs = new Set<string>();
for (const entry of entries) {
const parts = entry.entryName.split('/');
if (parts.length > 1) {
rootDirs.add(parts[0]);
}
}
// 如果只有一个根目录,则去除它
if (rootDirs.size === 1) {
const singleRoot = Array.from(rootDirs)[0];
// 检查 manifest.json 是否在根目录下
const hasManifestInRoot = entries.some(e =>
e.entryName === 'manifest.json' || e.entryName.startsWith(`${singleRoot}/manifest.json`)
);
if (hasManifestInRoot) {
// 将所有文件从根目录下提取出来
for (const entry of entries) {
if (entry.isDirectory) {
continue;
}
// 去除根目录前缀
let newPath = entry.entryName;
if (newPath.startsWith(`${singleRoot}/`)) {
newPath = newPath.substring(singleRoot.length + 1);
}
// 跳过根目录本身
if (newPath === '' || newPath === singleRoot) {
continue;
}
// 提取文件
const targetPath = path.join(targetDir, newPath);
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.writeFile(targetPath, entry.getData());
}
return;
}
}
}
// 如果没有单一的根目录,直接解压
zip.extractAllTo(targetDir, true); zip.extractAllTo(targetDir, true);
} }
@@ -65,11 +116,11 @@ export async function readManifestFromDir(presetDir: string): Promise<ManifestFi
* 将manifest转换为PresetFile格式 * 将manifest转换为PresetFile格式
*/ */
export function manifestToPresetFile(manifest: ManifestFile): PresetFile { export function manifestToPresetFile(manifest: ManifestFile): PresetFile {
const { Providers, Router, PORT, HOST, API_TIMEOUT_MS, PROXY_URL, LOG, LOG_LEVEL, StatusLine, NON_INTERACTIVE_MODE, requiredInputs, ...metadata } = manifest; const { Providers, Router, StatusLine, NON_INTERACTIVE_MODE, schema, ...metadata } = manifest;
return { return {
metadata, metadata,
config: { Providers, Router, PORT, HOST, API_TIMEOUT_MS, PROXY_URL, LOG, LOG_LEVEL, StatusLine, NON_INTERACTIVE_MODE }, config: { Providers, Router, StatusLine, NON_INTERACTIVE_MODE },
requiredInputs, schema,
}; };
} }
@@ -237,3 +288,55 @@ export async function isPresetInstalled(presetName: string): Promise<boolean> {
return false; return false;
} }
} }
/**
* 列出所有已安装的预设
* @returns PresetInfo 数组
*/
export async function listPresets(): Promise<PresetInfo[]> {
const presetsDir = PRESETS_DIR;
const presets: PresetInfo[] = [];
try {
await fs.access(presetsDir);
} catch {
return presets;
}
// 读取目录下的所有子目录
const entries = await fs.readdir(presetsDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const presetName = entry.name;
const presetDir = path.join(presetsDir, presetName);
const manifestPath = path.join(presetDir, 'manifest.json');
try {
// 检查 manifest.json 是否存在
await fs.access(manifestPath);
// 读取 manifest.json
const content = await fs.readFile(manifestPath, 'utf-8');
const manifest = JSON5.parse(content) as ManifestFile;
// 获取目录创建时间
const stats = await fs.stat(presetDir);
presets.push({
name: manifest.name || presetName,
version: manifest.version,
description: manifest.description,
author: manifest.author,
config: manifestToPresetFile(manifest).config,
});
} catch {
// 忽略无效的预设目录(没有 manifest.json 或读取失败)
// 可以选择跳过或者添加到列表中标记为错误
continue;
}
}
}
return presets;
}

View File

@@ -2,72 +2,24 @@
* 配置合并策略 * 配置合并策略
*/ */
import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig, ProviderConflictAction } from './types'; import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig } from './types';
/** /**
* 合并 Provider 配置 * 合并 Provider 配置
* 如果 provider 已存在则直接覆盖,否则添加
*/ */
async function mergeProviders( function mergeProviders(
existing: ProviderConfig[], existing: ProviderConfig[],
incoming: ProviderConfig[], incoming: ProviderConfig[]
strategy: MergeStrategy, ): ProviderConfig[] {
onProviderConflict?: (providerName: string) => Promise<ProviderConflictAction>
): Promise<ProviderConfig[]> {
const result = [...existing]; const result = [...existing];
const existingNames = new Set(existing.map(p => p.name)); const existingNames = new Map(existing.map(p => [p.name, result.findIndex(x => x.name === p.name)]));
for (const provider of incoming) { for (const provider of incoming) {
if (existingNames.has(provider.name)) { const existingIndex = existingNames.get(provider.name);
// Provider 已存在,需要处理冲突 if (existingIndex !== undefined) {
let action: ProviderConflictAction; // Provider 已存在,直接覆盖
result[existingIndex] = provider;
if (strategy === MergeStrategy.ASK && onProviderConflict) {
action = await onProviderConflict(provider.name);
} else if (strategy === MergeStrategy.OVERWRITE) {
action = 'overwrite';
} else if (strategy === MergeStrategy.MERGE) {
action = 'merge';
} else {
action = 'skip';
}
switch (action) {
case 'keep':
// 保留现有,不做任何操作
break;
case 'overwrite':
const index = result.findIndex(p => p.name === provider.name);
result[index] = provider;
break;
case 'merge':
const existingProvider = result.find(p => p.name === provider.name)!;
// 合并模型列表,去重
const mergedModels = [...new Set([
...existingProvider.models,
...provider.models,
])];
existingProvider.models = mergedModels;
// 合并 transformer 配置
if (provider.transformer) {
if (!existingProvider.transformer) {
existingProvider.transformer = provider.transformer;
} else {
// 合并 transformer.use
if (provider.transformer.use && existingProvider.transformer.use) {
const mergedTransformers = [...new Set([
...existingProvider.transformer.use,
...provider.transformer.use,
])];
existingProvider.transformer.use = mergedTransformers as any;
}
}
}
break;
case 'skip':
// 跳过,不做任何操作
break;
}
} else { } else {
// 新 Provider直接添加 // 新 Provider直接添加
result.push(provider); result.push(provider);
@@ -216,7 +168,6 @@ async function mergeOtherConfig(
* 合并交互回调接口 * 合并交互回调接口
*/ */
export interface MergeCallbacks { export interface MergeCallbacks {
onProviderConflict?: (providerName: string) => Promise<ProviderConflictAction>;
onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>; onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>;
onTransformerConflict?: (transformerPath: string) => Promise<'keep' | 'overwrite' | 'skip'>; onTransformerConflict?: (transformerPath: string) => Promise<'keep' | 'overwrite' | 'skip'>;
onConfigConflict?: (key: string) => Promise<boolean>; onConfigConflict?: (key: string) => Promise<boolean>;
@@ -240,11 +191,9 @@ export async function mergeConfig(
// 合并 Providers // 合并 Providers
if (presetConfig.Providers) { if (presetConfig.Providers) {
result.Providers = await mergeProviders( result.Providers = mergeProviders(
result.Providers || [], result.Providers || [],
presetConfig.Providers, presetConfig.Providers
strategy,
callbacks?.onProviderConflict
); );
} }

View File

@@ -128,9 +128,9 @@ function sanitizeObject(
sanitizedObj[key] = value; sanitizedObj[key] = value;
// 仍然需要记录为必需输入,但使用已有环境变量 // 仍然需要记录为必需输入,但使用已有环境变量
const envVarName = extractEnvVarName(value); const envVarName = extractEnvVarName(value);
if (envVarName && !requiredInputs.some(input => input.field === currentPath)) { if (envVarName && !requiredInputs.some(input => input.id === currentPath)) {
requiredInputs.push({ requiredInputs.push({
field: currentPath, id: currentPath,
prompt: `Enter ${key}`, prompt: `Enter ${key}`,
placeholder: envVarName, placeholder: envVarName,
}); });
@@ -163,7 +163,7 @@ function sanitizeObject(
// 记录为必需输入 // 记录为必需输入
requiredInputs.push({ requiredInputs.push({
field: currentPath, id: currentPath,
prompt: `Enter ${key}`, prompt: `Enter ${key}`,
placeholder: envVarName, placeholder: envVarName,
}); });

View File

@@ -140,12 +140,6 @@ export interface PresetConfigSection {
Providers?: ProviderConfig[]; Providers?: ProviderConfig[];
Router?: RouterConfig; Router?: RouterConfig;
transformers?: TransformerConfig[]; transformers?: TransformerConfig[];
PORT?: number;
HOST?: string;
API_TIMEOUT_MS?: number;
PROXY_URL?: string;
LOG?: boolean;
LOG_LEVEL?: string;
StatusLine?: any; StatusLine?: any;
NON_INTERACTIVE_MODE?: boolean; NON_INTERACTIVE_MODE?: boolean;
[key: string]: any; [key: string]: any;
@@ -243,5 +237,11 @@ export interface SanitizeResult {
sanitizedCount: number; sanitizedCount: number;
} }
// Provider 冲突处理动作 // Preset 信息(用于列表展示)
export type ProviderConflictAction = 'keep' | 'overwrite' | 'merge' | 'skip'; export interface PresetInfo {
name: string; // 预设名称
version?: string; // 版本号
description?: string; // 描述
author?: string; // 作者
config: PresetConfigSection;
}

View File

@@ -11,9 +11,11 @@
}, },
"dependencies": { "dependencies": {
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",

View File

@@ -144,10 +144,45 @@ export function Presets() {
const handleInstallFromMarket = async (preset: MarketPreset) => { const handleInstallFromMarket = async (preset: MarketPreset) => {
try { try {
setInstallingFromMarket(preset.id); setInstallingFromMarket(preset.id);
// 第一步:安装预设(解压到目录)
await api.installPresetFromGitHub(preset.repo, preset.name); await api.installPresetFromGitHub(preset.repo, preset.name);
// 第二步:获取预设详情(检查是否需要配置)
try {
const detail = await api.getPreset(preset.name);
const presetDetail: PresetDetail = { ...preset, ...detail };
// 检查是否需要配置
if (detail.schema && detail.schema.length > 0) {
// 需要配置,打开配置对话框
setSelectedPreset(presetDetail);
// 初始化默认值
const initialValues: Record<string, any> = {};
for (const input of detail.schema) {
initialValues[input.id] = input.defaultValue ?? '';
}
setSecrets(initialValues);
// 关闭市场对话框,打开详情对话框
setMarketDialogOpen(false);
setDetailDialogOpen(true);
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
} else {
// 不需要配置,直接完成
setToast({ message: t('presets.preset_installed'), type: 'success' }); setToast({ message: t('presets.preset_installed'), type: 'success' });
setMarketDialogOpen(false); setMarketDialogOpen(false);
await loadPresets(); await loadPresets();
}
} catch (error) {
// 获取详情失败,但安装成功了,刷新列表
console.error('Failed to get preset details after installation:', error);
setToast({ message: t('presets.preset_installed'), type: 'success' });
setMarketDialogOpen(false);
await loadPresets();
}
} catch (error: any) { } catch (error: any) {
console.error('Failed to install preset:', error); console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' }); setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
@@ -214,21 +249,81 @@ export function Presets() {
try { try {
setIsInstalling(true); setIsInstalling(true);
if (installMethod === 'url' && installUrl) { // 验证输入
await api.installPresetFromUrl(installUrl, installName || undefined); if (installMethod === 'url' && !installUrl) {
} else if (installMethod === 'file' && installFile) { setToast({ message: t('presets.please_provide_url'), type: 'warning' });
await api.uploadPresetFile(installFile, installName || undefined); return;
} else { }
setToast({ message: t('presets.please_provide_file_or_url'), type: 'warning' }); if (installMethod === 'file' && !installFile) {
setToast({ message: t('presets.please_provide_file'), type: 'warning' });
return; return;
} }
// 确定预设名称
const presetName = installName || (
installMethod === 'file'
? installFile!.name.replace('.ccrsets', '')
: installUrl!.split('/').pop()!.replace('.ccrsets', '')
);
// 第一步:安装预设(解压到目录)
if (installMethod === 'url' && installUrl) {
await api.installPresetFromUrl(installUrl, presetName);
} else if (installMethod === 'file' && installFile) {
await api.uploadPresetFile(installFile, presetName);
} else {
return;
}
// 第二步:获取预设详情(检查是否需要配置)
try {
const detail = await api.getPreset(presetName);
// 检查是否需要配置
if (detail.schema && detail.schema.length > 0) {
// 需要配置,打开配置对话框
setSelectedPreset({
id: presetName,
name: presetName,
version: detail.version || '1.0.0',
installed: true,
...detail
});
// 初始化默认值
const initialValues: Record<string, any> = {};
for (const input of detail.schema) {
initialValues[input.id] = input.defaultValue ?? '';
}
setSecrets(initialValues);
// 关闭安装对话框,打开详情对话框
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
setDetailDialogOpen(true);
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
} else {
// 不需要配置,直接完成
setToast({ message: t('presets.preset_installed'), type: 'success' }); setToast({ message: t('presets.preset_installed'), type: 'success' });
setInstallDialogOpen(false); setInstallDialogOpen(false);
setInstallUrl(''); setInstallUrl('');
setInstallFile(null); setInstallFile(null);
setInstallName(''); setInstallName('');
await loadPresets(); await loadPresets();
}
} catch (error) {
// 获取详情失败,但安装成功了,刷新列表
console.error('Failed to get preset details after installation:', error);
setToast({ message: t('presets.preset_installed'), type: 'success' });
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
await loadPresets();
}
} catch (error: any) { } catch (error: any) {
console.error('Failed to install preset:', error); console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' }); setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
@@ -262,6 +357,8 @@ export function Presets() {
setToast({ message: t('presets.preset_applied'), type: 'success' }); setToast({ message: t('presets.preset_applied'), type: 'success' });
setDetailDialogOpen(false); setDetailDialogOpen(false);
setSecrets({}); setSecrets({});
// 刷新预设列表
await loadPresets();
} catch (error: any) { } catch (error: any) {
console.error('Failed to apply preset:', error); console.error('Failed to apply preset:', error);
setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' }); setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' });
@@ -443,7 +540,7 @@ export function Presets() {
)} )}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex-1 overflow-y-auto py-4"> <div className="flex-1 overflow-y-auto py-4 px-2">
{selectedPreset?.description && ( {selectedPreset?.description && (
<p className="text-gray-700 mb-4">{selectedPreset.description}</p> <p className="text-gray-700 mb-4">{selectedPreset.description}</p>
)} )}

View File

@@ -87,7 +87,7 @@ export function DynamicConfigForm({
const visible = new Set<string>(); const visible = new Set<string>();
for (const field of schema) { for (const field of schema) {
if (shouldShowField(field, values)) { if (shouldShowField(field)) {
visible.add(field.id); visible.add(field.id);
} }
} }
@@ -334,7 +334,7 @@ export function DynamicConfigForm({
{field.type === 'select' && ( {field.type === 'select' && (
<Select <Select
value={values[field.id] || ''} value={values[field.id] || ''}
onValueChange={(value) => updateValue(field.id, value)} onValueChange={(value: string) => updateValue(field.id, value)}
disabled={isSubmitting} disabled={isSubmitting}
> >
<SelectTrigger id={`field-${field.id}`}> <SelectTrigger id={`field-${field.id}`}>
@@ -367,9 +367,9 @@ export function DynamicConfigForm({
<Checkbox <Checkbox
id={`field-${field.id}-${option.value}`} id={`field-${field.id}-${option.value}`}
checked={Array.isArray(values[field.id]) && values[field.id].includes(option.value)} checked={Array.isArray(values[field.id]) && values[field.id].includes(option.value)}
onCheckedChange={(checked) => { onCheckedChange={(checked: boolean | 'indeterminate') => {
const current = Array.isArray(values[field.id]) ? values[field.id] : []; const current = Array.isArray(values[field.id]) ? values[field.id] : [];
if (checked) { if (checked === true) {
updateValue(field.id, [...current, option.value]); updateValue(field.id, [...current, option.value]);
} else { } else {
updateValue(field.id, current.filter((v: any) => v !== option.value)); updateValue(field.id, current.filter((v: any) => v !== option.value));
@@ -397,7 +397,7 @@ export function DynamicConfigForm({
<Checkbox <Checkbox
id={`field-${field.id}`} id={`field-${field.id}`}
checked={values[field.id] || false} checked={values[field.id] || false}
onCheckedChange={(checked) => updateValue(field.id, checked)} onCheckedChange={(checked: boolean | 'indeterminate') => updateValue(field.id, checked)}
disabled={isSubmitting} disabled={isSubmitting}
/> />
<Label htmlFor={`field-${field.id}`} className="text-sm font-normal cursor-pointer"> <Label htmlFor={`field-${field.id}`} className="text-sm font-normal cursor-pointer">
@@ -412,7 +412,7 @@ export function DynamicConfigForm({
id={`field-${field.id}`} id={`field-${field.id}`}
placeholder={field.placeholder} placeholder={field.placeholder}
value={values[field.id] || ''} value={values[field.id] || ''}
onChange={(e) => updateValue(field.id, e.target.value)} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => updateValue(field.id, e.target.value)}
rows={field.rows || 5} rows={field.rows || 5}
disabled={isSubmitting} disabled={isSubmitting}
/> />

View File

@@ -139,9 +139,10 @@ class ApiClient {
} }
// DELETE request // DELETE request
async delete<T>(endpoint: string): Promise<T> { async delete<T>(endpoint: string, body?: any): Promise<T> {
return this.apiFetch<T>(endpoint, { return this.apiFetch<T>(endpoint, {
method: 'DELETE', method: 'DELETE',
body: JSON.stringify(body || {}),
}); });
} }
@@ -300,7 +301,7 @@ class ApiClient {
// Delete preset // Delete preset
async deletePreset(name: string): Promise<any> { async deletePreset(name: string): Promise<any> {
return this.delete<any>(`/presets/${encodeURIComponent(name)}`); return this.delete<any>(`/presets/${encodeURIComponent(name)}`, {});
} }
// Get market presets // Get market presets

View File

@@ -282,6 +282,9 @@
"load_presets_failed": "Failed to load presets", "load_presets_failed": "Failed to load presets",
"load_preset_details_failed": "Failed to load preset details", "load_preset_details_failed": "Failed to load preset details",
"please_fill_field": "Please fill in {{field}}", "please_fill_field": "Please fill in {{field}}",
"load_market_failed": "Failed to load market presets" "load_market_failed": "Failed to load market presets",
"preset_installed_config_required": "Preset installed, please complete configuration",
"please_provide_file": "Please provide a preset file",
"please_provide_url": "Please provide a preset URL"
} }
} }

View File

@@ -282,6 +282,9 @@
"load_presets_failed": "加载预设失败", "load_presets_failed": "加载预设失败",
"load_preset_details_failed": "加载预设详情失败", "load_preset_details_failed": "加载预设详情失败",
"please_fill_field": "请填写 {{field}}", "please_fill_field": "请填写 {{field}}",
"load_market_failed": "加载市场预设失败" "load_market_failed": "加载市场预设失败",
"preset_installed_config_required": "预设已安装,请完成配置",
"please_provide_file": "请提供预设文件",
"please_provide_url": "请提供预设 URL"
} }
} }

216
pnpm-lock.yaml generated
View File

@@ -62,7 +62,7 @@ importers:
version: 10.4.23(postcss@8.5.6) version: 10.4.23(postcss@8.5.6)
tailwindcss: tailwindcss:
specifier: '3' specifier: '3'
version: 3.4.19 version: 3.4.19(tsx@4.21.0)
packages/cli: packages/cli:
dependencies: dependencies:
@@ -107,6 +107,55 @@ importers:
specifier: ^5.8.2 specifier: ^5.8.2
version: 5.8.3 version: 5.8.3
packages/core:
dependencies:
'@anthropic-ai/sdk':
specifier: ^0.54.0
version: 0.54.0
'@fastify/cors':
specifier: ^11.0.1
version: 11.1.0
'@google/genai':
specifier: ^1.7.0
version: 1.24.0
dotenv:
specifier: ^16.5.0
version: 16.6.1
fastify:
specifier: ^5.4.0
version: 5.6.1
google-auth-library:
specifier: ^10.1.0
version: 10.4.0
json5:
specifier: ^2.2.3
version: 2.2.3
jsonrepair:
specifier: ^3.13.0
version: 3.13.1
openai:
specifier: ^5.6.0
version: 5.23.2(ws@8.18.3)
undici:
specifier: ^7.10.0
version: 7.16.0
uuid:
specifier: ^11.1.0
version: 11.1.0
devDependencies:
'@types/node':
specifier: ^24.0.15
version: 24.7.0
esbuild:
specifier: ^0.25.1
version: 0.25.10
tsx:
specifier: ^4.20.3
version: 4.21.0
typescript:
specifier: ^5.8.2
version: 5.8.3
packages/server: packages/server:
dependencies: dependencies:
'@fastify/multipart': '@fastify/multipart':
@@ -116,8 +165,8 @@ importers:
specifier: ^8.2.0 specifier: ^8.2.0
version: 8.2.0 version: 8.2.0
'@musistudio/llms': '@musistudio/llms':
specifier: ^1.0.51 specifier: workspace:*
version: 1.0.51(ws@8.18.3) version: link:../core
adm-zip: adm-zip:
specifier: ^0.5.16 specifier: ^0.5.16
version: 0.5.16 version: 0.5.16
@@ -198,6 +247,9 @@ importers:
'@monaco-editor/react': '@monaco-editor/react':
specifier: ^4.7.0 specifier: ^4.7.0
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-checkbox':
specifier: ^1.3.3
version: 1.3.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-dialog': '@radix-ui/react-dialog':
specifier: ^1.1.14 specifier: ^1.1.14
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -207,6 +259,9 @@ importers:
'@radix-ui/react-popover': '@radix-ui/react-popover':
specifier: ^1.1.14 specifier: ^1.1.14
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-select':
specifier: ^2.2.6
version: 2.2.6(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-slot': '@radix-ui/react-slot':
specifier: ^1.2.3 specifier: ^1.2.3
version: 1.2.4(@types/react@18.3.27)(react@19.2.3) version: 1.2.4(@types/react@18.3.27)(react@19.2.3)
@@ -221,7 +276,7 @@ importers:
version: 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.11 specifier: ^4.1.11
version: 4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)) version: 4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -288,7 +343,7 @@ importers:
version: 19.2.3(@types/react@18.3.27) version: 19.2.3(@types/react@18.3.27)
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^4.6.0 specifier: ^4.6.0
version: 4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)) version: 4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
autoprefixer: autoprefixer:
specifier: ^10.4.21 specifier: ^10.4.21
version: 10.4.23(postcss@8.5.6) version: 10.4.23(postcss@8.5.6)
@@ -321,10 +376,10 @@ importers:
version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)
vite: vite:
specifier: ^7.0.4 specifier: ^7.0.4
version: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) version: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
vite-plugin-singlefile: vite-plugin-singlefile:
specifier: ^2.3.0 specifier: ^2.3.0
version: 2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)) version: 2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
packages: packages:
@@ -2117,9 +2172,6 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@musistudio/llms@1.0.51':
resolution: {integrity: sha512-38T9NYrL9/Ombwx4YkGZSryYPtyaSMWJb3aGsepY8m9nbwCST60V+68xKVEaWyri6EfN8D3kenU0W8+okfVSmQ==}
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -2155,6 +2207,9 @@ packages:
'@polka/url@1.0.0-next.29': '@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
'@radix-ui/primitive@1.1.3': '@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
@@ -2171,6 +2226,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-checkbox@1.3.3':
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
peerDependencies:
'@types/react': '18'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.7': '@radix-ui/react-collection@1.1.7':
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
peerDependencies: peerDependencies:
@@ -2372,6 +2440,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-select@2.2.6':
resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
peerDependencies:
'@types/react': '18'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.3': '@radix-ui/react-slot@1.2.3':
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
peerDependencies: peerDependencies:
@@ -4517,6 +4598,9 @@ packages:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
get-tsconfig@4.13.0:
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
github-slugger@1.5.0: github-slugger@1.5.0:
resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
@@ -6703,6 +6787,9 @@ packages:
resolve-pathname@3.0.0: resolve-pathname@3.0.0:
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
resolve@1.22.10: resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -7245,6 +7332,11 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.21.0:
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
engines: {node: '>=18.0.0'}
hasBin: true
tw-animate-css@1.4.0: tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
@@ -10217,28 +10309,6 @@ snapshots:
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
'@musistudio/llms@1.0.51(ws@8.18.3)':
dependencies:
'@anthropic-ai/sdk': 0.54.0
'@fastify/cors': 11.1.0
'@google/genai': 1.24.0
dotenv: 16.6.1
fastify: 5.6.1
google-auth-library: 10.4.0
json5: 2.2.3
jsonrepair: 3.13.1
openai: 5.23.2(ws@8.18.3)
undici: 7.16.0
uuid: 11.1.0
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- bufferutil
- encoding
- supports-color
- utf-8-validate
- ws
- zod
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@@ -10270,6 +10340,8 @@ snapshots:
'@polka/url@1.0.0-next.29': {} '@polka/url@1.0.0-next.29': {}
'@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.3': {} '@radix-ui/primitive@1.1.3': {}
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
@@ -10281,6 +10353,22 @@ snapshots:
'@types/react': 18.3.27 '@types/react': 18.3.27
'@types/react-dom': 19.2.3(@types/react@18.3.27) '@types/react-dom': 19.2.3(@types/react@18.3.27)
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
optionalDependencies:
'@types/react': 18.3.27
'@types/react-dom': 19.2.3(@types/react@18.3.27)
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies: dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
@@ -10475,6 +10563,35 @@ snapshots:
'@types/react': 18.3.27 '@types/react': 18.3.27
'@types/react-dom': 19.2.3(@types/react@18.3.27) '@types/react-dom': 19.2.3(@types/react@18.3.27)
'@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.3)
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
aria-hidden: 1.2.6
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@19.2.3)
optionalDependencies:
'@types/react': 18.3.27
'@types/react-dom': 19.2.3(@types/react@18.3.27)
'@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.3)': '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.3)':
dependencies: dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
@@ -10877,12 +10994,12 @@ snapshots:
postcss: 8.5.6 postcss: 8.5.6
tailwindcss: 4.1.18 tailwindcss: 4.1.18
'@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))': '@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))':
dependencies: dependencies:
'@tailwindcss/node': 4.1.18 '@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18 '@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18 tailwindcss: 4.1.18
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
'@trysound/sax@0.2.0': {} '@trysound/sax@0.2.0': {}
@@ -11218,7 +11335,7 @@ snapshots:
'@vercel/oidc@3.0.5': {} '@vercel/oidc@3.0.5': {}
'@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))': '@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
@@ -11226,7 +11343,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.27 '@rolldown/pluginutils': 1.0.0-beta.27
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
react-refresh: 0.17.0 react-refresh: 0.17.0
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -12818,6 +12935,10 @@ snapshots:
get-stream@6.0.1: {} get-stream@6.0.1: {}
get-tsconfig@4.13.0:
dependencies:
resolve-pkg-maps: 1.0.0
github-slugger@1.5.0: {} github-slugger@1.5.0: {}
glob-parent@5.1.2: glob-parent@5.1.2:
@@ -14646,12 +14767,13 @@ snapshots:
'@csstools/utilities': 2.0.0(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6)
postcss: 8.5.6 postcss: 8.5.6
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0):
dependencies: dependencies:
lilconfig: 3.1.3 lilconfig: 3.1.3
optionalDependencies: optionalDependencies:
jiti: 1.21.7 jiti: 1.21.7
postcss: 8.5.6 postcss: 8.5.6
tsx: 4.21.0
postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1(esbuild@0.25.10)): postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1(esbuild@0.25.10)):
dependencies: dependencies:
@@ -15389,6 +15511,8 @@ snapshots:
resolve-pathname@3.0.0: {} resolve-pathname@3.0.0: {}
resolve-pkg-maps@1.0.0: {}
resolve@1.22.10: resolve@1.22.10:
dependencies: dependencies:
is-core-module: 2.16.1 is-core-module: 2.16.1
@@ -15851,7 +15975,7 @@ snapshots:
dependencies: dependencies:
tailwindcss: 4.1.18 tailwindcss: 4.1.18
tailwindcss@3.4.19: tailwindcss@3.4.19(tsx@4.21.0):
dependencies: dependencies:
'@alloc/quick-lru': 5.2.0 '@alloc/quick-lru': 5.2.0
arg: 5.0.2 arg: 5.0.2
@@ -15870,7 +15994,7 @@ snapshots:
postcss: 8.5.6 postcss: 8.5.6
postcss-import: 15.1.0(postcss@8.5.6) postcss-import: 15.1.0(postcss@8.5.6)
postcss-js: 4.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6)
postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)
postcss-nested: 6.2.0(postcss@8.5.6) postcss-nested: 6.2.0(postcss@8.5.6)
postcss-selector-parser: 6.1.2 postcss-selector-parser: 6.1.2
resolve: 1.22.10 resolve: 1.22.10
@@ -15999,6 +16123,13 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tsx@4.21.0:
dependencies:
esbuild: 0.27.2
get-tsconfig: 4.13.0
optionalDependencies:
fsevents: 2.3.3
tw-animate-css@1.4.0: {} tw-animate-css@1.4.0: {}
type-check@0.4.0: type-check@0.4.0:
@@ -16189,13 +16320,13 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.3 vfile-message: 4.0.3
vite-plugin-singlefile@2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)): vite-plugin-singlefile@2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)):
dependencies: dependencies:
micromatch: 4.0.8 micromatch: 4.0.8
rollup: 4.54.0 rollup: 4.54.0
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1) vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1): vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0):
dependencies: dependencies:
esbuild: 0.27.2 esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
@@ -16209,6 +16340,7 @@ snapshots:
jiti: 2.6.1 jiti: 2.6.1
lightningcss: 1.30.2 lightningcss: 1.30.2
terser: 5.44.1 terser: 5.44.1
tsx: 4.21.0
void-elements@3.1.0: {} void-elements@3.1.0: {}

View File

@@ -5,7 +5,11 @@ const { execSync } = require('child_process');
console.log('Building Claude Code Router (Monorepo)...'); console.log('Building Claude Code Router (Monorepo)...');
try { try {
// Build shared package first // Build core package first (@musistudio/llms)
console.log('Building core package (@musistudio/llms)...');
execSync('node scripts/build-core.js', { stdio: 'inherit' });
// Build shared package
console.log('Building shared package...'); console.log('Building shared package...');
execSync('node scripts/build-shared.js', { stdio: 'inherit' }); execSync('node scripts/build-shared.js', { stdio: 'inherit' });
@@ -15,6 +19,7 @@ try {
console.log('\n✅ Build completed successfully!'); console.log('\n✅ Build completed successfully!');
console.log('\nArtifacts are available in packages/*/dist:'); console.log('\nArtifacts are available in packages/*/dist:');
console.log(' - packages/core/dist/ (Core package: @musistudio/llms)');
console.log(' - packages/shared/dist/ (Shared package)'); console.log(' - packages/shared/dist/ (Shared package)');
console.log(' - packages/cli/dist/ (CLI + UI + tiktoken)'); console.log(' - packages/cli/dist/ (CLI + UI + tiktoken)');
console.log(' - packages/server/dist/ (Server standalone)'); console.log(' - packages/server/dist/ (Server standalone)');

View File

@@ -2,6 +2,7 @@
set -e set -e
# 发布脚本 # 发布脚本
# - Core 包作为 @musistudio/llms npm 包发布
# - CLI 包作为 @CCR/cli npm 包发布 # - CLI 包作为 @CCR/cli npm 包发布
# - Server 包发布为 Docker 镜像 # - Server 包发布为 Docker 镜像
@@ -37,7 +38,39 @@ case "$PUBLISH_TYPE" in
esac esac
# =========================== # ===========================
# 发布 npm 包 # 发布 Core npm 包 (@musistudio/llms)
# ===========================
publish_core_npm() {
echo ""
echo "========================================="
echo "发布 npm 包 @musistudio/llms"
echo "========================================="
# 检查是否已登录 npm
if ! npm whoami &>/dev/null; then
echo "错误: 未登录 npm请先运行: npm login"
exit 1
fi
CORE_DIR="../packages/core"
CORE_VERSION=$(node -p "require('../packages/core/package.json').version")
# 复制 README 到 core 包
cp ../README.md "$CORE_DIR/" 2>/dev/null || echo "README.md 不存在,跳过..."
cp ../LICENSE "$CORE_DIR/" 2>/dev/null || echo "LICENSE 文件不存在,跳过..."
# 发布到 npm
cd "$CORE_DIR"
echo "执行 npm publish..."
npm publish --access public
echo ""
echo "✅ Core npm 包发布成功!"
echo " 包名: @musistudio/llms@${CORE_VERSION}"
}
# ===========================
# 发布 CLI npm 包
# =========================== # ===========================
publish_npm() { publish_npm() {
echo "" echo ""
@@ -138,6 +171,7 @@ publish_docker() {
# 执行发布 # 执行发布
# =========================== # ===========================
if [ "$PUBLISH_TYPE" = "npm" ] || [ "$PUBLISH_TYPE" = "all" ]; then if [ "$PUBLISH_TYPE" = "npm" ] || [ "$PUBLISH_TYPE" = "all" ]; then
publish_core_npm
publish_npm publish_npm
fi fi