mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
fix: auto-sync CLI tools availability on first config creation (Issue #95)
**问题描述**: 新安装 CCW 后,默认配置中所有 CLI 工具 enabled: true,但实际上用户可能没有安装这些工具,导致执行任务时尝试调用未安装的工具而失败。 **根本原因**: - DEFAULT_TOOLS_CONFIG 中所有工具默认 enabled: true - 首次创建配置时不检测工具实际可用性 - 现有的 syncBuiltinToolsAvailability() 只在用户手动触发时才执行 **修复内容**: 1. 新增 ensureClaudeCliToolsAsync() 异步版本 - 在创建默认配置后自动调用 syncBuiltinToolsAvailability() - 通过 which/where 命令检测工具实际可用性 - 根据检测结果自动调整 enabled 状态 2. 更新两个关键 API 端点使用新函数 - /api/cli/endpoints - 获取 API 端点列表 - /api/cli/tools-config - 获取 CLI 工具配置 **效果**: - 首次安装时自动检测并禁用未安装的工具 - 避免调用不可用工具导致的错误 - 用户可在 Dashboard 中看到准确的工具状态 Fixes #95
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
loadClaudeCliTools,
|
loadClaudeCliTools,
|
||||||
ensureClaudeCliTools,
|
ensureClaudeCliTools,
|
||||||
|
ensureClaudeCliToolsAsync,
|
||||||
saveClaudeCliTools,
|
saveClaudeCliTools,
|
||||||
loadClaudeCliSettings,
|
loadClaudeCliSettings,
|
||||||
saveClaudeCliSettings,
|
saveClaudeCliSettings,
|
||||||
@@ -329,16 +330,18 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
|
|
||||||
// API: Get all API endpoints (for --tool custom --model <id>)
|
// API: Get all API endpoints (for --tool custom --model <id>)
|
||||||
if (pathname === '/api/cli/endpoints' && req.method === 'GET') {
|
if (pathname === '/api/cli/endpoints' && req.method === 'GET') {
|
||||||
try {
|
(async () => {
|
||||||
// Use ensureClaudeCliTools to auto-create config if missing
|
try {
|
||||||
const config = ensureClaudeCliTools(initialPath);
|
// Use ensureClaudeCliToolsAsync to auto-create config with availability sync
|
||||||
const endpoints = getApiEndpointsFromTools(config);
|
const config = await ensureClaudeCliToolsAsync(initialPath);
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
const endpoints = getApiEndpointsFromTools(config);
|
||||||
res.end(JSON.stringify({ endpoints }));
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
} catch (err) {
|
res.end(JSON.stringify({ endpoints }));
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
} catch (err) {
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
}
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
}
|
||||||
|
})();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,21 +823,23 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
|
|
||||||
// API: Get CLI Tools Config from .claude/cli-tools.json (with fallback to global)
|
// API: Get CLI Tools Config from .claude/cli-tools.json (with fallback to global)
|
||||||
if (pathname === '/api/cli/tools-config' && req.method === 'GET') {
|
if (pathname === '/api/cli/tools-config' && req.method === 'GET') {
|
||||||
try {
|
(async () => {
|
||||||
// Use ensureClaudeCliTools to auto-create config if missing
|
try {
|
||||||
const toolsConfig = ensureClaudeCliTools(initialPath);
|
// Use ensureClaudeCliToolsAsync to auto-create config with availability sync
|
||||||
const settingsConfig = loadClaudeCliSettings(initialPath);
|
const toolsConfig = await ensureClaudeCliToolsAsync(initialPath);
|
||||||
const info = getClaudeCliToolsInfo(initialPath);
|
const settingsConfig = loadClaudeCliSettings(initialPath);
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
const info = getClaudeCliToolsInfo(initialPath);
|
||||||
res.end(JSON.stringify({
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
tools: toolsConfig,
|
res.end(JSON.stringify({
|
||||||
settings: settingsConfig,
|
tools: toolsConfig,
|
||||||
_configInfo: info
|
settings: settingsConfig,
|
||||||
}));
|
_configInfo: info
|
||||||
} catch (err) {
|
}));
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
} catch (err) {
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
}
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
}
|
||||||
|
})();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,56 @@ export function ensureClaudeCliTools(projectDir: string, createInProject: boolea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async version of ensureClaudeCliTools with automatic availability sync
|
||||||
|
* Creates default config in global ~/.claude directory and syncs with actual tool availability
|
||||||
|
* @param projectDir - Project directory path (used for reading existing project config)
|
||||||
|
* @param createInProject - DEPRECATED: Always creates in global dir. Kept for backward compatibility.
|
||||||
|
* @returns The config that was created/exists
|
||||||
|
*/
|
||||||
|
export async function ensureClaudeCliToolsAsync(projectDir: string, createInProject: boolean = false): Promise<ClaudeCliToolsConfig & { _source?: string }> {
|
||||||
|
const resolved = resolveConfigPath(projectDir);
|
||||||
|
|
||||||
|
if (resolved.source !== 'default') {
|
||||||
|
// Config exists, load and return it
|
||||||
|
return loadClaudeCliTools(projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config doesn't exist - create in global directory only
|
||||||
|
debugLog('[claude-cli-tools] Config not found, creating default cli-tools.json in ~/.claude');
|
||||||
|
|
||||||
|
const defaultConfig: ClaudeCliToolsConfig = { ...DEFAULT_TOOLS_CONFIG };
|
||||||
|
|
||||||
|
// Always create in global directory (user-level config), respecting CCW_DATA_DIR
|
||||||
|
const claudeHome = process.env.CCW_DATA_DIR
|
||||||
|
? path.join(process.env.CCW_DATA_DIR, '.claude')
|
||||||
|
: path.join(os.homedir(), '.claude');
|
||||||
|
if (!fs.existsSync(claudeHome)) {
|
||||||
|
fs.mkdirSync(claudeHome, { recursive: true });
|
||||||
|
}
|
||||||
|
const globalPath = getGlobalConfigPath();
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(globalPath, JSON.stringify(defaultConfig, null, 2), 'utf-8');
|
||||||
|
debugLog(`[claude-cli-tools] Created default config at: ${globalPath}`);
|
||||||
|
|
||||||
|
// Auto-sync with actual tool availability on first creation
|
||||||
|
try {
|
||||||
|
debugLog('[claude-cli-tools] Auto-syncing tool availability on first creation...');
|
||||||
|
const syncResult = await syncBuiltinToolsAvailability(projectDir);
|
||||||
|
debugLog(`[claude-cli-tools] Auto-sync completed: enabled=[${syncResult.changes.enabled.join(', ')}], disabled=[${syncResult.changes.disabled.join(', ')}]`);
|
||||||
|
return { ...syncResult.config, _source: 'global' };
|
||||||
|
} catch (syncErr) {
|
||||||
|
console.warn('[claude-cli-tools] Failed to auto-sync availability:', syncErr);
|
||||||
|
// Return default config if sync fails
|
||||||
|
return { ...defaultConfig, _source: 'global' };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[claude-cli-tools] Failed to create global config:', err);
|
||||||
|
return { ...defaultConfig, _source: 'default' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load CLI tools configuration from global ~/.claude/cli-tools.json
|
* Load CLI tools configuration from global ~/.claude/cli-tools.json
|
||||||
* Falls back to default config if not found.
|
* Falls back to default config if not found.
|
||||||
|
|||||||
Reference in New Issue
Block a user