mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat(cli-manager): add CLI wrapper endpoints management and UI integration
- Introduced functions to load and toggle CLI wrapper endpoints from the API. - Updated the CLI manager UI to display and manage CLI wrapper endpoints. - Removed CodexLens and Semantic Search from the tools section, now managed in their dedicated pages. feat(codexlens-manager): move File Watcher card to the CodexLens Manager page - Relocated the File Watcher card from the right column to the main content area of the CodexLens Manager page. refactor(claude-cli-tools): enhance CLI tools configuration and migration - Added support for new tool types: 'cli-wrapper' and 'api-endpoint'. - Updated migration logic to handle new tool types and preserve endpoint IDs. - Deprecated previous custom endpoint handling in favor of the new structure. feat(cli-executor-core): integrate CLI settings for custom endpoint execution - Implemented execution logic for custom CLI封装 endpoints using settings files. - Enhanced error handling and output logging for CLI executions. - Updated tool identification logic to support both built-in tools and custom endpoints.
This commit is contained in:
@@ -22,10 +22,20 @@ export interface ClaudeCliTool {
|
||||
primaryModel?: string;
|
||||
secondaryModel?: string;
|
||||
tags: string[];
|
||||
type?: 'builtin' | 'cli-wrapper' | 'api-endpoint'; // Tool type: builtin, cli-wrapper, or api-endpoint
|
||||
id?: string; // Required for api-endpoint type (endpoint ID for settings lookup)
|
||||
}
|
||||
|
||||
export type CliToolName = 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode';
|
||||
export type CliToolName = 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode' | string;
|
||||
|
||||
// @deprecated Use tools with type: 'api-endpoint' instead
|
||||
export interface ClaudeApiEndpoint {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
// @deprecated Use tools with type: 'cli-wrapper' or 'api-endpoint' instead
|
||||
export interface ClaudeCustomEndpoint {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -44,8 +54,9 @@ export interface ClaudeCliToolsConfig {
|
||||
$schema?: string;
|
||||
version: string;
|
||||
models?: Record<string, string[]>; // PREDEFINED_MODELS
|
||||
tools: Record<string, ClaudeCliTool>;
|
||||
customEndpoints: ClaudeCustomEndpoint[];
|
||||
tools: Record<string, ClaudeCliTool>; // All tools: builtin, cli-wrapper, api-endpoint
|
||||
apiEndpoints?: ClaudeApiEndpoint[]; // @deprecated Use tools with type: 'api-endpoint' instead
|
||||
customEndpoints?: ClaudeCustomEndpoint[]; // @deprecated Use tools with type: 'cli-wrapper' or 'api-endpoint' instead
|
||||
}
|
||||
|
||||
// New: Settings-only config (cli-settings.json)
|
||||
@@ -103,41 +114,46 @@ const PREDEFINED_MODELS: Record<CliToolName, string[]> = {
|
||||
};
|
||||
|
||||
const DEFAULT_TOOLS_CONFIG: ClaudeCliToolsConfig = {
|
||||
version: '3.0.0',
|
||||
version: '3.2.0',
|
||||
models: { ...PREDEFINED_MODELS },
|
||||
tools: {
|
||||
gemini: {
|
||||
enabled: true,
|
||||
primaryModel: 'gemini-2.5-pro',
|
||||
secondaryModel: 'gemini-2.5-flash',
|
||||
tags: []
|
||||
tags: [],
|
||||
type: 'builtin'
|
||||
},
|
||||
qwen: {
|
||||
enabled: true,
|
||||
primaryModel: 'coder-model',
|
||||
secondaryModel: 'coder-model',
|
||||
tags: []
|
||||
tags: [],
|
||||
type: 'builtin'
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
primaryModel: 'gpt-5.2',
|
||||
secondaryModel: 'gpt-5.2',
|
||||
tags: []
|
||||
tags: [],
|
||||
type: 'builtin'
|
||||
},
|
||||
claude: {
|
||||
enabled: true,
|
||||
primaryModel: 'sonnet',
|
||||
secondaryModel: 'haiku',
|
||||
tags: []
|
||||
tags: [],
|
||||
type: 'builtin'
|
||||
},
|
||||
opencode: {
|
||||
enabled: true,
|
||||
primaryModel: 'opencode/glm-4.7-free',
|
||||
secondaryModel: 'opencode/glm-4.7-free',
|
||||
tags: []
|
||||
tags: [],
|
||||
type: 'builtin'
|
||||
}
|
||||
},
|
||||
customEndpoints: []
|
||||
}
|
||||
// Note: api-endpoint type tools are added dynamically via addClaudeApiEndpoint
|
||||
};
|
||||
|
||||
const DEFAULT_SETTINGS_CONFIG: ClaudeCliSettingsConfig = {
|
||||
@@ -222,17 +238,18 @@ function ensureToolTags(tool: Partial<ClaudeCliTool>): ClaudeCliTool {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate config from older versions to v3.0.0
|
||||
* Migrate config from older versions to v3.2.0
|
||||
* v3.2.0: All endpoints (cli-wrapper, api-endpoint) are in tools with type field
|
||||
*/
|
||||
function migrateConfig(config: any, projectDir: string): ClaudeCliToolsConfig {
|
||||
const version = parseFloat(config.version || '1.0');
|
||||
|
||||
// Already v3.x, no migration needed
|
||||
if (version >= 3.0) {
|
||||
// Already v3.2+, no migration needed
|
||||
if (version >= 3.2) {
|
||||
return config as ClaudeCliToolsConfig;
|
||||
}
|
||||
|
||||
console.log(`[claude-cli-tools] Migrating config from v${config.version || '1.0'} to v3.0.0`);
|
||||
console.log(`[claude-cli-tools] Migrating config from v${config.version || '1.0'} to v3.2.0`);
|
||||
|
||||
// Try to load legacy cli-config.json for model data
|
||||
let legacyCliConfig: any = null;
|
||||
@@ -258,7 +275,9 @@ function migrateConfig(config: any, projectDir: string): ClaudeCliToolsConfig {
|
||||
enabled: t.enabled ?? legacyTool?.enabled ?? true,
|
||||
primaryModel: t.primaryModel ?? legacyTool?.primaryModel ?? DEFAULT_TOOLS_CONFIG.tools[key]?.primaryModel,
|
||||
secondaryModel: t.secondaryModel ?? legacyTool?.secondaryModel ?? DEFAULT_TOOLS_CONFIG.tools[key]?.secondaryModel,
|
||||
tags: t.tags ?? legacyTool?.tags ?? []
|
||||
tags: t.tags ?? legacyTool?.tags ?? [],
|
||||
type: t.type ?? DEFAULT_TOOLS_CONFIG.tools[key]?.type ?? 'builtin',
|
||||
id: t.id // Preserve id for api-endpoint type
|
||||
};
|
||||
}
|
||||
|
||||
@@ -270,16 +289,57 @@ function migrateConfig(config: any, projectDir: string): ClaudeCliToolsConfig {
|
||||
enabled: legacyTool?.enabled ?? defaultTool.enabled,
|
||||
primaryModel: legacyTool?.primaryModel ?? defaultTool.primaryModel,
|
||||
secondaryModel: legacyTool?.secondaryModel ?? defaultTool.secondaryModel,
|
||||
tags: legacyTool?.tags ?? defaultTool.tags
|
||||
tags: legacyTool?.tags ?? defaultTool.tags,
|
||||
type: defaultTool.type ?? 'builtin'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate customEndpoints (v3.0 and below): cli-wrapper -> tools, others -> api-endpoint tools
|
||||
const customEndpoints = config.customEndpoints || [];
|
||||
for (const ep of customEndpoints) {
|
||||
if (ep.tags?.includes('cli-wrapper')) {
|
||||
// CLI wrapper becomes a tool with type: 'cli-wrapper'
|
||||
if (!migratedTools[ep.name]) {
|
||||
migratedTools[ep.name] = {
|
||||
enabled: ep.enabled ?? true,
|
||||
tags: ep.tags.filter((t: string) => t !== 'cli-wrapper'),
|
||||
type: 'cli-wrapper'
|
||||
};
|
||||
console.log(`[claude-cli-tools] Migrated cli-wrapper "${ep.name}" to tools`);
|
||||
}
|
||||
} else {
|
||||
// Pure API endpoint becomes a tool with type: 'api-endpoint'
|
||||
if (!migratedTools[ep.name]) {
|
||||
migratedTools[ep.name] = {
|
||||
enabled: ep.enabled ?? true,
|
||||
tags: [],
|
||||
type: 'api-endpoint',
|
||||
id: ep.id // Store endpoint ID for settings lookup
|
||||
};
|
||||
console.log(`[claude-cli-tools] Migrated API endpoint "${ep.name}" to tools`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate apiEndpoints (v3.1): convert to tools with type: 'api-endpoint'
|
||||
const apiEndpoints = config.apiEndpoints || [];
|
||||
for (const ep of apiEndpoints) {
|
||||
if (!migratedTools[ep.name]) {
|
||||
migratedTools[ep.name] = {
|
||||
enabled: ep.enabled ?? true,
|
||||
tags: [],
|
||||
type: 'api-endpoint',
|
||||
id: ep.id // Store endpoint ID for settings lookup
|
||||
};
|
||||
console.log(`[claude-cli-tools] Migrated API endpoint "${ep.name}" to tools`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: '3.0.0',
|
||||
version: '3.2.0',
|
||||
models: { ...PREDEFINED_MODELS },
|
||||
tools: migratedTools,
|
||||
customEndpoints: config.customEndpoints || [],
|
||||
$schema: config.$schema
|
||||
};
|
||||
}
|
||||
@@ -324,7 +384,7 @@ export function ensureClaudeCliTools(projectDir: string, createInProject: boolea
|
||||
* Load CLI tools configuration from global ~/.claude/cli-tools.json
|
||||
* Falls back to default config if not found.
|
||||
*
|
||||
* Automatically migrates older config versions to v3.0.0
|
||||
* Automatically migrates older config versions to v3.2.0
|
||||
*/
|
||||
export function loadClaudeCliTools(projectDir: string): ClaudeCliToolsConfig & { _source?: string } {
|
||||
const resolved = resolveConfigPath(projectDir);
|
||||
@@ -337,27 +397,24 @@ export function loadClaudeCliTools(projectDir: string): ClaudeCliToolsConfig & {
|
||||
const content = fs.readFileSync(resolved.path, 'utf-8');
|
||||
const parsed = JSON.parse(content) as Partial<ClaudeCliCombinedConfig>;
|
||||
|
||||
// Migrate older versions to v3.0.0
|
||||
// Migrate older versions to v3.2.0
|
||||
const migrated = migrateConfig(parsed, projectDir);
|
||||
const needsSave = migrated.version !== parsed.version;
|
||||
|
||||
// Merge tools with defaults and ensure required fields exist
|
||||
const mergedTools: Record<string, ClaudeCliTool> = {};
|
||||
for (const [key, tool] of Object.entries({ ...DEFAULT_TOOLS_CONFIG.tools, ...(migrated.tools || {}) })) {
|
||||
mergedTools[key] = ensureToolTags(tool);
|
||||
mergedTools[key] = {
|
||||
...ensureToolTags(tool),
|
||||
type: tool.type ?? 'builtin',
|
||||
id: tool.id // Preserve id for api-endpoint type
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure customEndpoints have tags
|
||||
const mergedEndpoints = (migrated.customEndpoints || []).map(ep => ({
|
||||
...ep,
|
||||
tags: ep.tags ?? []
|
||||
}));
|
||||
|
||||
const config: ClaudeCliToolsConfig & { _source?: string } = {
|
||||
version: migrated.version || DEFAULT_TOOLS_CONFIG.version,
|
||||
models: migrated.models || DEFAULT_TOOLS_CONFIG.models,
|
||||
tools: mergedTools,
|
||||
customEndpoints: mergedEndpoints,
|
||||
$schema: migrated.$schema,
|
||||
_source: resolved.source
|
||||
};
|
||||
@@ -513,27 +570,43 @@ export function updateClaudeDefaultTool(
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom endpoint
|
||||
* Add API endpoint as a tool with type: 'api-endpoint'
|
||||
* Usage: --tool <name> or --tool custom --model <id>
|
||||
*/
|
||||
export function addClaudeCustomEndpoint(
|
||||
export function addClaudeApiEndpoint(
|
||||
projectDir: string,
|
||||
endpoint: { id: string; name: string; enabled: boolean; tags?: string[] }
|
||||
endpoint: { id: string; name: string; enabled: boolean }
|
||||
): ClaudeCliToolsConfig {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
|
||||
const newEndpoint: ClaudeCustomEndpoint = {
|
||||
id: endpoint.id,
|
||||
name: endpoint.name,
|
||||
// Add as a tool with type: 'api-endpoint'
|
||||
config.tools[endpoint.name] = {
|
||||
enabled: endpoint.enabled,
|
||||
tags: endpoint.tags || []
|
||||
tags: [],
|
||||
type: 'api-endpoint',
|
||||
id: endpoint.id // Store endpoint ID for settings lookup
|
||||
};
|
||||
|
||||
// Check if endpoint already exists
|
||||
const existingIndex = config.customEndpoints.findIndex(e => e.id === endpoint.id);
|
||||
if (existingIndex >= 0) {
|
||||
config.customEndpoints[existingIndex] = newEndpoint;
|
||||
} else {
|
||||
config.customEndpoints.push(newEndpoint);
|
||||
saveClaudeCliTools(projectDir, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove API endpoint tool by id or name
|
||||
*/
|
||||
export function removeClaudeApiEndpoint(
|
||||
projectDir: string,
|
||||
endpointId: string
|
||||
): ClaudeCliToolsConfig {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
|
||||
// Find the tool by id or name
|
||||
const toolToRemove = Object.entries(config.tools).find(
|
||||
([name, t]) => t.type === 'api-endpoint' && (t.id === endpointId || name === endpointId || name.toLowerCase() === endpointId.toLowerCase())
|
||||
);
|
||||
|
||||
if (toolToRemove) {
|
||||
delete config.tools[toolToRemove[0]];
|
||||
}
|
||||
|
||||
saveClaudeCliTools(projectDir, config);
|
||||
@@ -541,14 +614,57 @@ export function addClaudeCustomEndpoint(
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove custom endpoint
|
||||
* @deprecated Use addClaudeApiEndpoint instead
|
||||
* Adds tool to config based on tags:
|
||||
* - cli-wrapper tag -> type: 'cli-wrapper'
|
||||
* - others -> type: 'api-endpoint'
|
||||
*/
|
||||
export function addClaudeCustomEndpoint(
|
||||
projectDir: string,
|
||||
endpoint: { id: string; name: string; enabled: boolean; tags?: string[] }
|
||||
): ClaudeCliToolsConfig {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
|
||||
if (endpoint.tags?.includes('cli-wrapper')) {
|
||||
// CLI wrapper tool
|
||||
config.tools[endpoint.name] = {
|
||||
enabled: endpoint.enabled,
|
||||
tags: endpoint.tags.filter(t => t !== 'cli-wrapper'),
|
||||
type: 'cli-wrapper'
|
||||
};
|
||||
} else {
|
||||
// API endpoint tool
|
||||
config.tools[endpoint.name] = {
|
||||
enabled: endpoint.enabled,
|
||||
tags: [],
|
||||
type: 'api-endpoint',
|
||||
id: endpoint.id
|
||||
};
|
||||
}
|
||||
|
||||
saveClaudeCliTools(projectDir, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove endpoint tool (cli-wrapper or api-endpoint)
|
||||
*/
|
||||
export function removeClaudeCustomEndpoint(
|
||||
projectDir: string,
|
||||
endpointId: string
|
||||
): ClaudeCliToolsConfig {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
config.customEndpoints = config.customEndpoints.filter(e => e.id !== endpointId);
|
||||
|
||||
// Find the tool by id or name (cli-wrapper or api-endpoint type)
|
||||
const toolToRemove = Object.entries(config.tools).find(
|
||||
([name, t]) => (t.type === 'cli-wrapper' || t.type === 'api-endpoint') &&
|
||||
(name === endpointId || name.toLowerCase() === endpointId.toLowerCase() || t.id === endpointId)
|
||||
);
|
||||
|
||||
if (toolToRemove) {
|
||||
delete config.tools[toolToRemove[0]];
|
||||
}
|
||||
|
||||
saveClaudeCliTools(projectDir, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,179 @@ export function killCurrentCliProcess(): boolean {
|
||||
import { executeLiteLLMEndpoint } from './litellm-executor.js';
|
||||
import { findEndpointById } from '../config/litellm-api-config-manager.js';
|
||||
|
||||
// CLI Settings (CLI封装) integration
|
||||
import { loadEndpointSettings, getSettingsFilePath, findEndpoint } from '../config/cli-settings-manager.js';
|
||||
import { loadClaudeCliTools } from './claude-cli-tools.js';
|
||||
|
||||
/**
|
||||
* Execute Claude CLI with custom settings file (CLI封装)
|
||||
*/
|
||||
interface ClaudeWithSettingsParams {
|
||||
prompt: string;
|
||||
settingsPath: string;
|
||||
endpointId: string;
|
||||
mode: 'analysis' | 'write' | 'auto';
|
||||
workingDir: string;
|
||||
cd?: string;
|
||||
includeDirs?: string[];
|
||||
customId?: string;
|
||||
onOutput?: (unit: CliOutputUnit) => void;
|
||||
}
|
||||
|
||||
async function executeClaudeWithSettings(params: ClaudeWithSettingsParams): Promise<ExecutionOutput> {
|
||||
const { prompt, settingsPath, endpointId, mode, workingDir, cd, includeDirs, customId, onOutput } = params;
|
||||
|
||||
const startTime = Date.now();
|
||||
const conversationId = customId || `${Date.now()}-${endpointId}`;
|
||||
|
||||
// Build claude command with --settings flag
|
||||
const args: string[] = [
|
||||
'--settings', settingsPath,
|
||||
'--print' // Non-interactive mode
|
||||
];
|
||||
|
||||
// Add mode-specific flags
|
||||
if (mode === 'write') {
|
||||
args.push('--dangerously-skip-permissions');
|
||||
}
|
||||
|
||||
// Add working directory if specified
|
||||
if (cd) {
|
||||
args.push('--cd', cd);
|
||||
}
|
||||
|
||||
// Add include directories
|
||||
if (includeDirs && includeDirs.length > 0) {
|
||||
for (const dir of includeDirs) {
|
||||
args.push('--add-dir', dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Add prompt as argument
|
||||
args.push('-p', prompt);
|
||||
|
||||
debugLog('CLAUDE_SETTINGS', `Executing claude with settings`, {
|
||||
settingsPath,
|
||||
endpointId,
|
||||
mode,
|
||||
workingDir,
|
||||
args
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const command = 'claude';
|
||||
const commandToSpawn = isWindows ? escapeWindowsArg(command) : command;
|
||||
const argsToSpawn = isWindows ? args.map(escapeWindowsArg) : args;
|
||||
|
||||
const child = spawn(commandToSpawn, argsToSpawn, {
|
||||
cwd: workingDir,
|
||||
shell: isWindows,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// Track current child process for cleanup
|
||||
currentChildProcess = child;
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
const outputUnits: CliOutputUnit[] = [];
|
||||
|
||||
child.stdout!.on('data', (data: Buffer) => {
|
||||
const text = data.toString();
|
||||
stdout += text;
|
||||
|
||||
const unit: CliOutputUnit = {
|
||||
type: 'stdout',
|
||||
content: text,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
outputUnits.push(unit);
|
||||
|
||||
if (onOutput) {
|
||||
onOutput(unit);
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr!.on('data', (data: Buffer) => {
|
||||
const text = data.toString();
|
||||
stderr += text;
|
||||
|
||||
const unit: CliOutputUnit = {
|
||||
type: 'stderr',
|
||||
content: text,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
outputUnits.push(unit);
|
||||
|
||||
if (onOutput) {
|
||||
onOutput(unit);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
currentChildProcess = null;
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
// Determine status
|
||||
let status: 'success' | 'error' = 'success';
|
||||
if (code !== 0) {
|
||||
const hasValidOutput = stdout.trim().length > 0;
|
||||
const hasFatalError = stderr.includes('FATAL') ||
|
||||
stderr.includes('Authentication failed') ||
|
||||
stderr.includes('API key');
|
||||
|
||||
if (hasValidOutput && !hasFatalError) {
|
||||
status = 'success';
|
||||
} else {
|
||||
status = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
const execution: ExecutionRecord = {
|
||||
id: conversationId,
|
||||
timestamp: new Date(startTime).toISOString(),
|
||||
tool: 'claude',
|
||||
model: endpointId, // Use endpoint ID as model identifier
|
||||
mode,
|
||||
prompt,
|
||||
status,
|
||||
exit_code: code,
|
||||
duration_ms: duration,
|
||||
output: {
|
||||
stdout: stdout.substring(0, 10240),
|
||||
stderr: stderr.substring(0, 2048),
|
||||
truncated: stdout.length > 10240 || stderr.length > 2048
|
||||
}
|
||||
};
|
||||
|
||||
const conversation = convertToConversation(execution);
|
||||
|
||||
// Save to history
|
||||
try {
|
||||
saveConversation(workingDir, conversation);
|
||||
} catch (err) {
|
||||
console.error('[CLI Executor] Failed to save CLI封装 history:', (err as Error).message);
|
||||
}
|
||||
|
||||
resolve({
|
||||
success: status === 'success',
|
||||
execution,
|
||||
conversation,
|
||||
stdout,
|
||||
stderr
|
||||
});
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
currentChildProcess = null;
|
||||
reject(new Error(`Failed to spawn claude: ${error.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Native resume support
|
||||
import {
|
||||
trackNewSession,
|
||||
@@ -100,9 +273,14 @@ import {
|
||||
getPrimaryModel
|
||||
} from './cli-config-manager.js';
|
||||
|
||||
// Built-in CLI tools
|
||||
const BUILTIN_CLI_TOOLS = ['gemini', 'qwen', 'codex', 'opencode', 'claude'] as const;
|
||||
type BuiltinCliTool = typeof BUILTIN_CLI_TOOLS[number];
|
||||
|
||||
// Define Zod schema for validation
|
||||
// tool accepts built-in tools or custom endpoint IDs (CLI封装)
|
||||
const ParamsSchema = z.object({
|
||||
tool: z.enum(['gemini', 'qwen', 'codex', 'opencode']),
|
||||
tool: z.string().min(1, 'Tool is required'), // Accept any tool ID (built-in or custom endpoint)
|
||||
prompt: z.string().min(1, 'Prompt is required'),
|
||||
mode: z.enum(['analysis', 'write', 'auto']).default('analysis'),
|
||||
format: z.enum(['plain', 'yaml', 'json']).default('plain'), // Multi-turn prompt concatenation format
|
||||
@@ -220,6 +398,116 @@ async function executeCliTool(
|
||||
}
|
||||
}
|
||||
|
||||
// Check if tool is a custom CLI封装 endpoint (not a built-in tool)
|
||||
const isBuiltinTool = BUILTIN_CLI_TOOLS.includes(tool as BuiltinCliTool);
|
||||
if (!isBuiltinTool) {
|
||||
// Check if it's a CLI封装 endpoint (by ID or name)
|
||||
const cliSettings = findEndpoint(tool);
|
||||
if (cliSettings && cliSettings.enabled) {
|
||||
// Route to Claude CLI with --settings flag
|
||||
const settingsPath = getSettingsFilePath(cliSettings.id);
|
||||
const displayName = cliSettings.name !== cliSettings.id ? `${cliSettings.name} (${cliSettings.id})` : cliSettings.id;
|
||||
if (onOutput) {
|
||||
onOutput({
|
||||
type: 'stderr',
|
||||
content: `[Routing to CLI封装 endpoint: ${displayName} via claude --settings]\n`,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// Execute claude CLI with settings file
|
||||
const result = await executeClaudeWithSettings({
|
||||
prompt,
|
||||
settingsPath,
|
||||
endpointId: cliSettings.id,
|
||||
mode,
|
||||
workingDir,
|
||||
cd,
|
||||
includeDirs: includeDirs ? includeDirs.split(',').map(d => d.trim()) : undefined,
|
||||
customId,
|
||||
onOutput: onOutput || undefined
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check cli-tools.json for CLI wrapper tools or API endpoints
|
||||
const cliToolsConfig = loadClaudeCliTools(workingDir);
|
||||
|
||||
// First check if tool is a cli-wrapper in tools section
|
||||
const cliWrapperTool = Object.entries(cliToolsConfig.tools).find(
|
||||
([name, t]) => name.toLowerCase() === tool.toLowerCase() && t.type === 'cli-wrapper' && t.enabled
|
||||
);
|
||||
if (cliWrapperTool) {
|
||||
const [toolName] = cliWrapperTool;
|
||||
// Check if there's a corresponding CLI封装 settings file
|
||||
const cliSettingsForTool = findEndpoint(toolName);
|
||||
if (cliSettingsForTool) {
|
||||
const settingsPath = getSettingsFilePath(cliSettingsForTool.id);
|
||||
if (onOutput) {
|
||||
onOutput({
|
||||
type: 'stderr',
|
||||
content: `[Routing to CLI wrapper tool: ${toolName} via claude --settings]\n`,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const result = await executeClaudeWithSettings({
|
||||
prompt,
|
||||
settingsPath,
|
||||
endpointId: cliSettingsForTool.id,
|
||||
mode,
|
||||
workingDir,
|
||||
cd,
|
||||
includeDirs: includeDirs ? includeDirs.split(',').map(d => d.trim()) : undefined,
|
||||
customId,
|
||||
onOutput: onOutput || undefined
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check tools with type: 'api-endpoint' (for --tool custom --model <id>)
|
||||
const apiEndpointTool = Object.entries(cliToolsConfig.tools).find(
|
||||
([name, t]) => t.type === 'api-endpoint' && t.enabled &&
|
||||
(t.id === tool || name === tool || name.toLowerCase() === tool.toLowerCase())
|
||||
);
|
||||
if (apiEndpointTool) {
|
||||
const [toolName, toolConfig] = apiEndpointTool;
|
||||
const endpointId = toolConfig.id || toolName;
|
||||
// Check if there's a corresponding CLI封装 settings file
|
||||
const cliSettingsForEndpoint = findEndpoint(endpointId);
|
||||
if (cliSettingsForEndpoint) {
|
||||
const settingsPath = getSettingsFilePath(cliSettingsForEndpoint.id);
|
||||
if (onOutput) {
|
||||
onOutput({
|
||||
type: 'stderr',
|
||||
content: `[Routing to API endpoint: ${toolName} via claude --settings]\n`,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const result = await executeClaudeWithSettings({
|
||||
prompt,
|
||||
settingsPath,
|
||||
endpointId: cliSettingsForEndpoint.id,
|
||||
mode,
|
||||
workingDir,
|
||||
cd,
|
||||
includeDirs: includeDirs ? includeDirs.split(',').map(d => d.trim()) : undefined,
|
||||
customId,
|
||||
onOutput: onOutput || undefined
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Tool not found
|
||||
throw new Error(`Unknown tool: ${tool}. Use one of: ${BUILTIN_CLI_TOOLS.join(', ')} or a registered CLI封装 endpoint name.`);
|
||||
}
|
||||
|
||||
// Get SQLite store for native session lookup
|
||||
const store = await getSqliteStore(workingDir);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user