vigthoria-cli 1.9.2 → 1.9.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.
@@ -254,14 +254,36 @@ class PreviewCommand {
254
254
  * Open URL in default browser
255
255
  */
256
256
  openBrowser(url) {
257
- const { exec } = require('child_process');
257
+ const { execFile } = require('child_process');
258
258
  const platform = process.platform;
259
- const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
260
- exec(`${cmd} ${url}`, (err) => {
261
- if (err) {
262
- this.logger.debug(`Could not auto-open browser: ${err.message}`);
259
+ // Validate URL scheme before passing to OS prevents shell injection via crafted URLs
260
+ try {
261
+ const parsed = new URL(url);
262
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
263
+ return;
264
+ const safeUrl = parsed.href;
265
+ if (platform === 'darwin') {
266
+ execFile('open', [safeUrl], (err) => {
267
+ if (err)
268
+ this.logger.debug(`Could not auto-open browser: ${err.message}`);
269
+ });
263
270
  }
264
- });
271
+ else if (platform === 'win32') {
272
+ execFile('cmd', ['/c', 'start', '', safeUrl], (err) => {
273
+ if (err)
274
+ this.logger.debug(`Could not auto-open browser: ${err.message}`);
275
+ });
276
+ }
277
+ else {
278
+ execFile('xdg-open', [safeUrl], (err) => {
279
+ if (err)
280
+ this.logger.debug(`Could not auto-open browser: ${err.message}`);
281
+ });
282
+ }
283
+ }
284
+ catch {
285
+ this.logger.debug(`Skipping browser open — invalid URL: ${url}`);
286
+ }
265
287
  }
266
288
  /**
267
289
  * Show consolidated diff of recent agent changes using git
@@ -322,7 +344,10 @@ class PreviewCommand {
322
344
  let oldContent = '';
323
345
  if (modified.includes(relPath)) {
324
346
  try {
325
- oldContent = execSync(`git show HEAD:${relPath}`, { cwd: projectPath, encoding: 'utf-8' });
347
+ // Validate relPath is a safe relative path before interpolating — prevents injection
348
+ if (/^[a-zA-Z0-9_\-./]+$/.test(relPath) && !relPath.includes('..')) {
349
+ oldContent = execSync(`git show HEAD:${relPath}`, { cwd: projectPath, encoding: 'utf-8' });
350
+ }
326
351
  }
327
352
  catch {
328
353
  // File is new or not tracked
@@ -460,17 +460,17 @@ class RepoCommand {
460
460
  const os = require('os');
461
461
  const platform = os.platform();
462
462
  if (platform === 'win32') {
463
- // Use PowerShell's Expand-Archive on Windows
464
- const { execSync } = await import('child_process');
465
- execSync(`powershell -Command "Expand-Archive -Path '${tempArchive}' -DestinationPath '${outputPath}' -Force"`, {
466
- stdio: 'ignore',
467
- windowsHide: true
468
- });
463
+ // Use PowerShell's Expand-Archive args as array to prevent injection
464
+ const { execFileSync } = await import('child_process');
465
+ execFileSync('powershell', [
466
+ '-NoProfile', '-NonInteractive', '-Command',
467
+ `Expand-Archive -Path '${tempArchive.replace(/'/g, "''")}' -DestinationPath '${outputPath.replace(/'/g, "''")}' -Force`
468
+ ], { stdio: 'ignore', windowsHide: true });
469
469
  }
470
470
  else {
471
- // Use unzip on Unix-like systems
472
- const { execSync } = await import('child_process');
473
- execSync(`unzip -o "${tempArchive}" -d "${outputPath}"`, { stdio: 'ignore' });
471
+ // Use unzip on Unix-like systems — args as array to prevent injection
472
+ const { execFileSync } = await import('child_process');
473
+ execFileSync('unzip', ['-o', tempArchive, '-d', outputPath], { stdio: 'ignore' });
474
474
  }
475
475
  fs.unlinkSync(tempArchive);
476
476
  }
@@ -750,14 +750,20 @@ class RepoCommand {
750
750
  console.log(chalk_1.default.bold(` ${data.url}\n`));
751
751
  if (options.browser) {
752
752
  try {
753
- const { execSync } = await import('child_process');
753
+ const { execFileSync } = await import('child_process');
754
754
  const platform = process.platform;
755
+ // Validate URL scheme before passing to OS — prevents shell injection
756
+ const parsedUrl = new URL(data.url);
757
+ if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
758
+ throw new Error('Unsafe URL scheme for browser open');
759
+ }
760
+ const safeUrl = parsedUrl.href;
755
761
  if (platform === 'win32')
756
- execSync(`start "" "${data.url}"`, { stdio: 'ignore' });
762
+ execFileSync('cmd', ['/c', 'start', '', safeUrl], { stdio: 'ignore', windowsHide: true });
757
763
  else if (platform === 'darwin')
758
- execSync(`open "${data.url}"`, { stdio: 'ignore' });
764
+ execFileSync('open', [safeUrl], { stdio: 'ignore' });
759
765
  else
760
- execSync(`xdg-open "${data.url}"`, { stdio: 'ignore' });
766
+ execFileSync('xdg-open', [safeUrl], { stdio: 'ignore' });
761
767
  }
762
768
  catch { /* browser open is optional */ }
763
769
  }
@@ -0,0 +1,9 @@
1
+ type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
2
+ interface UpdateOptions {
3
+ check?: boolean;
4
+ packageManager?: PackageManager;
5
+ global?: boolean;
6
+ }
7
+ export declare function updateCommand(options?: UpdateOptions): Promise<void>;
8
+ export declare function update(): Promise<void>;
9
+ export default updateCommand;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.updateCommand = updateCommand;
37
+ exports.update = update;
38
+ const node_child_process_1 = require("node:child_process");
39
+ const node_fs_1 = require("node:fs");
40
+ const path = __importStar(require("node:path"));
41
+ const node_util_1 = require("node:util");
42
+ const tools_js_1 = require("../utils/tools.js");
43
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
44
+ function markSuccessExit() {
45
+ process.exitCode = 0;
46
+ }
47
+ function markErrorExit() {
48
+ process.exitCode = 1;
49
+ }
50
+ function findPackageJson(startDir) {
51
+ let currentDir = startDir;
52
+ for (let depth = 0; depth < 8; depth += 1) {
53
+ const candidate = path.join(currentDir, 'package.json');
54
+ if ((0, node_fs_1.existsSync)(candidate)) {
55
+ return candidate;
56
+ }
57
+ const parentDir = path.join(currentDir, '..');
58
+ if (parentDir === currentDir) {
59
+ break;
60
+ }
61
+ currentDir = parentDir;
62
+ }
63
+ return null;
64
+ }
65
+ function readOwnPackageJson() {
66
+ const currentFile = __filename;
67
+ const packagePath = findPackageJson(path.join(currentFile, '..'));
68
+ if (!packagePath) {
69
+ throw new Error('Unable to locate package.json for the Vigthoria CLI.');
70
+ }
71
+ try {
72
+ return JSON.parse((0, node_fs_1.readFileSync)(packagePath, 'utf8'));
73
+ }
74
+ catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ throw new Error(`Unable to read package metadata: ${message}`);
77
+ }
78
+ }
79
+ function compareVersions(a, b) {
80
+ const parse = (version) => version.replace(/^v/, '').split(/[.-]/).map((part) => {
81
+ const value = Number.parseInt(part, 10);
82
+ return Number.isNaN(value) ? 0 : value;
83
+ });
84
+ const left = parse(a);
85
+ const right = parse(b);
86
+ const length = Math.max(left.length, right.length);
87
+ for (let index = 0; index < length; index += 1) {
88
+ const leftValue = left[index] ?? 0;
89
+ const rightValue = right[index] ?? 0;
90
+ if (leftValue > rightValue)
91
+ return 1;
92
+ if (leftValue < rightValue)
93
+ return -1;
94
+ }
95
+ return 0;
96
+ }
97
+ function quoteCmdArg(value) {
98
+ if (!/[\s"&()<>^|]/.test(value)) {
99
+ return value;
100
+ }
101
+ return `"${value.replace(/(\\*)"/g, '$1$1\\"')}"`;
102
+ }
103
+ function toPlatformCommand(command, args) {
104
+ if (process.platform !== 'win32') {
105
+ return { command, args };
106
+ }
107
+ const cmdPath = process.env.ComSpec || path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe');
108
+ return {
109
+ command: cmdPath,
110
+ args: ['/d', '/s', '/c', [command, ...args].map(quoteCmdArg).join(' ')],
111
+ };
112
+ }
113
+ async function execPackageManager(command, args, timeout) {
114
+ const platformCommand = toPlatformCommand(command, args);
115
+ const { stdout } = await execFileAsync(platformCommand.command, platformCommand.args, {
116
+ timeout,
117
+ maxBuffer: 1024 * 1024,
118
+ windowsHide: true,
119
+ });
120
+ return stdout;
121
+ }
122
+ async function getLatestVersion(packageName) {
123
+ try {
124
+ const stdout = await execPackageManager('npm', ['view', packageName, 'version'], 30_000);
125
+ const latest = stdout.trim();
126
+ if (!latest) {
127
+ throw new Error('npm returned an empty version response');
128
+ }
129
+ return latest;
130
+ }
131
+ catch (error) {
132
+ const message = error instanceof Error ? error.message : String(error);
133
+ throw new Error(`Unable to check the latest published version: ${message}`);
134
+ }
135
+ }
136
+ async function getVersionInfo() {
137
+ const packageJson = readOwnPackageJson();
138
+ const packageName = packageJson.name;
139
+ const current = packageJson.version;
140
+ if (!packageName || !current) {
141
+ throw new Error('The CLI package metadata must include both name and version.');
142
+ }
143
+ const latest = await getLatestVersion(packageName);
144
+ return {
145
+ current,
146
+ latest,
147
+ packageName,
148
+ updateAvailable: compareVersions(current, latest) < 0,
149
+ };
150
+ }
151
+ function resolveInstallCommand(packageManager, packageName, installGlobal) {
152
+ const target = `${packageName}@latest`;
153
+ switch (packageManager) {
154
+ case 'pnpm':
155
+ return { command: 'pnpm', args: installGlobal ? ['add', '--global', target] : ['add', '-D', target] };
156
+ case 'yarn':
157
+ return { command: 'yarn', args: installGlobal ? ['global', 'add', target] : ['add', '--dev', target] };
158
+ case 'bun':
159
+ return { command: 'bun', args: installGlobal ? ['add', '--global', target] : ['add', '--dev', target] };
160
+ case 'npm':
161
+ default:
162
+ return { command: 'npm', args: installGlobal ? ['install', '--global', target] : ['install', '--save-dev', target] };
163
+ }
164
+ }
165
+ function formatCommand(command) {
166
+ return [command.command, ...command.args].map(quoteCmdArg).join(' ');
167
+ }
168
+ async function runInstall(packageManager, packageName, installGlobal) {
169
+ const installCommand = resolveInstallCommand(packageManager, packageName, installGlobal);
170
+ await execPackageManager(installCommand.command, installCommand.args, 120_000);
171
+ }
172
+ async function updateCommand(options = {}) {
173
+ try {
174
+ const info = await getVersionInfo();
175
+ console.log(`Vigthoria CLI current version: ${info.current}`);
176
+ console.log(`Vigthoria CLI latest version: ${info.latest}`);
177
+ if (!info.updateAvailable) {
178
+ console.log('You are already running the latest Vigthoria CLI.');
179
+ markSuccessExit();
180
+ return;
181
+ }
182
+ console.log(`Update available for ${info.packageName}: ${info.current} → ${info.latest}`);
183
+ if (options.check) {
184
+ markSuccessExit();
185
+ return;
186
+ }
187
+ const packageManager = options.packageManager ?? 'npm';
188
+ const installGlobal = options.global ?? true;
189
+ console.log(`Installing ${info.packageName}@latest with ${packageManager}...`);
190
+ if (process.platform === 'win32') {
191
+ const installerResult = await (0, tools_js_1.installUpdateWindows)();
192
+ if (!installerResult.success && installerResult.error === 'ENOENT') {
193
+ const fallbackCommand = resolveInstallCommand(packageManager, info.packageName, installGlobal);
194
+ console.warn('Windows update installer was not found (ENOENT).');
195
+ console.warn('The bundled Windows installer may be missing from this installation.');
196
+ console.warn(`Manual installation command: ${formatCommand(fallbackCommand)}`);
197
+ console.warn('If automatic fallback fails, run the command above manually or download the latest Windows installer from the Vigthoria release page.');
198
+ await execPackageManager(fallbackCommand.command, fallbackCommand.args, 120_000);
199
+ console.log('Package manager installation finished successfully.');
200
+ }
201
+ else if (!installerResult.success) {
202
+ throw new Error(installerResult.error || 'Windows installer failed to start.');
203
+ }
204
+ else {
205
+ console.log('Windows installer started successfully. Complete the installer prompts to finish updating.');
206
+ }
207
+ }
208
+ else {
209
+ await runInstall(packageManager, info.packageName, installGlobal);
210
+ console.log('Package manager installation finished successfully.');
211
+ }
212
+ console.log('Vigthoria CLI update complete.');
213
+ markSuccessExit();
214
+ }
215
+ catch (error) {
216
+ const message = error instanceof Error ? error.message : String(error);
217
+ console.error(`Update failed: ${message}`);
218
+ markErrorExit();
219
+ }
220
+ }
221
+ async function update() {
222
+ try {
223
+ await updateCommand();
224
+ if (process.exitCode === 1) {
225
+ return;
226
+ }
227
+ markSuccessExit();
228
+ }
229
+ catch (error) {
230
+ const message = error instanceof Error ? error.message : String(error);
231
+ console.error(`Update failed: ${message}`);
232
+ markErrorExit();
233
+ }
234
+ }
235
+ exports.default = updateCommand;
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * vigthoria workflow - Manage repeatable VigFlow workflows
16
16
  * vigthoria operator - Start BMAD operator mode
17
17
  */
18
+ export declare function validateReleaseMetadata(): boolean;
18
19
  export declare function setupErrorHandlers(): void;
19
- export declare function main(): Promise<void>;
20
+ export declare function main(args: string[]): Promise<void>;
20
21
  export declare const __cliErrorHandlingReady = true;
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
54
54
  };
55
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
56
  exports.__cliErrorHandlingReady = void 0;
57
+ exports.validateReleaseMetadata = validateReleaseMetadata;
57
58
  exports.setupErrorHandlers = setupErrorHandlers;
58
59
  exports.main = main;
59
60
  const commander_1 = require("commander");
@@ -96,17 +97,19 @@ function getInvokedBinaryName() {
96
97
  return path.basename(executable, path.extname(executable)).toLowerCase();
97
98
  }
98
99
  // Get version from package.json dynamically
100
+ function getPackageMetadataPaths() {
101
+ return [
102
+ path.join(__dirname, '..', 'package.json'),
103
+ path.join(__dirname, '..', '..', 'package.json'),
104
+ path.join(process.cwd(), 'package.json'),
105
+ path.join(process.cwd(), 'node_modules', 'vigthoria-cli', 'package.json'),
106
+ // Also check global npm paths on Windows
107
+ path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'vigthoria-cli', 'package.json'),
108
+ ];
109
+ }
99
110
  function getVersion() {
100
111
  try {
101
- // Try multiple paths to find package.json
102
- const possiblePaths = [
103
- path.join(__dirname, '..', 'package.json'),
104
- path.join(__dirname, '..', '..', 'package.json'),
105
- path.join(process.cwd(), 'node_modules', 'vigthoria-cli', 'package.json'),
106
- // Also check global npm paths on Windows
107
- path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'vigthoria-cli', 'package.json'),
108
- ];
109
- for (const p of possiblePaths) {
112
+ for (const p of getPackageMetadataPaths()) {
110
113
  if (fs.existsSync(p)) {
111
114
  const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
112
115
  if (pkg.name === 'vigthoria-cli') {
@@ -116,9 +119,59 @@ function getVersion() {
116
119
  }
117
120
  }
118
121
  catch (e) {
119
- // Fallback to hardcoded version
122
+ const message = e instanceof Error ? e.message : String(e);
123
+ if (process.env.VIGTHORIA_DEBUG_VERSION === '1') {
124
+ console.error(chalk_1.default.gray(`Unable to read package version: ${message}`));
125
+ }
126
+ }
127
+ return '1.9.3';
128
+ }
129
+ function validateReleaseMetadata() {
130
+ try {
131
+ const packagePath = getPackageMetadataPaths().find((candidate) => fs.existsSync(candidate));
132
+ if (!packagePath) {
133
+ return false;
134
+ }
135
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
136
+ const packageDir = path.dirname(packagePath);
137
+ const readmePath = path.join(packageDir, 'README.md');
138
+ if (!fs.existsSync(readmePath)) {
139
+ return false;
140
+ }
141
+ const readme = fs.readFileSync(readmePath, 'utf8');
142
+ const bins = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin : {};
143
+ const requiredReadmePhrases = [
144
+ 'npm install -g vigthoria-cli',
145
+ 'curl -fsSL https://cli.vigthoria.io/install.sh | bash',
146
+ 'irm https://cli.vigthoria.io/install.ps1 | iex',
147
+ 'vigthoria login',
148
+ 'vigthoria chat',
149
+ 'vig c',
150
+ 'vigthoria edit',
151
+ 'vigthoria update',
152
+ 'vigthoria doctor',
153
+ 'version 1.9.3',
154
+ 'vigthoria-cli-1.9.3.tgz',
155
+ ];
156
+ return (pkg.name === 'vigthoria-cli' &&
157
+ pkg.version === '1.9.3' &&
158
+ pkg.description === 'Vigthoria Coder CLI - AI-powered terminal coding assistant' &&
159
+ pkg.main === 'dist/index.js' &&
160
+ bins.vigthoria === 'dist/index.js' &&
161
+ bins.vig === 'dist/index.js' &&
162
+ 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));
167
+ }
168
+ catch (error) {
169
+ const message = error instanceof Error ? error.message : String(error);
170
+ if (process.env.VIGTHORIA_DEBUG_VERSION === '1') {
171
+ console.error(chalk_1.default.gray(`Release metadata validation failed: ${message}`));
172
+ }
173
+ return false;
120
174
  }
121
- return '1.9.2';
122
175
  }
123
176
  const VERSION = getVersion();
124
177
  const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
@@ -177,6 +230,13 @@ async function installGlobalPackageWithNpm(packageSpec) {
177
230
  const { execFileSync, execSync } = await import('child_process');
178
231
  const attempts = [];
179
232
  if (process.platform === 'win32') {
233
+ // First-choice: cmd.exe /c npm — resolves npm.cmd shims on any Windows Node setup
234
+ const comspec = process.env.COMSPEC || 'cmd.exe';
235
+ attempts.push({
236
+ label: 'cmd.exe /c npm',
237
+ command: comspec,
238
+ args: ['/c', 'npm', 'install', '-g', packageSpec],
239
+ });
180
240
  const npmExecPath = process.env.npm_execpath;
181
241
  if (npmExecPath && fs.existsSync(npmExecPath)) {
182
242
  attempts.push({
@@ -403,14 +463,16 @@ function setupErrorHandlers() {
403
463
  }
404
464
  });
405
465
  }
406
- async function main() {
466
+ async function main(args) {
407
467
  const program = new commander_1.Command();
408
468
  const config = new config_js_2.Config();
409
469
  const logger = new logger_js_1.Logger();
410
470
  const invokedBinaryName = getInvokedBinaryName();
411
- const firstArg = process.argv[2];
412
- const jsonOutputRequested = process.argv.includes('--json');
413
- const directPromptRequested = process.argv.includes('--prompt') || process.argv.includes('-P');
471
+ const argv = [...args];
472
+ const firstArg = argv[2];
473
+ const jsonOutputRequested = argv.includes('--json');
474
+ const directPromptRequested = argv.includes('--prompt') || argv.includes('-P');
475
+ const isLegionCortexRequest = invokedBinaryName === 'vigthoria' && argv[2] === 'legion' && argv.includes('--cortex');
414
476
  if (invokedBinaryName === 'vigthoria-chat') {
415
477
  const knownCommands = new Set([
416
478
  'chat', 'chat-resume', 'agent', 'edit', 'generate', 'explain', 'fix', 'review', 'cancel',
@@ -418,17 +480,18 @@ async function main() {
418
480
  '--help', '-h', '--version', '-V', 'help', 'version',
419
481
  ]);
420
482
  if (!firstArg || firstArg.startsWith('-') || !knownCommands.has(firstArg)) {
421
- process.argv.splice(2, 0, 'chat');
483
+ argv.splice(2, 0, 'chat');
422
484
  }
423
485
  }
424
- const requestedCommand = resolveRequestedCommand(process.argv);
425
- // Skip gateway JWT auth when running on-server with a service key (e.g., GodMode on-box).
486
+ const requestedCommand = resolveRequestedCommand(argv);
487
+ // Skip gateway JWT auth when running on-server with a service key (e.g., Cortex on-box).
426
488
  // The service key is checked by Hyper Loop directly — no user session needed.
427
489
  const hasServiceKey = !!(process.env.HYPERLOOP_SERVICE_KEY ||
428
490
  process.env.V3_SERVICE_KEY);
429
- if (!hasServiceKey && requestedCommand && isAuthProtectedCommand(requestedCommand)) {
491
+ if (!hasServiceKey && !isLegionCortexRequest && requestedCommand && isAuthProtectedCommand(requestedCommand)) {
430
492
  const authOk = await enforceGatewayAuthSession(config, logger, jsonOutputRequested);
431
493
  if (!authOk) {
494
+ process.exitCode = 1;
432
495
  return;
433
496
  }
434
497
  }
@@ -988,13 +1051,18 @@ Examples:
988
1051
  .description('Run parallel tasks via Hyper Loop Legion orchestrator')
989
1052
  .option('--workers', 'List available Legion workers')
990
1053
  .option('--status', 'Show Legion infrastructure status')
991
- .option('--godmode', 'Estimate, isolate, parallel-attack, and synthesize with strongest models')
992
- .option('--approve', 'Auto-approve Godmode execution prompt')
1054
+ .option('--cortex', 'Vigthoria Cortex: maximum intelligence execution')
1055
+ .option('--approve', 'Auto-approve Cortex execution prompt')
993
1056
  .option('--no-approve', 'Require interactive approval before execution')
994
- .option('--auto-charge', 'Attempt direct VigCoin top-up when Godmode balance is low')
995
- .option('--plan-only', 'Run Godmode estimator only; do not execute Legion job')
996
- .option('--models <csv>', 'Comma-separated model IDs to constrain Godmode selection')
997
- .option('--timeout <seconds>', 'Override Legion execution timeout in seconds (defaults to server policy)')
1057
+ .option('--auto-charge', 'Attempt direct VigCoin top-up when Cortex balance is low')
1058
+ .option('--plan-only', 'Run Cortex estimator only; do not execute Legion job')
1059
+ .option('--force-budget', 'Allow execution when estimated budget exceeds the hard safe-stop ceiling')
1060
+ .option('--ignore-preflight', 'Bypass mandatory local preflight checks (no warranty)')
1061
+ .option('--speed', 'Enable speed mode (allows optional role skips when convergence is detected)')
1062
+ .option('--repro-cmd <command>', 'Local reproduction/preflight command to validate before cloud spend')
1063
+ .option('--expect-repro-fail', 'Require repro command to fail (non-zero) before proceeding')
1064
+ .option('--models <csv>', 'Comma-separated model IDs to constrain Cortex selection')
1065
+ .option('-t, --timeout <seconds>', 'Override Legion execution timeout in seconds (defaults to server policy)')
998
1066
  .option('-w, --worker <name>', 'Execute a specific worker')
999
1067
  .option('-p, --project <path>', 'Project directory', process.cwd())
1000
1068
  .action(async (request, options) => {
@@ -1003,11 +1071,16 @@ Examples:
1003
1071
  await legion.run(request, {
1004
1072
  workers: options.workers,
1005
1073
  status: options.status,
1006
- godmode: options.godmode,
1074
+ cortex: Boolean(options.cortex),
1007
1075
  approve: options.approve,
1008
1076
  noApprove: options.approve === false,
1009
1077
  autoCharge: options.autoCharge,
1010
1078
  planOnly: options.planOnly,
1079
+ forceBudget: options.forceBudget,
1080
+ ignorePreflight: options.ignorePreflight,
1081
+ speed: options.speed,
1082
+ reproCmd: options.reproCmd,
1083
+ expectReproFail: options.expectReproFail,
1011
1084
  models: options.models,
1012
1085
  timeoutSec: Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : undefined,
1013
1086
  worker: options.worker,
@@ -1060,8 +1133,11 @@ Examples:
1060
1133
  // Auth commands
1061
1134
  program
1062
1135
  .command('login')
1063
- .description('Login to Vigthoria Coder')
1064
- .option('-t, --token <token>', 'API token')
1136
+ .description('Login to Vigthoria Coder (prompts for credentials when run without flags)')
1137
+ .option('-t, --token <token>', 'API token for token-based authentication')
1138
+ .option('-e, --email <email>', 'Account email for credential-based login')
1139
+ .option('-p, --password <password>', 'Account password for credential-based login')
1140
+ .option('--device', 'Use OAuth device flow (requires server support)')
1065
1141
  .action(async (options) => {
1066
1142
  await (0, auth_js_1.handleLogin)(options);
1067
1143
  });
@@ -1083,13 +1159,14 @@ Examples:
1083
1159
  .action(() => {
1084
1160
  const checks = [
1085
1161
  ['Node.js', process.version],
1086
- ['Platform', `linux x64`],
1162
+ ['Platform', `${process.platform} ${process.arch}`],
1087
1163
  ['Working directory', process.cwd()],
1088
1164
  ];
1089
1165
  console.log('Vigthoria CLI diagnostics');
1090
1166
  for (const [label, value] of checks) {
1091
1167
  console.log(`- ${label}: ${value}`);
1092
1168
  }
1169
+ process.exitCode = 0;
1093
1170
  });
1094
1171
  // Config command
1095
1172
  program
@@ -1264,7 +1341,7 @@ Examples:
1264
1341
  await legion.run(undefined, {
1265
1342
  status: true,
1266
1343
  workers: false,
1267
- godmode: false,
1344
+ cortex: false,
1268
1345
  approve: false,
1269
1346
  noApprove: true,
1270
1347
  planOnly: false,
@@ -1317,12 +1394,16 @@ Examples:
1317
1394
  }
1318
1395
  try {
1319
1396
  // Default to chat if no command
1320
- if (process.argv.length === 2) {
1397
+ if (args.length === 2) {
1321
1398
  const chat = new chat_js_1.ChatCommand(config, logger);
1322
1399
  await chat.run({ model: 'code', project: process.cwd() });
1400
+ process.exitCode = 0;
1323
1401
  return;
1324
1402
  }
1325
- await program.parseAsync(process.argv);
1403
+ await program.parseAsync(args);
1404
+ if (process.exitCode === undefined || process.exitCode === 0) {
1405
+ process.exitCode = 0;
1406
+ }
1326
1407
  }
1327
1408
  catch (error) {
1328
1409
  reportCliError(error, jsonOutputRequested);
@@ -1336,7 +1417,7 @@ process.on('unhandledRejection', (reason) => {
1336
1417
  });
1337
1418
  async function bootstrapCli() {
1338
1419
  try {
1339
- await main();
1420
+ await main(process.argv);
1340
1421
  }
1341
1422
  catch (err) {
1342
1423
  handleFatalCliError(err, process.argv.includes('--json'));