vigthoria-cli 1.10.36 → 1.10.47

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.
Files changed (62) hide show
  1. package/dist/commands/agent-session-menu.d.ts +19 -0
  2. package/dist/commands/agent-session-menu.js +155 -0
  3. package/dist/commands/auth.js +68 -51
  4. package/dist/commands/bridge.js +19 -12
  5. package/dist/commands/cancel.js +22 -15
  6. package/dist/commands/chat.d.ts +0 -22
  7. package/dist/commands/chat.js +402 -1084
  8. package/dist/commands/config.js +73 -33
  9. package/dist/commands/deploy.js +123 -83
  10. package/dist/commands/device.js +61 -21
  11. package/dist/commands/edit.js +39 -32
  12. package/dist/commands/explain.js +25 -18
  13. package/dist/commands/generate.js +44 -37
  14. package/dist/commands/hub.js +102 -95
  15. package/dist/commands/index.js +46 -41
  16. package/dist/commands/legion.js +186 -146
  17. package/dist/commands/review.js +36 -29
  18. package/dist/commands/security.js +12 -5
  19. package/dist/commands/wallet.js +35 -28
  20. package/dist/commands/workflow.js +20 -13
  21. package/dist/utils/brain-hub-client.d.ts +32 -0
  22. package/dist/utils/brain-hub-client.js +52 -0
  23. package/dist/utils/bridge-client.js +52 -11
  24. package/dist/utils/codebase-indexer.d.ts +59 -0
  25. package/dist/utils/codebase-indexer.js +351 -0
  26. package/dist/utils/context-ranker.js +21 -15
  27. package/dist/utils/files.js +42 -5
  28. package/dist/utils/logger.js +50 -42
  29. package/dist/utils/persona.js +8 -3
  30. package/dist/utils/post-write-validator.js +29 -22
  31. package/dist/utils/project-memory.js +23 -16
  32. package/dist/utils/task-display.js +20 -13
  33. package/dist/utils/workspace-brain-service.d.ts +43 -0
  34. package/dist/utils/workspace-brain-service.js +158 -0
  35. package/dist/utils/workspace-cache.js +26 -18
  36. package/dist/utils/workspace-stream.js +63 -21
  37. package/package.json +3 -6
  38. package/scripts/release/validate-no-go-gates.sh +1 -1
  39. package/dist/commands/fork.d.ts +0 -17
  40. package/dist/commands/fork.js +0 -164
  41. package/dist/commands/history.d.ts +0 -17
  42. package/dist/commands/history.js +0 -113
  43. package/dist/commands/preview.d.ts +0 -55
  44. package/dist/commands/preview.js +0 -467
  45. package/dist/commands/replay.d.ts +0 -18
  46. package/dist/commands/replay.js +0 -156
  47. package/dist/commands/repo.d.ts +0 -97
  48. package/dist/commands/repo.js +0 -773
  49. package/dist/commands/update.d.ts +0 -9
  50. package/dist/commands/update.js +0 -201
  51. package/dist/index.d.ts +0 -21
  52. package/dist/index.js +0 -1823
  53. package/dist/utils/api.d.ts +0 -572
  54. package/dist/utils/api.js +0 -6548
  55. package/dist/utils/cli-state.d.ts +0 -54
  56. package/dist/utils/cli-state.js +0 -185
  57. package/dist/utils/config.d.ts +0 -85
  58. package/dist/utils/config.js +0 -267
  59. package/dist/utils/session.d.ts +0 -118
  60. package/dist/utils/session.js +0 -423
  61. package/dist/utils/tools.d.ts +0 -274
  62. package/dist/utils/tools.js +0 -3502
package/dist/index.js DELETED
@@ -1,1823 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Vigthoria CLI - AI-Powered Terminal Coding Assistant
4
- *
5
- * Usage:
6
- * vigthoria chat - Start interactive chat
7
- * vigthoria edit <file> - Edit a file with AI assistance
8
- * vigthoria generate <desc> - Generate code from description
9
- * vigthoria explain <file> - Explain code in a file
10
- * vigthoria fix <file> - Fix issues in a file
11
- * vigthoria review <file> - Review code quality
12
- * vigthoria login - Authenticate with Vigthoria
13
- * vigthoria config - Configure settings
14
- * vigthoria hub - Discover & activate API modules
15
- * vigthoria workflow - Manage repeatable VigFlow workflows
16
- * vigthoria operator - Start BMAD operator mode
17
- */
18
- import { Command, Option } from 'commander';
19
- import { ChatCommand } from './commands/chat.js';
20
- import { EditCommand } from './commands/edit.js';
21
- import { GenerateCommand } from './commands/generate.js';
22
- import { ExplainCommand } from './commands/explain.js';
23
- import { handleLogin, handleLogout, statusAction } from './commands/auth.js';
24
- import { ConfigCommand } from './commands/config.js';
25
- import { ReviewCommand } from './commands/review.js';
26
- import { HubCommand } from './commands/hub.js';
27
- import { RepoCommand } from './commands/repo.js';
28
- import { DeployCommand } from './commands/deploy.js';
29
- import { BridgeCommand } from './commands/bridge.js';
30
- import { DeviceCommand } from './commands/device.js';
31
- import { WorkflowCommand } from './commands/workflow.js';
32
- import { PreviewCommand } from './commands/preview.js';
33
- import { LegionCommand } from './commands/legion.js';
34
- import { HistoryCommand } from './commands/history.js';
35
- import { ReplayCommand } from './commands/replay.js';
36
- import { ForkCommand } from './commands/fork.js';
37
- import { CancelCommand } from './commands/cancel.js';
38
- import { SecurityCommand } from './commands/security.js';
39
- import { WalletCommand } from './commands/wallet.js';
40
- import { Config } from './utils/config.js';
41
- import { Logger, CH } from './utils/logger.js';
42
- import chalk from 'chalk';
43
- import * as fs from 'fs';
44
- import * as path from 'path';
45
- import * as os from 'os';
46
- import { fileURLToPath } from 'url';
47
- import { createHash } from 'crypto';
48
- import axios from 'axios';
49
- // ESM shim — TypeScript emits ESM under "type": "module", so __dirname
50
- // is not available natively. Restore the CommonJS-style helper so the
51
- // pre-existing package-discovery logic continues to work unchanged.
52
- const __filename = fileURLToPath(import.meta.url);
53
- const __dirname = path.dirname(__filename);
54
- import { APIClient, sanitizeUserFacingErrorText } from './utils/api.js';
55
- import { getCliStateFile, isOfflineMode, isSafeNpmPackageSpec, isUpdateCheckSuppressed, readCachedLatestVersion, readGatewayPreflightCache, writeCachedLatestVersion, writeGatewayPreflightCache, } from './utils/cli-state.js';
56
- function isApiError(error) {
57
- return Boolean(error &&
58
- typeof error === 'object' &&
59
- typeof error.status === 'number' &&
60
- typeof error.message === 'string' &&
61
- typeof error.code === 'string');
62
- }
63
- function getInvokedBinaryName() {
64
- const executable = process.argv[1] || 'vigthoria';
65
- return path.basename(executable, path.extname(executable)).toLowerCase();
66
- }
67
- function detectRuntimeEnvironment(cwd) {
68
- const osPlatform = process.platform;
69
- const normalizedPlatform = osPlatform === 'win32'
70
- ? 'windows'
71
- : osPlatform === 'darwin'
72
- ? 'macos'
73
- : osPlatform === 'linux'
74
- ? 'linux'
75
- : 'unknown';
76
- const release = os.release();
77
- const isWSL = normalizedPlatform === 'linux' && (/microsoft/i.test(release)
78
- || Boolean(process.env.WSL_DISTRO_NAME)
79
- || Boolean(process.env.WSL_INTEROP));
80
- let isContainer = false;
81
- try {
82
- if (fs.existsSync('/.dockerenv')) {
83
- isContainer = true;
84
- }
85
- else if (fs.existsSync('/proc/1/cgroup')) {
86
- const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
87
- isContainer = /(docker|containerd|kubepods|podman)/i.test(cgroup);
88
- }
89
- }
90
- catch {
91
- isContainer = false;
92
- }
93
- const isCodespaces = String(process.env.CODESPACES || '').toLowerCase() === 'true';
94
- const isCI = String(process.env.CI || '').toLowerCase() === 'true';
95
- const isMobileLikeRuntime = Boolean(process.env.TERMUX_VERSION
96
- || process.env.ANDROID_ROOT
97
- || process.env.IOS_SIMULATOR_DEVICE_NAME
98
- || /\/var\/mobile\//.test(cwd));
99
- const workspaceKind = cwd.startsWith('/var/www/') || cwd.startsWith('/opt/vigthoria')
100
- ? 'server-workspace'
101
- : 'local-machine';
102
- return {
103
- osPlatform,
104
- normalizedPlatform,
105
- arch: process.arch,
106
- release,
107
- isWSL,
108
- isContainer,
109
- isCodespaces,
110
- isCI,
111
- isMobileLikeRuntime,
112
- workspaceKind,
113
- cwd,
114
- shell: process.env.SHELL || process.env.ComSpec || null,
115
- };
116
- }
117
- // Get version from package.json dynamically
118
- function getPackageMetadataPaths() {
119
- return [
120
- path.join(__dirname, '..', 'package.json'),
121
- path.join(__dirname, '..', '..', 'package.json'),
122
- path.join(process.cwd(), 'package.json'),
123
- path.join(process.cwd(), 'node_modules', 'vigthoria-cli', 'package.json'),
124
- // Also check global npm paths on Windows
125
- path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'vigthoria-cli', 'package.json'),
126
- ];
127
- }
128
- function getVersion() {
129
- try {
130
- for (const p of getPackageMetadataPaths()) {
131
- if (fs.existsSync(p)) {
132
- const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
133
- if (pkg.name === 'vigthoria-cli') {
134
- return pkg.version;
135
- }
136
- }
137
- }
138
- }
139
- catch (e) {
140
- const message = e instanceof Error ? e.message : String(e);
141
- if (process.env.VIGTHORIA_DEBUG_VERSION === '1') {
142
- console.error(chalk.gray(`Unable to read package version: ${message}`));
143
- }
144
- }
145
- return '0.0.0';
146
- }
147
- export function validateReleaseMetadata() {
148
- try {
149
- const packagePath = getPackageMetadataPaths().find((candidate) => fs.existsSync(candidate));
150
- if (!packagePath) {
151
- return false;
152
- }
153
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
154
- const packageDir = path.dirname(packagePath);
155
- const readmePath = path.join(packageDir, 'README.md');
156
- if (!fs.existsSync(readmePath)) {
157
- return false;
158
- }
159
- const readme = fs.readFileSync(readmePath, 'utf8');
160
- const bins = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin : {};
161
- const requiredReadmePhrases = [
162
- 'npm install -g vigthoria-cli',
163
- 'curl -fsSL https://cli.vigthoria.io/install.sh | bash',
164
- 'irm https://cli.vigthoria.io/install.ps1 | iex',
165
- 'vigthoria login',
166
- 'vigthoria chat',
167
- 'vig c',
168
- 'vigthoria edit',
169
- 'vigthoria update',
170
- 'vigthoria doctor',
171
- ];
172
- return (pkg.name === 'vigthoria-cli' &&
173
- typeof pkg.version === 'string' &&
174
- pkg.description === 'Vigthoria Coder CLI - AI-powered terminal coding assistant' &&
175
- pkg.main === 'dist/index.js' &&
176
- bins.vigthoria === 'dist/index.js' &&
177
- bins.vig === 'dist/index.js' &&
178
- bins['vigthoria-chat'] === 'dist/index.js' &&
179
- requiredReadmePhrases.every((phrase) => readme.includes(phrase)));
180
- }
181
- catch (error) {
182
- const message = error instanceof Error ? error.message : String(error);
183
- if (process.env.VIGTHORIA_DEBUG_VERSION === '1') {
184
- console.error(chalk.gray(`Release metadata validation failed: ${message}`));
185
- }
186
- return false;
187
- }
188
- }
189
- function compareVersions(v1, v2) {
190
- const parts1 = v1.split('.').map(Number);
191
- const parts2 = v2.split('.').map(Number);
192
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
193
- const p1 = parts1[i] || 0;
194
- const p2 = parts2[i] || 0;
195
- if (p1 < p2)
196
- return -1;
197
- if (p1 > p2)
198
- return 1;
199
- }
200
- return 0;
201
- }
202
- const VERSION = getVersion();
203
- function hiddenGrantOption() {
204
- return new Option('--grant', 'Use optional Wiener Grantler persona for this invocation').hideHelp();
205
- }
206
- function allowInsecureCliVersionInstall() {
207
- return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_ALLOW_INSECURE_CLI_VERSION || ''));
208
- }
209
- function extractVersionFromUpdateTarget(target) {
210
- const match = target.match(/(?:@|[-_])([0-9]+\.[0-9]+\.[0-9]+)(?:\.tgz)?(?:$|[^0-9])/);
211
- return match ? match[1] : null;
212
- }
213
- function isBelowSecureCliBaseline(version) {
214
- return compareVersions(version, VIGTHORIA_MIN_SECURE_CLI_VERSION) < 0;
215
- }
216
- const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
217
- const VIGTHORIA_MIN_SECURE_CLI_VERSION = '1.10.22';
218
- function resolveManifestEntry(manifest, channel) {
219
- const normalized = String(channel || 'stable').trim() || 'stable';
220
- if (manifest.channels && manifest.channels[normalized]) {
221
- return manifest.channels[normalized];
222
- }
223
- const direct = manifest[normalized];
224
- if (direct && typeof direct === 'object') {
225
- return direct;
226
- }
227
- if (normalized === 'stable') {
228
- const rootVersion = typeof manifest.version === 'string' ? manifest.version : '';
229
- const rootUrl = typeof manifest.url === 'string' ? manifest.url : '';
230
- const rootSha = typeof manifest.sha256 === 'string' ? manifest.sha256 : undefined;
231
- if (rootVersion && rootUrl) {
232
- return { version: rootVersion, url: rootUrl, sha256: rootSha };
233
- }
234
- }
235
- return null;
236
- }
237
- async function downloadFile(url, targetPath) {
238
- const response = await axios.get(url, {
239
- responseType: 'arraybuffer',
240
- timeout: 20000,
241
- maxRedirects: 5,
242
- validateStatus: (status) => status >= 200 && status < 300,
243
- });
244
- fs.writeFileSync(targetPath, Buffer.from(response.data));
245
- }
246
- function sha256File(filePath) {
247
- const hash = createHash('sha256');
248
- const data = fs.readFileSync(filePath);
249
- hash.update(data);
250
- return hash.digest('hex');
251
- }
252
- async function installGlobalPackageWithNpm(packageSpec) {
253
- const { execFileSync, execSync } = await import('child_process');
254
- const attempts = [];
255
- if (process.platform === 'win32') {
256
- // First-choice: cmd.exe /c npm — resolves npm.cmd shims on any Windows Node setup
257
- const comspec = process.env.COMSPEC || 'cmd.exe';
258
- attempts.push({
259
- label: 'cmd.exe /c npm',
260
- command: comspec,
261
- args: ['/c', 'npm', 'install', '-g', packageSpec],
262
- });
263
- const npmExecPath = process.env.npm_execpath;
264
- if (npmExecPath && fs.existsSync(npmExecPath)) {
265
- attempts.push({
266
- label: 'node+npm_execpath',
267
- command: process.execPath,
268
- args: [npmExecPath, 'install', '-g', packageSpec],
269
- });
270
- }
271
- try {
272
- const resolvedNpmCmd = execSync('where npm.cmd', {
273
- encoding: 'utf8',
274
- stdio: ['pipe', 'pipe', 'pipe'],
275
- windowsHide: true,
276
- }).split(/\r?\n/).map((line) => line.trim()).find(Boolean);
277
- if (resolvedNpmCmd) {
278
- attempts.push({
279
- label: 'where npm.cmd',
280
- command: resolvedNpmCmd,
281
- args: ['install', '-g', packageSpec],
282
- });
283
- }
284
- }
285
- catch {
286
- // Fall through to generic attempts below.
287
- }
288
- attempts.push({
289
- label: 'npm.cmd',
290
- command: 'npm.cmd',
291
- args: ['install', '-g', packageSpec],
292
- });
293
- // Last-resort Windows shell fallback for PATH/cmd shim edge-cases.
294
- attempts.push({
295
- label: 'npm via shell',
296
- command: 'npm',
297
- args: ['install', '-g', packageSpec],
298
- shell: true,
299
- });
300
- }
301
- else {
302
- let resolvedNpm = '';
303
- try {
304
- resolvedNpm = execSync('which npm', {
305
- encoding: 'utf8',
306
- stdio: ['pipe', 'pipe', 'pipe'],
307
- }).trim();
308
- }
309
- catch {
310
- // Keep resolvedNpm empty and use plain `npm` below.
311
- }
312
- attempts.push({
313
- label: resolvedNpm ? 'which npm' : 'npm',
314
- command: resolvedNpm || 'npm',
315
- args: ['install', '-g', packageSpec],
316
- });
317
- }
318
- let lastError = null;
319
- for (const attempt of attempts) {
320
- try {
321
- console.log(chalk.gray(`[update] installer executable: ${attempt.command}${attempt.shell ? ' (shell)' : ''}`));
322
- execFileSync(attempt.command, attempt.args, {
323
- stdio: 'inherit',
324
- windowsHide: true,
325
- shell: attempt.shell === true,
326
- });
327
- return;
328
- }
329
- catch (error) {
330
- lastError = error;
331
- const code = error?.code || error?.status || 'unknown';
332
- console.error(chalk.yellow(`[update] install attempt failed via ${attempt.label}: ${code}`));
333
- }
334
- }
335
- const finalCode = lastError?.code || lastError?.status || 'unknown';
336
- throw new Error(`Unable to launch npm installer after ${attempts.length} attempt(s). Last error: ${finalCode}`);
337
- }
338
- function renderUpdateBanner(latestVersion) {
339
- if (compareVersions(latestVersion, '1.4.0') >= 0 && compareVersions(VERSION, '1.4.0') < 0) {
340
- console.log(chalk.red.bold(`\n${CH.warnEmoji} SECURITY UPDATE AVAILABLE`));
341
- console.log(chalk.red(` Version ${VERSION} has security vulnerabilities.`));
342
- console.log(chalk.yellow(` Please update to ${latestVersion} immediately:`));
343
- console.log(chalk.white.bold(' npm install -g vigthoria-cli@latest\n'));
344
- }
345
- else {
346
- console.log(chalk.yellow(`\n${CH.warnEmoji} Update available: ${VERSION} -> ${latestVersion}`));
347
- console.log(chalk.gray(' Run `vigthoria update` to install\n'));
348
- }
349
- }
350
- // Check for updates quietly on startup. Cached (24 h) so we never block
351
- // startup on `npm view`. Honours VIGTHORIA_OFFLINE / VIGTHORIA_NO_UPDATE_CHECK.
352
- async function checkForUpdatesQuietly() {
353
- if (isUpdateCheckSuppressed()) {
354
- return;
355
- }
356
- const cached = readCachedLatestVersion();
357
- if (cached) {
358
- if (compareVersions(cached, VERSION) > 0) {
359
- renderUpdateBanner(cached);
360
- }
361
- return;
362
- }
363
- // Cache miss: probe npm registry in the background, with a hard 5 s
364
- // timeout. Result is cached for 24 h so subsequent runs are instant.
365
- try {
366
- const { execSync } = await import('child_process');
367
- const npmVersion = execSync('npm view vigthoria-cli version', {
368
- encoding: 'utf8',
369
- timeout: 5000,
370
- stdio: ['pipe', 'pipe', 'pipe'],
371
- windowsHide: true,
372
- }).trim();
373
- if (!npmVersion) {
374
- return;
375
- }
376
- writeCachedLatestVersion(npmVersion);
377
- if (compareVersions(npmVersion, VERSION) > 0) {
378
- renderUpdateBanner(npmVersion);
379
- }
380
- }
381
- catch {
382
- // Network or npm failure should never block CLI; do not cache failure.
383
- }
384
- }
385
- const VIGTHORIA_GATEWAY_AUTH_FAILURE_MESSAGE = 'Vigthoria Gate way user authentification failed. Please log out and login again.';
386
- function resolveRequestedCommand(argv) {
387
- for (let i = 2; i < argv.length; i++) {
388
- const token = String(argv[i] || '').trim();
389
- if (!token || token.startsWith('-'))
390
- continue;
391
- return token;
392
- }
393
- return '';
394
- }
395
- function isAuthProtectedCommand(command) {
396
- const protectedCommands = new Set([
397
- 'chat', 'c', 'chat-resume',
398
- 'agent', 'a', 'operator', 'op',
399
- 'edit', 'e', 'generate', 'g', 'explain', 'x', 'fix', 'f', 'review', 'r',
400
- 'workflow', 'flow', 'hub', 'marketplace', 'deploy', 'host', 'preview', 'device',
401
- 'legion', 'history', 'runs', 'replay', 'fork', 'cancel',
402
- 'repo', 'repository',
403
- ]);
404
- return protectedCommands.has(command);
405
- }
406
- async function enforceGatewayAuthSession(config, logger, jsonOutputRequested) {
407
- if (!config.isAuthenticated()) {
408
- return true;
409
- }
410
- // Offline mode disables every outbound preflight call.
411
- if (isOfflineMode()) {
412
- return true;
413
- }
414
- // Short-lived cache so back-to-back CLI invocations don't re-probe.
415
- const cached = readGatewayPreflightCache();
416
- if (cached === true) {
417
- return true;
418
- }
419
- const api = new APIClient(config, logger);
420
- try {
421
- const tokenCheck = await api.validateToken();
422
- if (!tokenCheck.valid) {
423
- writeGatewayPreflightCache(false);
424
- if (jsonOutputRequested) {
425
- process.exitCode = 1;
426
- console.log(JSON.stringify({ success: false, error: VIGTHORIA_GATEWAY_AUTH_FAILURE_MESSAGE }, null, 2));
427
- }
428
- else {
429
- console.log(chalk.red(VIGTHORIA_GATEWAY_AUTH_FAILURE_MESSAGE));
430
- }
431
- return false;
432
- }
433
- writeGatewayPreflightCache(true);
434
- return true;
435
- }
436
- catch {
437
- // Network or transient backend outages should not be misclassified as auth failure.
438
- return true;
439
- }
440
- finally {
441
- api.destroy();
442
- }
443
- }
444
- function scrubMessageForUser(message) {
445
- try {
446
- return sanitizeUserFacingErrorText(message) || message;
447
- }
448
- catch {
449
- return message;
450
- }
451
- }
452
- function normalizeCliError(error) {
453
- if (error instanceof Error) {
454
- const extended = error;
455
- const responseMessage = typeof extended.response?.data?.error === 'string'
456
- ? extended.response.data.error
457
- : typeof extended.response?.data?.message === 'string'
458
- ? extended.response.data.message
459
- : undefined;
460
- const rawMessage = responseMessage || extended.message || 'An unexpected CLI error occurred.';
461
- return {
462
- message: scrubMessageForUser(rawMessage),
463
- code: extended.code,
464
- status: extended.status || extended.statusCode || extended.response?.status,
465
- details: extended.details || extended.response?.data,
466
- };
467
- }
468
- if (error && typeof error === 'object') {
469
- const value = error;
470
- const rawMessage = typeof value.message === 'string' && value.message.trim() ? value.message : 'An unexpected CLI error occurred.';
471
- return {
472
- message: scrubMessageForUser(rawMessage),
473
- code: typeof value.code === 'string' || typeof value.code === 'number' ? value.code : undefined,
474
- status: typeof value.status === 'number' ? value.status : undefined,
475
- details: value.details,
476
- };
477
- }
478
- const rawMessage = typeof error === 'string' && error.trim() ? error : 'An unexpected CLI error occurred.';
479
- return {
480
- message: scrubMessageForUser(rawMessage),
481
- };
482
- }
483
- function formatCliError(error, jsonOutputRequested = false) {
484
- const normalized = normalizeCliError(error);
485
- if (jsonOutputRequested) {
486
- return JSON.stringify({ success: false, error: normalized.message, code: normalized.code, status: normalized.status, details: normalized.details }, null, 2);
487
- }
488
- const parts = [chalk.red('Error:'), normalized.message];
489
- if (normalized.code)
490
- parts.push(chalk.gray(`[${normalized.code}]`));
491
- if (normalized.status)
492
- parts.push(chalk.gray(`(HTTP ${normalized.status})`));
493
- return parts.join(' ');
494
- }
495
- function reportCliError(error, jsonOutputRequested = false) {
496
- const output = formatCliError(error, jsonOutputRequested);
497
- if (jsonOutputRequested) {
498
- console.log(output);
499
- }
500
- else {
501
- console.error(output);
502
- }
503
- }
504
- function handleFatalCliError(error, jsonOutputRequested = false) {
505
- reportCliError(error, jsonOutputRequested);
506
- process.exitCode = 1;
507
- }
508
- let errorHandlersInstalled = false;
509
- export function setupErrorHandlers() {
510
- if (errorHandlersInstalled) {
511
- return;
512
- }
513
- errorHandlersInstalled = true;
514
- process.on('unhandledRejection', (reason) => {
515
- const rejection = reason instanceof Error ? reason : new Error(String(reason || 'Unhandled promise rejection'));
516
- handleFatalCliError(rejection, process.argv.includes('--json'));
517
- process.exit(1);
518
- });
519
- process.on('uncaughtException', (error) => {
520
- handleFatalCliError(error, process.argv.includes('--json'));
521
- });
522
- process.on('warning', (warning) => {
523
- if (process.env.VIGTHORIA_DEBUG === '1') {
524
- reportCliError(warning, process.argv.includes('--json'));
525
- }
526
- });
527
- }
528
- export async function main(args) {
529
- const program = new Command();
530
- const config = new Config();
531
- const logger = new Logger();
532
- const invokedBinaryName = getInvokedBinaryName();
533
- const argv = [...args];
534
- const firstArg = argv[2];
535
- const jsonOutputRequested = argv.includes('--json');
536
- const directPromptRequested = argv.includes('--prompt') || argv.includes('-P');
537
- const helpOrVersionRequested = argv.some((token) => token === '--help' || token === '-h' || token === '--version' || token === '-V' ||
538
- token === 'help' || token === 'version');
539
- const isLegionCortexRequest = invokedBinaryName === 'vigthoria' && argv[2] === 'legion' && argv.includes('--cortex');
540
- if (invokedBinaryName === 'vigthoria-chat') {
541
- const knownCommands = new Set([
542
- 'chat', 'chat-resume', 'agent', 'edit', 'generate', 'explain', 'fix', 'review', 'cancel',
543
- 'hub', 'repo', 'deploy', 'operator', 'workflow', 'flow', 'device', 'security', 'vsec', 'login', 'logout', 'status', 'config', 'update',
544
- '--help', '-h', '--version', '-V', 'help', 'version',
545
- ]);
546
- if (!firstArg || firstArg.startsWith('-') || !knownCommands.has(firstArg)) {
547
- argv.splice(2, 0, 'chat');
548
- }
549
- }
550
- const requestedCommand = resolveRequestedCommand(argv);
551
- // Skip gateway JWT auth when running on-server with a service key (e.g., Cortex on-box).
552
- // The service key is checked by Hyper Loop directly — no user session needed.
553
- const hasServiceKey = !!(process.env.HYPERLOOP_SERVICE_KEY ||
554
- process.env.V3_SERVICE_KEY);
555
- if (!hasServiceKey && !isLegionCortexRequest && requestedCommand && isAuthProtectedCommand(requestedCommand)) {
556
- const authOk = await enforceGatewayAuthSession(config, logger, jsonOutputRequested);
557
- if (!authOk) {
558
- process.exitCode = 1;
559
- return;
560
- }
561
- }
562
- // Banner - Fixed alignment with proper padding
563
- const boxWidth = 61; // Inner content width
564
- const titleText = 'VIGTHORIA CLI - AI-Powered Coding Assistant';
565
- const versionText = `Version ${VERSION}`;
566
- // Calculate padding for centering
567
- const titlePad = Math.floor((boxWidth - 4 - titleText.length) / 2);
568
- const versionPad = Math.floor((boxWidth - 4 - versionText.length) / 2);
569
- if (process.env.VIGTHORIA_NO_BANNER !== '1' &&
570
- !jsonOutputRequested &&
571
- !directPromptRequested &&
572
- !helpOrVersionRequested) {
573
- console.log(chalk.cyan(CH.dTl + CH.dH.repeat(boxWidth) + CH.dTr));
574
- console.log(chalk.cyan(CH.dV + ' '.repeat(boxWidth) + CH.dV));
575
- console.log(chalk.cyan(CH.dV) + ' '.repeat(titlePad) + chalk.bold.white('VIGTHORIA CLI') + chalk.cyan(' - AI-Powered Coding Assistant') + ' '.repeat(boxWidth - titlePad - titleText.length) + chalk.cyan(CH.dV));
576
- console.log(chalk.cyan(CH.dV) + ' '.repeat(versionPad) + chalk.gray(versionText) + ' '.repeat(boxWidth - versionPad - versionText.length) + chalk.cyan(CH.dV));
577
- console.log(chalk.cyan(CH.dV + ' '.repeat(boxWidth) + CH.dV));
578
- console.log(chalk.cyan(CH.dBl + CH.dH.repeat(boxWidth) + CH.dBr));
579
- console.log();
580
- }
581
- // Check for updates in background (don't wait). Skip for JSON / direct
582
- // prompt / help / version invocations to keep them deterministic.
583
- if (!isUpdateCheckSuppressed() &&
584
- !jsonOutputRequested &&
585
- !directPromptRequested &&
586
- !helpOrVersionRequested) {
587
- void checkForUpdatesQuietly();
588
- }
589
- program
590
- .name(invokedBinaryName === 'vigthoria-chat' ? 'vigthoria-chat' : 'vigthoria')
591
- .description('AI-powered terminal coding assistant for Vigthoria Coder subscribers')
592
- .version(VERSION)
593
- .addOption(hiddenGrantOption());
594
- // Chat command - Interactive mode (Agent mode is default for best results)
595
- program
596
- .command('chat')
597
- .alias('c')
598
- .description('Start interactive chat with Vigthoria AI')
599
- .option('-m, --model <model>', 'Select AI model (agent, code, code-35b, code-9b, balanced, balanced-4b, cloud, ultra)')
600
- .option('-p, --project <path>', 'Set project context path')
601
- .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
602
- .option('-a, --agent', 'Enable agentic mode (default: true for best quality)', true)
603
- .option('--no-agent', 'Disable agentic mode (simple chat only)')
604
- .option('-r, --resume', 'Resume last session for this project', false)
605
- .option('--prompt <text>', 'Run a single prompt directly and exit')
606
- .option('-w, --workflow <selector>', 'Route prompts through a named or explicit VigFlow workflow target')
607
- .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
608
- .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
609
- .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
610
- .addOption(hiddenGrantOption())
611
- .action(async (options) => {
612
- const chat = new ChatCommand(config, logger);
613
- await chat.run({
614
- model: options.model,
615
- project: options.project,
616
- projectProvided: typeof options.project === 'string' && options.project.trim().length > 0,
617
- newProject: options.newProject,
618
- agent: options.agent,
619
- workflow: options.workflow,
620
- json: options.json,
621
- autoApprove: options.autoApprove,
622
- resume: options.resume,
623
- prompt: options.prompt,
624
- bridge: options.bridge,
625
- grant: options.grant || program.opts().grant === true,
626
- });
627
- });
628
- program
629
- .command('chat-resume')
630
- .description('Resume the latest chat session for the current or specified project')
631
- .option('-m, --model <model>', 'Select AI model (agent, code, code-35b, code-9b, balanced, balanced-4b, cloud, ultra)')
632
- .option('-p, --project <path>', 'Set project context path')
633
- .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
634
- .option('-a, --agent', 'Enable agentic mode (default: true for best quality)', true)
635
- .option('--no-agent', 'Disable agentic mode (simple chat only)')
636
- .option('--prompt <text>', 'Run a single prompt directly and exit after resuming context')
637
- .option('-w, --workflow <selector>', 'Route prompts through a named or explicit VigFlow workflow target')
638
- .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
639
- .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
640
- .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
641
- .addOption(hiddenGrantOption())
642
- .action(async (options) => {
643
- const chat = new ChatCommand(config, logger);
644
- await chat.run({
645
- model: options.model,
646
- project: options.project,
647
- projectProvided: typeof options.project === 'string' && options.project.trim().length > 0,
648
- newProject: options.newProject,
649
- agent: options.agent,
650
- workflow: options.workflow,
651
- json: options.json,
652
- autoApprove: options.autoApprove,
653
- resume: true,
654
- prompt: options.prompt,
655
- bridge: options.bridge,
656
- grant: options.grant || program.opts().grant === true,
657
- });
658
- });
659
- // Agent command - Agentic mode (Vigthoria Autonomous)
660
- // Uses Vigthoria v3 Code 35B or Vigthoria Cloud for complex tasks
661
- program
662
- .command('agent')
663
- .alias('a')
664
- .description('Start agentic mode - AI can read/write files, run commands')
665
- .option('-m, --model <model>', 'Select AI model (agent, code, cloud, ultra)')
666
- .option('-p, --project <path>', 'Set project context path')
667
- .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
668
- .option('--prompt <text>', 'Run a single agent prompt directly and exit')
669
- .option('-w, --workflow <selector>', 'Run the prompt through a named or explicit VigFlow workflow target')
670
- .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
671
- .option('--auto-approve', 'Auto-approve all actions (dangerous!)', false)
672
- .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
673
- .addOption(hiddenGrantOption())
674
- .action(async (options) => {
675
- const chat = new ChatCommand(config, logger);
676
- await chat.run({
677
- model: options.model,
678
- project: options.project,
679
- projectProvided: typeof options.project === 'string' && options.project.trim().length > 0,
680
- newProject: options.newProject,
681
- agent: true,
682
- operator: false,
683
- workflow: options.workflow,
684
- json: options.json,
685
- autoApprove: options.autoApprove,
686
- prompt: options.prompt,
687
- bridge: options.bridge,
688
- grant: options.grant || program.opts().grant === true,
689
- });
690
- });
691
- program
692
- .command('operator')
693
- .alias('op')
694
- .description('Start BMAD operator mode for infrastructure and system workflows')
695
- .option('-m, --model <model>', 'Select operator model (code, agent, cloud)')
696
- .option('-p, --project <path>', 'Set project context path')
697
- .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
698
- .option('--prompt <text>', 'Run a single operator prompt directly and exit')
699
- .option('-w, --workflow <selector>', 'Run the prompt through a named or explicit VigFlow workflow target')
700
- .option('--save-plan', 'Save the completed BMAD plan into VigFlow for rerun and audit', false)
701
- .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
702
- .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
703
- .addOption(hiddenGrantOption())
704
- .action(async (options) => {
705
- const chat = new ChatCommand(config, logger);
706
- await chat.run({
707
- model: options.model,
708
- project: options.project,
709
- projectProvided: typeof options.project === 'string' && options.project.trim().length > 0,
710
- newProject: options.newProject,
711
- agent: false,
712
- operator: true,
713
- workflow: options.workflow,
714
- savePlan: options.savePlan,
715
- json: options.json,
716
- prompt: options.prompt,
717
- bridge: options.bridge,
718
- grant: options.grant || program.opts().grant === true,
719
- });
720
- });
721
- program
722
- .command('bridge')
723
- .description('Inspect local DevTools Bridge availability for browser debugging tasks')
724
- .command('status')
725
- .description('Show live DevTools Bridge status')
726
- .action(async () => {
727
- const bridge = new BridgeCommand(config, logger);
728
- await bridge.status();
729
- });
730
- const deviceCommand = program
731
- .command('device')
732
- .alias('adb')
733
- .description('Android Developer Bridge commands via local ADB');
734
- deviceCommand
735
- .command('status')
736
- .description('Show ADB availability and connected Android devices')
737
- .option('--json', 'Emit JSON output', false)
738
- .action(async (options) => {
739
- const device = new DeviceCommand(config, logger);
740
- await device.status(options);
741
- });
742
- deviceCommand
743
- .command('list')
744
- .description('List connected Android devices')
745
- .option('--json', 'Emit JSON output', false)
746
- .action(async (options) => {
747
- const device = new DeviceCommand(config, logger);
748
- await device.list(options);
749
- });
750
- deviceCommand
751
- .command('screenshot')
752
- .description('Capture a screenshot from an Android device')
753
- .option('-d, --device <id>', 'ADB device id')
754
- .option('-o, --output <path>', 'Output PNG path')
755
- .action(async (options) => {
756
- const device = new DeviceCommand(config, logger);
757
- await device.screenshot(options);
758
- });
759
- deviceCommand
760
- .command('install <apk>')
761
- .description('Install or update an APK on an Android device')
762
- .option('-d, --device <id>', 'ADB device id')
763
- .option('--json', 'Emit JSON output', false)
764
- .action(async (apk, options) => {
765
- const device = new DeviceCommand(config, logger);
766
- await device.install(apk, options);
767
- });
768
- deviceCommand
769
- .command('launch <package>')
770
- .description('Launch an Android app by package name')
771
- .option('-d, --device <id>', 'ADB device id')
772
- .option('-a, --activity <activity>', 'Activity class, for example .MainActivity')
773
- .option('--json', 'Emit JSON output', false)
774
- .action(async (packageName, options) => {
775
- const device = new DeviceCommand(config, logger);
776
- await device.launch(packageName, options);
777
- });
778
- deviceCommand
779
- .command('logs')
780
- .description('Read Android logcat output')
781
- .option('-d, --device <id>', 'ADB device id')
782
- .option('-n, --lines <count>', 'Number of log lines for snapshot mode', '250')
783
- .option('-f, --follow', 'Follow logcat until interrupted', false)
784
- .option('--json', 'Emit JSON output for snapshot mode', false)
785
- .action(async (options) => {
786
- const device = new DeviceCommand(config, logger);
787
- await device.logs(options);
788
- });
789
- deviceCommand
790
- .command('tcpip')
791
- .description('Enable wireless ADB tcpip mode on the selected USB-connected device')
792
- .option('-d, --device <id>', 'ADB device id')
793
- .option('-p, --port <port>', 'Wireless ADB port', '5555')
794
- .option('--json', 'Emit JSON output', false)
795
- .action(async (options) => {
796
- const device = new DeviceCommand(config, logger);
797
- await device.tcpip(options);
798
- });
799
- deviceCommand
800
- .command('connect <address>')
801
- .description('Connect to a wireless ADB device, for example 192.168.1.30:5555')
802
- .option('--json', 'Emit JSON output', false)
803
- .action(async (address, options) => {
804
- const device = new DeviceCommand(config, logger);
805
- await device.connect(address, options);
806
- });
807
- deviceCommand
808
- .command('disconnect [address]')
809
- .description('Disconnect a wireless ADB device')
810
- .option('--json', 'Emit JSON output', false)
811
- .action(async (address, options) => {
812
- const device = new DeviceCommand(config, logger);
813
- await device.disconnect(address, options);
814
- });
815
- deviceCommand.action(async () => {
816
- const device = new DeviceCommand(config, logger);
817
- await device.status({});
818
- });
819
- // Security command group - Security DevOps scans and fix plans
820
- const securityCommand = program
821
- .command('security')
822
- .alias('vsec')
823
- .description('Run security scans, scores, and fix plans via Vigthoria MCP security tools');
824
- securityCommand
825
- .command('scan')
826
- .description('Scan project for security issues')
827
- .option('-d, --dir <path>', 'Directory to scan (default: current directory)')
828
- .option('--json', 'Emit JSON output', false)
829
- .action(async (options) => {
830
- const security = new SecurityCommand(config, logger);
831
- await security.scan({ dir: options.dir, json: options.json });
832
- });
833
- securityCommand
834
- .command('score')
835
- .description('Calculate project security score')
836
- .option('-d, --dir <path>', 'Directory to score (default: current directory)')
837
- .option('--json', 'Emit JSON output', false)
838
- .action(async (options) => {
839
- const security = new SecurityCommand(config, logger);
840
- await security.score({ dir: options.dir, json: options.json });
841
- });
842
- securityCommand
843
- .command('fix')
844
- .description('Generate security fix plan')
845
- .option('-d, --dir <path>', 'Directory to inspect (default: current directory)')
846
- .option('-i, --issue-id <id>', 'Single issue id to target')
847
- .option('--apply', 'Request auto-apply mode (safe mode still requires explicit patching)', false)
848
- .option('--json', 'Emit JSON output', false)
849
- .action(async (options) => {
850
- const security = new SecurityCommand(config, logger);
851
- await security.fix({ dir: options.dir, issueId: options.issueId, apply: options.apply, json: options.json });
852
- });
853
- // Edit command - Edit files with AI
854
- program
855
- .command('edit <file>')
856
- .alias('e')
857
- .description('Edit a file with AI assistance')
858
- .option('-i, --instruction <text>', 'Editing instruction')
859
- .option('-m, --model <model>', 'Select AI model', 'code')
860
- .option('--apply', 'Automatically apply changes without confirmation', false)
861
- .action(async (file, options) => {
862
- const edit = new EditCommand(config, logger);
863
- await edit.run(file, options);
864
- });
865
- // Generate command - Generate code
866
- program
867
- .command('generate <description>')
868
- .alias('g')
869
- .description('Generate code from description')
870
- .option('-l, --language <lang>', 'Target language (auto-detected from description, or specify: javascript, typescript, python, html, etc.)')
871
- .option('-o, --output <file>', 'Output file path')
872
- .option('-m, --model <model>', 'Select AI model', 'code')
873
- .option('-p, --pro', 'Senior Developer Mode: plan, generate, quality check (recommended)', false)
874
- .action(async (description, options) => {
875
- const generate = new GenerateCommand(config, logger);
876
- await generate.run(description, options);
877
- });
878
- // Explain command - Explain code
879
- program
880
- .command('explain <file>')
881
- .alias('x')
882
- .description('Explain code in a file')
883
- .option('-l, --lines <range>', 'Line range (e.g., 1-50)')
884
- .option('-d, --detail <level>', 'Detail level (brief, normal, detailed)', 'normal')
885
- .action(async (file, options) => {
886
- const explain = new ExplainCommand(config, logger);
887
- await explain.run(file, options);
888
- });
889
- // Fix command - Fix code issues
890
- program
891
- .command('fix [file]')
892
- .alias('f')
893
- .description('Fix issues in a file')
894
- .option('-t, --type <type>', 'Fix type (bugs, style, security, performance)', 'bugs')
895
- .option('--apply', 'Automatically apply fixes', false)
896
- .action(async (file, options) => {
897
- if (!file) {
898
- logger.error('Usage: vigthoria fix <file> [--type bugs|style|security|performance] [--apply]');
899
- process.exitCode = 1;
900
- return;
901
- }
902
- const edit = new EditCommand(config, logger);
903
- await edit.fix(file, options);
904
- });
905
- // Review command - Code review
906
- program
907
- .command('review <file>')
908
- .alias('r')
909
- .description('Review code quality')
910
- .option('-f, --format <format>', 'Output format (text, json, markdown)', 'text')
911
- .action(async (file, options) => {
912
- const review = new ReviewCommand(config, logger);
913
- await review.run(file, options);
914
- });
915
- const workflowCommand = program
916
- .command('workflow')
917
- .alias('flow')
918
- .description('List, instantiate, and run repeatable VigFlow workflows');
919
- workflowCommand
920
- .command('templates')
921
- .alias('catalog')
922
- .description('List available VigFlow templates')
923
- .option('-c, --category <category>', 'Filter templates by category')
924
- .option('-s, --search <query>', 'Search template names, descriptions, and tags')
925
- .option('--json', 'Emit machine-readable JSON output', false)
926
- .action(async (options) => {
927
- const workflow = new WorkflowCommand(config, logger);
928
- await workflow.templates(options);
929
- });
930
- workflowCommand
931
- .command('list')
932
- .alias('ls')
933
- .description('List workflows created for the current account')
934
- .option('--json', 'Emit machine-readable JSON output', false)
935
- .action(async (options) => {
936
- const workflow = new WorkflowCommand(config, logger);
937
- await workflow.list(options);
938
- });
939
- workflowCommand
940
- .command('use-template <templateId>')
941
- .description('Create a workflow from a built-in or saved template')
942
- .option('-n, --name <name>', 'Override the workflow name')
943
- .option('-v, --variables <json>', 'Template variables as a JSON object')
944
- .option('--json', 'Emit machine-readable JSON output', false)
945
- .action(async (templateId, options) => {
946
- const workflow = new WorkflowCommand(config, logger);
947
- await workflow.useTemplate(templateId, options);
948
- });
949
- workflowCommand
950
- .command('run <workflowId>')
951
- .description('Run a workflow immediately')
952
- .option('-d, --data <json>', 'Execution input data as a JSON object')
953
- .option('--no-brain', 'Do not attach local Project Brain context to this workflow run')
954
- .option('--json', 'Emit machine-readable JSON output', false)
955
- .action(async (workflowId, options) => {
956
- const workflow = new WorkflowCommand(config, logger);
957
- await workflow.run(workflowId, options);
958
- });
959
- workflowCommand
960
- .command('status <executionId>')
961
- .description('Get the current or final status of a workflow execution')
962
- .option('--no-brain', 'Do not remember this workflow status in local Project Brain')
963
- .option('--json', 'Emit machine-readable JSON output', false)
964
- .action(async (executionId, options) => {
965
- const workflow = new WorkflowCommand(config, logger);
966
- await workflow.status(executionId, options);
967
- });
968
- workflowCommand.action(async () => {
969
- const workflow = new WorkflowCommand(config, logger);
970
- await workflow.templates({});
971
- });
972
- // ==================== HUB / MARKETPLACE COMMANDS ====================
973
- // Hub command - Discover and activate API modules
974
- const hubCommand = program
975
- .command('hub')
976
- .alias('marketplace')
977
- .description('Discover, search, and activate Vigthoria API modules');
978
- hubCommand
979
- .command('discover')
980
- .alias('d')
981
- .description('Interactive module discovery - find the right APIs for your project')
982
- .action(async () => {
983
- const hub = new HubCommand(config, logger);
984
- await hub.discover();
985
- });
986
- hubCommand
987
- .command('list')
988
- .alias('ls')
989
- .description('List all available API modules')
990
- .option('-c, --category <category>', 'Filter by category (payments, communication, ai, creative, media)')
991
- .action(async (options) => {
992
- const hub = new HubCommand(config, logger);
993
- await hub.list(options);
994
- });
995
- hubCommand
996
- .command('search <query>')
997
- .alias('find')
998
- .description('Semantic search for modules (e.g., "generate background music for my app")')
999
- .action(async (query) => {
1000
- const hub = new HubCommand(config, logger);
1001
- await hub.search(query);
1002
- });
1003
- hubCommand
1004
- .command('activate <module>')
1005
- .alias('enable')
1006
- .description('Activate a module for your API key (enables pay-as-you-go)')
1007
- .action(async (module) => {
1008
- const hub = new HubCommand(config, logger);
1009
- await hub.activate(module);
1010
- });
1011
- hubCommand
1012
- .command('active')
1013
- .description('Show your currently active modules')
1014
- .action(async () => {
1015
- const hub = new HubCommand(config, logger);
1016
- await hub.active();
1017
- });
1018
- hubCommand
1019
- .command('info <module>')
1020
- .alias('details')
1021
- .description('Get detailed information about a module')
1022
- .action(async (module) => {
1023
- const hub = new HubCommand(config, logger);
1024
- await hub.info(module);
1025
- });
1026
- // Default hub action shows discover
1027
- hubCommand.action(async () => {
1028
- const hub = new HubCommand(config, logger);
1029
- await hub.discover();
1030
- });
1031
- // ==================== REPO COMMANDS ====================
1032
- // Repo command - Push/Pull projects to/from Vigthoria Repository
1033
- const repoCommand = program
1034
- .command('repo')
1035
- .alias('repository')
1036
- .description('Push and pull projects to/from your Vigthoria Repository');
1037
- repoCommand
1038
- .command('push [path]')
1039
- .alias('upload')
1040
- .description('Push current or specified project to Vigthoria Repo')
1041
- .option('-n, --name <projectName>', 'Project name override for the remote repo')
1042
- .option('-v, --visibility <type>', 'Set visibility (private, restricted, public)', 'private')
1043
- .option('-d, --description <text>', 'Project description')
1044
- .option('-f, --force', 'Overwrite existing project', false)
1045
- .option('-y, --yes', 'Run non-interactively with provided/default values', false)
1046
- .option('--open-in <engine>', 'After push, open project in engine: shop, visual, game')
1047
- .option('--browser', 'Open result URL in default browser', false)
1048
- .action(async (pathArg, options) => {
1049
- const repo = new RepoCommand(config, logger);
1050
- await repo.push({
1051
- path: pathArg,
1052
- name: options.name,
1053
- visibility: options.visibility,
1054
- description: options.description,
1055
- force: options.force,
1056
- yes: options.yes
1057
- });
1058
- if (options.openIn) {
1059
- const engine = options.openIn;
1060
- if (!['shop', 'visual', 'game'].includes(engine)) {
1061
- logger.warn(`Unknown engine "${engine}". Use: shop, visual, game`);
1062
- }
1063
- else {
1064
- await repo.openIn(engine, options.name || undefined, { browser: options.browser });
1065
- }
1066
- }
1067
- });
1068
- repoCommand
1069
- .command('pull <name>')
1070
- .alias('download')
1071
- .description('Pull a project from your Vigthoria Repo')
1072
- .option('-o, --output <path>', 'Output directory path')
1073
- .option('-f, --force', 'Overwrite existing directory', false)
1074
- .action(async (name, options) => {
1075
- const repo = new RepoCommand(config, logger);
1076
- await repo.pull(name, {
1077
- output: options.output,
1078
- force: options.force
1079
- });
1080
- });
1081
- repoCommand
1082
- .command('list')
1083
- .alias('ls')
1084
- .description('List all your projects in Vigthoria Repo')
1085
- .option('-v, --visibility <type>', 'Filter by visibility (private, restricted, public)')
1086
- .action(async (options) => {
1087
- const repo = new RepoCommand(config, logger);
1088
- await repo.list({ visibility: options.visibility });
1089
- });
1090
- repoCommand
1091
- .command('status')
1092
- .description('Show sync status of current project')
1093
- .action(async () => {
1094
- const repo = new RepoCommand(config, logger);
1095
- await repo.status();
1096
- });
1097
- repoCommand
1098
- .command('share <name>')
1099
- .description('Generate a shareable link for a project')
1100
- .option('-e, --expires <duration>', 'Link expiration (e.g., 7d, 24h, 30m)', '7d')
1101
- .action(async (name, options) => {
1102
- const repo = new RepoCommand(config, logger);
1103
- await repo.share(name, { expires: options.expires });
1104
- });
1105
- repoCommand
1106
- .command('delete <name>')
1107
- .alias('rm')
1108
- .description('Remove a project from your Vigthoria Repo')
1109
- .action(async (name) => {
1110
- const repo = new RepoCommand(config, logger);
1111
- await repo.delete(name);
1112
- });
1113
- repoCommand
1114
- .command('clone <url>')
1115
- .description('Clone a public project from Vigthoria Repo')
1116
- .option('-o, --output <path>', 'Output directory path')
1117
- .option('-f, --force', 'Overwrite existing directory', false)
1118
- .action(async (url, options) => {
1119
- const repo = new RepoCommand(config, logger);
1120
- await repo.clone(url, {
1121
- output: options.output,
1122
- force: options.force
1123
- });
1124
- });
1125
- repoCommand
1126
- .command('open-in <engine> [projectName]')
1127
- .description('Open a Vigthoria Repo project in the Shop Engine, Visual Editor, or Gaming Engine')
1128
- .option('--browser', 'Open result URL in default browser', false)
1129
- .option('--shop-id <id>', 'Target shop ID (for shop engine)', 'default')
1130
- .addHelpText('after', `
1131
- Examples:
1132
- vigthoria repo open-in shop my-store
1133
- vigthoria repo open-in visual my-website
1134
- vigthoria repo open-in game my-game --browser`)
1135
- .action(async (engine, projectName, options) => {
1136
- if (!['shop', 'visual', 'game'].includes(engine)) {
1137
- logger.error(`Unknown engine "${engine}". Valid options: shop, visual, game`);
1138
- process.exit(1);
1139
- }
1140
- const repo = new RepoCommand(config, logger);
1141
- await repo.openIn(engine, projectName, {
1142
- browser: options.browser,
1143
- shopId: options.shopId
1144
- });
1145
- });
1146
- // Default repo action shows help with available subcommands
1147
- repoCommand.action(() => {
1148
- repoCommand.outputHelp();
1149
- });
1150
- // ==================== DEPLOY COMMANDS ====================
1151
- // Deploy command - Host projects on Vigthoria
1152
- const deployCommand = program
1153
- .command('deploy')
1154
- .alias('host')
1155
- .description('Deploy and host your project on Vigthoria infrastructure');
1156
- deployCommand
1157
- .command('preview')
1158
- .description('Deploy to free preview URL')
1159
- .option('-p, --project <path>', 'Project directory path', process.cwd())
1160
- .action(async (options) => {
1161
- const deploy = new DeployCommand(config, logger);
1162
- await deploy.deployToPreview(options.project);
1163
- });
1164
- deployCommand
1165
- .command('subdomain <name>')
1166
- .description('Deploy to yourname.vigthoria.io')
1167
- .option('-p, --project <path>', 'Project directory path', process.cwd())
1168
- .action(async (name, options) => {
1169
- const deploy = new DeployCommand(config, logger);
1170
- await deploy.deployToSubdomain(name, options.project);
1171
- });
1172
- deployCommand
1173
- .command('custom <domain>')
1174
- .description('Deploy to your custom domain')
1175
- .option('-p, --project <path>', 'Project directory path', process.cwd())
1176
- .action(async (domain, options) => {
1177
- const deploy = new DeployCommand(config, logger);
1178
- await deploy.deployToCustomDomain(domain, options.project);
1179
- });
1180
- deployCommand
1181
- .command('list')
1182
- .alias('ls')
1183
- .description('List all your deployments')
1184
- .action(async () => {
1185
- const deploy = new DeployCommand(config, logger);
1186
- await deploy.list();
1187
- });
1188
- deployCommand
1189
- .command('plans')
1190
- .description('Show hosting plans and pricing')
1191
- .action(async () => {
1192
- const deploy = new DeployCommand(config, logger);
1193
- await deploy.showPlans();
1194
- });
1195
- deployCommand
1196
- .command('status [domain]')
1197
- .description('Check deployment status')
1198
- .action(async (domain) => {
1199
- const deploy = new DeployCommand(config, logger);
1200
- await deploy.status(domain);
1201
- });
1202
- deployCommand
1203
- .command('verify <domain>')
1204
- .description('Verify DNS configuration for custom domain')
1205
- .action(async (domain) => {
1206
- const deploy = new DeployCommand(config, logger);
1207
- await deploy.verify(domain);
1208
- });
1209
- deployCommand
1210
- .command('remove <domain>')
1211
- .alias('rm')
1212
- .description('Remove a deployment')
1213
- .action(async (domain) => {
1214
- const deploy = new DeployCommand(config, logger);
1215
- await deploy.remove(domain);
1216
- });
1217
- // Default deploy action shows interactive wizard
1218
- deployCommand
1219
- .option('-s, --subdomain <name>', 'Deploy to subdomain')
1220
- .option('-d, --domain <domain>', 'Deploy to custom domain')
1221
- .option('-p, --project <path>', 'Project directory path', process.cwd())
1222
- .action(async (options) => {
1223
- const deploy = new DeployCommand(config, logger);
1224
- await deploy.deploy({
1225
- subdomain: options.subdomain,
1226
- domain: options.domain,
1227
- project: options.project
1228
- });
1229
- });
1230
- // ==================== PREVIEW COMMAND ====================
1231
- program
1232
- .command('preview')
1233
- .description('Preview project locally with visual diffs and proof validation')
1234
- .option('-p, --project <path>', 'Project directory path', process.cwd())
1235
- .option('-e, --entry <file>', 'Entry HTML file (auto-detected if omitted)')
1236
- .option('--port <number>', 'Local server port', parseInt)
1237
- .option('--no-open', 'Do not auto-open browser')
1238
- .option('--diff', 'Show consolidated diff of recent agent changes')
1239
- .option('--proof', 'Run Template Service preview gate and persist proof bundle')
1240
- .option('--screenshot', 'Capture screenshot via Puppeteer')
1241
- .action(async (options) => {
1242
- const preview = new PreviewCommand(config, logger);
1243
- await preview.run({
1244
- project: options.project,
1245
- entry: options.entry,
1246
- port: options.port,
1247
- open: options.open,
1248
- diff: options.diff,
1249
- proof: options.proof,
1250
- screenshot: options.screenshot,
1251
- });
1252
- });
1253
- // ==================== LEGION COMMAND ====================
1254
- program
1255
- .command('legion [request]')
1256
- .description('Run parallel tasks via Hyper Loop Legion orchestrator')
1257
- .option('--workers', 'List available Legion workers')
1258
- .option('--status', 'Show Legion infrastructure status')
1259
- .option('--cortex', 'Vigthoria Cortex: maximum intelligence execution')
1260
- .option('--approve', 'Auto-approve Cortex execution prompt')
1261
- .option('--no-approve', 'Require interactive approval before execution')
1262
- .option('--auto-charge', 'Attempt direct VigCoin top-up when Cortex balance is low')
1263
- .option('--plan-only', 'Run Cortex estimator only; do not execute Legion job')
1264
- .option('--force-budget', 'Allow execution when estimated budget exceeds the hard safe-stop ceiling')
1265
- .option('--ignore-preflight', 'Bypass mandatory local preflight checks (no warranty)')
1266
- .option('--speed', 'Enable speed mode (allows optional role skips when convergence is detected)')
1267
- .option('--repro-cmd <command>', 'Local reproduction/preflight command to validate before cloud spend')
1268
- .option('--expect-repro-fail', 'Require repro command to fail (non-zero) before proceeding')
1269
- .option('--models <csv>', 'Comma-separated model IDs to constrain Cortex selection')
1270
- .option('-t, --timeout <seconds>', 'Override Legion execution timeout in seconds (defaults to server policy)')
1271
- .option('-w, --worker <name>', 'Execute a specific worker')
1272
- .option('-p, --project <path>', 'Project directory', process.cwd())
1273
- .action(async (request, options) => {
1274
- const legion = new LegionCommand(config, logger);
1275
- const parsedTimeout = Number.parseInt(String(options.timeout || ''), 10);
1276
- await legion.run(request, {
1277
- workers: options.workers,
1278
- status: options.status,
1279
- cortex: Boolean(options.cortex),
1280
- approve: options.approve,
1281
- noApprove: options.approve === false,
1282
- autoCharge: options.autoCharge,
1283
- planOnly: options.planOnly,
1284
- forceBudget: options.forceBudget,
1285
- ignorePreflight: options.ignorePreflight,
1286
- speed: options.speed,
1287
- reproCmd: options.reproCmd,
1288
- expectReproFail: options.expectReproFail,
1289
- models: options.models,
1290
- timeoutSec: Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : undefined,
1291
- worker: options.worker,
1292
- project: options.project,
1293
- });
1294
- });
1295
- // ==================== REPLAY & FORK COMMANDS ====================
1296
- program
1297
- .command('history')
1298
- .alias('runs')
1299
- .description('List recent V3 agent runs')
1300
- .option('-n, --limit <count>', 'Number of runs to show', '20')
1301
- .option('-p, --project <path>', 'Project directory', process.cwd())
1302
- .option('--json', 'Machine-readable JSON output', false)
1303
- .action(async (options) => {
1304
- const history = new HistoryCommand(config, logger);
1305
- await history.run({ limit: parseInt(options.limit, 10) || 20, json: options.json, project: options.project });
1306
- });
1307
- program
1308
- .command('replay <runId>')
1309
- .description('Replay events from a V3 agent run step-by-step')
1310
- .option('-s, --speed <ms>', 'Delay between events in ms', '200')
1311
- .option('-p, --project <path>', 'Project directory', process.cwd())
1312
- .option('--json', 'Machine-readable JSON output', false)
1313
- .action(async (runId, options) => {
1314
- const replay = new ReplayCommand(config, logger);
1315
- await replay.run(runId, { speed: parseInt(options.speed, 10) || 200, json: options.json, project: options.project });
1316
- });
1317
- program
1318
- .command('fork <runId> [message]')
1319
- .description('Fork from an existing V3 agent run with new instructions')
1320
- .option('-e, --event-index <index>', 'Fork from specific event index', '0')
1321
- .option('-p, --project <path>', 'Project directory', process.cwd())
1322
- .option('--json', 'Machine-readable JSON output', false)
1323
- .action(async (runId, message, options) => {
1324
- const fork = new ForkCommand(config, logger);
1325
- await fork.run(runId, message || '', { eventIndex: parseInt(options.eventIndex, 10) || 0, project: options.project, json: options.json });
1326
- });
1327
- program
1328
- .command('cancel [contextId]')
1329
- .description('Cancel an in-flight V3 agent run (by context_id), or use --all to cancel every active run')
1330
- .option('-a, --all', 'Cancel all currently-active runs', false)
1331
- .option('-l, --list', 'List active runs without cancelling', false)
1332
- .option('--json', 'Machine-readable JSON output', false)
1333
- .action(async (contextId, options) => {
1334
- const cancel = new CancelCommand(config, logger);
1335
- await cancel.run(contextId, { all: options.all, list: options.list, json: options.json });
1336
- });
1337
- // ==================== AUTH COMMANDS ====================
1338
- // Auth commands
1339
- program
1340
- .command('login')
1341
- .description('Login to Vigthoria Coder (prompts for credentials when run without flags)')
1342
- .option('-t, --token <token>', 'API token for token-based authentication')
1343
- .option('-e, --email <email>', 'Account email for credential-based login')
1344
- .option('-p, --password <password>', 'Account password for credential-based login')
1345
- .option('--device', 'Use OAuth device flow (requires server support)')
1346
- .action(async (options) => {
1347
- await handleLogin(options);
1348
- });
1349
- program
1350
- .command('logout')
1351
- .description('Logout from Vigthoria Coder')
1352
- .action(async () => {
1353
- await handleLogout(null);
1354
- });
1355
- program
1356
- .command('status')
1357
- .description('Show authentication and subscription status')
1358
- .action(async () => {
1359
- await statusAction();
1360
- });
1361
- program
1362
- .command('doctor')
1363
- .description('Run local Vigthoria CLI diagnostics (no network calls unless --check-api)')
1364
- .option('--check-api', 'Also probe Vigthoria API reachability', false)
1365
- .option('--json', 'Emit machine-readable JSON output', false)
1366
- .action(async (options) => {
1367
- const apiUrl = String(config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
1368
- const modelsApiUrl = String(config.get('modelsApiUrl') || 'https://api.vigthoria.io').replace(/\/$/, '');
1369
- const offline = isOfflineMode();
1370
- const updateSuppressed = isUpdateCheckSuppressed();
1371
- const subscription = config.get('subscription') || { plan: null, status: null, expiresAt: null };
1372
- const runtime = detectRuntimeEnvironment(process.cwd());
1373
- const warnings = [];
1374
- if (runtime.normalizedPlatform === 'unknown') {
1375
- warnings.push('Unknown OS platform detected. Run `vigthoria doctor --check-api` before agent tasks.');
1376
- }
1377
- if (runtime.isMobileLikeRuntime) {
1378
- warnings.push('Mobile-like runtime detected (Termux/iOS-style path). Vigthoria CLI agent tooling is optimized for desktop/server shells.');
1379
- }
1380
- if (runtime.isWSL) {
1381
- warnings.push('WSL detected. Prefer Linux-style paths and keep projects inside the WSL filesystem for stable tool execution.');
1382
- }
1383
- if (/127\.0\.0\.1:1/.test(modelsApiUrl)) {
1384
- warnings.push('modelsApiUrl is set to 127.0.0.1:1 (unreachable). Set modelsApiUrl to https://api.vigthoria.io or your local model router.');
1385
- }
1386
- const report = {
1387
- cliVersion: VERSION,
1388
- nodeVersion: process.version,
1389
- platform: `${process.platform} ${process.arch}`,
1390
- runtimeEnvironment: runtime,
1391
- cwd: process.cwd(),
1392
- homeDir: os.homedir(),
1393
- configPath: config.getConfigPath(),
1394
- stateFile: getCliStateFile(),
1395
- apiUrl,
1396
- modelsApiUrl,
1397
- loggedIn: config.isAuthenticated(),
1398
- subscriptionPlan: subscription.plan || null,
1399
- subscriptionStatus: subscription.status || null,
1400
- offlineMode: offline,
1401
- updateCheckSuppressed: updateSuppressed,
1402
- warnings,
1403
- envOverrides: {
1404
- VIGTHORIA_API_URL: process.env.VIGTHORIA_API_URL || null,
1405
- VIGTHORIA_V3_AGENT_URL: process.env.VIGTHORIA_V3_AGENT_URL || null,
1406
- VIGTHORIA_OFFLINE: process.env.VIGTHORIA_OFFLINE || null,
1407
- VIGTHORIA_TOKEN: process.env.VIGTHORIA_TOKEN ? '<set>' : null,
1408
- HYPERLOOP_SERVICE_KEY: process.env.HYPERLOOP_SERVICE_KEY ? '<set>' : null,
1409
- V3_SERVICE_KEY: process.env.V3_SERVICE_KEY ? '<set>' : null,
1410
- },
1411
- };
1412
- if (options.checkApi && !offline) {
1413
- try {
1414
- const probe = await axios.get(`${apiUrl}/api/health`, { timeout: 4000, validateStatus: () => true });
1415
- report.apiHealth = probe.status >= 200 && probe.status < 400 ? 'online' : `status ${probe.status}`;
1416
- }
1417
- catch (error) {
1418
- report.apiHealth = `unreachable (${error.message})`;
1419
- }
1420
- try {
1421
- const probe = await axios.get(`${modelsApiUrl}/health`, { timeout: 4000, validateStatus: () => true });
1422
- report.modelsApiHealth = probe.status >= 200 && probe.status < 400 ? 'online' : `status ${probe.status}`;
1423
- }
1424
- catch (error) {
1425
- report.modelsApiHealth = `unreachable (${error.message})`;
1426
- }
1427
- }
1428
- if (options.json) {
1429
- console.log(JSON.stringify(report, null, 2));
1430
- }
1431
- else {
1432
- console.log(chalk.bold('Vigthoria CLI diagnostics'));
1433
- for (const [key, value] of Object.entries(report)) {
1434
- if (value && typeof value === 'object') {
1435
- console.log(chalk.gray(`- ${key}:`));
1436
- for (const [subKey, subVal] of Object.entries(value)) {
1437
- console.log(chalk.gray(` - ${subKey}: ${subVal ?? '(none)'}`));
1438
- }
1439
- }
1440
- else {
1441
- console.log(chalk.gray(`- ${key}: ${value ?? '(none)'}`));
1442
- }
1443
- }
1444
- if (!options.checkApi) {
1445
- console.log(chalk.gray('\nTip: pass --check-api to verify API reachability.'));
1446
- }
1447
- if (warnings.length > 0) {
1448
- console.log(chalk.yellow('\nEnvironment warnings:'));
1449
- for (const warning of warnings) {
1450
- console.log(chalk.yellow(`- ${warning}`));
1451
- }
1452
- }
1453
- }
1454
- process.exitCode = 0;
1455
- });
1456
- // Start command - beginner quick guide
1457
- program
1458
- .command('start')
1459
- .alias('guide')
1460
- .description('Show beginner quick-start steps and recommended first commands')
1461
- .option('--json', 'Emit machine-readable guide output', false)
1462
- .action(async (options) => {
1463
- const steps = [
1464
- '1. Authenticate: vigthoria login',
1465
- '2. Verify account: vigthoria status',
1466
- '3. Initialize project: vigthoria init',
1467
- '4. Start coding: vigthoria chat --agent',
1468
- '5. Check for updates: vigthoria update --check',
1469
- ];
1470
- if (options.json) {
1471
- console.log(JSON.stringify({
1472
- command: 'start',
1473
- steps,
1474
- tips: [
1475
- 'Use vigthoria init --non-interactive for CI/bootstrap scripts.',
1476
- 'Run vigthoria doctor when environment checks fail.',
1477
- ],
1478
- }, null, 2));
1479
- }
1480
- else {
1481
- console.log();
1482
- console.log(chalk.bold('Vigthoria quick start'));
1483
- for (const step of steps) {
1484
- console.log(chalk.gray('- ' + step));
1485
- }
1486
- console.log();
1487
- console.log(chalk.gray('Tip: use vigthoria init --profile safe --non-interactive for conservative defaults.'));
1488
- }
1489
- process.exitCode = 0;
1490
- });
1491
- // Config command
1492
- program
1493
- .command('config')
1494
- .description('Configure Vigthoria CLI settings')
1495
- .option('-s, --set <key=value>', 'Set a configuration value')
1496
- .option('-g, --get <key>', 'Get a configuration value')
1497
- .option('-l, --list', 'List all settings')
1498
- .option('-r, --reset', 'Reset to defaults')
1499
- .action(async (options) => {
1500
- const configCmd = new ConfigCommand(config, logger);
1501
- await configCmd.run(options);
1502
- });
1503
- // Update command - Check for and install updates
1504
- program
1505
- .command('update')
1506
- .alias('upgrade')
1507
- .description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://coder.vigthoria.io/releases/manifest.json')
1508
- .option('-c, --check', 'Only check for updates, don\'t install')
1509
- .option('-f, --from <target>', 'Install update from npm spec, local .tgz path, or URL')
1510
- .option('-m, --manifest <url>', 'Update manifest URL for server-driven releases')
1511
- .option('--channel <name>', 'Release channel to use from manifest (default: stable)', 'stable')
1512
- .option('--allow-downgrade', 'Allow installing an older version from custom update source')
1513
- .action(async (options) => {
1514
- const { execSync } = await import('child_process');
1515
- const updateTarget = typeof options.from === 'string' ? options.from.trim() : '';
1516
- const manifestUrl = typeof options.manifest === 'string' ? options.manifest.trim() : VIGTHORIA_DEFAULT_MANIFEST_URL.trim();
1517
- const channel = typeof options.channel === 'string' ? options.channel.trim() : 'stable';
1518
- const allowDowngrade = !!options.allowDowngrade;
1519
- if (updateTarget) {
1520
- if (!isSafeNpmPackageSpec(updateTarget)) {
1521
- console.error(chalk.red('Refusing to run installer with unsafe package spec.'));
1522
- console.error(chalk.gray('Allowed characters: A-Z a-z 0-9 . _ - / @ : + \\ ='));
1523
- process.exitCode = 1;
1524
- return;
1525
- }
1526
- try {
1527
- if (!allowInsecureCliVersionInstall()) {
1528
- const detectedVersion = extractVersionFromUpdateTarget(updateTarget);
1529
- if (!detectedVersion) {
1530
- console.error(chalk.red('Refusing custom update source: unable to verify CLI version in update target.'));
1531
- console.error(chalk.gray(`Allowed floor: ${VIGTHORIA_MIN_SECURE_CLI_VERSION}. Set VIGTHORIA_ALLOW_INSECURE_CLI_VERSION=1 only for trusted admin recovery.`));
1532
- process.exitCode = 1;
1533
- return;
1534
- }
1535
- if (isBelowSecureCliBaseline(detectedVersion)) {
1536
- console.error(chalk.red(`Refusing insecure CLI version ${detectedVersion}.`));
1537
- console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1538
- process.exitCode = 1;
1539
- return;
1540
- }
1541
- }
1542
- if (options.check) {
1543
- console.log(chalk.cyan(`Update source configured: ${updateTarget}`));
1544
- console.log(chalk.gray('Run `vigthoria update --from <target>` to install from this source'));
1545
- return;
1546
- }
1547
- console.log(chalk.cyan(`Installing update from ${updateTarget}...`));
1548
- await installGlobalPackageWithNpm(updateTarget);
1549
- console.log(chalk.green('Update installed successfully'));
1550
- console.log(chalk.gray('Please restart the CLI to use the new version'));
1551
- return;
1552
- }
1553
- catch (error) {
1554
- console.error(chalk.red('Failed to install update from target:'), error.message);
1555
- console.log(chalk.gray(`Try manually: npm install -g ${updateTarget}`));
1556
- process.exitCode = 1;
1557
- return;
1558
- }
1559
- }
1560
- if (manifestUrl) {
1561
- if (isOfflineMode()) {
1562
- console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping manifest update.'));
1563
- return;
1564
- }
1565
- try {
1566
- console.log(chalk.cyan(`Checking manifest channel ${channel}...`));
1567
- const response = await axios.get(manifestUrl, {
1568
- timeout: 10000,
1569
- maxRedirects: 5,
1570
- validateStatus: (status) => status >= 200 && status < 300,
1571
- });
1572
- const entry = resolveManifestEntry(response.data || {}, channel);
1573
- if (!entry || !entry.version || !entry.url) {
1574
- console.error(chalk.red(`Manifest missing valid release entry for channel: ${channel}`));
1575
- console.log(chalk.gray('Expected: channels.<channel>.version and channels.<channel>.url'));
1576
- process.exitCode = 1;
1577
- return;
1578
- }
1579
- if (!allowInsecureCliVersionInstall() && isBelowSecureCliBaseline(entry.version)) {
1580
- console.error(chalk.red(`Refusing insecure manifest CLI version ${entry.version} for channel ${channel}.`));
1581
- console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1582
- process.exitCode = 1;
1583
- return;
1584
- }
1585
- const currentVersion = VERSION;
1586
- const comparison = compareVersions(entry.version, currentVersion);
1587
- if (!allowDowngrade && comparison <= 0) {
1588
- console.log(chalk.green(`You are running the latest version for channel ${channel} (${currentVersion})`));
1589
- return;
1590
- }
1591
- console.log(chalk.yellow(`Update available from manifest: ${currentVersion} -> ${entry.version} (${channel})`));
1592
- if (entry.notes) {
1593
- console.log(chalk.gray(` ${entry.notes}`));
1594
- }
1595
- if (options.check) {
1596
- console.log(chalk.gray(`Install with: vigthoria update --manifest ${manifestUrl} --channel ${channel}`));
1597
- return;
1598
- }
1599
- const tmpFile = path.join(os.tmpdir(), `vigthoria-update-${Date.now()}.tgz`);
1600
- try {
1601
- console.log(chalk.cyan('Downloading release package...'));
1602
- await downloadFile(entry.url, tmpFile);
1603
- if (entry.sha256) {
1604
- const expected = entry.sha256.toLowerCase();
1605
- const actual = sha256File(tmpFile).toLowerCase();
1606
- if (actual !== expected) {
1607
- console.error(chalk.red('Release checksum verification failed'));
1608
- console.error(chalk.red(`Expected: ${expected}`));
1609
- console.error(chalk.red(`Actual: ${actual}`));
1610
- process.exitCode = 1;
1611
- return;
1612
- }
1613
- console.log(chalk.green('Checksum verification passed'));
1614
- }
1615
- else {
1616
- console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
1617
- console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
1618
- console.log(chalk.gray(' Set --manifest to a trusted source, or supply sha256 in the manifest entry.'));
1619
- }
1620
- console.log(chalk.cyan('Installing update...'));
1621
- await installGlobalPackageWithNpm(tmpFile);
1622
- console.log(chalk.green(`Updated to version ${entry.version}`));
1623
- console.log(chalk.gray('Please restart the CLI to use the new version'));
1624
- return;
1625
- }
1626
- finally {
1627
- if (fs.existsSync(tmpFile)) {
1628
- try {
1629
- fs.unlinkSync(tmpFile);
1630
- }
1631
- catch { }
1632
- }
1633
- }
1634
- }
1635
- catch (error) {
1636
- console.error(chalk.red('Failed to process manifest update:'), error.message);
1637
- console.log(chalk.gray('Falling back to npm registry update channel...'));
1638
- }
1639
- }
1640
- if (isOfflineMode()) {
1641
- console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping update check.'));
1642
- return;
1643
- }
1644
- console.log(chalk.cyan('Checking for updates...'));
1645
- try {
1646
- // Get latest version from npm - cross-platform
1647
- const latestVersion = execSync('npm view vigthoria-cli version', {
1648
- encoding: 'utf8',
1649
- stdio: ['pipe', 'pipe', 'pipe'],
1650
- windowsHide: true,
1651
- }).trim();
1652
- const currentVersion = VERSION;
1653
- if (!allowInsecureCliVersionInstall() && isBelowSecureCliBaseline(latestVersion)) {
1654
- console.error(chalk.red(`Refusing insecure npm latest CLI version ${latestVersion}.`));
1655
- console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1656
- process.exitCode = 1;
1657
- return;
1658
- }
1659
- // Use semantic version comparison (1.6.0 > 1.5.9)
1660
- const comparison = compareVersions(latestVersion, currentVersion);
1661
- if (comparison <= 0) {
1662
- console.log(chalk.green(`You are running the latest version (${currentVersion})`));
1663
- return;
1664
- }
1665
- console.log(chalk.yellow(`Update available: ${currentVersion} -> ${latestVersion}`));
1666
- if (options.check) {
1667
- console.log(chalk.gray('Run `vigthoria update` to install the update'));
1668
- return;
1669
- }
1670
- console.log(chalk.cyan('Installing update from npm registry...'));
1671
- await installGlobalPackageWithNpm('vigthoria-cli@latest');
1672
- console.log(chalk.green(`Updated to version ${latestVersion}`));
1673
- console.log(chalk.gray('Please restart the CLI to use the new version'));
1674
- }
1675
- catch (error) {
1676
- console.error(chalk.red('Failed to check/install via npm registry:'), error.message);
1677
- if (options.check) {
1678
- console.log(chalk.gray('npm registry check failed. Try again later or use --manifest with a valid release manifest URL.'));
1679
- return;
1680
- }
1681
- console.log(chalk.gray('Try manually: npm install -g vigthoria-cli@latest'));
1682
- process.exitCode = 1;
1683
- }
1684
- });
1685
- // Hyper Loop command alias (maps to legion --status for checklist gate 6.9)
1686
- program
1687
- .command('hyper-loop')
1688
- .description('Hyper Loop Legion orchestrator commands')
1689
- .command('status')
1690
- .description('Show Hyper Loop Legion infrastructure status')
1691
- .action(async () => {
1692
- const legion = new LegionCommand(config, logger);
1693
- await legion.run(undefined, {
1694
- status: true,
1695
- workers: false,
1696
- cortex: false,
1697
- approve: false,
1698
- noApprove: true,
1699
- planOnly: false,
1700
- models: undefined,
1701
- worker: undefined,
1702
- project: process.cwd(),
1703
- });
1704
- });
1705
- // DevTools command alias (maps to bridge for checklist gate 6.12)
1706
- program
1707
- .command('devtools')
1708
- .description('DevTools Bridge commands for browser debugging')
1709
- .command('connect')
1710
- .description('Connect to DevTools Bridge')
1711
- .action(async () => {
1712
- const bridge = new BridgeCommand(config, logger);
1713
- await bridge.status();
1714
- });
1715
- // Init command - Initialize project
1716
- program
1717
- .command('init')
1718
- .description('Initialize Vigthoria in current project')
1719
- .option('--model <model>', 'Default model (code, code-35b, code-9b, balanced, balanced-4b)')
1720
- .option('--ignore-patterns <patterns>', 'Comma-separated ignore patterns for this project')
1721
- .option('--auto-apply-fixes', 'Enable auto-apply fixes for this project profile')
1722
- .option('--profile <preset>', 'Quick preset: safe, balanced, fast')
1723
- .option('-y, --yes', 'Overwrite existing .vigthoria.json without prompting')
1724
- .option('--non-interactive', 'Do not prompt; use provided flags/defaults')
1725
- .action(async (options) => {
1726
- const configCmd = new ConfigCommand(config, logger);
1727
- await configCmd.init(options);
1728
- });
1729
- const codingCommandDefinitions = [
1730
- { name: 'edit', description: 'Edit code by describing the desired change', instruction: 'Edit the project according to this request' },
1731
- { name: 'generate', description: 'Generate code from a prompt', instruction: 'Generate code for this request' },
1732
- { name: 'explain', description: 'Explain code, errors, or project behavior', instruction: 'Explain this request clearly and practically' },
1733
- { name: 'review', description: 'Review code and suggest concrete improvements', instruction: 'Review the project or code for this request' },
1734
- { name: 'fix', description: 'Fix bugs, build failures, or test failures', instruction: 'Fix this issue in the project' },
1735
- ];
1736
- for (const commandDefinition of codingCommandDefinitions) {
1737
- program
1738
- .command(commandDefinition.name)
1739
- .description(commandDefinition.description)
1740
- .argument('[request...]', 'Request text for the coding assistant')
1741
- .option('-m, --model <model>', 'Model to use', 'code')
1742
- .option('-p, --project <path>', 'Project directory', process.cwd())
1743
- .action(async (requestParts = [], options) => {
1744
- const requestText = requestParts.join(' ').trim();
1745
- const chat = new ChatCommand(config, logger);
1746
- await chat.run({
1747
- model: options.model || 'code',
1748
- project: options.project || process.cwd(),
1749
- prompt: requestText ? `${commandDefinition.instruction}: ${requestText}` : commandDefinition.instruction,
1750
- });
1751
- });
1752
- }
1753
- // Wallet command — balance, transaction history, cloud access status
1754
- const walletCommand = program
1755
- .command('wallet')
1756
- .alias('w')
1757
- .description('Manage your VigCoin wallet and cloud access');
1758
- walletCommand
1759
- .command('balance')
1760
- .alias('bal')
1761
- .description('Show current VigCoin balance')
1762
- .option('--json', 'Emit JSON output', false)
1763
- .action(async (options) => {
1764
- const wallet = new WalletCommand();
1765
- await wallet.showBalance({ json: options.json });
1766
- });
1767
- walletCommand
1768
- .command('history')
1769
- .alias('hist')
1770
- .description('Show recent VigCoin transactions')
1771
- .option('-n, --n <number>', 'Number of transactions to show (default: 20)', parseInt)
1772
- .option('--json', 'Emit JSON output', false)
1773
- .action(async (options) => {
1774
- const wallet = new WalletCommand();
1775
- await wallet.showHistory({ n: options.n, json: options.json });
1776
- });
1777
- walletCommand
1778
- .command('cloud-status')
1779
- .alias('cs')
1780
- .description('Show cloud model access status and pricing')
1781
- .option('--json', 'Emit JSON output', false)
1782
- .action(async (options) => {
1783
- const wallet = new WalletCommand();
1784
- await wallet.showCloudStatus({ json: options.json });
1785
- });
1786
- try {
1787
- // Default to chat if no command
1788
- if (args.length === 2) {
1789
- const chat = new ChatCommand(config, logger);
1790
- await chat.run({ model: 'code', project: process.cwd() });
1791
- process.exitCode = 0;
1792
- return;
1793
- }
1794
- await program.parseAsync(args);
1795
- if (process.exitCode === undefined || process.exitCode === 0) {
1796
- process.exitCode = 0;
1797
- }
1798
- }
1799
- catch (error) {
1800
- reportCliError(error, jsonOutputRequested);
1801
- process.exitCode = 1;
1802
- }
1803
- }
1804
- setupErrorHandlers();
1805
- async function bootstrapCli() {
1806
- try {
1807
- await main(process.argv);
1808
- }
1809
- catch (err) {
1810
- handleFatalCliError(err, process.argv.includes('--json'));
1811
- process.exit(1);
1812
- }
1813
- }
1814
- void (async () => {
1815
- try {
1816
- await bootstrapCli();
1817
- }
1818
- catch (err) {
1819
- handleFatalCliError(err, process.argv.includes('--json'));
1820
- process.exit(1);
1821
- }
1822
- })();
1823
- export const __cliErrorHandlingReady = true;