Files
Claude-Code-Workflow/ccw/src/commands/tool.ts
catlog22 25ac862f46 feat(ccw): migrate backend to TypeScript
- Convert 40 JS files to TypeScript (CLI, tools, core, MCP server)
- Add Zod for runtime parameter validation
- Add type definitions in src/types/
- Keep src/templates/ as JavaScript (dashboard frontend)
- Update bin entries to use dist/
- Add tsconfig.json with strict mode
- Add backward-compatible exports for tests
- All 39 tests passing

Breaking changes: None (backward compatible)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-13 10:43:15 +08:00

202 lines
6.8 KiB
TypeScript

/**
* Tool Command - Execute and manage CCW tools
*/
import chalk from 'chalk';
import { listTools, executeTool, getTool, getAllToolSchemas } from '../tools/index.js';
interface ToolOptions {
name?: string;
}
interface ExecOptions {
path?: string;
old?: string;
new?: string;
action?: string;
query?: string;
limit?: string;
file?: string;
files?: string;
languages?: string;
mode?: string;
operation?: string;
line?: string;
text?: string;
dryRun?: boolean;
replaceAll?: boolean;
}
/**
* List all available tools
*/
async function listAction(): Promise<void> {
const tools = listTools();
if (tools.length === 0) {
console.log(chalk.yellow('No tools registered'));
return;
}
console.log(chalk.bold.cyan('\nAvailable Tools:\n'));
for (const tool of tools) {
console.log(chalk.bold.white(` ${tool.name}`));
console.log(chalk.gray(` ${tool.description}`));
if (tool.parameters?.properties) {
const props = tool.parameters.properties;
const required = tool.parameters.required || [];
console.log(chalk.gray(' Parameters:'));
for (const [name, schema] of Object.entries(props)) {
const req = required.includes(name) ? chalk.red('*') : '';
const defaultVal = (schema as any).default !== undefined ? chalk.gray(` (default: ${(schema as any).default})`) : '';
console.log(chalk.gray(` - ${name}${req}: ${(schema as any).description}${defaultVal}`));
}
}
console.log();
}
}
/**
* Show tool schema in MCP-compatible JSON format
*/
async function schemaAction(options: ToolOptions): Promise<void> {
const { name } = options;
if (name) {
const tool = getTool(name);
if (!tool) {
console.error(chalk.red(`Tool not found: ${name}`));
process.exit(1);
}
const schema = {
name: tool.name,
description: tool.description,
inputSchema: {
type: 'object',
properties: tool.parameters?.properties || {},
required: tool.parameters?.required || []
}
};
console.log(JSON.stringify(schema, null, 2));
} else {
const schemas = getAllToolSchemas();
console.log(JSON.stringify({ tools: schemas }, null, 2));
}
}
/**
* Execute a tool with given parameters
* @param {string} toolName - Tool name
* @param {string|undefined} jsonParams - JSON string of parameters
* @param {Object} options - CLI options
*/
async function execAction(toolName: string | undefined, jsonParams: string | undefined, options: ExecOptions): Promise<void> {
if (!toolName) {
console.error(chalk.red('Tool name is required'));
console.error(chalk.gray('Usage: ccw tool exec <tool_name> \'{"param": "value"}\''));
console.error(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old" --new "new"'));
console.error(chalk.gray(' ccw tool exec codex_lens --action search --query "pattern"'));
process.exit(1);
}
const tool = getTool(toolName);
if (!tool) {
console.error(chalk.red(`Tool not found: ${toolName}`));
console.error(chalk.gray('Use "ccw tool list" to see available tools'));
process.exit(1);
}
// Build params from CLI options or JSON
let params: any = {};
// Check if JSON params provided
if (jsonParams && jsonParams.trim().startsWith('{')) {
try {
params = JSON.parse(jsonParams);
} catch (e) {
const error = e as Error;
console.error(chalk.red('Invalid JSON parameters'));
console.error(chalk.gray(`Parse error: ${error.message}`));
process.exit(1);
}
} else if (toolName === 'edit_file') {
// Parameter mode for edit_file
if (!options.path || !options.old || !options.new) {
console.error(chalk.red('edit_file requires --path, --old, and --new parameters'));
console.error(chalk.gray('Usage: ccw tool exec edit_file --path file.txt --old "old text" --new "new text"'));
process.exit(1);
}
params.path = options.path;
params.oldText = options.old;
params.newText = options.new;
} else if (toolName === 'codex_lens') {
// Parameter mode for codex_lens
if (!options.action) {
console.error(chalk.red('codex_lens requires --action parameter'));
console.error(chalk.gray('Usage: ccw tool exec codex_lens --action search --query "pattern" --path .'));
console.error(chalk.gray('Actions: init, search, search_files, symbol, status, update, bootstrap, check'));
process.exit(1);
}
params.action = options.action;
if (options.path) params.path = options.path;
if (options.query) params.query = options.query;
if (options.limit) params.limit = parseInt(options.limit, 10);
if (options.file) params.file = options.file;
if (options.files) params.files = options.files.split(',').map(f => f.trim());
if (options.languages) params.languages = options.languages.split(',').map(l => l.trim());
} else if (jsonParams) {
// Non-JSON string provided but not for supported tools
console.error(chalk.red('Parameters must be valid JSON'));
console.error(chalk.gray(`Usage: ccw tool exec ${toolName} '{"param": "value"}'`));
process.exit(1);
}
// If no params provided, use empty object (tool may have defaults)
// Execute tool
const result = await executeTool(toolName, params);
// Always output JSON
console.log(JSON.stringify(result, null, 2));
}
/**
* Tool command entry point
* @param {string} subcommand - Subcommand (list, schema, exec)
* @param {string[]} args - Arguments array [toolName, jsonParams, ...]
* @param {Object} options - CLI options
*/
export async function toolCommand(subcommand: string, args: string | string[], options: ExecOptions): Promise<void> {
// args is now an array due to [args...] in cli.js
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
// Handle subcommands
switch (subcommand) {
case 'list':
await listAction();
break;
case 'schema':
await schemaAction({ name: argsArray[0] });
break;
case 'exec':
await execAction(argsArray[0], argsArray[1], options);
break;
default:
console.log(chalk.bold.cyan('\nCCW Tool System\n'));
console.log('Subcommands:');
console.log(chalk.gray(' list List all available tools'));
console.log(chalk.gray(' schema [name] Show tool schema (JSON)'));
console.log(chalk.gray(' exec <name> Execute a tool'));
console.log();
console.log('Usage:');
console.log(chalk.gray(' ccw tool list'));
console.log(chalk.gray(' ccw tool schema edit_file'));
console.log(chalk.gray(' ccw tool exec <tool_name> \'{"param": "value"}\''));
console.log(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old text" --new "new text"'));
console.log(chalk.gray(' ccw tool exec codex_lens --action search --query "def main" --path .'));
}
}