@vortex-ai/cli 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ import { Indexer } from "@vortex/engine";
2
+ import { createGithubClient } from "@vortex/github";
3
+ import { getGithubRepoInfo } from "@vortex/git";
4
+ import { solveCommand } from "./solve";
5
+
6
+ export async function solveIssueCommand(options: any) {
7
+ const { default: ora } = await import("ora");
8
+ const { default: chalk } = await import("chalk");
9
+
10
+ console.log(chalk.blue(`\nAutonomous Agent solving Issue #${options.id}`));
11
+
12
+ if (!process.env.GITHUB_TOKEN) {
13
+ console.log(chalk.yellow("⚠️ No GITHUB_TOKEN found. Using anonymous access (subject to rate limits)."));
14
+ }
15
+
16
+ const repoInfo = getGithubRepoInfo(process.cwd());
17
+ const owner = process.env.GITHUB_OWNER || repoInfo?.owner;
18
+ const repo = process.env.GITHUB_REPO || repoInfo?.repo;
19
+
20
+ if (!owner || !repo) {
21
+ console.error(chalk.red("Could not determine GitHub repository. Please run this command inside a git repository or set GITHUB_OWNER and GITHUB_REPO."));
22
+ return;
23
+ }
24
+
25
+ const spinner = ora(`Fetching Issue #${options.id} from ${owner}/${repo}...`).start();
26
+
27
+ try {
28
+ const github = createGithubClient(process.env.GITHUB_TOKEN);
29
+ const issue = await github.fetchIssue(owner, repo, options.id);
30
+ const comments = await github.fetchIssueComments(owner, repo, options.id);
31
+
32
+ spinner.text = `Issue fetched. Searching local vector database for relevant code...`;
33
+
34
+ const indexer = new Indexer();
35
+ const relevantContext = await indexer.hybridSearch(issue.title, 5);
36
+
37
+ spinner.succeed("Issue and Context fetched successfully!\n");
38
+
39
+
40
+ const prompt = `Solve the following GitHub issue:
41
+ # ${issue.title}
42
+
43
+ ${issue.body || "No description provided."}
44
+
45
+ ## Discussion Comments
46
+ ${comments.map((c: any, i: number) => `Comment ${i + 1} (@${c.user?.login}): ${c.body}`).join('\n')}
47
+
48
+ Please fix this issue in the codebase.`;
49
+
50
+
51
+ const contextChunks = relevantContext.map((c: any) => ({
52
+ file: c.file,
53
+ symbolPath: c.symbolPath || "anonymous",
54
+ content: c.content,
55
+ kind: c.kind || "unknown",
56
+ }));
57
+
58
+
59
+ await solveCommand(prompt, {
60
+ autoApprove: options.autoApprove,
61
+ maxSteps: options.maxSteps,
62
+ contextChunks: contextChunks
63
+ });
64
+
65
+ } catch (err) {
66
+ spinner.fail("Failed to setup solve-issue");
67
+ console.error(err);
68
+ }
69
+ }
@@ -0,0 +1,177 @@
1
+ import ora from "ora";
2
+ import * as readline from "node:readline/promises";
3
+ import { execSync } from "child_process";
4
+ import chalk from "chalk";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import {
8
+ AutonomousAgent,
9
+ FileReadTool,
10
+ FileWriteTool,
11
+ ShellExecuteTool,
12
+ GrepTool,
13
+ RagSearchTool,
14
+ ApprovalCallback,
15
+ AgentContextChunk
16
+ } from "@vortex/engine";
17
+ import { initDatabase } from "@vortex/db";
18
+ import { getGitRoot, isGitRepo } from "@vortex/git";
19
+ import { VectorStore, LocalEmbedder, BM25Index, HybridRetriever } from "@vortex/retrieval";
20
+
21
+ export async function solveCommand(prompt: string, options: { autoApprove?: boolean; maxSteps?: number; contextChunks?: AgentContextChunk[]; newProject?: string } = {}) {
22
+ let cwd = process.cwd();
23
+
24
+ if (options.newProject) {
25
+ const projectPath = path.resolve(cwd, options.newProject);
26
+ if (!fs.existsSync(projectPath)) {
27
+ fs.mkdirSync(projectPath, { recursive: true });
28
+ console.log(chalk.green(`\nCreated new project folder: ${projectPath}`));
29
+ }
30
+ cwd = projectPath;
31
+ process.chdir(cwd);
32
+
33
+ if (!fs.existsSync(path.join(cwd, ".git"))) {
34
+ try {
35
+ execSync("git init", { cwd, stdio: "ignore" });
36
+ console.log(chalk.green(`Initialized empty Git repository in ${cwd}`));
37
+ } catch (e) {
38
+ console.error(chalk.red("Failed to initialize git repository."));
39
+ }
40
+ }
41
+ }
42
+
43
+ let rootPath = cwd;
44
+
45
+ if (isGitRepo(cwd)) {
46
+ rootPath = getGitRoot(cwd);
47
+ }
48
+
49
+ console.log(`\nVortex Autonomous Agent activated`);
50
+ console.log(`Task: "${prompt}"\n`);
51
+
52
+ await initDatabase();
53
+
54
+ const spinner = ora("Initializing Vector Store for Project Memory...").start();
55
+
56
+ try {
57
+ const vectorStore = new VectorStore();
58
+ const bm25Index = new BM25Index();
59
+ const embedder = new LocalEmbedder();
60
+ const hybridRetriever = new HybridRetriever(vectorStore, bm25Index, embedder);
61
+ const agent = new AutonomousAgent();
62
+
63
+ if (options.maxSteps !== undefined) {
64
+ (agent as any).maxToolIterations = options.maxSteps;
65
+ }
66
+
67
+ spinner.text = "Thinking...";
68
+
69
+ const approvalCallback: ApprovalCallback = async (action: string, description: string): Promise<boolean> => {
70
+ if (options.autoApprove) return true;
71
+
72
+ const currentText = spinner.text;
73
+ spinner.stop();
74
+
75
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
76
+ let question = "";
77
+ if (action === "shell_execute") {
78
+ question = `\n${chalk.yellow('⚠️ Agent wants to execute command:')}\n> ${description}\nAllow? (y/N) `;
79
+ } else if (action === "write_file") {
80
+ question = `\n${chalk.yellow('⚠️ Agent wants to overwrite file:')} ${description}\nAllow? (y/N) `;
81
+ } else {
82
+ question = `\n${chalk.yellow('⚠️ Agent wants to perform action:')} ${action} on ${description}\nAllow? (y/N) `;
83
+ }
84
+
85
+ const answer = await rl.question(question);
86
+ rl.close();
87
+
88
+ spinner.start(currentText);
89
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
90
+ };
91
+
92
+
93
+ agent.registerTools([
94
+ new FileReadTool(rootPath),
95
+ new FileWriteTool(rootPath, vectorStore, embedder, approvalCallback),
96
+ new ShellExecuteTool(rootPath, approvalCallback),
97
+ new GrepTool(rootPath),
98
+ new RagSearchTool(hybridRetriever),
99
+ ]);
100
+
101
+ const result = await agent.run(
102
+ {
103
+ diff: prompt,
104
+ contextChunks: options.contextChunks || [],
105
+ },
106
+ {
107
+ onToolCall: (toolName, args) => {
108
+ if (toolName === "write_file") {
109
+ spinner.text = `Writing file: ${args.path}...`;
110
+ } else if (toolName === "shell_execute") {
111
+ spinner.text = `Executing shell command...`;
112
+ } else if (toolName === "rag_search") {
113
+ spinner.text = `Searching project memory for context...`;
114
+ } else {
115
+ spinner.text = `Agent is using tool: ${toolName}...`;
116
+ }
117
+ },
118
+ onToolResult: (toolName, toolResult) => {
119
+ if (toolName === "write_file" && toolResult.startsWith("Success")) {
120
+ spinner.stop();
121
+ try {
122
+ const match = toolResult.match(/Wrote to (.*) successfully/);
123
+ if (match && match[1]) {
124
+ const filePath = match[1];
125
+ console.log(`\n${chalk.green('✓ File updated:')} ${filePath}`);
126
+ const diffOut = execSync(`git diff --unified=1 ${filePath}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore']});
127
+ if (diffOut) {
128
+ console.log(chalk.gray(diffOut));
129
+ }
130
+ }
131
+ } catch (e) {
132
+
133
+ }
134
+ spinner.start("Thinking...");
135
+ }
136
+ }
137
+ }
138
+ );
139
+
140
+ spinner.succeed("Task completed");
141
+
142
+ try {
143
+ const statOut = execSync(`git diff --stat`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
144
+ if (statOut.trim()) {
145
+ console.log(`\n${chalk.cyan('=== Changed Files Summary ===')}`);
146
+ console.log(statOut);
147
+ }
148
+ } catch (e) {}
149
+
150
+ console.log(`\n${chalk.cyan('=== Final Output ===')}\n`);
151
+ console.log(result.summary);
152
+
153
+
154
+ const packageJsonPath = path.join(cwd, "package.json");
155
+ if (fs.existsSync(packageJsonPath)) {
156
+ try {
157
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
158
+ if (pkg.scripts && pkg.scripts.build) {
159
+ console.log(`\n${chalk.cyan('=== Verification ===')}`);
160
+ const buildSpinner = ora("Running build command...").start();
161
+ try {
162
+ const env = { ...process.env };
163
+ delete env.NODE_ENV;
164
+ execSync("npm run build", { stdio: "pipe", cwd, env });
165
+ buildSpinner.succeed("Build completed successfully.");
166
+ } catch (e: any) {
167
+ buildSpinner.fail("Build failed.");
168
+ console.error(chalk.red(e.stdout ? e.stdout.toString() : e.message));
169
+ }
170
+ }
171
+ } catch (e) {}
172
+ }
173
+ } catch (err: any) {
174
+ spinner.fail("Agent encountered an error");
175
+ console.error(err.message);
176
+ }
177
+ }
@@ -0,0 +1,54 @@
1
+ import { IntelligenceAgent } from "@vortex/engine";
2
+ import * as fs from "fs";
3
+
4
+ export async function suggestCommand(options: {
5
+ file: string;
6
+ apply?: boolean;
7
+ deep?: boolean;
8
+ }) {
9
+ const { default: ora } = await import("ora");
10
+ const { default: chalk } = await import("chalk");
11
+ const { default: boxen } = await import("boxen");
12
+ const { marked } = await import("marked");
13
+ const { default: TerminalRenderer } = await import("marked-terminal");
14
+
15
+ marked.setOptions({
16
+ renderer: new TerminalRenderer() as any,
17
+ });
18
+
19
+ console.log(
20
+ chalk.blue.bold(`\nGenerating Suggestions for: ${options.file}\n`)
21
+ );
22
+
23
+ if (!fs.existsSync(options.file)) {
24
+ console.error(chalk.red(`File not found: ${options.file}`));
25
+ return;
26
+ }
27
+
28
+ const spinner = ora("Analyzing file...").start();
29
+
30
+ try {
31
+ const content = fs.readFileSync(options.file, "utf8");
32
+ const agent = new IntelligenceAgent();
33
+
34
+ const suggestions = await agent.generateSuggestions(content);
35
+
36
+ spinner.succeed(chalk.green("Analysis complete!\n"));
37
+
38
+ const parsedSuggestions = await marked.parse(suggestions);
39
+
40
+ const formatted = boxen(parsedSuggestions.trim(), {
41
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
42
+ margin: { top: 1, bottom: 1 },
43
+ borderStyle: "double",
44
+ borderColor: "cyan",
45
+ title: chalk.cyan.bold(" AI Code Suggestions "),
46
+ titleAlignment: "center",
47
+ });
48
+
49
+ console.log(formatted);
50
+ } catch (err) {
51
+ spinner.fail(chalk.red("Failed to generate suggestions"));
52
+ console.error(err);
53
+ }
54
+ }
@@ -0,0 +1,56 @@
1
+ import { IntelligenceAgent } from "@vortex/engine";
2
+ import * as fs from "fs";
3
+ import * as chokidar from "chokidar";
4
+
5
+ export async function watchCommand(options: { deep?: boolean }) {
6
+ const { default: chalk } = await import("chalk");
7
+
8
+ console.log(
9
+ chalk.blue.bold("\nVortex Live Watcher\n")
10
+ );
11
+ console.log(
12
+ chalk.gray(" Watching repository changes for live AI feedback...")
13
+ );
14
+
15
+ if (options.deep) {
16
+ console.log(
17
+ chalk.yellow(
18
+ " ⚠️ Deep live analysis enabled (may consume more API tokens).\n"
19
+ )
20
+ );
21
+ }
22
+
23
+ const watcher = chokidar.watch(process.cwd(), {
24
+ ignored: /(^|[\/\\])\\..|node_modules|dist/,
25
+ persistent: true,
26
+ });
27
+
28
+ const agent = new IntelligenceAgent();
29
+ let isProcessing = false;
30
+
31
+ watcher.on("change", async (filePath) => {
32
+ if (isProcessing) return; // Debounce
33
+
34
+ console.log(
35
+ chalk.cyan(`\nDetected change in ${filePath}. Analyzing...`)
36
+ );
37
+ isProcessing = true;
38
+
39
+ try {
40
+ const content = fs.readFileSync(filePath, "utf8");
41
+ const feedback = await agent.generateSuggestions(content);
42
+
43
+ console.log(
44
+ chalk.green(`\n--- LIVE AI FEEDBACK FOR ${filePath} ---\n`)
45
+ );
46
+ console.log(feedback);
47
+ } catch (err) {
48
+ console.error(chalk.red("Analysis failed:"), err);
49
+ } finally {
50
+ isProcessing = false;
51
+ console.log(chalk.gray("\n Watching for more changes..."));
52
+ }
53
+ });
54
+
55
+ console.log(chalk.green(" Watcher started. Press Ctrl+C to exit.\n"));
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import * as path from "path";
5
+ import * as dotenv from "dotenv";
6
+ import * as os from "os";
7
+
8
+
9
+ const monorepoEnv = path.resolve(__dirname, "../../../.env");
10
+ if (require("fs").existsSync(monorepoEnv)) {
11
+ const envConfig = dotenv.parse(require("fs").readFileSync(monorepoEnv));
12
+ for (const k in envConfig) {
13
+ if (k === "NODE_ENV") continue;
14
+ if (!process.env[k]) process.env[k] = envConfig[k];
15
+ }
16
+ }
17
+
18
+
19
+ dotenv.config({ path: path.resolve(os.homedir(), ".vortexenv"), override: true });
20
+
21
+
22
+ const cwdEnv = path.resolve(process.cwd(), ".env");
23
+ if (require("fs").existsSync(cwdEnv)) {
24
+ const envConfig = dotenv.parse(require("fs").readFileSync(cwdEnv));
25
+ for (const k in envConfig) {
26
+ if (k === "NODE_ENV") continue;
27
+ process.env[k] = envConfig[k];
28
+ }
29
+ }
30
+
31
+ import { initCommand } from "./commands/init";
32
+ import { searchCommand } from "./commands/search";
33
+ import { reviewCommand } from "./commands/review";
34
+ import { issueCommand } from "./commands/issue";
35
+ import { graphCommand } from "./commands/graph";
36
+ import { suggestCommand } from "./commands/suggest";
37
+ import { fixNitbitsCommand } from "./commands/fix-nitbits";
38
+ import { analyzeCommand } from "./commands/analyze";
39
+ import { watchCommand } from "./commands/watch";
40
+ import { solveCommand } from "./commands/solve";
41
+ import { solveIssueCommand } from "./commands/solve-issue";
42
+ import { cacheCommand } from "./commands/cache";
43
+
44
+ const program = new Command();
45
+
46
+ program
47
+ .name("vortex")
48
+ .description("Developer Intelligence & PR Review Engine")
49
+ .version("0.1.0");
50
+
51
+ program.hook("preAction", (thisCommand, actionCommand) => {
52
+ if (actionCommand.opts().cache === false) {
53
+ process.env.VORTEX_DISABLE_CACHE = "true";
54
+ }
55
+ });
56
+
57
+ // ── Core Commands ──
58
+
59
+ program
60
+ .command("init")
61
+ .description("Initialize repository intelligence, embeddings, and PR history")
62
+ .option("--reindex", "Rebuild repository embeddings while preserving historical PR intelligence")
63
+ .action(initCommand);
64
+
65
+ program
66
+ .command("search")
67
+ .description("Search the indexed codebase semantically and get an AI explanation")
68
+ .requiredOption("-q, --query <text>", "Search query")
69
+ .option("-l, --limit <number>", "Number of results to consider", "5")
70
+ .option("--no-cache", "Disable LLM response caching")
71
+ .action(searchCommand);
72
+
73
+ program
74
+ .command("review")
75
+ .description("Review your changes using repository intelligence and historical PR patterns")
76
+ .option("--pr <number>", "Pull request number", Number)
77
+ .option("--deep", "Enable deep review analysis")
78
+ .option("--no-cache", "Disable LLM response caching")
79
+ .action(reviewCommand);
80
+
81
+ program
82
+ .command("issue")
83
+ .description("Analyze a GitHub issue, locate relevant codebase files, and propose a fix")
84
+ .requiredOption("--id <number>", "Issue number", Number)
85
+ .option("--no-cache", "Disable LLM response caching")
86
+ .action(issueCommand);
87
+
88
+ program
89
+ .command("graph")
90
+ .description("Generate a Mermaid JS dependency graph of the project or a specific file")
91
+ .option("--file <path>", "Filter graph to only include dependencies for a specific file")
92
+ .option("--detailed", "Include individual functions and classes in the graph instead of just files")
93
+ .action(graphCommand);
94
+
95
+ program
96
+ .command("solve")
97
+ .description("Autonomously solve a task by writing code and executing commands")
98
+ .argument("<prompt>", "The task you want the autonomous agent to solve")
99
+ .option("--auto-approve", "Skip interactive prompts for file writes and shell commands")
100
+ .option("--max-steps <number>", "Maximum number of agent loop iterations", Number, 30)
101
+ .option("--new-project <folder>", "Create a new project folder and initialize git before solving")
102
+ .action((prompt, options) => solveCommand(prompt, options));
103
+
104
+ program
105
+ .command("solve-issue")
106
+ .description("Autonomously solve a GitHub issue using local RAG context")
107
+ .requiredOption("--id <number>", "Issue number", Number)
108
+ .option("--auto-approve", "Skip interactive prompts for file writes and shell commands")
109
+ .option("--max-steps <number>", "Maximum number of agent loop iterations", Number, 30)
110
+ .action(solveIssueCommand);
111
+
112
+ // ── AI-Powered Commands ──
113
+
114
+ program
115
+ .command("suggest")
116
+ .description("Generate AI-powered code suggestions using repository patterns and historical intelligence")
117
+ .requiredOption("--file <path>", "Target file path")
118
+ .option("--apply", "Apply suggestions automatically")
119
+ .option("--deep", "Enable advanced contextual suggestions")
120
+ .option("--no-cache", "Disable LLM response caching")
121
+ .action(suggestCommand);
122
+
123
+ program
124
+ .command("fix-nitbits")
125
+ .description("Automatically fix formatting, comments, tests, CI issues, and repository-specific patterns")
126
+ .option("--safe", "Apply only deterministic safe fixes")
127
+ .option("--dry-run", "Preview fixes without modifying files")
128
+ .option("--files <paths>", "Comma-separated list of target files")
129
+ .action(fixNitbitsCommand);
130
+
131
+ program
132
+ .command("analyze")
133
+ .description("Analyze other contributors' PRs using repository history and architectural intelligence")
134
+ .requiredOption("--pr <number>", "Pull request number", Number)
135
+ .option("--deep", "Enable advanced PR intelligence analysis")
136
+ .option("--no-cache", "Disable LLM response caching")
137
+ .action(analyzeCommand);
138
+
139
+ program
140
+ .command("watch")
141
+ .description("Continuously monitor local changes and provide live review feedback")
142
+ .option("--deep", "Enable deep live analysis")
143
+ .action(watchCommand);
144
+
145
+ program.addCommand(cacheCommand);
146
+
147
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@vortex/typescript-config/base",
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", "dist"]
5
+ }
Binary file