feat: 更新工具列表,支持 .ccw 目录的安装和升级,调整相关路径

This commit is contained in:
catlog22
2026-02-07 23:51:26 +08:00
parent 50570a9820
commit b22298d868
11 changed files with 100 additions and 30 deletions

View File

@@ -26,7 +26,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Source directories to install (includes .codex with prompts folder)
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen', '.ccw'];
// Subdirectories that should always be installed to global (~/.claude/)
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
@@ -380,7 +380,10 @@ export async function installCommand(options: InstallOptions): Promise<void> {
for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir);
const destPath = join(installPath, dir);
// .ccw always installs to global ~/.ccw/ regardless of mode
const destBase = (mode === 'Path' && dir === '.ccw') ? homedir() : installPath;
const destPath = join(destBase, dir);
spinner.text = `Installing ${dir}...`;

View File

@@ -11,7 +11,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Source directories to install
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen', '.ccw'];
// Subdirectories that should always be installed to global (~/.claude/)
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
@@ -268,7 +268,10 @@ async function performUpgrade(manifest: any, sourceDir: string, version: string)
// Copy each directory
for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir);
const destPath = join(installPath, dir);
// .ccw always upgrades to global ~/.ccw/ regardless of mode
const destBase = (mode === 'Path' && dir === '.ccw') ? homedir() : installPath;
const destPath = join(destBase, dir);
// For Path mode on .claude, exclude global subdirs (they're already installed to global)
const excludeDirs = (mode === 'Path' && dir === '.claude') ? GLOBAL_SUBDIRS : [];

View File

@@ -117,6 +117,70 @@ export async function handleCcwRoutes(ctx: RouteContext): Promise<boolean> {
return true;
}
// API: CCW Install (non-interactive)
if (pathname === '/api/ccw/install' && req.method === 'POST') {
handlePostRequest(req, res, async (body) => {
const { mode = 'Global', path: installPath, force = true } = body as { mode?: string; path?: string; force?: boolean };
try {
const { spawn } = await import('child_process');
const args = ['install', '--mode', mode, '--force'];
if (mode === 'Path' && installPath) {
args.push('--path', installPath);
}
const installProcess = spawn('ccw', args, {
shell: true,
stdio: ['ignore', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
installProcess.stdout?.on('data', (data: Buffer) => {
const chunk = data.toString();
stdout += chunk;
broadcastToClients({
type: 'CCW_INSTALL_OUTPUT',
payload: { data: chunk }
});
});
installProcess.stderr?.on('data', (data: Buffer) => {
stderr += data.toString();
});
return new Promise((resolve) => {
installProcess.on('close', (code: number | null) => {
if (code === 0) {
broadcastToClients({
type: 'CCW_INSTALL_COMPLETED',
payload: { success: true, mode }
});
resolve({ success: true, message: 'Installation completed', mode, output: stdout });
} else {
resolve({ success: false, error: stderr || 'Installation failed', output: stdout, status: 500 });
}
});
installProcess.on('error', (err: Error) => {
resolve({ success: false, error: err.message, status: 500 });
});
// Timeout after 2 minutes
setTimeout(() => {
installProcess.kill();
resolve({ success: false, error: 'Installation timed out', status: 504 });
}, 120000);
});
} catch (err: unknown) {
return { success: false, error: err instanceof Error ? err.message : String(err), status: 500 };
}
});
return true;
}
// API: CCW Upgrade
if (pathname === '/api/ccw/upgrade' && req.method === 'POST') {
handlePostRequest(req, res, async (body) => {

View File

@@ -943,7 +943,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
}
// Find guidelines file path - always use user-level path
const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md');
const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'chinese-response.md');
if (existsSync(userGuidelinesPath)) {
guidelinesPath = userGuidelinesPath;
@@ -979,7 +979,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
try {
// Find guidelines file path - always use user-level path with ~ shorthand
const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md');
const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'chinese-response.md');
if (!existsSync(userGuidelinesPath)) {
return { error: 'Chinese response guidelines file not found at ~/.ccw/workflows/chinese-response.md', status: 404 };
@@ -1107,7 +1107,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
}
// Find guidelines file path
const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md');
const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md');
if (existsSync(userGuidelinesPath)) {
guidelinesPath = userGuidelinesPath;
@@ -1172,7 +1172,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
if (content) content += '\n';
// Read and add updated section
const cliToolsUsagePath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md');
const cliToolsUsagePath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md');
let cliToolsUsageContent = '';
if (existsSync(cliToolsUsagePath)) {
cliToolsUsageContent = readFileSync(cliToolsUsagePath, 'utf8');
@@ -1208,7 +1208,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
}
// Read cli-tools-usage.md content
const cliToolsUsagePath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md');
const cliToolsUsagePath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md');
let cliToolsUsageContent = '';
if (existsSync(cliToolsUsagePath)) {
cliToolsUsageContent = readFileSync(cliToolsUsagePath, 'utf8');
@@ -1266,7 +1266,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
}
// Find guidelines file path - always use user-level path
const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md');
const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'windows-platform.md');
if (existsSync(userGuidelinesPath)) {
guidelinesPath = userGuidelinesPath;
@@ -1301,7 +1301,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
const userClaudeDir = join(homedir(), '.claude');
// Find guidelines file path - always use user-level path with ~ shorthand
const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md');
const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'windows-platform.md');
if (!existsSync(userGuidelinesPath)) {
return { error: 'Windows platform guidelines file not found at ~/.ccw/workflows/windows-platform.md', status: 404 };

View File

@@ -28,8 +28,8 @@ function checkCcwInstallStatus(): {
missingFiles: string[];
installPath: string;
} {
const claudeDir = join(homedir(), '.claude');
const workflowsDir = join(claudeDir, 'workflows');
const ccwDir = join(homedir(), '.ccw');
const workflowsDir = join(ccwDir, 'workflows');
// Required workflow files for full functionality
const requiredFiles = [

View File

@@ -948,10 +948,10 @@ export function updateCodeIndexMcp(
// Only update global CLAUDE.md (consistent with Chinese response / Windows platform)
const globalClaudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
// Define patterns for all formats
const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g;
const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g;
const nonePattern = /@~\/\.claude\/workflows\/context-tools-none\.md/g;
// Define patterns for all formats (match both old .claude and new .ccw paths)
const codexlensPattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools\.md/g;
const acePattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools-ace\.md/g;
const nonePattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools-none\.md/g;
// Determine target file based on provider
const targetFile = provider === 'ace'

View File

@@ -37,7 +37,7 @@ export interface TemplateIndex {
// Constants
// ============================================================================
const TEMPLATES_BASE_DIR = join(homedir(), '.claude', 'workflows', 'cli-templates');
const TEMPLATES_BASE_DIR = join(homedir(), '.ccw', 'workflows', 'cli-templates');
const PROMPTS_DIR = join(TEMPLATES_BASE_DIR, 'prompts');
const PROTOCOLS_DIR = join(TEMPLATES_BASE_DIR, 'protocols');