UNPKG

miyabi-agent-sdk

Version:

Miyabi Autonomous Agent SDK - 7 Agents based on Shikigaku Theory with 100% cost reduction mode

328 lines (319 loc) 11.8 kB
#!/usr/bin/env node /** * Miyabi CLI - Autonomous Agent Command Line Interface * * Usage: * miyabi analyze <issue-number> # Analyze GitHub Issue * miyabi generate <issue-number> # Generate code for Issue * miyabi review <files...> # Review code files * miyabi workflow <issue-number> # Run full workflow (analyze → generate → review → PR) * * Options: * --use-claude-code Use local Claude Code CLI (free, default) * --use-anthropic-api Use Anthropic API (paid) * --repo <owner/repo> GitHub repository (default: from git remote) * --github-token <token> GitHub token (default: from GITHUB_TOKEN env) * --anthropic-key <key> Anthropic API key (for --use-anthropic-api) */ import { parseArgs } from "node:util"; import { IssueAgent } from "../agents/IssueAgent.js"; import { CodeGenAgent } from "../agents/CodeGenAgent.js"; import { ReviewAgent } from "../agents/ReviewAgent.js"; import { GitHubClient } from "../clients/GitHubClient.js"; /** * Parse CLI arguments */ function parseCLIArgs() { const { values, positionals } = parseArgs({ args: process.argv.slice(2), options: { "use-claude-code": { type: "boolean", default: true }, "use-anthropic-api": { type: "boolean", default: false }, repo: { type: "string" }, "github-token": { type: "string" }, "anthropic-key": { type: "string" }, }, allowPositionals: true, }); const [command, ...args] = positionals; // Parse repo (owner/repo format) let owner; let repo; if (values.repo) { const parts = values.repo.split("/"); if (parts.length === 2) { [owner, repo] = parts; } } // If --use-anthropic-api is specified, don't use Claude Code const useAnthropicAPI = values["use-anthropic-api"] === true; const useClaudeCode = useAnthropicAPI ? false : (values["use-claude-code"] !== false); return { command: command || "help", args, config: { useClaudeCode, useAnthropicAPI, repo, owner, githubToken: values["github-token"] || process.env.GITHUB_TOKEN, anthropicKey: values["anthropic-key"] || process.env.ANTHROPIC_API_KEY, }, }; } /** * Display help message */ function displayHelp() { console.log(` Miyabi CLI - Autonomous Agent Command Line Interface Usage: miyabi <command> [options] Commands: analyze <issue-number> Analyze GitHub Issue with IssueAgent generate <issue-number> Generate code for Issue with CodeGenAgent review <file1> <file2>... Review code files with ReviewAgent workflow <issue-number> Run full workflow (analyze → generate → review → PR) help Display this help message Options: --use-claude-code Use local Claude Code CLI (free, default) --use-anthropic-api Use Anthropic API (paid) --repo <owner/repo> GitHub repository (default: from git remote) --github-token <token> GitHub token (default: from GITHUB_TOKEN env) --anthropic-key <key> Anthropic API key (for --use-anthropic-api) Environment Variables: GITHUB_TOKEN GitHub personal access token ANTHROPIC_API_KEY Anthropic API key (for --use-anthropic-api) Examples: # Analyze issue #42 using Claude Code CLI (free) miyabi analyze 42 --repo owner/repo --github-token ghp_xxx # Generate code for issue #42 using Anthropic API miyabi generate 42 --use-anthropic-api --anthropic-key sk-ant-xxx # Review specific files miyabi review src/index.ts src/utils.ts # Run full workflow for issue #42 miyabi workflow 42 --repo owner/repo For more information, visit: https://github.com/ShunsukeHayashi/codex-miyabi `); } /** * Run analyze command */ async function runAnalyze(issueNumber, config) { if (!config.owner || !config.repo || !config.githubToken) { console.error("❌ Error: --repo and --github-token are required for analyze command"); process.exit(1); } console.log(`\n🔍 Analyzing Issue #${issueNumber}...\n`); const agent = new IssueAgent({ useClaudeCode: config.useClaudeCode, anthropicApiKey: config.anthropicKey, githubToken: config.githubToken, }); const result = await agent.analyze({ issueNumber, repository: config.repo, owner: config.owner, useRealAPI: true, }); if (!result.success) { console.error(`❌ Analysis failed: ${result.error}`); process.exit(1); } const data = result.data; console.log(`✅ Analysis complete!\n`); console.log(`Title: ${data.title}`); console.log(`Type: ${data.type}`); console.log(`Priority: ${data.priority}`); console.log(`Complexity: ${data.complexity}`); console.log(`\nLabels:`); data.labels.forEach((label) => console.log(` - ${label}`)); if (data.tokensUsed) { console.log(`\nTokens: ${data.tokensUsed.input} in, ${data.tokensUsed.output} out`); } if (data.cost !== undefined) { console.log(`Cost: $${data.cost.toFixed(4)}`); } } /** * Run generate command */ async function runGenerate(issueNumber, config) { if (!config.owner || !config.repo || !config.githubToken) { console.error("❌ Error: --repo and --github-token are required for generate command"); process.exit(1); } console.log(`\n🔨 Generating code for Issue #${issueNumber}...\n`); // First, fetch issue details const githubClient = new GitHubClient(config.githubToken); const issue = await githubClient.getIssue(config.owner, config.repo, issueNumber); const agent = new CodeGenAgent({ useClaudeCode: config.useClaudeCode, anthropicApiKey: config.anthropicKey, githubToken: config.githubToken, }); const result = await agent.generate({ taskId: `issue-${issueNumber}`, requirements: `${issue.title}\n\n${issue.body || ""}`, context: { repository: config.repo, owner: config.owner, baseBranch: "main", relatedFiles: [], }, language: "typescript", useRealAPI: true, }); if (!result.success) { console.error(`❌ Generation failed: ${result.error}`); process.exit(1); } const data = result.data; console.log(`✅ Code generation complete!\n`); console.log(`Files generated: ${data.files.length}`); data.files.forEach((file) => console.log(` - ${file.path} (${file.action})`)); console.log(`\nTests generated: ${data.tests.length}`); data.tests.forEach((test) => console.log(` - ${test.path}`)); console.log(`\nQuality Score: ${data.qualityScore}/100`); if (data.tokensUsed) { console.log(`Tokens: ${data.tokensUsed.input} in, ${data.tokensUsed.output} out`); } if (data.cost !== undefined) { console.log(`Cost: $${data.cost.toFixed(4)}`); } } /** * Run review command */ async function runReview(files, config) { if (files.length === 0) { console.error("❌ Error: At least one file path is required for review"); process.exit(1); } console.log(`\n🔍 Reviewing ${files.length} file(s)...\n`); // Read file contents (simplified - should read from filesystem) const fileContents = files.map((path) => ({ path, content: `// Mock content for ${path}`, action: "create", })); const agent = new ReviewAgent({ useClaudeCode: config.useClaudeCode, anthropicApiKey: config.anthropicKey, }); const result = await agent.review({ files: fileContents, standards: { minQualityScore: 80, requireTests: true, securityScan: true, }, useRealAPI: true, }); if (!result.success) { console.error(`❌ Review failed: ${result.error}`); process.exit(1); } const data = result.data; console.log(`✅ Review complete!\n`); console.log(`Quality Score: ${data.qualityScore}/100`); console.log(`Status: ${data.passed ? "✅ PASSED" : "❌ FAILED"}`); console.log(`Coverage: ${data.coverage.toFixed(1)}%`); if (data.issues.length > 0) { console.log(`\nIssues found: ${data.issues.length}`); data.issues.forEach((issue) => { const location = issue.line ? `:${issue.line}` : ""; console.log(` [${issue.severity}] ${issue.file}${location}: ${issue.message}`); }); } if (data.suggestions.length > 0) { console.log(`\nSuggestions:`); data.suggestions.forEach((suggestion) => console.log(` - ${suggestion}`)); } if (data.tokensUsed) { console.log(`\nTokens: ${data.tokensUsed.input} in, ${data.tokensUsed.output} out`); } if (data.cost !== undefined) { console.log(`Cost: $${data.cost.toFixed(4)}`); } } /** * Run full workflow command */ async function runWorkflow(issueNumber, config) { if (!config.owner || !config.repo || !config.githubToken) { console.error("❌ Error: --repo and --github-token are required for workflow command"); process.exit(1); } console.log(`\n🚀 Running full workflow for Issue #${issueNumber}...\n`); // Step 1: Analyze console.log("Step 1/5: Analyzing issue..."); await runAnalyze(issueNumber, config); // Step 2: Generate code console.log("\nStep 2/5: Generating code..."); await runGenerate(issueNumber, config); // Step 3: Review code console.log("\nStep 3/5: Reviewing code..."); // (Simplified - should review generated files) // Step 4: Run tests console.log("\nStep 4/5: Running tests..."); console.log("(Test execution not implemented yet)"); // Step 5: Create PR console.log("\nStep 5/5: Creating Pull Request..."); console.log("(PR creation not implemented yet)"); console.log("\n✅ Workflow complete!"); } /** * Main CLI entry point */ async function main() { const { command, args, config } = parseCLIArgs(); try { switch (command) { case "help": displayHelp(); break; case "analyze": { const issueNumber = parseInt(args[0], 10); if (isNaN(issueNumber)) { console.error("❌ Error: Invalid issue number"); process.exit(1); } await runAnalyze(issueNumber, config); break; } case "generate": { const issueNumber = parseInt(args[0], 10); if (isNaN(issueNumber)) { console.error("❌ Error: Invalid issue number"); process.exit(1); } await runGenerate(issueNumber, config); break; } case "review": await runReview(args, config); break; case "workflow": { const issueNumber = parseInt(args[0], 10); if (isNaN(issueNumber)) { console.error("❌ Error: Invalid issue number"); process.exit(1); } await runWorkflow(issueNumber, config); break; } default: console.error(`❌ Error: Unknown command '${command}'`); displayHelp(); process.exit(1); } } catch (error) { console.error(`\n❌ Error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } } // Run CLI main(); //# sourceMappingURL=miyabi.js.map