vigthoria-cli 1.9.5 → 1.9.8
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 +1 -1
- package/dist/commands/auth.js +87 -8
- package/dist/commands/bridge.js +3 -8
- package/dist/commands/chat.js +31 -19
- package/dist/commands/legion.js +20 -11
- package/dist/commands/security.d.ts +20 -0
- package/dist/commands/security.js +98 -0
- package/dist/index.js +41 -15
- package/dist/utils/api.js +133 -40
- package/dist/utils/config.js +1 -1
- package/dist/utils/tools.js +29 -5
- package/install.ps1 +322 -0
- package/install.sh +314 -0
- package/package.json +12 -3
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +159 -0
- package/scripts/release/publish-cli-release.sh +73 -0
- package/scripts/release/validate-no-go-gates.sh +129 -0
- package/scripts/release/verify-runtime-consistency.mjs +64 -0
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ If you see `ENOTFOUND registry.npmjs.org`, try these solutions:
|
|
|
99
99
|
4. **Direct tarball download:**
|
|
100
100
|
```bash
|
|
101
101
|
# Download directly
|
|
102
|
-
npm install -g https://coder.vigthoria.io/releases/vigthoria-cli-1.9.
|
|
102
|
+
npm install -g https://coder.vigthoria.io/releases/vigthoria-cli-1.9.8.tgz
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
5. **Use Git clone method (no npm registry needed):**
|
package/dist/commands/auth.js
CHANGED
|
@@ -20,6 +20,7 @@ const fs_1 = require("fs");
|
|
|
20
20
|
const os_1 = require("os");
|
|
21
21
|
const path_1 = __importDefault(require("path"));
|
|
22
22
|
const readline_1 = __importDefault(require("readline"));
|
|
23
|
+
const config_js_1 = require("../utils/config.js");
|
|
23
24
|
const DEFAULT_API_URL = 'https://coder.vigthoria.io';
|
|
24
25
|
const CONFIG_DIR = path_1.default.join((0, os_1.homedir)(), '.vigthoria');
|
|
25
26
|
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
@@ -65,6 +66,36 @@ function ensureConfigDir() {
|
|
|
65
66
|
// Best-effort on non-POSIX filesystems.
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
function syncSharedConfig(config) {
|
|
70
|
+
try {
|
|
71
|
+
const shared = new config_js_1.Config();
|
|
72
|
+
shared.set('apiUrl', trimTrailingSlash(config.apiUrl || getApiUrl()));
|
|
73
|
+
if (config.token) {
|
|
74
|
+
shared.set('authToken', config.token);
|
|
75
|
+
}
|
|
76
|
+
if (config.user?.id) {
|
|
77
|
+
shared.set('userId', String(config.user.id));
|
|
78
|
+
}
|
|
79
|
+
if (config.user?.email) {
|
|
80
|
+
shared.set('email', String(config.user.email));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Keep legacy auth flow working even if shared config write fails.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function clearSharedConfigAuth() {
|
|
88
|
+
try {
|
|
89
|
+
const shared = new config_js_1.Config();
|
|
90
|
+
shared.set('authToken', null);
|
|
91
|
+
shared.set('refreshToken', null);
|
|
92
|
+
shared.set('userId', null);
|
|
93
|
+
shared.set('email', null);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Ignore shared config clear failures during logout.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
68
99
|
function humanMessage(error) {
|
|
69
100
|
const raw = error instanceof Error ? error.message : String(error);
|
|
70
101
|
if (/^\s*</.test(raw) || /<!doctype html/i.test(raw)) {
|
|
@@ -128,24 +159,44 @@ function saveAuthConfig(config) {
|
|
|
128
159
|
catch {
|
|
129
160
|
// Best-effort on non-POSIX filesystems.
|
|
130
161
|
}
|
|
162
|
+
syncSharedConfig(config);
|
|
131
163
|
}
|
|
132
164
|
function clearAuthConfig() {
|
|
133
165
|
if ((0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
134
166
|
(0, fs_1.rmSync)(CONFIG_FILE, { force: true });
|
|
135
167
|
}
|
|
168
|
+
clearSharedConfigAuth();
|
|
136
169
|
}
|
|
137
170
|
function getAuthToken() {
|
|
138
171
|
return process.env.VIGTHORIA_TOKEN || loadAuthConfig().token;
|
|
139
172
|
}
|
|
140
173
|
async function requestJson(url, init) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
174
|
+
const timeoutMsRaw = Number(process.env.VIGTHORIA_AUTH_TIMEOUT_MS || 8000);
|
|
175
|
+
const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0 ? timeoutMsRaw : 8000;
|
|
176
|
+
const controller = new AbortController();
|
|
177
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
178
|
+
let response;
|
|
179
|
+
try {
|
|
180
|
+
response = await fetch(url, {
|
|
181
|
+
...init,
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
headers: {
|
|
184
|
+
accept: 'application/json',
|
|
185
|
+
'content-type': 'application/json',
|
|
186
|
+
...(init.headers || {}),
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const name = error?.name;
|
|
192
|
+
if (name === 'AbortError') {
|
|
193
|
+
throw new Error(`Authentication request timed out after ${timeoutMs}ms`);
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
clearTimeout(timeoutHandle);
|
|
199
|
+
}
|
|
149
200
|
const text = await response.text();
|
|
150
201
|
let body = {};
|
|
151
202
|
if (text.trim()) {
|
|
@@ -425,10 +476,38 @@ async function statusAction() {
|
|
|
425
476
|
catch {
|
|
426
477
|
// Keep local status available even if remote whoami endpoint is down.
|
|
427
478
|
}
|
|
479
|
+
const sharedConfig = new config_js_1.Config();
|
|
480
|
+
let overallStatus = 'Unknown';
|
|
481
|
+
let coderStatus = 'Unknown';
|
|
482
|
+
let modelsStatus = 'Unknown';
|
|
483
|
+
try {
|
|
484
|
+
const [coderResponse, modelsResponse] = await Promise.all([
|
|
485
|
+
fetch(`${trimTrailingSlash(config.apiUrl)}/api/health`),
|
|
486
|
+
fetch('https://api.vigthoria.io/health'),
|
|
487
|
+
]);
|
|
488
|
+
const coderJson = await coderResponse.json().catch(() => ({}));
|
|
489
|
+
const modelsJson = await modelsResponse.json().catch(() => ({}));
|
|
490
|
+
const coderOk = coderResponse.ok && (coderJson.status === 'ok' || coderJson.healthy === true);
|
|
491
|
+
const modelsOk = modelsResponse.ok && (modelsJson.status === 'ok' || modelsJson.status === 'healthy' || modelsJson.healthy === true);
|
|
492
|
+
coderStatus = coderOk ? 'Online' : 'Offline';
|
|
493
|
+
modelsStatus = modelsOk ? 'Online' : 'Offline';
|
|
494
|
+
overallStatus = coderOk && modelsOk ? 'Healthy' : 'Degraded';
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
// Status should still render even if health probes fail.
|
|
498
|
+
}
|
|
499
|
+
const plan = String(sharedConfig.get('subscription')?.plan || '').trim();
|
|
500
|
+
console.log(chalk_1.default.white('Account Status'));
|
|
428
501
|
console.log(chalk_1.default.green('Logged in.'));
|
|
429
502
|
if (user?.email) {
|
|
430
503
|
console.log(`Account: ${user.email}`);
|
|
431
504
|
}
|
|
505
|
+
if (plan) {
|
|
506
|
+
console.log(`Plan: ${plan.toUpperCase()}`);
|
|
507
|
+
}
|
|
508
|
+
console.log(`Overall: ${overallStatus}`);
|
|
509
|
+
console.log(`Coder API: ${coderStatus}`);
|
|
510
|
+
console.log(`Models API: ${modelsStatus}`);
|
|
432
511
|
console.log(`API: ${config.apiUrl}`);
|
|
433
512
|
process.exitCode = 0;
|
|
434
513
|
}
|
package/dist/commands/bridge.js
CHANGED
|
@@ -19,14 +19,9 @@ class BridgeCommand {
|
|
|
19
19
|
console.log();
|
|
20
20
|
console.log(chalk_1.default.white('DevTools Bridge:'));
|
|
21
21
|
console.log(chalk_1.default.gray(' Status: ') + (bridge.ok ? chalk_1.default.green('Reachable') : chalk_1.default.yellow('Not running')));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (bridge.error && bridge.ok === false) {
|
|
26
|
-
const isExpectedDown = /timed? out|ECONNREFUSED|connect ECONNREFUSED|Connection refused/i.test(bridge.error);
|
|
27
|
-
if (!isExpectedDown) {
|
|
28
|
-
console.log(chalk_1.default.gray(' Detail: ') + chalk_1.default.yellow(bridge.error));
|
|
29
|
-
}
|
|
22
|
+
if (!bridge.ok) {
|
|
23
|
+
const detail = String(bridge.error || 'Connection refused').trim() || 'Connection refused';
|
|
24
|
+
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.yellow(detail));
|
|
30
25
|
}
|
|
31
26
|
console.log(chalk_1.default.gray(' Browser tasks: ') + (bridge.ok
|
|
32
27
|
? chalk_1.default.green('Local browser observability is available for debugging flows.')
|
package/dist/commands/chat.js
CHANGED
|
@@ -66,9 +66,12 @@ const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
|
66
66
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
67
67
|
})();
|
|
68
68
|
const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
69
|
-
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS
|
|
69
|
+
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS;
|
|
70
|
+
if (!rawValue) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
70
73
|
const parsed = Number.parseInt(rawValue, 10);
|
|
71
|
-
return Number.isFinite(parsed) && parsed
|
|
74
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
72
75
|
})();
|
|
73
76
|
class ChatCommand {
|
|
74
77
|
config;
|
|
@@ -192,7 +195,9 @@ class ChatCommand {
|
|
|
192
195
|
if (attempt >= retries || (!this.isNetworkError(error) && !this.isTimeoutError(error))) {
|
|
193
196
|
this.handleApiError(error, context);
|
|
194
197
|
}
|
|
195
|
-
|
|
198
|
+
if (!this.jsonOutput) {
|
|
199
|
+
this.logger.warn(`${context} failed due to ${this.isTimeoutError(error) ? 'timeout' : 'network error'}; retrying (${attempt + 1}/${retries})...`);
|
|
200
|
+
}
|
|
196
201
|
await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
|
|
197
202
|
}
|
|
198
203
|
}
|
|
@@ -221,7 +226,9 @@ class ChatCommand {
|
|
|
221
226
|
reason: 'governance-blocked-model',
|
|
222
227
|
};
|
|
223
228
|
this.currentModel = effectiveModel;
|
|
224
|
-
this.
|
|
229
|
+
if (!this.jsonOutput) {
|
|
230
|
+
this.logger.warn(`Model ${requestedModel} is not permitted for no-agent chat; using ${effectiveModel}`);
|
|
231
|
+
}
|
|
225
232
|
return;
|
|
226
233
|
}
|
|
227
234
|
this.modelGovernanceFallback = null;
|
|
@@ -248,7 +255,7 @@ class ChatCommand {
|
|
|
248
255
|
}
|
|
249
256
|
operatorAccessMessage() {
|
|
250
257
|
const currentPlan = this.config.get('subscription').plan || 'free';
|
|
251
|
-
return `Operator mode requires Enterprise or admin access. Current plan: ${currentPlan}.`;
|
|
258
|
+
return `Operator mode requires Pro, Ultra, Enterprise, or admin access. Current plan: ${currentPlan}.`;
|
|
252
259
|
}
|
|
253
260
|
getDefaultChatModel() {
|
|
254
261
|
const preferredModel = String(this.config.get('preferences').defaultModel || '').trim().toLowerCase();
|
|
@@ -877,11 +884,16 @@ class ChatCommand {
|
|
|
877
884
|
return;
|
|
878
885
|
}
|
|
879
886
|
if (options.prompt) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
887
|
+
const bridgePromptTimeoutMs = (() => {
|
|
888
|
+
const rawValue = process.env.VIGTHORIA_BRIDGE_PROMPT_TIMEOUT_MS || process.env.V3_BRIDGE_PROMPT_TIMEOUT_MS;
|
|
889
|
+
if (!rawValue) {
|
|
890
|
+
return 0;
|
|
891
|
+
}
|
|
892
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
893
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
894
|
+
})();
|
|
883
895
|
let timedOut = false;
|
|
884
|
-
const timeoutId = options.bridge
|
|
896
|
+
const timeoutId = options.bridge && bridgePromptTimeoutMs > 0
|
|
885
897
|
? setTimeout(() => {
|
|
886
898
|
timedOut = true;
|
|
887
899
|
const b = (0, bridge_client_js_1.getBridgeClient)();
|
|
@@ -890,10 +902,10 @@ class ChatCommand {
|
|
|
890
902
|
b.destroy();
|
|
891
903
|
}
|
|
892
904
|
if (!this.jsonOutput) {
|
|
893
|
-
this.logger.error('Bridge prompt timed out after
|
|
905
|
+
this.logger.error('Bridge prompt timed out after ' + Math.round(bridgePromptTimeoutMs / 1000) + ' seconds.');
|
|
894
906
|
}
|
|
895
907
|
process.exitCode = 1;
|
|
896
|
-
},
|
|
908
|
+
}, bridgePromptTimeoutMs)
|
|
897
909
|
: null;
|
|
898
910
|
await this.handleDirectPrompt(options.prompt);
|
|
899
911
|
if (timeoutId)
|
|
@@ -1093,7 +1105,7 @@ class ChatCommand {
|
|
|
1093
1105
|
async handleDirectPrompt(prompt) {
|
|
1094
1106
|
// Suppress all setup banners in direct-prompt mode so only the final
|
|
1095
1107
|
// answer reaches stdout. Interactive (REPL) mode still shows them.
|
|
1096
|
-
if (!this.jsonOutput
|
|
1108
|
+
if (!this.jsonOutput) {
|
|
1097
1109
|
console.log(chalk_1.default.cyan('Running single prompt in direct mode.'));
|
|
1098
1110
|
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
1099
1111
|
console.log(chalk_1.default.gray(`Project: ${this.currentProjectPath}`));
|
|
@@ -1745,12 +1757,12 @@ class ChatCommand {
|
|
|
1745
1757
|
tool: 'read_file',
|
|
1746
1758
|
args: { path: targetFile },
|
|
1747
1759
|
};
|
|
1748
|
-
if (!this.jsonOutput
|
|
1760
|
+
if (!this.jsonOutput) {
|
|
1749
1761
|
console.log(chalk_1.default.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
1750
1762
|
}
|
|
1751
1763
|
const readResult = await this.tools.execute(readCall);
|
|
1752
1764
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
1753
|
-
if (!this.jsonOutput
|
|
1765
|
+
if (!this.jsonOutput) {
|
|
1754
1766
|
console.log(readResult.success ? chalk_1.default.gray(readSummary) : chalk_1.default.red(readSummary));
|
|
1755
1767
|
}
|
|
1756
1768
|
this.messages.push({ role: 'system', content: readSummary });
|
|
@@ -1793,12 +1805,12 @@ class ChatCommand {
|
|
|
1793
1805
|
content: rewrittenContent,
|
|
1794
1806
|
},
|
|
1795
1807
|
};
|
|
1796
|
-
if (!this.jsonOutput
|
|
1808
|
+
if (!this.jsonOutput) {
|
|
1797
1809
|
console.log(chalk_1.default.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
1798
1810
|
}
|
|
1799
1811
|
const writeResult = await this.tools.execute(writeCall);
|
|
1800
1812
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
1801
|
-
if (!this.jsonOutput
|
|
1813
|
+
if (!this.jsonOutput) {
|
|
1802
1814
|
console.log(writeResult.success ? chalk_1.default.gray(writeSummary) : chalk_1.default.red(writeSummary));
|
|
1803
1815
|
}
|
|
1804
1816
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
@@ -1972,7 +1984,7 @@ class ChatCommand {
|
|
|
1972
1984
|
}
|
|
1973
1985
|
else if (this.v3StreamingStarted) {
|
|
1974
1986
|
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
1975
|
-
if (!this.
|
|
1987
|
+
if (!this.jsonOutput) {
|
|
1976
1988
|
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
1977
1989
|
}
|
|
1978
1990
|
}
|
|
@@ -1988,7 +2000,7 @@ class ChatCommand {
|
|
|
1988
2000
|
}
|
|
1989
2001
|
console.log('V3 agent workflow completed.');
|
|
1990
2002
|
}
|
|
1991
|
-
if (!this.jsonOutput &&
|
|
2003
|
+
if (!this.jsonOutput && previewGate?.required) {
|
|
1992
2004
|
if (previewGate.passed) {
|
|
1993
2005
|
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
1994
2006
|
}
|
|
@@ -2897,7 +2909,7 @@ class ChatCommand {
|
|
|
2897
2909
|
}
|
|
2898
2910
|
// In direct-prompt mode (--prompt), suppress verbose tool output so
|
|
2899
2911
|
// only the final answer prints. In interactive mode, show full detail.
|
|
2900
|
-
const verbose = !this.jsonOutput
|
|
2912
|
+
const verbose = !this.jsonOutput;
|
|
2901
2913
|
for (const call of toolCalls) {
|
|
2902
2914
|
if (verbose) {
|
|
2903
2915
|
console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
|
package/dist/commands/legion.js
CHANGED
|
@@ -76,6 +76,7 @@ const HYPERLOOP_URLS = (0, api_js_1.isServerRuntime)()
|
|
|
76
76
|
const CORTEX_WARN_BUDGET_USD = 3.5;
|
|
77
77
|
const CORTEX_HARD_BUDGET_USD = 5.0;
|
|
78
78
|
const CORTEX_MAX_ROUNDS = 2;
|
|
79
|
+
const CORTEX_PLATFORM_FEE_PCT = 10;
|
|
79
80
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
80
81
|
class LegionCommand {
|
|
81
82
|
config;
|
|
@@ -209,13 +210,13 @@ class LegionCommand {
|
|
|
209
210
|
if (billingQuote.retryAdjustedUsd > CORTEX_WARN_BUDGET_USD) {
|
|
210
211
|
console.log(chalk_1.default.yellow(`Estimated spend exceeds warning threshold ($${CORTEX_WARN_BUDGET_USD.toFixed(2)}).`));
|
|
211
212
|
}
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
let billingGate = await this.evaluateBillingGate(billingQuote);
|
|
214
|
+
this.printCortexQuote(workspace, scan, quote, billingQuote, billingGate);
|
|
215
|
+
if (billingQuote.retryAdjustedUsd > CORTEX_HARD_BUDGET_USD && !options.forceBudget && !billingGate.masterAdminFree) {
|
|
216
|
+
console.log(chalk_1.default.red(`Estimated spend exceeds hard budget ceiling (${CORTEX_HARD_BUDGET_USD.toFixed(2)}).`));
|
|
214
217
|
console.log(chalk_1.default.yellow('Re-run with --force-budget to continue.'));
|
|
215
218
|
return;
|
|
216
219
|
}
|
|
217
|
-
let billingGate = await this.evaluateBillingGate(billingQuote);
|
|
218
|
-
this.printCortexQuote(workspace, scan, quote, billingQuote, billingGate);
|
|
219
220
|
if (options.planOnly) {
|
|
220
221
|
console.log(chalk_1.default.green('Cortex estimator complete (plan-only).'));
|
|
221
222
|
return;
|
|
@@ -326,6 +327,7 @@ class LegionCommand {
|
|
|
326
327
|
tier: previousQuote.tier,
|
|
327
328
|
vigcoinRateUsd: previousQuote.vigcoinRateUsd,
|
|
328
329
|
vigcoinRequired: retryAdjustedUsd / previousQuote.vigcoinRateUsd,
|
|
330
|
+
platformFeePct: previousQuote.platformFeePct,
|
|
329
331
|
};
|
|
330
332
|
}
|
|
331
333
|
async confirmAdditionalLoopCharge(nextQuote, nextRound, execution) {
|
|
@@ -640,7 +642,9 @@ class LegionCommand {
|
|
|
640
642
|
const baseUsd = quote.reduce((sum, r) => sum + r.estCostUsd, 0);
|
|
641
643
|
const marginPctRaw = Number.parseFloat(String(process.env.VIGTHORIA_CORTEX_MARGIN_PCT || process.env.VIGTHORIA_GODMODE_MARGIN_PCT || '10'));
|
|
642
644
|
const marginPct = Number.isFinite(marginPctRaw) ? Math.max(0, marginPctRaw) : 10;
|
|
643
|
-
const
|
|
645
|
+
const platformFeePct = CORTEX_PLATFORM_FEE_PCT;
|
|
646
|
+
const platformFeeMultiplier = 1 + (platformFeePct / 100);
|
|
647
|
+
const finalUsd = baseUsd * (1 + (marginPct / 100)) * platformFeeMultiplier;
|
|
644
648
|
// Retry-adjusted estimate: all 7 critical roles can trigger the quality-gate
|
|
645
649
|
// retry in state_manager.py (adds +2 iterations per degraded role).
|
|
646
650
|
// Probability of retry per critical role ≈ 45% (from production telemetry).
|
|
@@ -651,13 +655,13 @@ class LegionCommand {
|
|
|
651
655
|
const retryExtra = quote
|
|
652
656
|
.filter((r) => CRITICAL_ROLES.has(r.role))
|
|
653
657
|
.reduce((sum, r) => sum + r.estCostUsd * RETRY_PROB * RETRY_ITER_RATIO, 0);
|
|
654
|
-
const retryAdjustedUsd = (baseUsd + retryExtra) * (1 + (marginPct / 100));
|
|
658
|
+
const retryAdjustedUsd = (baseUsd + retryExtra) * (1 + (marginPct / 100)) * platformFeeMultiplier;
|
|
655
659
|
// Range bounds.
|
|
656
660
|
const rangeMinUsd = finalUsd; // single pass, no retries
|
|
657
661
|
const maxRetryExtra = quote
|
|
658
662
|
.filter((r) => CRITICAL_ROLES.has(r.role))
|
|
659
663
|
.reduce((sum, r) => sum + r.estCostUsd * RETRY_ITER_RATIO, 0);
|
|
660
|
-
const rangeMaxUsd = (baseUsd + maxRetryExtra) * (1 + (marginPct / 100));
|
|
664
|
+
const rangeMaxUsd = (baseUsd + maxRetryExtra) * (1 + (marginPct / 100)) * platformFeeMultiplier;
|
|
661
665
|
const vigcoinRateRaw = Number.parseFloat(String(process.env.VIGTHORIA_VIGCOIN_USD_RATE || '1'));
|
|
662
666
|
const vigcoinRateUsd = Number.isFinite(vigcoinRateRaw) && vigcoinRateRaw > 0 ? vigcoinRateRaw : 1;
|
|
663
667
|
const vigcoinRequired = retryAdjustedUsd / vigcoinRateUsd;
|
|
@@ -671,6 +675,7 @@ class LegionCommand {
|
|
|
671
675
|
tier,
|
|
672
676
|
vigcoinRateUsd,
|
|
673
677
|
vigcoinRequired,
|
|
678
|
+
platformFeePct,
|
|
674
679
|
};
|
|
675
680
|
}
|
|
676
681
|
async evaluateBillingGate(billingQuote) {
|
|
@@ -1069,6 +1074,7 @@ class LegionCommand {
|
|
|
1069
1074
|
return;
|
|
1070
1075
|
}
|
|
1071
1076
|
console.log(chalk_1.default.gray(` Estimated total (USD): $${billingQuote.retryAdjustedUsd.toFixed(4)}`) + chalk_1.default.gray(' (retry-adjusted expected)'));
|
|
1077
|
+
console.log(chalk_1.default.gray(` Platform fee: +${billingQuote.platformFeePct.toFixed(0)}% (already included in all estimates)`));
|
|
1072
1078
|
console.log(chalk_1.default.gray(` VigCoin rate: 1 VIG = $${billingQuote.vigcoinRateUsd.toFixed(4)}`));
|
|
1073
1079
|
console.log(chalk_1.default.gray(` VigCoin required: ${billingQuote.vigcoinRequired.toFixed(3)}`));
|
|
1074
1080
|
if (gate.wallet.vigcoinBalance !== null) {
|
|
@@ -1104,7 +1110,9 @@ class LegionCommand {
|
|
|
1104
1110
|
console.log();
|
|
1105
1111
|
console.log(chalk_1.default.white(' Role assignment and estimated cost:'));
|
|
1106
1112
|
for (const row of quote) {
|
|
1107
|
-
|
|
1113
|
+
const publicModelLabel = row.requestedModel || row.model.replace(/^openrouter:/i, '').split('/').pop() || 'managed-model';
|
|
1114
|
+
const roleEstWithFee = row.estCostUsd * (1 + (billingQuote.platformFeePct / 100));
|
|
1115
|
+
console.log(chalk_1.default.gray(` ${logger_js_1.CH.bullet} ${row.role.padEnd(11)} ${publicModelLabel} $${roleEstWithFee.toFixed(4)}`));
|
|
1108
1116
|
}
|
|
1109
1117
|
console.log();
|
|
1110
1118
|
console.log(chalk_1.default.yellow(` Cost range (single-pass best case): $${billingQuote.rangeMinUsd.toFixed(4)}`));
|
|
@@ -1113,6 +1121,7 @@ class LegionCommand {
|
|
|
1113
1121
|
console.log(chalk_1.default.gray(' Retry model: 45% chance per critical role triggers quality-gate (+40% iterations per retry).'));
|
|
1114
1122
|
console.log(chalk_1.default.gray(' A mid-run checkpoint will appear when 70% of the expected estimate is consumed.'));
|
|
1115
1123
|
console.log(chalk_1.default.gray(' Flow: Estimate -> Isolation -> Parallel Attack -> Synthesis'));
|
|
1124
|
+
console.log(chalk_1.default.gray(` All displayed costs include platform fee (+${billingQuote.platformFeePct.toFixed(0)}%).`));
|
|
1116
1125
|
console.log();
|
|
1117
1126
|
this.printBillingGateSummary(billingQuote, gate);
|
|
1118
1127
|
console.log();
|
|
@@ -1224,7 +1233,7 @@ class LegionCommand {
|
|
|
1224
1233
|
: 10;
|
|
1225
1234
|
const baseEstimateUsd = (cortexExecution?.quote || []).reduce((s, q) => s + q.estCostUsd, 0);
|
|
1226
1235
|
const budgetCheckpointThreshold = cortexExecution
|
|
1227
|
-
? baseEstimateUsd * (1 + marginPctMidrun / 100) * 0.70
|
|
1236
|
+
? baseEstimateUsd * (1 + marginPctMidrun / 100) * (1 + CORTEX_PLATFORM_FEE_PCT / 100) * 0.70
|
|
1228
1237
|
: Infinity;
|
|
1229
1238
|
let accumulatedEstUsd = 0;
|
|
1230
1239
|
let budgetCheckpointFired = false;
|
|
@@ -1274,7 +1283,7 @@ class LegionCommand {
|
|
|
1274
1283
|
}
|
|
1275
1284
|
// Track spend and trigger mid-run budget checkpoint.
|
|
1276
1285
|
const stepRole = String(evt.step_id || '');
|
|
1277
|
-
accumulatedEstUsd += roleQuoteIndex.get(stepRole) || 0;
|
|
1286
|
+
accumulatedEstUsd += (roleQuoteIndex.get(stepRole) || 0) * (1 + CORTEX_PLATFORM_FEE_PCT / 100);
|
|
1278
1287
|
completedRoleSummaries.push({ role: stepRole, status: String(evt.status || ''), summary: stepSummaryRaw.slice(0, 200) });
|
|
1279
1288
|
if (!budgetCheckpointFired
|
|
1280
1289
|
&& cortexExecution
|
|
@@ -1288,7 +1297,7 @@ class LegionCommand {
|
|
|
1288
1297
|
.map((q) => q.role);
|
|
1289
1298
|
const remainingEstUsd = (cortexExecution.quote || [])
|
|
1290
1299
|
.filter((q) => remainingRoles.includes(q.role))
|
|
1291
|
-
.reduce((s, q) => s + q.estCostUsd, 0);
|
|
1300
|
+
.reduce((s, q) => s + (q.estCostUsd * (1 + CORTEX_PLATFORM_FEE_PCT / 100)), 0);
|
|
1292
1301
|
console.log();
|
|
1293
1302
|
console.log(chalk_1.default.bold.yellow(' ━━━ Mid-Run Budget Checkpoint ━━━'));
|
|
1294
1303
|
console.log(chalk_1.default.gray(` Consumed so far (estimated): $${accumulatedEstUsd.toFixed(4)}`));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config } from '../utils/config.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
interface SecurityOptions {
|
|
4
|
+
dir?: string;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
issueId?: string;
|
|
7
|
+
apply?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class SecurityCommand {
|
|
10
|
+
private config;
|
|
11
|
+
private logger;
|
|
12
|
+
constructor(config: Config, logger: Logger);
|
|
13
|
+
private getMcpBaseUrl;
|
|
14
|
+
private resolveDir;
|
|
15
|
+
private execute;
|
|
16
|
+
scan(options: SecurityOptions): Promise<void>;
|
|
17
|
+
score(options: SecurityOptions): Promise<void>;
|
|
18
|
+
fix(options: SecurityOptions): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SecurityCommand = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
class SecurityCommand {
|
|
10
|
+
config;
|
|
11
|
+
logger;
|
|
12
|
+
constructor(config, logger) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
getMcpBaseUrl() {
|
|
17
|
+
const fromEnv = process.env.VIGTHORIA_MCP_URL || process.env.MCP_SERVER_URL;
|
|
18
|
+
if (fromEnv && fromEnv.trim()) {
|
|
19
|
+
return fromEnv.replace(/\/$/, '');
|
|
20
|
+
}
|
|
21
|
+
return 'http://127.0.0.1:4008';
|
|
22
|
+
}
|
|
23
|
+
resolveDir(dir) {
|
|
24
|
+
return path_1.default.resolve(dir || process.cwd());
|
|
25
|
+
}
|
|
26
|
+
async execute(tool, parameters) {
|
|
27
|
+
const baseUrl = this.getMcpBaseUrl();
|
|
28
|
+
const response = await axios_1.default.post(`${baseUrl}/mcp/execute`, {
|
|
29
|
+
tool,
|
|
30
|
+
parameters,
|
|
31
|
+
context: {
|
|
32
|
+
source: 'vigthoria-cli',
|
|
33
|
+
client: 'vsec'
|
|
34
|
+
}
|
|
35
|
+
}, {
|
|
36
|
+
timeout: 120000,
|
|
37
|
+
validateStatus: (status) => status >= 200 && status < 500,
|
|
38
|
+
});
|
|
39
|
+
if (!response.data?.success) {
|
|
40
|
+
const errorText = response.data?.error || `Tool ${tool} failed`;
|
|
41
|
+
throw new Error(errorText);
|
|
42
|
+
}
|
|
43
|
+
const result = response.data?.result || {};
|
|
44
|
+
return result.result || result;
|
|
45
|
+
}
|
|
46
|
+
async scan(options) {
|
|
47
|
+
const dir = this.resolveDir(options.dir);
|
|
48
|
+
const result = await this.execute('security_scan', { dir });
|
|
49
|
+
if (options.json) {
|
|
50
|
+
console.log(JSON.stringify(result, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.logger.info(`Security scan complete for: ${dir}`);
|
|
54
|
+
this.logger.info(`Score: ${result.score}/100 (${result.grade})`);
|
|
55
|
+
this.logger.info(`Files scanned: ${result.scannedFiles}`);
|
|
56
|
+
this.logger.info(`Issues found: ${result.issueCount}`);
|
|
57
|
+
const issues = Array.isArray(result.issues) ? result.issues : [];
|
|
58
|
+
for (const issue of issues.slice(0, 20)) {
|
|
59
|
+
console.log(`- [${issue.severity}] ${issue.id} ${issue.file}:${issue.line} ${issue.message}`);
|
|
60
|
+
}
|
|
61
|
+
if (issues.length > 20) {
|
|
62
|
+
console.log(`... ${issues.length - 20} more issues`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async score(options) {
|
|
66
|
+
const dir = this.resolveDir(options.dir);
|
|
67
|
+
const result = await this.execute('security_score', { dir });
|
|
68
|
+
if (options.json) {
|
|
69
|
+
console.log(JSON.stringify(result, null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.logger.info(`Security score for ${dir}`);
|
|
73
|
+
this.logger.info(`${result.score}/100 (${result.grade})`);
|
|
74
|
+
this.logger.info(result.summary || '');
|
|
75
|
+
}
|
|
76
|
+
async fix(options) {
|
|
77
|
+
const dir = this.resolveDir(options.dir);
|
|
78
|
+
const result = await this.execute('security_fix', {
|
|
79
|
+
dir,
|
|
80
|
+
issue_id: options.issueId,
|
|
81
|
+
confirm: Boolean(options.apply),
|
|
82
|
+
});
|
|
83
|
+
if (options.json) {
|
|
84
|
+
console.log(JSON.stringify(result, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.logger.info(`Security fix plan for ${dir}`);
|
|
88
|
+
this.logger.info(result.message || 'Fix plan generated');
|
|
89
|
+
const actions = Array.isArray(result.plannedActions) ? result.plannedActions : [];
|
|
90
|
+
for (const action of actions.slice(0, 25)) {
|
|
91
|
+
console.log(`- ${action.issue_id} ${action.file}: ${action.action}`);
|
|
92
|
+
}
|
|
93
|
+
if (actions.length > 25) {
|
|
94
|
+
console.log(`... ${actions.length - 25} more actions`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.SecurityCommand = SecurityCommand;
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ const history_js_1 = require("./commands/history.js");
|
|
|
76
76
|
const replay_js_1 = require("./commands/replay.js");
|
|
77
77
|
const fork_js_1 = require("./commands/fork.js");
|
|
78
78
|
const cancel_js_1 = require("./commands/cancel.js");
|
|
79
|
+
const security_js_1 = require("./commands/security.js");
|
|
79
80
|
const config_js_2 = require("./utils/config.js");
|
|
80
81
|
const logger_js_1 = require("./utils/logger.js");
|
|
81
82
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -124,7 +125,7 @@ function getVersion() {
|
|
|
124
125
|
console.error(chalk_1.default.gray(`Unable to read package version: ${message}`));
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
|
-
return '
|
|
128
|
+
return '0.0.0';
|
|
128
129
|
}
|
|
129
130
|
function validateReleaseMetadata() {
|
|
130
131
|
try {
|
|
@@ -150,20 +151,15 @@ function validateReleaseMetadata() {
|
|
|
150
151
|
'vigthoria edit',
|
|
151
152
|
'vigthoria update',
|
|
152
153
|
'vigthoria doctor',
|
|
153
|
-
'version 1.9.3',
|
|
154
|
-
'vigthoria-cli-1.9.3.tgz',
|
|
155
154
|
];
|
|
156
155
|
return (pkg.name === 'vigthoria-cli' &&
|
|
157
|
-
pkg.version === '
|
|
156
|
+
typeof pkg.version === 'string' &&
|
|
158
157
|
pkg.description === 'Vigthoria Coder CLI - AI-powered terminal coding assistant' &&
|
|
159
158
|
pkg.main === 'dist/index.js' &&
|
|
160
159
|
bins.vigthoria === 'dist/index.js' &&
|
|
161
160
|
bins.vig === 'dist/index.js' &&
|
|
162
161
|
bins['vigthoria-chat'] === 'dist/index.js' &&
|
|
163
|
-
requiredReadmePhrases.every((phrase) => readme.includes(phrase))
|
|
164
|
-
/Version\s+1\.9\.3/i.test(readme) &&
|
|
165
|
-
!readme.includes('vigthoria-cli-1.9.2.tgz') &&
|
|
166
|
-
!/version\s+1\.9\.2/i.test(readme));
|
|
162
|
+
requiredReadmePhrases.every((phrase) => readme.includes(phrase)));
|
|
167
163
|
}
|
|
168
164
|
catch (error) {
|
|
169
165
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -173,12 +169,6 @@ function validateReleaseMetadata() {
|
|
|
173
169
|
return false;
|
|
174
170
|
}
|
|
175
171
|
}
|
|
176
|
-
const VERSION = getVersion();
|
|
177
|
-
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
|
|
178
|
-
/**
|
|
179
|
-
* Compare semantic versions properly
|
|
180
|
-
* Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
181
|
-
*/
|
|
182
172
|
function compareVersions(v1, v2) {
|
|
183
173
|
const parts1 = v1.split('.').map(Number);
|
|
184
174
|
const parts2 = v2.split('.').map(Number);
|
|
@@ -192,6 +182,8 @@ function compareVersions(v1, v2) {
|
|
|
192
182
|
}
|
|
193
183
|
return 0;
|
|
194
184
|
}
|
|
185
|
+
const VERSION = getVersion();
|
|
186
|
+
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
|
|
195
187
|
function resolveManifestEntry(manifest, channel) {
|
|
196
188
|
const normalized = String(channel || 'stable').trim() || 'stable';
|
|
197
189
|
if (manifest.channels && manifest.channels[normalized]) {
|
|
@@ -476,7 +468,7 @@ async function main(args) {
|
|
|
476
468
|
if (invokedBinaryName === 'vigthoria-chat') {
|
|
477
469
|
const knownCommands = new Set([
|
|
478
470
|
'chat', 'chat-resume', 'agent', 'edit', 'generate', 'explain', 'fix', 'review', 'cancel',
|
|
479
|
-
'hub', 'repo', 'deploy', 'operator', 'workflow', 'flow', 'login', 'logout', 'status', 'config', 'update',
|
|
471
|
+
'hub', 'repo', 'deploy', 'operator', 'workflow', 'flow', 'security', 'vsec', 'login', 'logout', 'status', 'config', 'update',
|
|
480
472
|
'--help', '-h', '--version', '-V', 'help', 'version',
|
|
481
473
|
]);
|
|
482
474
|
if (!firstArg || firstArg.startsWith('-') || !knownCommands.has(firstArg)) {
|
|
@@ -647,6 +639,40 @@ async function main(args) {
|
|
|
647
639
|
const bridge = new bridge_js_1.BridgeCommand(config, logger);
|
|
648
640
|
await bridge.status();
|
|
649
641
|
});
|
|
642
|
+
// Security command group - Security DevOps scans and fix plans
|
|
643
|
+
const securityCommand = program
|
|
644
|
+
.command('security')
|
|
645
|
+
.alias('vsec')
|
|
646
|
+
.description('Run security scans, scores, and fix plans via Vigthoria MCP security tools');
|
|
647
|
+
securityCommand
|
|
648
|
+
.command('scan')
|
|
649
|
+
.description('Scan project for security issues')
|
|
650
|
+
.option('-d, --dir <path>', 'Directory to scan (default: current directory)')
|
|
651
|
+
.option('--json', 'Emit JSON output', false)
|
|
652
|
+
.action(async (options) => {
|
|
653
|
+
const security = new security_js_1.SecurityCommand(config, logger);
|
|
654
|
+
await security.scan({ dir: options.dir, json: options.json });
|
|
655
|
+
});
|
|
656
|
+
securityCommand
|
|
657
|
+
.command('score')
|
|
658
|
+
.description('Calculate project security score')
|
|
659
|
+
.option('-d, --dir <path>', 'Directory to score (default: current directory)')
|
|
660
|
+
.option('--json', 'Emit JSON output', false)
|
|
661
|
+
.action(async (options) => {
|
|
662
|
+
const security = new security_js_1.SecurityCommand(config, logger);
|
|
663
|
+
await security.score({ dir: options.dir, json: options.json });
|
|
664
|
+
});
|
|
665
|
+
securityCommand
|
|
666
|
+
.command('fix')
|
|
667
|
+
.description('Generate security fix plan')
|
|
668
|
+
.option('-d, --dir <path>', 'Directory to inspect (default: current directory)')
|
|
669
|
+
.option('-i, --issue-id <id>', 'Single issue id to target')
|
|
670
|
+
.option('--apply', 'Request auto-apply mode (safe mode still requires explicit patching)', false)
|
|
671
|
+
.option('--json', 'Emit JSON output', false)
|
|
672
|
+
.action(async (options) => {
|
|
673
|
+
const security = new security_js_1.SecurityCommand(config, logger);
|
|
674
|
+
await security.fix({ dir: options.dir, issueId: options.issueId, apply: options.apply, json: options.json });
|
|
675
|
+
});
|
|
650
676
|
// Edit command - Edit files with AI
|
|
651
677
|
program
|
|
652
678
|
.command('edit <file>')
|