@voria/cli 0.0.3 → 0.0.5
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/README.md +75 -380
- package/bin/voria +635 -481
- package/docs/CHANGELOG.md +19 -0
- package/docs/USER_GUIDE.md +34 -5
- package/package.json +1 -1
- package/python/voria/__init__.py +1 -1
- package/python/voria/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/__pycache__/engine.cpython-312.pyc +0 -0
- package/python/voria/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/__pycache__/setup.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__pycache__/loop.cpython-312.pyc +0 -0
- package/python/voria/core/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/executor/__pycache__/executor.cpython-312.pyc +0 -0
- package/python/voria/core/executor/executor.py +5 -0
- package/python/voria/core/github/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/github/__pycache__/client.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__init__.py +16 -0
- package/python/voria/core/llm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/base.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/claude_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/deepseek_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/gemini_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/kimi_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/minimax_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/modal_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/model_discovery.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/openai_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/siliconflow_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/base.py +12 -0
- package/python/voria/core/llm/claude_provider.py +46 -0
- package/python/voria/core/llm/deepseek_provider.py +109 -0
- package/python/voria/core/llm/gemini_provider.py +44 -0
- package/python/voria/core/llm/kimi_provider.py +109 -0
- package/python/voria/core/llm/minimax_provider.py +187 -0
- package/python/voria/core/llm/modal_provider.py +33 -0
- package/python/voria/core/llm/model_discovery.py +104 -155
- package/python/voria/core/llm/openai_provider.py +33 -0
- package/python/voria/core/llm/siliconflow_provider.py +109 -0
- package/python/voria/core/patcher/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/patcher/__pycache__/patcher.cpython-312.pyc +0 -0
- package/python/voria/core/setup.py +4 -1
- package/python/voria/core/testing/__pycache__/definitions.cpython-312.pyc +0 -0
- package/python/voria/core/testing/__pycache__/runner.cpython-312.pyc +0 -0
- package/python/voria/core/testing/definitions.py +87 -0
- package/python/voria/core/testing/runner.py +324 -0
- package/python/voria/engine.py +736 -232
package/bin/voria
CHANGED
|
@@ -53,102 +53,182 @@ const BANNER = `
|
|
|
53
53
|
|
|
54
54
|
const HELP_TEXT = `
|
|
55
55
|
${BANNER}
|
|
56
|
-
${colors.dim("
|
|
57
|
-
${colors.
|
|
58
|
-
${colors.dim("
|
|
56
|
+
${colors.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
57
|
+
${colors.bold(colors.blue(" AI-Powered Autonomous Fixer v0.0.5"))}
|
|
58
|
+
${colors.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
59
59
|
|
|
60
60
|
${colors.bold(colors.blue("USAGE:"))}
|
|
61
61
|
voria [command] [options]
|
|
62
62
|
|
|
63
|
-
${colors.bold(colors.blue("
|
|
64
|
-
${colors.blue("🚀")} voria --init ${colors.dim("Initialize project & choose
|
|
65
|
-
${colors.blue("⚙️")}
|
|
63
|
+
${colors.bold(colors.blue("SETUP:"))}
|
|
64
|
+
${colors.blue("🚀")} voria --init ${colors.dim("Initialize project & choose LLM provider")}
|
|
65
|
+
${colors.blue("⚙️")} voria --config ${colors.dim("Configure LLM provider and settings")}
|
|
66
66
|
${colors.blue("🔑")} voria --set-github-token ${colors.dim("Set your GitHub personal access token")}
|
|
67
|
-
${colors.blue("📋")} voria --list-issues <repo>${colors.dim("List issues in a repository")}
|
|
68
|
-
${colors.blue("🔭")} voria --graph ${colors.dim("Show codebase dependency graph")}
|
|
69
|
-
${colors.blue("❓")} voria --help ${colors.dim("Show this help message")}
|
|
70
|
-
${colors.blue("🏷️")} voria --version ${colors.dim("Show current version")}
|
|
71
67
|
|
|
72
|
-
${colors.bold(colors.blue("
|
|
73
|
-
${colors.blue("
|
|
68
|
+
${colors.bold(colors.blue("SECURITY & TESTING:"))}
|
|
69
|
+
${colors.blue("🛡️")} voria scan ${colors.dim("Full security audit — run ALL tests in parallel")}
|
|
70
|
+
${colors.blue("🧪")} voria test <test_id> ${colors.dim("Run a specific security/performance test")}
|
|
71
|
+
${colors.blue("📋")} voria list-tests ${colors.dim("List all 25+ available tests")}
|
|
72
|
+
${colors.blue("👁️")} voria watch ${colors.dim("File watcher — re-runs tests on code changes")}
|
|
73
|
+
${colors.blue("📊")} voria diff [a] [b] ${colors.dim("Compare security posture between git refs")}
|
|
74
|
+
${colors.blue("⚡")} voria benchmark <url> ${colors.dim("HTTP benchmarking — p50/p95/p99 latencies")}
|
|
75
|
+
${colors.blue("🏗️")} voria ci ${colors.dim("CI/CD scan — outputs SARIF for GitHub Security")}
|
|
76
|
+
|
|
77
|
+
${colors.bold(colors.blue("ISSUE FIXING:"))}
|
|
78
|
+
${colors.blue("🐞")} voria issue <repo> <num> ${colors.dim("Fix a specific GitHub issue with AI")}
|
|
79
|
+
${colors.blue("🔧")} voria fix --auto <r> <n> ${colors.dim("Auto-apply patch and run tests")}
|
|
80
|
+
${colors.blue("📝")} voria plan <description> ${colors.dim("Generate fix plan without applying")}
|
|
74
81
|
${colors.blue("📦")} voria apply <patch> ${colors.dim("Apply a patch file")}
|
|
82
|
+
${colors.blue("📋")} voria --list-issues <r> ${colors.dim("List issues in a repository")}
|
|
83
|
+
|
|
84
|
+
${colors.bold(colors.blue("PROJECT:"))}
|
|
75
85
|
${colors.blue("📊")} voria status ${colors.dim("Show current project status")}
|
|
76
86
|
${colors.blue("💰")} voria token info ${colors.dim("Show token usage info")}
|
|
77
87
|
${colors.blue("📝")} voria logs ${colors.dim("Show recent activity logs")}
|
|
78
88
|
${colors.blue("🔄")} voria reset ${colors.dim("Reset project configuration")}
|
|
89
|
+
${colors.blue("🔭")} voria --graph ${colors.dim("Show codebase dependency graph")}
|
|
79
90
|
|
|
80
91
|
${colors.bold(colors.blue("OPTIONS:"))}
|
|
81
|
-
--
|
|
82
|
-
--
|
|
92
|
+
--category <cat> ${colors.dim("Filter scan by category (security/performance/stress/quality/all)")}
|
|
93
|
+
--requests <n> ${colors.dim("Number of requests for benchmark (default: 100)")}
|
|
94
|
+
--concurrency <n> ${colors.dim("Concurrency for benchmark (default: 10)")}
|
|
83
95
|
--apply ${colors.dim("Automatically create PR after fixing")}
|
|
84
96
|
--dry-run ${colors.dim("Don't create PR, just propose")}
|
|
85
97
|
--verbose, -v ${colors.dim("Show detailed output")}
|
|
86
98
|
|
|
87
99
|
${colors.bold(colors.blue("EXAMPLES:"))}
|
|
88
|
-
${colors.cyan("#
|
|
89
|
-
voria
|
|
100
|
+
${colors.cyan("voria --init")} ${colors.dim("# Setup with your LLM provider")}
|
|
101
|
+
${colors.cyan("voria scan")} ${colors.dim("# Full security audit")}
|
|
102
|
+
${colors.cyan("voria scan --category performance")} ${colors.dim("# Performance tests only")}
|
|
103
|
+
${colors.cyan("voria test hardcoded_secrets")} ${colors.dim("# Run specific test")}
|
|
104
|
+
${colors.cyan("voria diff main feature-branch")} ${colors.dim("# Compare branches")}
|
|
105
|
+
${colors.cyan("voria benchmark http://localhost:3000")} ${colors.dim("# HTTP benchmark")}
|
|
106
|
+
${colors.cyan("voria issue owner/repo 42 --apply")} ${colors.dim("# Fix issue and create PR")}
|
|
107
|
+
${colors.cyan("voria ci")} ${colors.dim("# CI/CD SARIF output")}
|
|
108
|
+
|
|
109
|
+
${colors.bold(colors.blue("DOCS:"))} ${colors.blue("https://github.com/Srizdebnath/voria")}
|
|
110
|
+
`;
|
|
90
111
|
|
|
91
|
-
|
|
92
|
-
voria issue owner/repo 42 --apply
|
|
112
|
+
const VERSION = '0.0.5';
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
voria plan "Remove deprecated function" --llm claude --model opus-4.6
|
|
114
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
96
115
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
function log(msg) { console.log(`${colors.blue("│")} ${msg}`); }
|
|
117
|
+
function logHeader(msg) { console.log(`\n${colors.bgBlue(colors.bold(` ${msg} `))}`); }
|
|
118
|
+
function logDivider() { console.log(colors.blue("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")); }
|
|
119
|
+
function logSuccess(msg) { console.log(`${colors.green("✓")} ${msg}`); }
|
|
120
|
+
function logError(msg) { console.error(`${colors.red("✖")} ${msg}`); }
|
|
121
|
+
function logInfo(msg) { console.log(`${colors.blue("ℹ")} ${colors.dim(msg)}`); }
|
|
122
|
+
function logWarn(msg) { console.log(`${colors.yellow("⚠")} ${msg}`); }
|
|
102
123
|
|
|
103
124
|
// Load configuration
|
|
104
125
|
function loadConfig() {
|
|
105
126
|
const config = {};
|
|
106
|
-
|
|
107
127
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
108
128
|
try {
|
|
109
|
-
|
|
110
|
-
Object.assign(config, data);
|
|
129
|
+
Object.assign(config, JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')));
|
|
111
130
|
} catch (e) {
|
|
112
|
-
|
|
131
|
+
logWarn(`Error reading global config: ${e.message}`);
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
|
-
|
|
116
134
|
if (fs.existsSync(PROJECT_CONFIG)) {
|
|
117
135
|
try {
|
|
118
|
-
|
|
119
|
-
Object.assign(config, data);
|
|
136
|
+
Object.assign(config, JSON.parse(fs.readFileSync(PROJECT_CONFIG, 'utf-8')));
|
|
120
137
|
} catch (e) {
|
|
121
|
-
|
|
138
|
+
logWarn(`Error reading project config: ${e.message}`);
|
|
122
139
|
}
|
|
123
140
|
}
|
|
124
|
-
|
|
125
141
|
return config;
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
// Save configuration
|
|
129
144
|
function saveConfig(config, isGlobal = true) {
|
|
130
145
|
const file = isGlobal ? CONFIG_FILE : PROJECT_CONFIG;
|
|
131
146
|
fs.writeFileSync(file, JSON.stringify(config, null, 2));
|
|
132
147
|
}
|
|
133
148
|
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
function requireInit() {
|
|
150
|
+
const config = loadConfig();
|
|
151
|
+
if (!config.llm_api_key && !config.llm_provider) {
|
|
152
|
+
logError(`voria not initialized. Run ${colors.cyan("voria --init")}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
return config;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getApiKey(config) {
|
|
159
|
+
let apiKey = config.llm_api_key;
|
|
160
|
+
if (!apiKey) {
|
|
161
|
+
const envKey = `${(config.llm_provider || 'openai').toUpperCase()}_API_KEY`;
|
|
162
|
+
apiKey = process.env[envKey];
|
|
163
|
+
}
|
|
164
|
+
if (!apiKey) {
|
|
165
|
+
logError(`API key not found. Run ${colors.cyan("voria --config")} or set env var.`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
return apiKey;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fetchModels(provider, apiKey) {
|
|
172
|
+
try {
|
|
173
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'voria', 'core', 'llm', 'model_discovery.py');
|
|
174
|
+
const output = execSync(`python3 "${scriptPath}" "${provider}" "${apiKey}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
175
|
+
return JSON.parse(output);
|
|
176
|
+
} catch (e) { return []; }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Python Engine IPC ───────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
async function callPythonEngine(command) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
const pythonScript = path.join(__dirname, '..', 'python', 'voria', 'engine.py');
|
|
184
|
+
const pythonPath = path.join(__dirname, '..', 'python');
|
|
185
|
+
const venvPath = path.join(__dirname, '..', 'venv', 'bin', 'python');
|
|
186
|
+
const pythonCmd = fs.existsSync(venvPath) ? venvPath : 'python3';
|
|
187
|
+
const env = { ...process.env };
|
|
188
|
+
env.PYTHONPATH = pythonPath + (env.PYTHONPATH ? ':' + env.PYTHONPATH : '');
|
|
189
|
+
|
|
190
|
+
const python = spawn(pythonCmd, [pythonScript], {
|
|
191
|
+
cwd: process.cwd(), stdio: ['pipe', 'pipe', 'pipe'], env
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let output = '';
|
|
195
|
+
let errorOutput = '';
|
|
196
|
+
python.stdout.on('data', (d) => { output += d.toString(); });
|
|
197
|
+
python.stderr.on('data', (d) => { errorOutput += d.toString(); });
|
|
198
|
+
|
|
199
|
+
python.on('close', (code) => {
|
|
200
|
+
if (code !== 0) logInfo(`Python engine debug: ${errorOutput.slice(-300)}`);
|
|
201
|
+
try {
|
|
202
|
+
const lines = output.trim().split('\n').filter(l => l.length > 0);
|
|
203
|
+
const lastLine = lines[lines.length - 1];
|
|
204
|
+
resolve(JSON.parse(lastLine));
|
|
205
|
+
} catch (e) {
|
|
206
|
+
reject(new Error(`Failed to parse engine response: ${e.message}`));
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
python.stdin.write(JSON.stringify(command) + '\n');
|
|
211
|
+
python.stdin.end();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─── Setup & Config ──────────────────────────────────────────────
|
|
216
|
+
|
|
136
217
|
async function runSetup() {
|
|
137
218
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
138
219
|
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
139
220
|
|
|
140
221
|
try {
|
|
141
222
|
console.log(BANNER);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
223
|
+
logHeader("PROJECT INITIALIZATION");
|
|
224
|
+
log(colors.blue("Configuring your intelligence backend"));
|
|
225
|
+
logDivider();
|
|
226
|
+
|
|
146
227
|
if (!fs.existsSync(path.join(process.cwd(), '.git'))) {
|
|
147
|
-
|
|
148
|
-
console.log(` voria works best when integrated with Git.\n`);
|
|
228
|
+
logWarn(`Not in a Git repository. voria works best with Git.`);
|
|
149
229
|
}
|
|
150
230
|
|
|
151
|
-
console.log(
|
|
231
|
+
console.log(`\n${colors.bold(colors.blue("Choose LLM Provider:"))}`);
|
|
152
232
|
console.log(` ${colors.blue("1)")} OpenAI (GPT-4o, GPT-4 Mini)`);
|
|
153
233
|
console.log(` ${colors.blue("2)")} Claude (Anthropic)`);
|
|
154
234
|
console.log(` ${colors.blue("3)")} Gemini (Google)`);
|
|
@@ -163,494 +243,558 @@ async function runSetup() {
|
|
|
163
243
|
const provider = providers[parseInt(choice) - 1] || 'openai';
|
|
164
244
|
|
|
165
245
|
const apiKey = await question(`${colors.cyan(provider.toUpperCase() + " API Key")} › `);
|
|
166
|
-
|
|
167
|
-
if (!apiKey) {
|
|
168
|
-
console.log(`\n${colors.red("✖")} API key is required.`);
|
|
169
|
-
rl.close();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
246
|
+
if (!apiKey) { logError("API key is required."); rl.close(); return; }
|
|
172
247
|
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
models.
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
|
|
248
|
+
logInfo("Fetching latest models...");
|
|
249
|
+
const fetchedModels = fetchModels(provider, apiKey);
|
|
250
|
+
const models = fetchedModels.length > 0 ? fetchedModels.map(m => m.name) : (PROVIDER_MODELS[provider] || ['gpt-4o']);
|
|
251
|
+
|
|
252
|
+
console.log(`\n${colors.bold(colors.blue("Select Model:"))}`);
|
|
253
|
+
models.forEach((m, i) => console.log(` ${colors.blue((i + 1) + ")")} ${m}`));
|
|
179
254
|
const modelChoice = await question(`\n${colors.cyan("selection")} ${colors.dim("(1)")} › `) || '1';
|
|
180
255
|
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
181
256
|
|
|
182
257
|
const budget = await question(`${colors.cyan("Daily Budget ($)")} ${colors.dim("(10.0)")} › `) || '10.0';
|
|
183
258
|
|
|
184
|
-
console.log(`\n${colors.bold("Test Framework:")}`);
|
|
259
|
+
console.log(`\n${colors.bold(colors.blue("Test Framework:"))}`);
|
|
185
260
|
console.log(` ${colors.blue("1)")} pytest ${colors.dim("(Python)")}`);
|
|
186
261
|
console.log(` ${colors.blue("2)")} Jest ${colors.dim("(JavaScript)")}`);
|
|
187
262
|
console.log(` ${colors.blue("3)")} Go testing`);
|
|
188
|
-
|
|
189
263
|
const frameworkChoice = await question(`${colors.cyan("selection")} › `) || '1';
|
|
190
264
|
const frameworks = ['pytest', 'jest', 'go'];
|
|
191
265
|
const framework = frameworks[parseInt(frameworkChoice) - 1] || 'pytest';
|
|
192
266
|
|
|
193
267
|
const config = {
|
|
194
268
|
llm_provider: provider,
|
|
195
|
-
|
|
269
|
+
llm_model: selectedModel,
|
|
196
270
|
daily_budget: parseFloat(budget),
|
|
197
271
|
test_framework: framework,
|
|
198
272
|
created_at: new Date().toISOString()
|
|
199
273
|
};
|
|
200
274
|
|
|
201
|
-
saveConfig({ ...config,
|
|
275
|
+
saveConfig({ ...config, llm_api_key: apiKey }, true);
|
|
202
276
|
saveConfig({ ...config, api_key_env: `${provider.toUpperCase()}_API_KEY` }, false);
|
|
203
277
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.log(
|
|
209
|
-
|
|
278
|
+
logDivider();
|
|
279
|
+
logSuccess(colors.bold("voria initialized successfully!"));
|
|
280
|
+
log(`${colors.dim("Provider:")} ${colors.blue(provider)} | ${colors.dim("Model:")} ${colors.cyan(selectedModel)}`);
|
|
281
|
+
log(`${colors.dim("Config:")} ${colors.blue(CONFIG_FILE)}`);
|
|
282
|
+
console.log(`\n${colors.bold(colors.blue("Next Steps:"))}`);
|
|
283
|
+
log(`Run: ${colors.cyan("voria --set-github-token")}`);
|
|
284
|
+
log(`Run: ${colors.cyan("voria scan")} or ${colors.cyan("voria test hardcoded_secrets")}\n`);
|
|
210
285
|
rl.close();
|
|
211
286
|
} catch (error) {
|
|
212
|
-
|
|
287
|
+
logError(`Setup error: ${error.message}`);
|
|
213
288
|
rl.close();
|
|
214
289
|
process.exit(1);
|
|
215
290
|
}
|
|
216
291
|
}
|
|
217
292
|
|
|
218
|
-
// Configure existing setup
|
|
219
293
|
async function runConfig() {
|
|
220
294
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
221
295
|
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
222
296
|
|
|
223
297
|
try {
|
|
224
|
-
|
|
225
|
-
console.log(` ${colors.dim("┃")} ${colors.blue("Adjusting your local parameters")} ${colors.dim("┃")}`);
|
|
226
|
-
console.log(` ${colors.dim("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")}\n`);
|
|
227
|
-
|
|
298
|
+
logHeader("SETTINGS ENGINE");
|
|
228
299
|
const config = loadConfig();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
300
|
+
log(`${colors.dim("Provider:")} ${colors.blue(config.llm_provider || 'None')}`);
|
|
301
|
+
log(`${colors.dim("Model:")} ${colors.cyan(config.llm_model || 'None')}`);
|
|
302
|
+
log(`${colors.dim("Budget:")} ${colors.green("$" + (config.daily_budget || '10.0'))}`);
|
|
303
|
+
log(`${colors.dim("Framework:")} ${colors.cyan(config.test_framework || 'None')}\n`);
|
|
233
304
|
|
|
234
305
|
const change = await question(`${colors.cyan("edit")} ${colors.dim("(provider/budget/framework/none)")} › `);
|
|
235
|
-
|
|
236
306
|
if (change === 'provider') {
|
|
237
307
|
const provider = await question(`${colors.cyan("new provider")} › `);
|
|
238
308
|
const apiKey = await question(`${colors.cyan("api key")} › `);
|
|
239
309
|
config.llm_provider = provider;
|
|
240
|
-
saveConfig({ ...config,
|
|
241
|
-
|
|
310
|
+
saveConfig({ ...config, llm_api_key: apiKey }, true);
|
|
311
|
+
logSuccess("Provider updated");
|
|
242
312
|
} else if (change === 'budget') {
|
|
243
|
-
|
|
244
|
-
config.daily_budget = parseFloat(budget);
|
|
313
|
+
config.daily_budget = parseFloat(await question(`${colors.cyan("new budget")} › `));
|
|
245
314
|
saveConfig(config, true);
|
|
246
|
-
|
|
315
|
+
logSuccess("Budget updated");
|
|
247
316
|
} else if (change === 'framework') {
|
|
248
|
-
|
|
249
|
-
config.test_framework = framework;
|
|
317
|
+
config.test_framework = await question(`${colors.cyan("new framework")} › `);
|
|
250
318
|
saveConfig(config, false);
|
|
251
|
-
|
|
319
|
+
logSuccess("Test framework updated");
|
|
252
320
|
}
|
|
321
|
+
logSuccess("Settings saved");
|
|
322
|
+
rl.close();
|
|
323
|
+
} catch (error) {
|
|
324
|
+
logError(`Config error: ${error.message}`);
|
|
325
|
+
rl.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
253
328
|
|
|
254
|
-
|
|
329
|
+
async function setGitHubToken() {
|
|
330
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
331
|
+
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
332
|
+
try {
|
|
333
|
+
logHeader("GitHub Integration");
|
|
334
|
+
logInfo(`Generate a token at: ${colors.blue("https://github.com/settings/tokens")}`);
|
|
335
|
+
logInfo(`Required scope: 'repo' (Full control of private repositories)`);
|
|
336
|
+
const token = await question(`\n${colors.cyan("GitHub Token")} › `);
|
|
337
|
+
if (!token) { logError("Token is required"); rl.close(); return; }
|
|
338
|
+
const config = loadConfig();
|
|
339
|
+
config.github_token = token;
|
|
340
|
+
saveConfig(config, true);
|
|
341
|
+
logSuccess(colors.bold("GitHub token saved successfully!"));
|
|
255
342
|
rl.close();
|
|
256
343
|
} catch (error) {
|
|
257
|
-
|
|
344
|
+
logError(`Error: ${error.message}`);
|
|
258
345
|
rl.close();
|
|
259
346
|
}
|
|
260
347
|
}
|
|
261
348
|
|
|
262
|
-
//
|
|
349
|
+
// ─── Status & Utility ────────────────────────────────────────────
|
|
350
|
+
|
|
263
351
|
function showStatus() {
|
|
264
|
-
|
|
352
|
+
logHeader("voria Status");
|
|
265
353
|
const config = loadConfig();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
console.log(` ${colors.bold("LLM Provider:")} ${colors.blue(config.llm_provider)}`);
|
|
274
|
-
console.log(` ${colors.bold("Daily Budget:")} ${colors.green("$" + config.daily_budget)}`);
|
|
275
|
-
console.log(` ${colors.bold("Test Framework:")} ${colors.cyan(config.test_framework)}`);
|
|
276
|
-
console.log(` ${colors.bold("Global Config:")} ${colors.dim(CONFIG_FILE)}\n`);
|
|
354
|
+
if (!config.llm_provider) { logError(`Not initialized. Run ${colors.cyan("voria --init")}\n`); return; }
|
|
355
|
+
logSuccess("voria is configured");
|
|
356
|
+
log(`${colors.bold("Provider:")} ${colors.blue(config.llm_provider)}`);
|
|
357
|
+
log(`${colors.bold("Model:")} ${colors.cyan(config.llm_model || 'default')}`);
|
|
358
|
+
log(`${colors.bold("Budget:")} ${colors.green("$" + config.daily_budget)}`);
|
|
359
|
+
log(`${colors.bold("Framework:")} ${colors.cyan(config.test_framework)}`);
|
|
360
|
+
log(`${colors.bold("Config:")} ${colors.dim(CONFIG_FILE)}\n`);
|
|
277
361
|
}
|
|
278
362
|
|
|
279
|
-
// Show logs
|
|
280
363
|
function showLogs() {
|
|
281
364
|
const logsFile = path.join(voria_HOME, 'voria.log');
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const logs = fs.readFileSync(logsFile, 'utf-8');
|
|
289
|
-
const lines = logs.split('\n').slice(-50);
|
|
290
|
-
|
|
291
|
-
console.log('\n📝 Recent logs (last 50 lines):\n');
|
|
292
|
-
console.log(lines.join('\n'));
|
|
365
|
+
if (!fs.existsSync(logsFile)) { log(colors.dim("No logs yet")); return; }
|
|
366
|
+
logHeader("Recent Logs");
|
|
367
|
+
const lines = fs.readFileSync(logsFile, 'utf-8').split('\n').slice(-50);
|
|
368
|
+
lines.forEach(l => log(colors.dim(l)));
|
|
293
369
|
}
|
|
294
370
|
|
|
295
|
-
// Reset configuration
|
|
296
371
|
function reset() {
|
|
297
|
-
const config = loadConfig();
|
|
298
|
-
|
|
299
|
-
if (Object.keys(config).length === 0) {
|
|
300
|
-
console.log('\n✅ No configuration to reset\n');
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
372
|
if (fs.existsSync(PROJECT_CONFIG)) {
|
|
305
373
|
fs.unlinkSync(PROJECT_CONFIG);
|
|
306
|
-
|
|
374
|
+
logSuccess(`Removed project config: ${PROJECT_CONFIG}`);
|
|
307
375
|
}
|
|
308
|
-
|
|
309
|
-
console.log('✅ Configuration reset\n');
|
|
376
|
+
logSuccess("Configuration reset");
|
|
310
377
|
}
|
|
311
378
|
|
|
312
|
-
//
|
|
313
|
-
async function setGitHubToken() {
|
|
314
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
315
|
-
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
console.log(`\n${colors.bgBlue(colors.bold(" 🔗 GitHub Integration "))}`);
|
|
319
|
-
console.log(`${colors.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}\n`);
|
|
320
|
-
|
|
321
|
-
console.log(`${colors.cyan("ℹ")} ${colors.dim("Generate a token at:")} ${colors.blue("https://github.com/settings/tokens")}`);
|
|
322
|
-
console.log(`${colors.dim(" Required scope: 'repo' (Full control of private repositories)")}\n`);
|
|
379
|
+
// ─── Issue Fixing ────────────────────────────────────────────────
|
|
323
380
|
|
|
324
|
-
const token = await question(`${colors.cyan("GitHub Token")} › `);
|
|
325
|
-
|
|
326
|
-
if (!token) {
|
|
327
|
-
console.log(`\n${colors.red("✖")} Token is required\n`);
|
|
328
|
-
rl.close();
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const config = loadConfig();
|
|
333
|
-
config.github_token = token;
|
|
334
|
-
saveConfig(config, true);
|
|
335
|
-
|
|
336
|
-
console.log(`\n${colors.green("✓")} ${colors.bold("GitHub token saved successfully!")}\n`);
|
|
337
|
-
rl.close();
|
|
338
|
-
} catch (error) {
|
|
339
|
-
console.error(colors.red('Error saving token:'), error);
|
|
340
|
-
rl.close();
|
|
341
|
-
process.exit(1);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// List issues from a repository
|
|
346
381
|
async function listIssues(repoArg, config) {
|
|
347
|
-
// Parse repo from argument
|
|
348
382
|
let owner, repo;
|
|
349
|
-
|
|
350
383
|
if (repoArg.includes('github.com')) {
|
|
351
384
|
const match = repoArg.match(/github\.com[/:]([^/]+)\/([^/\s]+)/);
|
|
352
|
-
if (!match) {
|
|
353
|
-
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
owner = match[1];
|
|
357
|
-
repo = match[2].replace('.git', '');
|
|
385
|
+
if (!match) { logError("Invalid GitHub URL"); return; }
|
|
386
|
+
owner = match[1]; repo = match[2].replace('.git', '');
|
|
358
387
|
} else if (repoArg.includes('/')) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
repo = parts[1];
|
|
362
|
-
} else {
|
|
363
|
-
console.log(`\n${colors.red("✖")} Invalid repository format`);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
388
|
+
[owner, repo] = repoArg.split('/');
|
|
389
|
+
} else { logError("Invalid repository format"); return; }
|
|
366
390
|
|
|
367
391
|
const githubToken = config.github_token || process.env.GITHUB_TOKEN;
|
|
368
|
-
if (!githubToken) {
|
|
369
|
-
console.log(`\n${colors.red("✖")} GitHub token not found. Run ${colors.yellow("voria --set-github-token")}`);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
console.log(`\n${colors.blue("📋")} Fetching issues from ${colors.cyan(owner + "/" + repo)}...\n`);
|
|
392
|
+
if (!githubToken) { logError(`GitHub token not found. Run ${colors.cyan("voria --set-github-token")}`); return; }
|
|
374
393
|
|
|
394
|
+
logInfo(`Fetching issues from ${colors.cyan(owner + "/" + repo)}...`);
|
|
375
395
|
try {
|
|
376
|
-
const response = await callPythonEngine({
|
|
377
|
-
|
|
378
|
-
owner: owner,
|
|
379
|
-
repo: repo,
|
|
380
|
-
github_token: githubToken,
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
if (response.status === 'error') {
|
|
384
|
-
console.error(`${colors.red("✖")} Error: ${response.message}\n`);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
396
|
+
const response = await callPythonEngine({ command: 'list_issues', owner, repo, github_token: githubToken });
|
|
397
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
388
398
|
const issues = response.data?.issues || [];
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
console.log(`${colors.yellow("📭")} No open issues found\n`);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
console.log(`${colors.bgBlue(colors.bold(" 📋 Open Issues "))} ${colors.dim("in " + owner + "/" + repo + " (" + issues.length + " total)")}\n`);
|
|
396
|
-
|
|
399
|
+
if (issues.length === 0) { log(colors.dim("No open issues found")); return; }
|
|
400
|
+
logHeader(`Open Issues in ${owner}/${repo}`);
|
|
397
401
|
for (const issue of issues) {
|
|
398
|
-
const labels = issue.labels?.length > 0
|
|
399
|
-
|
|
400
|
-
: '';
|
|
401
|
-
|
|
402
|
-
console.log(`${colors.brightBlue("#" + issue.number)} ${colors.bold(issue.title)}${labels}`);
|
|
403
|
-
console.log(` ${colors.dim("🔗")} ${colors.blue(issue.url)}`);
|
|
404
|
-
|
|
405
|
-
if (issue.body) {
|
|
406
|
-
const snippet = issue.body.length > 100 ? issue.body.substring(0, 100) + "..." : issue.body;
|
|
407
|
-
console.log(` ${colors.dim("📝")} ${colors.dim(snippet.replace(/\n/g, " "))}`);
|
|
408
|
-
}
|
|
409
|
-
console.log('');
|
|
402
|
+
const labels = issue.labels?.length > 0 ? ` ${colors.yellow("[" + issue.labels.join(", ") + "]")}` : '';
|
|
403
|
+
log(`${colors.brightBlue("#" + issue.number)} ${colors.bold(issue.title)}${labels}`);
|
|
410
404
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
} catch (error) {
|
|
415
|
-
console.error(`${colors.red("✖")} Error: ${error.message}\n`);
|
|
416
|
-
}
|
|
405
|
+
logDivider();
|
|
406
|
+
logInfo(`To fix: ${colors.cyan("voria issue " + owner + "/" + repo + " <number> --apply")}`);
|
|
407
|
+
} catch (error) { logError(error.message); }
|
|
417
408
|
}
|
|
418
409
|
|
|
419
|
-
// Run plan command with LLM - Actually call Python engine
|
|
420
|
-
// Enhanced plan command
|
|
421
410
|
async function runPlanCommand(description, config, args) {
|
|
422
411
|
console.log(BANNER);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
console.log(`${colors.blue("⏳")} Analyzing codebase...`);
|
|
429
|
-
console.log(`${colors.blue("⏳")} Designing solution architecture...\n`);
|
|
412
|
+
logHeader("voria Plan");
|
|
413
|
+
log(colors.cyan(description));
|
|
414
|
+
logDivider();
|
|
415
|
+
logInfo("Analyzing codebase...");
|
|
416
|
+
logInfo("Designing solution architecture...");
|
|
430
417
|
|
|
431
418
|
try {
|
|
432
|
-
|
|
433
|
-
if (!apiKey) {
|
|
434
|
-
const envKey = `${(config.llm_provider || 'openai').toUpperCase()}_API_KEY`;
|
|
435
|
-
apiKey = process.env[envKey];
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (!apiKey) {
|
|
439
|
-
console.error(`${colors.red("✖")} Error: API key not found. Run ${colors.yellow("voria --config")}\n`);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
|
|
419
|
+
const apiKey = getApiKey(config);
|
|
443
420
|
const response = await callPythonEngine({
|
|
444
|
-
command: 'plan',
|
|
445
|
-
|
|
446
|
-
repo_path: process.cwd(),
|
|
447
|
-
provider: config.llm_provider || 'openai',
|
|
448
|
-
api_key: apiKey,
|
|
449
|
-
model: config.model,
|
|
421
|
+
command: 'plan', description, repo_path: process.cwd(),
|
|
422
|
+
provider: config.llm_provider || 'openai', api_key: apiKey, model: config.llm_model,
|
|
450
423
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
424
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
425
|
+
logSuccess(colors.bold("Strategic Plan Generated!"));
|
|
426
|
+
if (response.data?.plan) {
|
|
427
|
+
logHeader("Project Blueprint");
|
|
428
|
+
console.log(typeof response.data.plan === 'string' ? response.data.plan : JSON.stringify(response.data.plan, null, 2));
|
|
455
429
|
}
|
|
430
|
+
if (args.includes('--dry-run')) { logInfo("Dry Run Mode — no changes made"); }
|
|
431
|
+
else { logInfo(`Ready to implement! Run: ${colors.cyan("voria issue <repo> <number> --apply")}`); }
|
|
432
|
+
} catch (error) { logError(error.message); }
|
|
433
|
+
}
|
|
456
434
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
435
|
+
async function runIssueCommand(repo, issueNumber, config, args) {
|
|
436
|
+
console.log(BANNER);
|
|
437
|
+
logHeader("voria Issue Fix");
|
|
438
|
+
log(`${colors.bold("Issue:")} ${colors.brightBlue("#" + issueNumber)} in ${colors.cyan(repo)}`);
|
|
439
|
+
log(`${colors.bold("AI:")} ${colors.blue(config.llm_provider || 'openai')} / ${colors.cyan(config.llm_model || 'default')}`);
|
|
440
|
+
log(`${colors.bold("Budget:")} ${colors.green("$" + (config.daily_budget || 10.0))}/day`);
|
|
441
|
+
logDivider();
|
|
442
|
+
logInfo("Fetching issue details...");
|
|
443
|
+
logInfo("Analyzing code context...");
|
|
444
|
+
logInfo("Generating optimized fix...");
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const apiKey = getApiKey(config);
|
|
448
|
+
const githubToken = config.github_token || process.env.GITHUB_TOKEN;
|
|
449
|
+
if (!githubToken) { logError(`GitHub token not found. Run ${colors.cyan("voria --set-github-token")}`); return; }
|
|
450
|
+
|
|
451
|
+
const response = await callPythonEngine({
|
|
452
|
+
command: 'issue', issue_number: parseInt(issueNumber), repo, repo_path: process.cwd(),
|
|
453
|
+
provider: config.llm_provider || 'openai', api_key: apiKey, github_token: githubToken, model: config.llm_model,
|
|
454
|
+
});
|
|
455
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
456
|
+
logSuccess(colors.bold("Solution Generated!"));
|
|
457
|
+
if (response.data?.issue_title) log(`${colors.bold("Title:")} ${response.data.issue_title}`);
|
|
464
458
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
459
|
+
if (args.includes('--apply')) {
|
|
460
|
+
logInfo("Creating Pull Request...");
|
|
461
|
+
const prResponse = await callPythonEngine({
|
|
462
|
+
command: 'create_pr', owner: repo.split('/')[0], repo: repo.split('/')[1],
|
|
463
|
+
issue_number: parseInt(issueNumber), patch: response.data.patch, github_token: githubToken
|
|
464
|
+
});
|
|
465
|
+
if (prResponse.status === 'success') {
|
|
466
|
+
logSuccess(colors.bold("Pull Request Created!"));
|
|
467
|
+
log(`${colors.bold("URL:")} ${colors.brightBlue(prResponse.data.pr_url)}`);
|
|
468
|
+
log(`${colors.bold("Branch:")} ${colors.cyan(prResponse.data.branch)}`);
|
|
469
|
+
} else { logError(`Failed to create PR: ${prResponse.message}`); }
|
|
468
470
|
} else {
|
|
469
|
-
|
|
470
|
-
console.log(` Run: ${colors.bold("voria issue <repo> <number> --apply")}\n`);
|
|
471
|
+
logInfo(`Solution ready. Run with ${colors.bold("--apply")} to create PR.`);
|
|
471
472
|
}
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error(`${colors.red("✖")} Error: ${error.message}\n`);
|
|
474
|
-
}
|
|
473
|
+
} catch (error) { logError(error.message); }
|
|
475
474
|
}
|
|
476
475
|
|
|
477
|
-
//
|
|
478
|
-
|
|
476
|
+
// ─── Tier 1: scan ────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
async function runScanCommand(config, args) {
|
|
479
479
|
console.log(BANNER);
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
console.log(`${colors.bold("💰 Budget:")} ${colors.green("$" + (config.daily_budget || 10.0))}/day`);
|
|
485
|
-
console.log(`${colors.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}\n`);
|
|
480
|
+
logHeader("voria Security Scan");
|
|
481
|
+
|
|
482
|
+
const categoryIdx = args.indexOf('--category');
|
|
483
|
+
const category = categoryIdx !== -1 && args[categoryIdx + 1] ? args[categoryIdx + 1] : 'all';
|
|
486
484
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
485
|
+
log(`${colors.bold("Category:")} ${colors.cyan(category)}`);
|
|
486
|
+
log(`${colors.bold("Provider:")} ${colors.blue(config.llm_provider)} / ${colors.cyan(config.llm_model || 'default')}`);
|
|
487
|
+
logDivider();
|
|
488
|
+
logInfo("Running all security tests in parallel...");
|
|
489
|
+
logInfo("This may take a few minutes depending on codebase size...");
|
|
490
490
|
|
|
491
491
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
const apiKey = getApiKey(config);
|
|
493
|
+
const response = await callPythonEngine({
|
|
494
|
+
command: 'scan', provider: config.llm_provider, api_key: apiKey,
|
|
495
|
+
model: config.llm_model, repo_path: process.cwd(), category,
|
|
496
|
+
});
|
|
497
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
498
|
+
|
|
499
|
+
const scan = response.data?.scan || {};
|
|
500
|
+
logDivider();
|
|
501
|
+
logHeader("Scan Results");
|
|
502
|
+
log(`${colors.bold("Total Tests:")} ${colors.blue(scan.total_tests)}`);
|
|
503
|
+
log(`${colors.bold("Passed:")} ${colors.green(scan.passed)}`);
|
|
504
|
+
log(`${colors.bold("Failed:")} ${colors.red(scan.failed)}`);
|
|
505
|
+
log(`${colors.bold("Warnings:")} ${colors.yellow(scan.warnings)}`);
|
|
506
|
+
log(`${colors.bold("Avg Score:")} ${colors.brightBlue(scan.avg_score + "/100")}`);
|
|
507
|
+
|
|
508
|
+
if (scan.findings?.length > 0) {
|
|
509
|
+
logHeader("Top Findings");
|
|
510
|
+
scan.findings.slice(0, 10).forEach(f => {
|
|
511
|
+
const sev = f.severity === 'high' ? colors.red("HIGH") : f.severity === 'medium' ? colors.yellow("MED") : colors.dim("LOW");
|
|
512
|
+
log(` ${sev} ${colors.dim(f.file || '')} ${f.description || ''}`);
|
|
513
|
+
});
|
|
496
514
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return;
|
|
515
|
+
if (scan.recommendations?.length > 0) {
|
|
516
|
+
logHeader("Recommendations");
|
|
517
|
+
scan.recommendations.slice(0, 5).forEach(r => log(` ${colors.blue("→")} ${r}`));
|
|
501
518
|
}
|
|
519
|
+
logDivider();
|
|
520
|
+
logSuccess(`Scan complete: ${colors.blue(response.message)}`);
|
|
521
|
+
} catch (error) { logError(error.message); }
|
|
522
|
+
}
|
|
502
523
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
524
|
+
// ─── Tier 1: test & list-tests ───────────────────────────────────
|
|
525
|
+
|
|
526
|
+
async function runTestCommand(testId, config) {
|
|
527
|
+
logHeader(`Running Test: ${testId}`);
|
|
528
|
+
logInfo(`Provider: ${colors.blue(config.llm_provider)} | Model: ${colors.cyan(config.llm_model || 'default')}`);
|
|
529
|
+
logDivider();
|
|
508
530
|
|
|
531
|
+
try {
|
|
532
|
+
const apiKey = getApiKey(config);
|
|
509
533
|
const response = await callPythonEngine({
|
|
510
|
-
command: '
|
|
511
|
-
|
|
512
|
-
repo: repo,
|
|
513
|
-
repo_path: process.cwd(),
|
|
514
|
-
provider: config.llm_provider || 'openai',
|
|
515
|
-
api_key: apiKey,
|
|
516
|
-
github_token: githubToken,
|
|
517
|
-
model: config.model,
|
|
534
|
+
command: 'test', test_id: testId, provider: config.llm_provider,
|
|
535
|
+
api_key: apiKey, model: config.llm_model, repo_path: process.cwd(),
|
|
518
536
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
537
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
538
|
+
|
|
539
|
+
const result = response.data?.result?.result || response.data?.result || {};
|
|
540
|
+
logDivider();
|
|
541
|
+
const status = result.status === 'passed' ? colors.green("PASSED") : result.status === 'failed' ? colors.red("FAILED") : colors.yellow("WARNING");
|
|
542
|
+
log(`${colors.bold("Status:")} ${status}`);
|
|
543
|
+
log(`${colors.bold("Score:")} ${colors.brightBlue((result.score || 0) + "/100")}`);
|
|
544
|
+
if (result.summary) log(`${colors.bold("Summary:")} ${result.summary}`);
|
|
545
|
+
if (result.findings?.length > 0) {
|
|
546
|
+
result.findings.slice(0, 5).forEach(f => {
|
|
547
|
+
log(` ${colors.yellow("→")} ${f.description || JSON.stringify(f)}`);
|
|
548
|
+
});
|
|
523
549
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (response.data) {
|
|
528
|
-
console.log(`${colors.bold("📝 Title:")} ${response.data.issue_title}`);
|
|
550
|
+
if (result.recommendations?.length > 0) {
|
|
551
|
+
result.recommendations.forEach(r => log(` ${colors.blue("💡")} ${r}`));
|
|
529
552
|
}
|
|
553
|
+
logDivider();
|
|
554
|
+
} catch (error) { logError(error.message); }
|
|
555
|
+
}
|
|
530
556
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
557
|
+
async function runListTests(config) {
|
|
558
|
+
logHeader("Available Tests");
|
|
559
|
+
try {
|
|
560
|
+
const response = await callPythonEngine({ command: 'list_tests' });
|
|
561
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
562
|
+
const tests = response.data?.tests || [];
|
|
563
|
+
const categories = {};
|
|
564
|
+
tests.forEach(t => {
|
|
565
|
+
if (!categories[t.category]) categories[t.category] = [];
|
|
566
|
+
categories[t.category].push(t);
|
|
567
|
+
});
|
|
568
|
+
for (const [cat, items] of Object.entries(categories)) {
|
|
569
|
+
console.log(`\n ${colors.bold(colors.blue(cat.toUpperCase()))}`);
|
|
570
|
+
items.forEach(t => {
|
|
571
|
+
log(` ${colors.cyan(t.id.padEnd(25))} ${t.name} ${colors.dim("— " + (t.description || '').slice(0, 50))}`);
|
|
541
572
|
});
|
|
573
|
+
}
|
|
574
|
+
logDivider();
|
|
575
|
+
logInfo(`Run a test: ${colors.cyan("voria test <test_id>")}`);
|
|
576
|
+
logInfo(`Full scan: ${colors.cyan("voria scan")}`);
|
|
577
|
+
} catch (error) { logError(error.message); }
|
|
578
|
+
}
|
|
542
579
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
580
|
+
// ─── Tier 1: watch ───────────────────────────────────────────────
|
|
581
|
+
|
|
582
|
+
async function runWatchCommand(config) {
|
|
583
|
+
logHeader("voria Watch Mode");
|
|
584
|
+
logInfo("Monitoring source files for changes...");
|
|
585
|
+
logInfo("Press Ctrl+C to stop\n");
|
|
586
|
+
|
|
587
|
+
const extensions = new Set(['.py', '.js', '.ts', '.go', '.rs', '.java']);
|
|
588
|
+
const ignore = ['node_modules', '.git', 'venv', '__pycache__', 'target'];
|
|
589
|
+
|
|
590
|
+
function getSnapshot(dir) {
|
|
591
|
+
const snap = {};
|
|
592
|
+
try {
|
|
593
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
594
|
+
for (const e of entries) {
|
|
595
|
+
if (ignore.some(i => e.name.includes(i))) continue;
|
|
596
|
+
const fp = path.join(dir, e.name);
|
|
597
|
+
if (e.isDirectory()) Object.assign(snap, getSnapshot(fp));
|
|
598
|
+
else if (extensions.has(path.extname(e.name))) {
|
|
599
|
+
try { snap[fp] = fs.statSync(fp).mtimeMs; } catch {}
|
|
600
|
+
}
|
|
549
601
|
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
console.log(` Run with ${colors.bold("--apply")} to automatically create a PR.\n`);
|
|
553
|
-
}
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error(`${colors.red("✖")} Error: ${error.message}\n`);
|
|
602
|
+
} catch {}
|
|
603
|
+
return snap;
|
|
556
604
|
}
|
|
605
|
+
|
|
606
|
+
let prev = getSnapshot(process.cwd());
|
|
607
|
+
logInfo(`Watching ${Object.keys(prev).length} files...`);
|
|
608
|
+
|
|
609
|
+
const testIds = ['hardcoded_secrets', 'xss', 'sql_injection'];
|
|
610
|
+
const apiKey = getApiKey(config);
|
|
611
|
+
|
|
612
|
+
const interval = setInterval(async () => {
|
|
613
|
+
const curr = getSnapshot(process.cwd());
|
|
614
|
+
const changed = [];
|
|
615
|
+
for (const [fp, mtime] of Object.entries(curr)) {
|
|
616
|
+
if (!prev[fp] || prev[fp] !== mtime) changed.push(fp);
|
|
617
|
+
}
|
|
618
|
+
if (changed.length > 0) {
|
|
619
|
+
logInfo(`${colors.yellow(changed.length + " file(s) changed:")} ${changed.map(f => path.basename(f)).join(', ')}`);
|
|
620
|
+
for (const tid of testIds) {
|
|
621
|
+
logInfo(`Re-running test: ${colors.cyan(tid)}...`);
|
|
622
|
+
try {
|
|
623
|
+
const r = await callPythonEngine({
|
|
624
|
+
command: 'test', test_id: tid, provider: config.llm_provider,
|
|
625
|
+
api_key: apiKey, model: config.llm_model, repo_path: process.cwd(),
|
|
626
|
+
});
|
|
627
|
+
const st = r.data?.result?.result?.status || r.data?.result?.status || 'unknown';
|
|
628
|
+
const icon = st === 'passed' ? colors.green("✓") : st === 'failed' ? colors.red("✖") : colors.yellow("⚠");
|
|
629
|
+
log(` ${icon} ${tid}: ${st}`);
|
|
630
|
+
} catch (e) { logError(`${tid}: ${e.message}`); }
|
|
631
|
+
}
|
|
632
|
+
prev = curr;
|
|
633
|
+
}
|
|
634
|
+
}, 2000);
|
|
635
|
+
|
|
636
|
+
process.on('SIGINT', () => { clearInterval(interval); logInfo("Watch mode stopped."); process.exit(0); });
|
|
637
|
+
// Keep alive
|
|
638
|
+
await new Promise(() => {});
|
|
557
639
|
}
|
|
558
640
|
|
|
559
|
-
//
|
|
560
|
-
async function callPythonEngine(command) {
|
|
561
|
-
const { spawn } = await import('child_process');
|
|
562
|
-
const pathMod = await import('path');
|
|
563
|
-
const fsMod = await import('fs');
|
|
564
|
-
|
|
565
|
-
return new Promise((resolve, reject) => {
|
|
566
|
-
const pythonScript = pathMod.default.join(__dirname, '..', 'python', 'voria', 'engine.py');
|
|
567
|
-
const pythonPath = pathMod.default.join(__dirname, '..', 'python');
|
|
568
|
-
|
|
569
|
-
// Check if virtual environment exists
|
|
570
|
-
const venvPath = pathMod.default.join(__dirname, '..', 'venv', 'bin', 'python');
|
|
571
|
-
const pythonCmd = fsMod.default.existsSync(venvPath) ? venvPath : 'python3';
|
|
572
|
-
|
|
573
|
-
// Add python directory to PYTHONPATH so voria module can be found
|
|
574
|
-
const env = { ...process.env };
|
|
575
|
-
const currentPath = env.PYTHONPATH || '';
|
|
576
|
-
env.PYTHONPATH = pythonPath + (currentPath ? ':' + currentPath : '');
|
|
577
|
-
|
|
578
|
-
const python = spawn(pythonCmd, [pythonScript], {
|
|
579
|
-
cwd: process.cwd(),
|
|
580
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
581
|
-
env: env
|
|
582
|
-
});
|
|
641
|
+
// ─── Tier 1: diff ────────────────────────────────────────────────
|
|
583
642
|
|
|
584
|
-
|
|
585
|
-
|
|
643
|
+
async function runDiffCommand(args, config) {
|
|
644
|
+
const refA = args[1] || 'HEAD~1';
|
|
645
|
+
const refB = args[2] || 'HEAD';
|
|
646
|
+
logHeader("voria Security Diff");
|
|
647
|
+
log(`${colors.bold("Comparing:")} ${colors.cyan(refA)} → ${colors.cyan(refB)}`);
|
|
648
|
+
logDivider();
|
|
586
649
|
|
|
587
|
-
|
|
588
|
-
|
|
650
|
+
try {
|
|
651
|
+
const response = await callPythonEngine({
|
|
652
|
+
command: 'diff', ref_a: refA, ref_b: refB, repo_path: process.cwd(),
|
|
589
653
|
});
|
|
654
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
655
|
+
const diff = response.data?.diff || {};
|
|
656
|
+
log(`${colors.bold("Files Changed:")} ${colors.blue(diff.total_changed)}`);
|
|
657
|
+
log(`${colors.bold("Risk Level:")} ${diff.risk_level === 'critical' ? colors.red(diff.risk_level) : diff.risk_level === 'elevated' ? colors.yellow(diff.risk_level) : colors.green(diff.risk_level)}`);
|
|
658
|
+
if (diff.high_risk_files?.length > 0) {
|
|
659
|
+
log(`${colors.bold(colors.red("High Risk:"))}`);
|
|
660
|
+
diff.high_risk_files.forEach(f => log(` ${colors.red("●")} ${f}`));
|
|
661
|
+
}
|
|
662
|
+
if (diff.medium_risk_files?.length > 0) {
|
|
663
|
+
log(`${colors.bold(colors.yellow("Medium Risk:"))}`);
|
|
664
|
+
diff.medium_risk_files.forEach(f => log(` ${colors.yellow("●")} ${f}`));
|
|
665
|
+
}
|
|
666
|
+
if (diff.recommendation) log(`\n${colors.blue("💡")} ${diff.recommendation}`);
|
|
667
|
+
logDivider();
|
|
668
|
+
} catch (error) { logError(error.message); }
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// ─── Tier 1: benchmark ──────────────────────────────────────────
|
|
672
|
+
|
|
673
|
+
async function runBenchmarkCommand(args) {
|
|
674
|
+
const url = args[1];
|
|
675
|
+
if (!url) { logError("URL required. Usage: voria benchmark <URL>"); return; }
|
|
590
676
|
|
|
591
|
-
|
|
592
|
-
|
|
677
|
+
const reqIdx = args.indexOf('--requests');
|
|
678
|
+
const conIdx = args.indexOf('--concurrency');
|
|
679
|
+
const requests = reqIdx !== -1 ? parseInt(args[reqIdx + 1]) : 100;
|
|
680
|
+
const concurrency = conIdx !== -1 ? parseInt(args[conIdx + 1]) : 10;
|
|
681
|
+
|
|
682
|
+
logHeader("voria HTTP Benchmark");
|
|
683
|
+
log(`${colors.bold("Target:")} ${colors.cyan(url)}`);
|
|
684
|
+
log(`${colors.bold("Requests:")} ${colors.blue(requests)}`);
|
|
685
|
+
log(`${colors.bold("Concurrency:")} ${colors.blue(concurrency)}`);
|
|
686
|
+
logDivider();
|
|
687
|
+
logInfo("Sending requests...");
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
const response = await callPythonEngine({
|
|
691
|
+
command: 'benchmark', url, requests, concurrency,
|
|
593
692
|
});
|
|
693
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
694
|
+
const bm = response.data?.benchmark || {};
|
|
695
|
+
logHeader("Benchmark Results");
|
|
696
|
+
log(`${colors.bold("Requests/sec:")} ${colors.green(bm.rps)}`);
|
|
697
|
+
log(`${colors.bold("Total Time:")} ${colors.blue(bm.total_time_sec + "s")}`);
|
|
698
|
+
log(`${colors.bold("Success:")} ${colors.green(bm.successful)} | ${colors.bold("Failed:")} ${colors.red(bm.failed)}`);
|
|
699
|
+
logDivider();
|
|
700
|
+
log(`${colors.bold("Latency (ms):")}`);
|
|
701
|
+
log(` ${colors.dim("avg:")} ${colors.blue(bm.latency_avg_ms)}`);
|
|
702
|
+
log(` ${colors.dim("p50:")} ${colors.green(bm.latency_p50_ms)}`);
|
|
703
|
+
log(` ${colors.dim("p95:")} ${colors.yellow(bm.latency_p95_ms)}`);
|
|
704
|
+
log(` ${colors.dim("p99:")} ${colors.red(bm.latency_p99_ms)}`);
|
|
705
|
+
log(` ${colors.dim("min:")} ${colors.dim(bm.latency_min_ms)} | ${colors.dim("max:")} ${colors.dim(bm.latency_max_ms)}`);
|
|
706
|
+
logDivider();
|
|
707
|
+
} catch (error) { logError(error.message); }
|
|
708
|
+
}
|
|
594
709
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
resolve(response);
|
|
607
|
-
} catch (e) {
|
|
608
|
-
reject(new Error(`Failed to parse Python response: ${e.message}`));
|
|
609
|
-
}
|
|
710
|
+
// ─── Tier 1: ci ──────────────────────────────────────────────────
|
|
711
|
+
|
|
712
|
+
async function runCiCommand(config) {
|
|
713
|
+
logHeader("voria CI/CD Scan");
|
|
714
|
+
logInfo("Running security tests and generating SARIF...");
|
|
715
|
+
|
|
716
|
+
try {
|
|
717
|
+
const apiKey = getApiKey(config);
|
|
718
|
+
const response = await callPythonEngine({
|
|
719
|
+
command: 'ci', provider: config.llm_provider, api_key: apiKey,
|
|
720
|
+
model: config.llm_model, repo_path: process.cwd(),
|
|
610
721
|
});
|
|
722
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
723
|
+
|
|
724
|
+
const sarif = response.data?.sarif;
|
|
725
|
+
if (sarif) {
|
|
726
|
+
const outPath = path.join(process.cwd(), 'voria-results.sarif');
|
|
727
|
+
fs.writeFileSync(outPath, JSON.stringify(sarif, null, 2));
|
|
728
|
+
logSuccess(`SARIF report written to: ${colors.cyan(outPath)}`);
|
|
729
|
+
log(`${colors.bold("Findings:")} ${colors.blue(response.data.findings_count)}`);
|
|
730
|
+
logInfo(`Upload to GitHub: ${colors.cyan("gh api repos/{owner}/{repo}/code-scanning/sarifs -f sarif=@voria-results.sarif")}`);
|
|
731
|
+
}
|
|
732
|
+
logDivider();
|
|
733
|
+
} catch (error) { logError(error.message); }
|
|
734
|
+
}
|
|
611
735
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
736
|
+
// ─── Tier 1: fix --auto ──────────────────────────────────────────
|
|
737
|
+
|
|
738
|
+
async function runFixAutoCommand(args, config) {
|
|
739
|
+
const repo = args[1];
|
|
740
|
+
const issueNumber = args[2];
|
|
741
|
+
if (!repo || !issueNumber) {
|
|
742
|
+
logError("Usage: voria fix --auto <owner/repo> <issue_number>");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
logHeader("voria Auto-Fix");
|
|
746
|
+
log(`${colors.bold("Issue:")} ${colors.brightBlue("#" + issueNumber)} in ${colors.cyan(repo)}`);
|
|
747
|
+
logDivider();
|
|
748
|
+
logInfo("Generating fix, auto-applying, and running tests...");
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
const apiKey = getApiKey(config);
|
|
752
|
+
const githubToken = config.github_token || process.env.GITHUB_TOKEN;
|
|
753
|
+
if (!githubToken) { logError("GitHub token required."); return; }
|
|
754
|
+
|
|
755
|
+
const response = await callPythonEngine({
|
|
756
|
+
command: 'issue', issue_number: parseInt(issueNumber), repo, repo_path: process.cwd(),
|
|
757
|
+
provider: config.llm_provider, api_key: apiKey, github_token: githubToken, model: config.llm_model, auto: true,
|
|
758
|
+
});
|
|
759
|
+
if (response.status === 'error') { logError(response.message); return; }
|
|
760
|
+
logSuccess("Patch generated and auto-applied!");
|
|
761
|
+
if (response.data?.files_modified) {
|
|
762
|
+
log(`${colors.bold("Files modified:")} ${response.data.files_modified.length}`);
|
|
763
|
+
}
|
|
764
|
+
logInfo("Running tests to verify fix...");
|
|
765
|
+
try {
|
|
766
|
+
const testResult = await callPythonEngine({
|
|
767
|
+
command: 'test', test_id: 'hardcoded_secrets', provider: config.llm_provider,
|
|
768
|
+
api_key: apiKey, model: config.llm_model, repo_path: process.cwd(),
|
|
769
|
+
});
|
|
770
|
+
logSuccess(`Post-fix test: ${testResult.data?.result?.result?.status || 'completed'}`);
|
|
771
|
+
} catch (e) { logWarn(`Post-fix test skipped: ${e.message}`); }
|
|
772
|
+
logDivider();
|
|
773
|
+
} catch (error) { logError(error.message); }
|
|
616
774
|
}
|
|
617
775
|
|
|
618
|
-
//
|
|
776
|
+
// ─── Rust Core ───────────────────────────────────────────────────
|
|
777
|
+
|
|
619
778
|
async function runRustCore(args) {
|
|
620
|
-
const { spawn } = await import('child_process');
|
|
621
|
-
const pathMod = await import('path');
|
|
622
|
-
const fsMod = await import('fs');
|
|
623
|
-
|
|
624
779
|
return new Promise((resolve, reject) => {
|
|
625
|
-
// Try to find the rust binary
|
|
626
780
|
const possiblePaths = [
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
781
|
+
path.join(__dirname, '..', 'target', 'release', 'voria'),
|
|
782
|
+
path.join(__dirname, '..', 'target', 'debug', 'voria'),
|
|
783
|
+
path.join(__dirname, '..', 'rust', 'target', 'release', 'voria'),
|
|
784
|
+
path.join(__dirname, '..', 'rust', 'target', 'debug', 'voria'),
|
|
631
785
|
];
|
|
632
|
-
|
|
633
|
-
let rustBin = possiblePaths.find(p => fsMod.default.existsSync(p));
|
|
634
|
-
|
|
786
|
+
let rustBin = possiblePaths.find(p => fs.existsSync(p));
|
|
635
787
|
if (!rustBin) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
rustBin = 'voria';
|
|
640
|
-
} catch (e) {
|
|
641
|
-
return reject(new Error('Rust core binary (voria) not found. Please run "cargo build --release" in the rust directory.'));
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
788
|
+
try { execSync('voria --version', { stdio: 'ignore' }); rustBin = 'voria'; }
|
|
789
|
+
catch (e) { return reject(new Error('Rust core binary not found.')); }
|
|
790
|
+
}
|
|
645
791
|
const child = spawn(rustBin, args, { stdio: 'inherit' });
|
|
646
|
-
child.on('exit', (code) => {
|
|
647
|
-
if (code === 0) resolve();
|
|
648
|
-
else reject(new Error(`Rust core exited with code ${code}`));
|
|
649
|
-
});
|
|
792
|
+
child.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`Exit code ${code}`)));
|
|
650
793
|
});
|
|
651
794
|
}
|
|
652
795
|
|
|
653
|
-
// Main CLI
|
|
796
|
+
// ─── Main CLI Router ─────────────────────────────────────────────
|
|
797
|
+
|
|
654
798
|
async function main() {
|
|
655
799
|
const args = process.argv.slice(2);
|
|
656
800
|
|
|
@@ -663,8 +807,7 @@ async function main() {
|
|
|
663
807
|
|
|
664
808
|
switch (command) {
|
|
665
809
|
case '--version':
|
|
666
|
-
|
|
667
|
-
console.log(`voria v${VERSION}`);
|
|
810
|
+
console.log(`${colors.blue("voria")} v${VERSION}`);
|
|
668
811
|
break;
|
|
669
812
|
|
|
670
813
|
case '--init':
|
|
@@ -681,14 +824,8 @@ async function main() {
|
|
|
681
824
|
|
|
682
825
|
case '--list-issues': {
|
|
683
826
|
const config = loadConfig();
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
console.log('\n❌ Please provide a repository');
|
|
687
|
-
console.log(' Example: voria --list-issues owner/repo');
|
|
688
|
-
console.log(' Or: voria --list-issues https://github.com/owner/repo\n');
|
|
689
|
-
process.exit(1);
|
|
690
|
-
}
|
|
691
|
-
await listIssues(repoArg, config);
|
|
827
|
+
if (!args[1]) { logError("Provide a repo. Example: voria --list-issues owner/repo"); process.exit(1); }
|
|
828
|
+
await listIssues(args[1], config);
|
|
692
829
|
break;
|
|
693
830
|
}
|
|
694
831
|
|
|
@@ -705,102 +842,119 @@ async function main() {
|
|
|
705
842
|
break;
|
|
706
843
|
|
|
707
844
|
case 'plan': {
|
|
708
|
-
const config =
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
process.exit(1);
|
|
713
|
-
}
|
|
714
|
-
const description = args.slice(1).join(' ');
|
|
715
|
-
if (!description) {
|
|
716
|
-
console.log('\n❌ Please provide a description');
|
|
717
|
-
console.log(' Example: voria plan "Remove deprecated login() method"\n');
|
|
718
|
-
process.exit(1);
|
|
719
|
-
}
|
|
720
|
-
await runPlanCommand(description, config, args);
|
|
845
|
+
const config = requireInit();
|
|
846
|
+
const desc = args.slice(1).filter(a => !a.startsWith('--')).join(' ');
|
|
847
|
+
if (!desc) { logError('Provide a description. Example: voria plan "Remove deprecated method"'); process.exit(1); }
|
|
848
|
+
await runPlanCommand(desc, config, args);
|
|
721
849
|
break;
|
|
722
850
|
}
|
|
723
851
|
|
|
724
852
|
case 'issue': {
|
|
725
|
-
const config =
|
|
726
|
-
if (
|
|
727
|
-
|
|
728
|
-
console.log(' Run: voria --init\n');
|
|
729
|
-
process.exit(1);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// Parse: voria issue owner/repo issue_number
|
|
733
|
-
const issueArgs = args.slice(1);
|
|
734
|
-
if (issueArgs.length < 2) {
|
|
735
|
-
console.log('\n❌ Please provide repository and issue number');
|
|
736
|
-
console.log(' Example: voria issue owner/repo 42\n');
|
|
737
|
-
process.exit(1);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const repo = issueArgs[0];
|
|
741
|
-
const issueNumber = issueArgs[1];
|
|
742
|
-
|
|
743
|
-
await runIssueCommand(repo, issueNumber, config, args);
|
|
853
|
+
const config = requireInit();
|
|
854
|
+
if (args.length < 3) { logError("Usage: voria issue owner/repo 42"); process.exit(1); }
|
|
855
|
+
await runIssueCommand(args[1], args[2], config, args);
|
|
744
856
|
break;
|
|
745
857
|
}
|
|
746
858
|
|
|
747
859
|
case 'apply': {
|
|
748
|
-
|
|
749
|
-
if (Object.keys(config).length === 0) {
|
|
750
|
-
console.log('\n❌ voria not initialized');
|
|
751
|
-
console.log(' Run: voria --init\n');
|
|
752
|
-
process.exit(1);
|
|
753
|
-
}
|
|
860
|
+
requireInit();
|
|
754
861
|
const patchFile = args[1];
|
|
755
|
-
if (!patchFile) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
862
|
+
if (!patchFile) { logError("Provide a patch file. Example: voria apply fix.patch"); process.exit(1); }
|
|
863
|
+
if (!fs.existsSync(patchFile)) { logError(`Patch file not found: ${patchFile}`); process.exit(1); }
|
|
864
|
+
logInfo(`Applying patch: ${patchFile}`);
|
|
865
|
+
try {
|
|
866
|
+
const response = await callPythonEngine({ command: 'apply', patch: fs.readFileSync(patchFile, 'utf-8'), repo_path: process.cwd() });
|
|
867
|
+
response.status === 'error' ? logError(response.message) : logSuccess(response.message);
|
|
868
|
+
} catch (error) { logError(error.message); }
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// ─── Tier 1 Commands ─────────────────────────────
|
|
873
|
+
|
|
874
|
+
case 'scan': {
|
|
875
|
+
const config = requireInit();
|
|
876
|
+
await runScanCommand(config, args);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
case 'test': {
|
|
881
|
+
const config = requireInit();
|
|
882
|
+
const testId = args[1];
|
|
883
|
+
if (!testId) { logError("Provide a test ID. Run 'voria list-tests' to see available tests."); process.exit(1); }
|
|
884
|
+
await runTestCommand(testId, config);
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
case 'list-tests': {
|
|
889
|
+
const config = loadConfig();
|
|
890
|
+
await runListTests(config);
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
case 'watch': {
|
|
895
|
+
const config = requireInit();
|
|
896
|
+
await runWatchCommand(config);
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
case 'diff': {
|
|
901
|
+
const config = loadConfig();
|
|
902
|
+
await runDiffCommand(args, config);
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
case 'benchmark': {
|
|
907
|
+
await runBenchmarkCommand(args);
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
case 'ci': {
|
|
912
|
+
const config = requireInit();
|
|
913
|
+
await runCiCommand(config);
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
case 'fix': {
|
|
918
|
+
const config = requireInit();
|
|
919
|
+
if (args.includes('--auto')) {
|
|
920
|
+
const fixArgs = args.filter(a => a !== 'fix' && a !== '--auto');
|
|
921
|
+
await runFixAutoCommand(fixArgs.length >= 2 ? fixArgs : args.slice(1), config);
|
|
922
|
+
} else {
|
|
923
|
+
logError("Usage: voria fix --auto <owner/repo> <issue_number>");
|
|
764
924
|
}
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
765
927
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
928
|
+
case 'token': {
|
|
929
|
+
requireInit();
|
|
930
|
+
logHeader("Token Usage");
|
|
769
931
|
try {
|
|
770
|
-
const response = await callPythonEngine({
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
console.error(`❌ Error: ${response.message}\n`);
|
|
778
|
-
} else {
|
|
779
|
-
console.log(`✅ ${response.message}\n`);
|
|
780
|
-
}
|
|
781
|
-
} catch (error) {
|
|
782
|
-
console.error(`❌ Error: ${error.message}\n`);
|
|
783
|
-
}
|
|
932
|
+
const response = await callPythonEngine({ command: 'token', subcommand: args[1] || 'info' });
|
|
933
|
+
if (response.data?.tokens) {
|
|
934
|
+
const t = response.data.tokens;
|
|
935
|
+
log(`${colors.bold("Used:")} ${colors.blue(t.used || 0)} tokens`);
|
|
936
|
+
log(`${colors.bold("Cost:")} ${colors.green("$" + (t.cost || 0))}`);
|
|
937
|
+
} else { logInfo(response.message); }
|
|
938
|
+
} catch (error) { logError(error.message); }
|
|
784
939
|
break;
|
|
785
940
|
}
|
|
786
941
|
|
|
787
942
|
case '--graph':
|
|
788
|
-
try {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
console.log(`${colors.yellow("💡")} Make sure to build the rust core: ${colors.bold("cargo build --release")}`);
|
|
943
|
+
try { await runRustCore(['--graph']); }
|
|
944
|
+
catch (error) {
|
|
945
|
+
logError(`Error running analysis: ${error.message}`);
|
|
946
|
+
logInfo(`Build rust core: ${colors.cyan("cargo build --release")}`);
|
|
793
947
|
}
|
|
794
948
|
break;
|
|
795
949
|
|
|
796
950
|
default:
|
|
797
|
-
|
|
798
|
-
|
|
951
|
+
logError(`Unknown command: ${colors.bold(command)}`);
|
|
952
|
+
logInfo(`Run ${colors.cyan("voria --help")} for usage.`);
|
|
799
953
|
process.exit(1);
|
|
800
954
|
}
|
|
801
955
|
}
|
|
802
956
|
|
|
803
957
|
main().catch(error => {
|
|
804
|
-
|
|
958
|
+
logError(error.message);
|
|
805
959
|
process.exit(1);
|
|
806
960
|
});
|