@vortex-ai/cli 0.1.45 → 0.1.50

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vortex-ai/cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.50",
4
4
  "description": "Vortex CLI - The main entry point",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
@@ -28,9 +28,11 @@ export async function configSet(provider: string, value: string) {
28
28
  envKey = "GEMINI_API_KEY";
29
29
  } else if (providerUpper === "GROQ") {
30
30
  envKey = "GROQ_API_KEY";
31
+ } else if (providerUpper === "OPENROUTER") {
32
+ envKey = "OPENROUTER_API_KEY";
31
33
  } else {
32
- console.log(chalk.red(`Invalid option: ${chalk.bold(provider)}. Supported options are 'gemini' and 'groq'.`));
33
- console.log(chalk.gray(`Usage: vortex config set gemini <your-key>`));
34
+ console.log(chalk.red(`Invalid option: ${chalk.bold(provider)}. Supported options are 'gemini', 'groq', and 'openrouter'.`));
35
+ console.log(chalk.gray(`Usage: vortex config set openrouter <your-key>`));
34
36
  process.exit(1);
35
37
  }
36
38
 
@@ -1,35 +1,50 @@
1
1
  import { Indexer } from "@vortex/engine";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
2
4
 
3
5
  export async function initCommand(options: any) {
4
6
  const { default: ora } = await import("ora");
5
7
  const { default: chalk } = await import("chalk");
6
8
 
7
- console.log(chalk.blue.bold("\nInitializing Vortex Intelligence Layer\n"));
8
-
9
- if (options.reindex) {
10
- console.log(chalk.yellow(" Reindex mode: Rebuilding embeddings and BM25 index"));
11
- console.log(chalk.yellow(" Historical review memory will be preserved\n"));
12
- }
9
+ console.log(`\n ${chalk.cyan('◆')} Vortex\n`);
13
10
 
11
+ const startTime = Date.now();
14
12
  const spinner = ora("Scanning repository and building indices...").start();
15
13
 
16
14
  const indexer = new Indexer();
17
15
  try {
18
16
  const stats = await indexer.indexRepository(process.cwd());
19
17
 
20
- spinner.succeed(chalk.green("Initialization complete!\n"));
21
-
22
- console.log(chalk.white.bold(" Index Statistics:"));
23
- console.log(chalk.gray(` Files processed: ${stats.filesProcessed}`));
24
- console.log(chalk.gray(` Chunks indexed: ${stats.chunksIndexed}`));
25
- console.log(chalk.cyan(` BM25 documents: ${stats.bm25Documents}`));
26
- console.log(chalk.gray(` Vector store: SQLite (Prisma)`));
27
- console.log(chalk.gray(` BM25 index: .vortex-bm25.json\n`));
18
+ const durationStr = ((Date.now() - startTime) / 1000).toFixed(1) + "s";
19
+ spinner.succeed(chalk.green(`${stats.filesProcessed} files indexed · ${durationStr}\n`));
20
+
21
+ // Automatically update .gitignore
22
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
23
+ if (fs.existsSync(gitignorePath)) {
24
+ let content = fs.readFileSync(gitignorePath, "utf8");
25
+ const entriesToAdd = [
26
+ ".vortex.db",
27
+ ".vortex-bm25.json",
28
+ ".vortex_backup/"
29
+ ];
30
+
31
+ let changed = false;
32
+ for (const entry of entriesToAdd) {
33
+ if (!content.includes(entry)) {
34
+ if (!content.endsWith("\n") && content.length > 0) {
35
+ content += "\n";
36
+ }
37
+ content += entry + "\n";
38
+ changed = true;
39
+ }
40
+ }
41
+
42
+ if (changed) {
43
+ fs.writeFileSync(gitignorePath, content, "utf8");
44
+ console.log(chalk.gray(` Updated .gitignore`));
45
+ }
46
+ }
28
47
 
29
- console.log(chalk.green(" Ready for:"));
30
- console.log(chalk.gray(" • vortex search -q \"your query\" (hybrid search)"));
31
- console.log(chalk.gray(" • vortex review --pr <number> (multi-agent review)"));
32
- console.log(chalk.gray(" • vortex issue --id <number> (issue analysis)\n"));
33
48
  } catch (err) {
34
49
  spinner.fail(chalk.red("Failed to initialize"));
35
50
  console.error(err);
@@ -19,11 +19,8 @@ export async function reviewCommand(options: any) {
19
19
  renderer: new TerminalRenderer() as any,
20
20
  });
21
21
 
22
- if (options.pr) {
23
- console.log(chalk.blue.bold(`\nMulti-Agent Review for PR #${options.pr}\n`));
24
- } else {
25
- console.log(chalk.blue.bold(`\nMulti-Agent Review for Local Changes\n`));
26
- }
22
+ const reviewTitle = options.pr ? `PR #${options.pr}` : 'local';
23
+ console.log(`\n ${chalk.cyan('◆')} Vortex · review · ${reviewTitle}\n`);
27
24
 
28
25
  if (!process.env.GITHUB_TOKEN) {
29
26
  console.log(
@@ -68,28 +65,32 @@ export async function reviewCommand(options: any) {
68
65
  }
69
66
  }
70
67
 
68
+ const diffLines = diff.split('\n').filter(l => l.startsWith('+++') || l.startsWith('---')).length / 2 || 1;
69
+ spinner.succeed(`Fetched diff · ~${Math.floor(diffLines)} files changed`);
71
70
 
72
- spinner.text = "Extracting architectural queries from diff...";
73
71
  const agent = new IntelligenceAgent();
74
72
  const indexer = new Indexer();
75
73
 
74
+ spinner.start("Scanning repository and building indices for fresh context...");
75
+ await indexer.indexRepository(process.cwd());
76
+
77
+ spinner.start("Extracting architectural queries from diff...");
78
+
76
79
  const queries = await agent.extractSearchQueriesFromDiff(diff);
77
80
 
78
81
  const allChunks: any[] = [];
79
82
  if (queries.length > 0) {
80
- spinner.text = `Hybrid searching for: ${queries.join(", ")}...`;
83
+ spinner.text = `Hybrid searching for context...`;
81
84
  for (const query of queries) {
82
85
  const results = await indexer.hybridSearch(query, 3);
83
86
  allChunks.push(...results);
84
87
  }
85
88
  }
86
89
 
87
-
88
90
  const uniqueChunks = Array.from(
89
91
  new Map(allChunks.map((c) => [c.id, c])).values()
90
92
  );
91
93
 
92
-
93
94
  spinner.text = "Checking memory for relevant past reviews...";
94
95
  const memoryService = new MemoryService();
95
96
  const memories = await memoryService.recallRelevantMemories(
@@ -97,19 +98,17 @@ export async function reviewCommand(options: any) {
97
98
  3
98
99
  );
99
100
 
100
-
101
- spinner.text = `Running multi-agent review (Security + Architecture + Synthesis)...`;
101
+ spinner.text = `Running multi-agent review (Security, Architecture, Synthesis)...`;
102
102
  const review = await agent.generateMultiAgentReview(
103
103
  diff,
104
104
  uniqueChunks,
105
105
  memories
106
106
  );
107
107
 
108
- spinner.succeed(
109
- chalk.green(
110
- `Review complete in ${(review.durationMs / 1000).toFixed(1)}s!\n`
111
- )
112
- );
108
+ spinner.stop();
109
+ console.log(` ${chalk.green('✔')} Security Agent ${review.agentOutputs.security.findings.length} findings`);
110
+ console.log(` ${chalk.green('✔')} Architecture Agent ${review.agentOutputs.architecture.findings.length} suggestions`);
111
+ console.log(` ${chalk.green('✔')} Synthesizer Report ready\n`);
113
112
 
114
113
 
115
114
  const parsedReview = await marked.parse(review.markdownReport);
@@ -150,37 +149,23 @@ export async function reviewCommand(options: any) {
150
149
  );
151
150
 
152
151
 
153
- const { security, architecture } = review.agentOutputs;
154
-
155
- console.log(
156
- chalk.dim(
157
- `\n Security: ${security.riskLevel} (${security.findings.length} findings)`
158
- )
159
- );
160
- security.findings.forEach((f: any, i: number) => {
161
- const severityColor =
162
- f.severity === "critical" || f.severity === "high"
163
- ? chalk.red
164
- : f.severity === "medium"
165
- ? chalk.yellow
166
- : chalk.gray;
167
- console.log(severityColor(` [${f.severity.toUpperCase()}] ${f.title}`));
168
- });
169
-
170
- console.log(
171
- chalk.dim(
172
- `\n Architecture: ${architecture.consistencyScore} (${architecture.findings.length} findings)`
173
- )
174
- );
175
- architecture.findings.forEach((f: any, i: number) => {
176
- const severityColor =
177
- f.severity === "breaking"
178
- ? chalk.red
179
- : f.severity === "major"
180
- ? chalk.yellow
181
- : chalk.gray;
182
- console.log(severityColor(` [${f.severity.toUpperCase()}] ${f.title}`));
183
- });
152
+ const { security, architecture, synthesis } = review.agentOutputs;
153
+
154
+ const secCount = security.findings.length;
155
+ const archCount = architecture.findings.length;
156
+
157
+ // Draw the summary bar chart
158
+ console.log(` ────────────────────────────────────────`);
159
+
160
+ const secBar = secCount > 0 ? chalk.red('█'.repeat(Math.min(secCount, 10))) : chalk.green('░');
161
+ console.log(` SECURITY ${secBar.padEnd(20)} ${secCount} issues`);
162
+
163
+ const archBar = archCount > 0 ? chalk.yellow('█'.repeat(Math.min(archCount, 10))) : chalk.green('░');
164
+ console.log(` ARCH ${archBar.padEnd(20)} ${archCount} suggestions`);
165
+
166
+ const logicBar = synthesis.verdict === 'SAFE_TO_MERGE' ? chalk.green('░') : chalk.red('█');
167
+ console.log(` LOGIC ${logicBar.padEnd(20)} ${synthesis.verdict.toLowerCase().replace(/_/g, ' ')}`);
168
+ console.log(` ────────────────────────────────────────\n`);
184
169
 
185
170
  if (uniqueChunks.length > 0) {
186
171
  console.log(chalk.cyan.dim("\n Cross-Referenced Architecture:"));
@@ -23,7 +23,7 @@ export async function searchCommand(options: any) {
23
23
  return;
24
24
  }
25
25
 
26
- spinner.text = `Found ${results.length} relevant code chunks. Analyzing with Gemini...`;
26
+ spinner.text = `Found ${results.length} relevant code chunks. Analyzing with AI engine...`;
27
27
 
28
28
  const memoryService = new MemoryService();
29
29
  const memories = await memoryService.recallRelevantMemories(options.query, 3);
@@ -8,11 +8,15 @@ import {
8
8
  AutonomousAgent,
9
9
  FileReadTool,
10
10
  FileWriteTool,
11
+ FileEditTool,
11
12
  ShellExecuteTool,
12
13
  GrepTool,
13
14
  RagSearchTool,
15
+ WebSearchTool,
14
16
  ApprovalCallback,
15
- AgentContextChunk
17
+ AgentContextChunk,
18
+ IntelligenceAgent,
19
+ Indexer
16
20
  } from "@vortex/engine";
17
21
  import { initDatabase } from "@vortex/db";
18
22
  import { getGitRoot, isGitRepo } from "@vortex/git";
@@ -29,7 +33,7 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
29
33
  }
30
34
  cwd = projectPath;
31
35
  process.chdir(cwd);
32
-
36
+
33
37
  if (!fs.existsSync(path.join(cwd, ".git"))) {
34
38
  try {
35
39
  execSync("git init", { cwd, stdio: "ignore" });
@@ -46,12 +50,11 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
46
50
  rootPath = getGitRoot(cwd);
47
51
  }
48
52
 
49
- console.log(`\nVortex Autonomous Agent activated`);
50
- console.log(`Task: "${prompt}"\n`);
53
+ console.log(`\n ${chalk.cyan('◆')} Vortex · solve\n`);
51
54
 
52
55
  await initDatabase();
53
56
 
54
- const spinner = ora("Initializing Vector Store for Project Memory...").start();
57
+ const spinner = ora("Reading codebase...").start();
55
58
 
56
59
  try {
57
60
  const vectorStore = new VectorStore();
@@ -59,19 +62,89 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
59
62
  const embedder = new LocalEmbedder();
60
63
  const hybridRetriever = new HybridRetriever(vectorStore, bm25Index, embedder);
61
64
  const agent = new AutonomousAgent();
62
-
65
+
63
66
  if (options.maxSteps !== undefined) {
64
67
  (agent as any).maxToolIterations = options.maxSteps;
65
68
  }
66
69
 
67
- spinner.text = "Thinking...";
70
+ if (!options.contextChunks || options.contextChunks.length === 0) {
71
+ try {
72
+ const indexer = new Indexer();
73
+ const relevantContext = await indexer.hybridSearch(prompt, 5);
74
+ options.contextChunks = relevantContext.map((c: any) => ({
75
+ file: c.file,
76
+ symbolPath: c.symbolPath || "anonymous",
77
+ content: c.content,
78
+ kind: c.kind || "unknown",
79
+ }));
80
+ } catch (e) {
81
+ console.warn(chalk.yellow("⚠️ Could not search vector store. Did you run 'vortex init'?"));
82
+ options.contextChunks = [];
83
+ }
84
+ }
85
+
86
+ spinner.text = "Planning approach...";
87
+ const intelligenceAgent = new IntelligenceAgent();
88
+ let executionPlanStr = await intelligenceAgent.generateExecutionPlan(prompt, options.contextChunks || []);
89
+
90
+ let planSummary = "task";
91
+ let stepCount = 0;
92
+ let executionPlan = "";
93
+ let filesToRead: string[] = [];
94
+ try {
95
+ const parsedPlan = JSON.parse(executionPlanStr);
96
+ planSummary = parsedPlan.summary || planSummary;
97
+ stepCount = parsedPlan.steps ? parsedPlan.steps.length : 0;
98
+ executionPlan = parsedPlan.steps ? parsedPlan.steps.map((s: string, i: number) => `${i + 1}. ${s}`).join("\n") : executionPlanStr;
99
+ filesToRead = parsedPlan.filesToRead || [];
100
+ } catch {
101
+ executionPlan = executionPlanStr;
102
+ }
103
+
104
+ if (filesToRead.length > 0) {
105
+ for (const file of filesToRead) {
106
+ try {
107
+ const fullPath = path.resolve(rootPath, file);
108
+ if (fs.existsSync(fullPath)) {
109
+ const content = fs.readFileSync(fullPath, "utf-8");
110
+ options.contextChunks!.push({ file, symbolPath: "file", content, kind: "file" });
111
+ }
112
+ } catch { }
113
+ }
114
+ }
115
+
116
+ spinner.stopAndPersist({ symbol: chalk.green('✔'), text: `Plan ready · ${planSummary} (${stepCount} steps)\n` });
117
+
118
+ console.log(`\n${chalk.cyan.dim("─── Execution Plan ───")}`);
119
+ console.log(chalk.gray(executionPlan));
120
+ console.log(`${chalk.cyan.dim("──────────────────────")}\n`);
121
+
122
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
123
+ const answer = await rl.question(` Proceed? (Y/n/edit) › `);
124
+
125
+ if (answer.toLowerCase() === 'n') {
126
+ console.log(chalk.red('\n ✖ Task aborted by user.\n'));
127
+ rl.close();
128
+ process.exit(0);
129
+ } else if (answer.toLowerCase() === 'edit') {
130
+ const edits = await rl.question(` Enter instructions › `);
131
+ if (edits.trim()) {
132
+ executionPlan += `\n\n### User Modifications\n${edits.trim()}`;
133
+ }
134
+ }
135
+ rl.close();
136
+
137
+ const enrichedPrompt = `Original Task:\n${prompt}\n\nExecution Plan generated by Architect:\n${executionPlan}`;
138
+
139
+ console.log(`\n ${chalk.cyan('◆')} Vortex · solving\n`);
140
+ spinner.start("Initializing agent...");
68
141
 
69
142
  const approvalCallback: ApprovalCallback = async (action: string, description: string): Promise<boolean> => {
70
143
  if (options.autoApprove) return true;
71
-
144
+
72
145
  const currentText = spinner.text;
73
146
  spinner.stop();
74
-
147
+
75
148
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
76
149
  let question = "";
77
150
  if (action === "shell_execute") {
@@ -81,10 +154,10 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
81
154
  } else {
82
155
  question = `\n${chalk.yellow('⚠️ Agent wants to perform action:')} ${action} on ${description}\nAllow? (y/N) `;
83
156
  }
84
-
157
+
85
158
  const answer = await rl.question(question);
86
159
  rl.close();
87
-
160
+
88
161
  spinner.start(currentText);
89
162
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
90
163
  };
@@ -93,26 +166,37 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
93
166
  agent.registerTools([
94
167
  new FileReadTool(rootPath),
95
168
  new FileWriteTool(rootPath, vectorStore, embedder, approvalCallback),
169
+ new FileEditTool(rootPath, vectorStore, embedder, approvalCallback),
96
170
  new ShellExecuteTool(rootPath, approvalCallback),
97
171
  new GrepTool(rootPath),
98
172
  new RagSearchTool(hybridRetriever),
173
+ new WebSearchTool(),
99
174
  ]);
100
175
 
176
+ const initialState = {
177
+ evidence: { filesRead: filesToRead, symbolsObserved: [], dependenciesObserved: [], externalSchemasFound: {}, confidence: 'LOW' as any },
178
+ plan: { steps: [], currentStepIndex: 0, completedSteps: [] },
179
+ execution: { filesModified: [], commandsRun: [], lastError: null, consecutiveFailures: 0 },
180
+ verification: { contractItems: [], passed: [], failed: [] },
181
+ verdict: 'IN_PROGRESS' as any
182
+ };
183
+
101
184
  const result = await agent.run(
102
185
  {
103
- diff: prompt,
186
+ diff: enrichedPrompt,
104
187
  contextChunks: options.contextChunks || [],
105
188
  },
106
189
  {
190
+ initialState,
107
191
  onToolCall: (toolName, args) => {
108
192
  if (toolName === "write_file") {
109
- spinner.text = `Writing file: ${args.path}...`;
193
+ spinner.text = `Writing ${args.path}...`;
194
+ } else if (toolName === "read_file") {
195
+ spinner.text = `Inspecting ${args.path}...`;
110
196
  } 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...`;
197
+ spinner.text = `Running verification...`;
114
198
  } else {
115
- spinner.text = `Agent is using tool: ${toolName}...`;
199
+ spinner.text = `Using ${toolName}...`;
116
200
  }
117
201
  },
118
202
  onToolResult: (toolName, toolResult) => {
@@ -121,15 +205,15 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
121
205
  try {
122
206
  const match = toolResult.match(/Wrote to (.*) successfully/);
123
207
  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
- }
208
+ const filePath = match[1];
209
+ const diffOut = execSync(`git diff --numstat ${filePath}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
210
+ const diffParts = diffOut.split(/\s+/);
211
+ const additions = diffParts[0] || '0';
212
+ const deletions = diffParts[1] || '0';
213
+ console.log(` ${chalk.cyan('●')} Writing ${filePath}`);
214
+ console.log(` ${chalk.green(`+ ${additions} lines`)} · ${chalk.red(`- ${deletions} lines`)}\n`);
130
215
  }
131
216
  } catch (e) {
132
-
133
217
  }
134
218
  spinner.start("Thinking...");
135
219
  }
@@ -137,38 +221,53 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
137
221
  }
138
222
  );
139
223
 
140
- spinner.succeed("Task completed");
224
+ let iterationCount = 1;
225
+ if (result.summary) {
226
+ const iterMatch = result.summary.match(/Iter (\d+)/);
227
+ if (iterMatch) iterationCount = parseInt(iterMatch[1], 10);
228
+ }
141
229
 
230
+ let filesChangedCount = 0;
142
231
  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);
232
+ const statOut = execSync(`git diff --shortstat`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
233
+ const filesMatch = statOut.match(/(\d+) file/);
234
+ if (filesMatch) {
235
+ filesChangedCount = parseInt(filesMatch[1], 10);
147
236
  }
148
237
  } catch (e) {}
149
238
 
150
- console.log(`\n${chalk.cyan('=== Final Output ===')}\n`);
151
- console.log(result.summary);
239
+ spinner.stop();
240
+ const isIncomplete = result.verdict === 'INCOMPLETE' || result.state?.verdict === 'INCOMPLETE';
241
+ const isComplete = !isIncomplete;
152
242
 
243
+ if (isIncomplete) {
244
+ console.log(`\n ${chalk.red('✖')} Task incomplete · stopped or failed`);
245
+ } else {
246
+ console.log(`\n ────────────────────────────────────────`);
247
+ console.log(` ${chalk.green('✔')} Task complete · ${filesChangedCount} files modified · ${iterationCount} iterations`);
248
+ console.log(` ────────────────────────────────────────\n`);
249
+ }
153
250
 
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));
251
+ if (isComplete) {
252
+ const packageJsonPath = path.join(cwd, "package.json");
253
+ if (fs.existsSync(packageJsonPath)) {
254
+ try {
255
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
256
+ if (pkg.scripts && pkg.scripts.build) {
257
+ console.log(`\n${chalk.cyan('=== Verification ===')}`);
258
+ const buildSpinner = ora("Running build command...").start();
259
+ try {
260
+ const env = { ...process.env };
261
+ delete env.NODE_ENV;
262
+ execSync("npm run build", { stdio: "pipe", cwd, env });
263
+ buildSpinner.succeed("Build completed successfully.");
264
+ } catch (e: any) {
265
+ buildSpinner.fail("Build failed.");
266
+ console.error(chalk.red(e.stdout ? e.stdout.toString() : e.message));
267
+ }
169
268
  }
170
- }
171
- } catch (e) {}
269
+ } catch (e) { }
270
+ }
172
271
  }
173
272
  } catch (err: any) {
174
273
  spinner.fail("Agent encountered an error");
package/src/index.ts CHANGED
@@ -4,8 +4,6 @@ import { Command } from "commander";
4
4
  import * as path from "path";
5
5
  import * as dotenv from "dotenv";
6
6
  import * as os from "os";
7
-
8
- // Suppress dotenv logging
9
7
  process.env.DOTENV_CONFIG_QUIET = "true";
10
8
 
11
9
 
@@ -36,13 +34,10 @@ import { searchCommand } from "./commands/search";
36
34
  import { reviewCommand } from "./commands/review";
37
35
  import { issueCommand } from "./commands/issue";
38
36
  import { graphCommand } from "./commands/graph";
39
- import { suggestCommand } from "./commands/suggest";
40
- import { fixNitbitsCommand } from "./commands/fix-nitbits";
41
- import { analyzeCommand } from "./commands/analyze";
37
+
42
38
  import { solveCommand } from "./commands/solve";
43
39
  import { solveIssueCommand } from "./commands/solve-issue";
44
40
  import { cacheCommand } from "./commands/cache";
45
- import { configSet, configGet, configList } from "./commands/config";
46
41
 
47
42
  const program = new Command();
48
43
 
@@ -53,10 +48,32 @@ program
53
48
  .description("Developer Intelligence & PR Review Engine")
54
49
  .version(version);
55
50
 
56
- program.hook("preAction", (thisCommand, actionCommand) => {
51
+ program.hook("preAction", async (thisCommand, actionCommand) => {
57
52
  if (actionCommand.opts().cache === false) {
58
53
  process.env.VORTEX_DISABLE_CACHE = "true";
59
54
  }
55
+
56
+ const cmdName = actionCommand.name();
57
+ if (['solve', 'solve-issue', 'review', 'suggest', 'analyze', 'search'].includes(cmdName)) {
58
+ const { default: ora } = await import("ora");
59
+ const { default: chalk } = await import("chalk");
60
+
61
+ console.log(`\n ${chalk.cyan('◆')} Vortex\n`);
62
+ const spinner = ora("Looking for model...").start();
63
+
64
+ if (process.env.GEMINI_API_KEY || process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY) {
65
+ const priorityString = process.env.VORTEX_MODEL_PRIORITY;
66
+ const models = priorityString
67
+ ? priorityString.split(",").map(s => s.trim()).filter(Boolean)
68
+ : ["nvidia/nemotron-3-ultra-550b-a55b:free", "nex-agi/nex-n2-pro:free", "openrouter/owl-alpha", "gemini-2.5-flash"];
69
+ const topModel = models[0] || "gemini";
70
+ spinner.succeed(`Model router active · priority: ${topModel}\n`);
71
+ } else {
72
+ spinner.fail(chalk.red("No model configured"));
73
+ console.log(`\n Run: ${chalk.cyan('vortex config set gemini "your_key_here"')}\n`);
74
+ process.exit(1);
75
+ }
76
+ }
60
77
  });
61
78
 
62
79
  // ── Core Commands ──
@@ -116,50 +133,6 @@ program
116
133
 
117
134
  // ── AI-Powered Commands ──
118
135
 
119
- program
120
- .command("suggest")
121
- .description("Generate AI-powered code suggestions using repository patterns and historical intelligence")
122
- .requiredOption("--file <path>", "Target file path")
123
- .option("--apply", "Apply suggestions automatically")
124
- .option("--deep", "Enable advanced contextual suggestions")
125
- .option("--no-cache", "Disable LLM response caching")
126
- .action(suggestCommand);
127
-
128
- program
129
- .command("fix-nitbits")
130
- .description("Automatically fix formatting, comments, tests, CI issues, and repository-specific patterns")
131
- .option("--safe", "Apply only deterministic safe fixes")
132
- .option("--dry-run", "Preview fixes without modifying files")
133
- .option("--files <paths>", "Comma-separated list of target files")
134
- .action(fixNitbitsCommand);
135
-
136
- program
137
- .command("analyze")
138
- .description("Analyze other contributors' PRs using repository history and architectural intelligence")
139
- .requiredOption("--pr <number>", "Pull request number", Number)
140
- .option("--deep", "Enable advanced PR intelligence analysis")
141
- .option("--no-cache", "Disable LLM response caching")
142
- .action(analyzeCommand);
143
-
144
- const configCmd = program
145
- .command("config")
146
- .description("Manage global configuration and API keys");
147
-
148
- configCmd
149
- .command("set <provider> <key>")
150
- .description("Set an API key for a specific provider (gemini or groq)")
151
- .action(configSet);
152
-
153
- configCmd
154
- .command("get <key>")
155
- .description("Get a global configuration value")
156
- .action(configGet);
157
-
158
- configCmd
159
- .command("list")
160
- .description("List all global configuration values")
161
- .action(configList);
162
-
163
136
  program.addCommand(cacheCommand);
164
137
 
165
138
  program.parse();