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
JavaScript
/**
* 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