vigthoria-cli 1.13.18 → 1.13.22

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 (185) hide show
  1. package/completions/_vigthoria +49 -0
  2. package/completions/vigthoria.bash +6 -0
  3. package/completions/vigthoria.fish +41 -0
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +84 -135
  6. package/dist/commands/background.d.ts +0 -2
  7. package/dist/commands/background.js +26 -34
  8. package/dist/commands/bridge.js +45 -41
  9. package/dist/commands/cancel.d.ts +1 -0
  10. package/dist/commands/cancel.js +22 -16
  11. package/dist/commands/chat.d.ts +5 -42
  12. package/dist/commands/chat.js +462 -1122
  13. package/dist/commands/coding-registration.d.ts +4 -0
  14. package/dist/commands/coding-registration.js +24 -0
  15. package/dist/commands/config.js +32 -42
  16. package/dist/commands/conversation-registration.d.ts +4 -0
  17. package/dist/commands/conversation-registration.js +122 -0
  18. package/dist/commands/deploy.d.ts +8 -2
  19. package/dist/commands/deploy.js +92 -90
  20. package/dist/commands/device.d.ts +1 -2
  21. package/dist/commands/device.js +37 -21
  22. package/dist/commands/doctor-registration.d.ts +4 -0
  23. package/dist/commands/doctor-registration.js +200 -0
  24. package/dist/commands/edit.js +22 -33
  25. package/dist/commands/explain.js +7 -10
  26. package/dist/commands/fork.d.ts +1 -2
  27. package/dist/commands/fork.js +72 -22
  28. package/dist/commands/game.d.ts +1 -1
  29. package/dist/commands/game.js +17 -6
  30. package/dist/commands/generate.js +7 -9
  31. package/dist/commands/history.js +11 -7
  32. package/dist/commands/hub.d.ts +2 -2
  33. package/dist/commands/hub.js +34 -35
  34. package/dist/commands/legion.d.ts +0 -1
  35. package/dist/commands/legion.js +62 -79
  36. package/dist/commands/music.d.ts +1 -2
  37. package/dist/commands/music.js +62 -27
  38. package/dist/commands/platform-registration.d.ts +4 -0
  39. package/dist/commands/platform-registration.js +45 -0
  40. package/dist/commands/preview.d.ts +0 -1
  41. package/dist/commands/preview.js +82 -35
  42. package/dist/commands/product-run-registration.d.ts +4 -0
  43. package/dist/commands/product-run-registration.js +490 -0
  44. package/dist/commands/replay.d.ts +1 -2
  45. package/dist/commands/replay.js +15 -11
  46. package/dist/commands/repo.d.ts +14 -7
  47. package/dist/commands/repo.js +245 -196
  48. package/dist/commands/review.js +9 -12
  49. package/dist/commands/security.d.ts +1 -2
  50. package/dist/commands/security.js +12 -6
  51. package/dist/commands/update-registration.d.ts +3 -0
  52. package/dist/commands/update-registration.js +338 -0
  53. package/dist/commands/v4-registration.d.ts +4 -0
  54. package/dist/commands/v4-registration.js +24 -0
  55. package/dist/commands/v4.d.ts +1 -1
  56. package/dist/commands/v4.js +46 -8
  57. package/dist/commands/wallet.d.ts +1 -3
  58. package/dist/commands/wallet.js +18 -45
  59. package/dist/commands/workflow.d.ts +2 -0
  60. package/dist/commands/workflow.js +52 -39
  61. package/dist/index.js +122 -1498
  62. package/dist/utils/agent-stream-state.d.ts +27 -0
  63. package/dist/utils/agent-stream-state.js +93 -0
  64. package/dist/utils/agentRoute.d.ts +2 -1
  65. package/dist/utils/agentRoute.js +22 -9
  66. package/dist/utils/agentRunOutcome.d.ts +16 -0
  67. package/dist/utils/agentRunOutcome.js +37 -2
  68. package/dist/utils/api-client-factory.d.ts +8 -0
  69. package/dist/utils/api-client-factory.js +8 -0
  70. package/dist/utils/api.d.ts +45 -269
  71. package/dist/utils/api.js +664 -3210
  72. package/dist/utils/auth-session.d.ts +41 -0
  73. package/dist/utils/auth-session.js +224 -0
  74. package/dist/utils/brain-hub-client.d.ts +0 -4
  75. package/dist/utils/brain-hub-client.js +6 -3
  76. package/dist/utils/bridge-client.js +2 -0
  77. package/dist/utils/capability-health-service.d.ts +48 -0
  78. package/dist/utils/capability-health-service.js +64 -0
  79. package/dist/utils/chat-prompt-policy.d.ts +18 -0
  80. package/dist/utils/chat-prompt-policy.js +61 -0
  81. package/dist/utils/chat-tool-call-parser.d.ts +11 -0
  82. package/dist/utils/chat-tool-call-parser.js +71 -0
  83. package/dist/utils/cli-state.d.ts +22 -4
  84. package/dist/utils/cli-state.js +105 -47
  85. package/dist/utils/code-operations-service.d.ts +104 -0
  86. package/dist/utils/code-operations-service.js +837 -0
  87. package/dist/utils/codebase-indexer.d.ts +1 -0
  88. package/dist/utils/codebase-indexer.js +28 -8
  89. package/dist/utils/command-contract.d.ts +61 -0
  90. package/dist/utils/command-contract.js +149 -0
  91. package/dist/utils/command-policy.d.ts +10 -0
  92. package/dist/utils/command-policy.js +54 -0
  93. package/dist/utils/config.d.ts +13 -5
  94. package/dist/utils/config.js +227 -99
  95. package/dist/utils/context-ranker.js +11 -2
  96. package/dist/utils/context-state.d.ts +39 -0
  97. package/dist/utils/context-state.js +52 -0
  98. package/dist/utils/deckEvents.d.ts +0 -5
  99. package/dist/utils/deckEvents.js +3 -2
  100. package/dist/utils/desktop-bridge-client.js +2 -0
  101. package/dist/utils/direct-output-policy.d.ts +3 -0
  102. package/dist/utils/direct-output-policy.js +76 -0
  103. package/dist/utils/durable-json.d.ts +57 -0
  104. package/dist/utils/durable-json.js +319 -0
  105. package/dist/utils/fastAgentRouter.d.ts +6 -7
  106. package/dist/utils/fastAgentRouter.js +21 -30
  107. package/dist/utils/files.js +21 -13
  108. package/dist/utils/frontend-preview-service.d.ts +76 -0
  109. package/dist/utils/frontend-preview-service.js +482 -0
  110. package/dist/utils/goaEvents.d.ts +0 -4
  111. package/dist/utils/goaEvents.js +5 -2
  112. package/dist/utils/logger.d.ts +2 -1
  113. package/dist/utils/logger.js +27 -20
  114. package/dist/utils/mcp-context-client.d.ts +24 -0
  115. package/dist/utils/mcp-context-client.js +58 -0
  116. package/dist/utils/model-governance.d.ts +18 -0
  117. package/dist/utils/model-governance.js +75 -0
  118. package/dist/utils/model-transport-service.d.ts +44 -0
  119. package/dist/utils/model-transport-service.js +111 -0
  120. package/dist/utils/mutation-journal.d.ts +42 -0
  121. package/dist/utils/mutation-journal.js +387 -0
  122. package/dist/utils/network-policy.d.ts +24 -0
  123. package/dist/utils/network-policy.js +202 -0
  124. package/dist/utils/operator-client.d.ts +45 -0
  125. package/dist/utils/operator-client.js +159 -0
  126. package/dist/utils/post-write-validator.js +7 -9
  127. package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
  128. package/dist/utils/preview-screenshot-adapter.js +36 -0
  129. package/dist/utils/process-policy.d.ts +23 -0
  130. package/dist/utils/process-policy.js +284 -0
  131. package/dist/utils/project-memory.d.ts +13 -3
  132. package/dist/utils/project-memory.js +70 -17
  133. package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
  134. package/dist/utils/prompt-workspace-resolver.js +64 -0
  135. package/dist/utils/release-install.d.ts +11 -0
  136. package/dist/utils/release-install.js +155 -0
  137. package/dist/utils/release-policy.d.ts +64 -0
  138. package/dist/utils/release-policy.js +293 -0
  139. package/dist/utils/requestIntent.js +22 -3
  140. package/dist/utils/runtime-capability.d.ts +8 -0
  141. package/dist/utils/runtime-capability.js +16 -0
  142. package/dist/utils/secret-policy.d.ts +20 -0
  143. package/dist/utils/secret-policy.js +160 -0
  144. package/dist/utils/session.d.ts +34 -2
  145. package/dist/utils/session.js +354 -110
  146. package/dist/utils/tool-approval-service.d.ts +17 -0
  147. package/dist/utils/tool-approval-service.js +78 -0
  148. package/dist/utils/tool-capability-providers.d.ts +57 -0
  149. package/dist/utils/tool-capability-providers.js +57 -0
  150. package/dist/utils/tools.d.ts +2 -23
  151. package/dist/utils/tools.js +216 -508
  152. package/dist/utils/update-policy.d.ts +18 -0
  153. package/dist/utils/update-policy.js +137 -0
  154. package/dist/utils/v3-agent-client.d.ts +40 -0
  155. package/dist/utils/v3-agent-client.js +165 -0
  156. package/dist/utils/v3-workspace-service.d.ts +28 -0
  157. package/dist/utils/v3-workspace-service.js +279 -0
  158. package/dist/utils/vigflow-client.d.ts +95 -0
  159. package/dist/utils/vigflow-client.js +105 -0
  160. package/dist/utils/workspace-boundary.d.ts +45 -0
  161. package/dist/utils/workspace-boundary.js +377 -0
  162. package/dist/utils/workspace-brain-service.d.ts +2 -0
  163. package/dist/utils/workspace-brain-service.js +11 -1
  164. package/dist/utils/workspace-cache.js +37 -15
  165. package/dist/utils/workspace-stream.js +35 -10
  166. package/install.ps1 +86 -143
  167. package/install.sh +76 -81
  168. package/package.json +44 -9
  169. package/release-policy.json +43 -0
  170. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  171. package/scripts/release/generate-completions.mjs +24 -0
  172. package/scripts/release/install-release.mjs +10 -0
  173. package/scripts/release/publish-cli-release.mjs +116 -0
  174. package/scripts/release/publish-cli-release.sh +2 -70
  175. package/scripts/release/resolve-release-manifest.mjs +26 -0
  176. package/scripts/release/test-balanced-model-live.sh +42 -0
  177. package/scripts/release/validate-live-service-gates.sh +90 -0
  178. package/scripts/release/validate-no-go-gates.sh +135 -172
  179. package/scripts/release/verify-runtime-consistency.mjs +4 -1
  180. package/dist/commands/index.d.ts +0 -12
  181. package/dist/commands/index.js +0 -194
  182. package/dist/commands/update.d.ts +0 -9
  183. package/dist/commands/update.js +0 -301
  184. package/dist/utils/templateInstantPath.d.ts +0 -18
  185. package/dist/utils/templateInstantPath.js +0 -228
@@ -3,6 +3,9 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import inquirer from 'inquirer';
5
5
  import { runPostWriteValidation } from '../utils/post-write-validator.js';
6
+ import { assertProcessNetworkAllowed } from '../utils/process-policy.js';
7
+ import { safeChildProcessEnv } from '../utils/secret-policy.js';
8
+ import { CliCommandError, formatSuccessJson } from '../utils/command-contract.js';
6
9
  const NAME = /^[a-zA-Z0-9][a-zA-Z0-9 _-]{0,63}$/;
7
10
  const manager = (root, requested) => {
8
11
  if (requested)
@@ -21,7 +24,11 @@ const manager = (root, requested) => {
21
24
  return 'bun';
22
25
  return 'npm';
23
26
  };
24
- const run = (pm, args, cwd) => new Promise((resolve, reject) => { const child = spawn(process.platform === 'win32' ? `${pm}.cmd` : pm, args, { cwd, stdio: 'inherit', windowsHide: true }); child.once('error', reject); child.once('exit', (code, signal) => code === 0 ? resolve() : reject(new Error(`${pm} exited with ${signal || code}`))); });
27
+ const run = (pm, args, cwd, timeoutMs = 10 * 60_000) => new Promise((resolve, reject) => { const executable = process.platform === 'win32' ? `${pm}.cmd` : pm; assertProcessNetworkAllowed(executable, args); const child = spawn(executable, args, { cwd, stdio: 'inherit', windowsHide: true, env: safeChildProcessEnv() }); let interrupted = false; const onInterrupt = () => { interrupted = true; child.kill('SIGINT'); }; process.once('SIGINT', onInterrupt); const timer = timeoutMs > 0 ? setTimeout(() => { child.kill('SIGTERM'); reject(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' })); }, timeoutMs) : null; const finish = () => { if (timer)
28
+ clearTimeout(timer); process.removeListener('SIGINT', onInterrupt); }; child.once('error', error => { finish(); reject(error); }); child.once('exit', (code, signal) => { finish(); if (interrupted)
29
+ reject(new CliCommandError(`${pm} command cancelled by user`, { code: 'GAME_PROCESS_CANCELLED', category: 'cancelled' }));
30
+ else
31
+ code === 0 ? resolve() : reject(new Error(`${pm} exited with ${signal || code}`)); }); });
25
32
  const html = `<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>VGE Game</title></head><body><canvas id="game"></canvas><div id="hud"><strong>Vigthoria Gaming Engine</strong><span>Ready</span></div><script type="module" src="/src/main.ts"></script></body></html>\n`;
26
33
  const style = `*{box-sizing:border-box}html,body,#game{width:100%;height:100%;margin:0;overflow:hidden}body{background:#030510;color:#e8f7ff;font-family:Inter,system-ui,sans-serif}#game{display:block;touch-action:none}#hud{position:fixed;left:24px;top:24px;display:flex;gap:16px;padding:12px 16px;border:1px solid #00d4ff66;border-radius:12px;background:#070a18cc;backdrop-filter:blur(12px);box-shadow:0 0 32px #7c3aed33}#hud span{color:#5eead4}\n`;
27
34
  const main = `import { ArcRotateCamera, Color3, Color4, DirectionalLight, Engine, HemisphericLight, MeshBuilder, PBRMaterial, Scene, ShadowGenerator, Vector3 } from '@babylonjs/core';
@@ -65,7 +72,7 @@ export class GameCommand {
65
72
  }
66
73
  async run(o) { const root = path.resolve(o.project || process.cwd()); this.project(root); const args = ['run', 'dev', '--', '--host', '127.0.0.1']; if (o.port)
67
74
  args.push('--port', String(o.port)); if (o.open !== false)
68
- args.push('--open'); await run(manager(root, o.packageManager), args, root); }
75
+ args.push('--open'); await run(manager(root, o.packageManager), args, root, 0); }
69
76
  async validate(o) {
70
77
  const root = path.resolve(o.project || process.cwd());
71
78
  const checks = [];
@@ -104,10 +111,14 @@ export class GameCommand {
104
111
  else if (/\.(html|[cm]?[jt]sx?)$/.test(e.name))
105
112
  files.push(f);
106
113
  } }; walk(root); return files.map(f => fs.readFileSync(f, 'utf8')).join('\n'); }
107
- finish(checks, json = false) { const passed = checks.every(x => x.passed); if (json)
108
- console.log(JSON.stringify({ passed, checks }, null, 2));
114
+ finish(checks, json = false) { const passed = checks.every(x => x.passed); if (!passed) {
115
+ if (!json)
116
+ for (const c of checks)
117
+ (c.passed ? this.logger.success.bind(this.logger) : this.logger.error.bind(this.logger))(`${c.check}${c.detail ? `: ${c.detail.trim().slice(0, 500)}` : ''}`);
118
+ throw new CliCommandError('Game validation failed', { code: 'GAME_VALIDATION_FAILED', details: { checks } });
119
+ } if (json)
120
+ console.log(formatSuccessJson('game validate', { passed, checks }));
109
121
  else
110
122
  for (const c of checks)
111
- (c.passed ? this.logger.success.bind(this.logger) : this.logger.error.bind(this.logger))(`${c.check}${c.detail ? `: ${c.detail.trim().slice(0, 500)}` : ''}`); if (!passed)
112
- process.exitCode = 1; return passed; }
123
+ this.logger.success(`${c.check}${c.detail ? `: ${c.detail.trim().slice(0, 500)}` : ''}`); return passed; }
113
124
  }
@@ -6,8 +6,9 @@
6
6
  import chalk from 'chalk';
7
7
  import inquirer from 'inquirer';
8
8
  import { createSpinner, CH } from '../utils/logger.js';
9
- import { APIClient, CLIError, classifyError, formatCLIError } from '../utils/api.js';
9
+ import { createAPIClient } from '../utils/api-client-factory.js';
10
10
  import { FileUtils } from '../utils/files.js';
11
+ import { CliCommandError, commandFailure } from '../utils/command-contract.js';
11
12
  export class GenerateCommand {
12
13
  config;
13
14
  logger;
@@ -16,20 +17,18 @@ export class GenerateCommand {
16
17
  constructor(config, logger) {
17
18
  this.config = config;
18
19
  this.logger = logger;
19
- this.api = new APIClient(config, logger);
20
+ this.api = createAPIClient(config, logger);
20
21
  this.fileUtils = new FileUtils(process.cwd(), config.get('project').ignorePatterns);
21
22
  }
22
23
  async run(description, options) {
23
24
  // Check auth
24
25
  if (!this.config.isAuthenticated()) {
25
- this.logger.error('Not authenticated. Run: vigthoria login');
26
- return;
26
+ throw new CliCommandError('Not authenticated. Run: vigthoria login', { code: 'AUTH_REQUIRED', category: 'authentication' });
27
27
  }
28
28
  // Server-side token validation — fail fast instead of waiting for 401
29
29
  const tokenCheck = await this.api.validateToken();
30
30
  if (!tokenCheck.valid) {
31
- this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
32
- return;
31
+ throw new CliCommandError(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login', { code: 'AUTH_SESSION_INVALID', category: 'authentication' });
33
32
  }
34
33
  // Determine mode
35
34
  const proMode = options.pro === true;
@@ -106,8 +105,7 @@ export class GenerateCommand {
106
105
  }
107
106
  catch (error) {
108
107
  spinner.stop();
109
- const cliErr = error instanceof CLIError ? error : classifyError(error);
110
- this.logger.error(formatCLIError(cliErr));
108
+ throw commandFailure(error, { code: 'GENERATE_FAILED' });
111
109
  }
112
110
  }
113
111
  /**
@@ -167,7 +165,7 @@ export class GenerateCommand {
167
165
  this.logger.success(`Code saved to ${filePath}`);
168
166
  }
169
167
  else {
170
- this.logger.error('Failed to save file');
168
+ throw new CliCommandError(`Failed to save file: ${filePath}`, { code: 'FILE_WRITE_FAILED' });
171
169
  }
172
170
  }
173
171
  async promptForAction(code, language) {
@@ -1,10 +1,11 @@
1
- import { isServerRuntime } from '../utils/api.js';
1
+ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
2
2
  /**
3
3
  * history.ts — List recent V3 agent runs with summaries.
4
4
  */
5
5
  import chalk from 'chalk';
6
6
  import { createRequire } from 'node:module';
7
7
  import { createSpinner, CH } from '../utils/logger.js';
8
+ import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
8
9
  const require = createRequire(import.meta.url);
9
10
  export class HistoryCommand {
10
11
  config;
@@ -29,7 +30,7 @@ export class HistoryCommand {
29
30
  }
30
31
  getBaseUrl() {
31
32
  const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
32
- const allowLocal = isServerRuntime() && process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
33
+ const allowLocal = hasLocalV3AgentCapability();
33
34
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
34
35
  process.env.V3_AGENT_URL ||
35
36
  (allowLocal ? 'http://127.0.0.1:8030' : null) ||
@@ -68,17 +69,20 @@ export class HistoryCommand {
68
69
  if (!resp.ok) {
69
70
  spinner.stop();
70
71
  if (resp.status === 404 || resp.status === 502 || resp.status === 503) {
71
- this.logger.error(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`);
72
+ throw new CliCommandError(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`, {
73
+ code: 'RUN_HISTORY_UNAVAILABLE', status: resp.status, category: 'network',
74
+ });
72
75
  }
73
76
  else {
74
- this.logger.error(`Failed to fetch runs: ${resp.status} ${resp.statusText}`);
77
+ throw new CliCommandError(`Failed to fetch runs: ${resp.status} ${resp.statusText}`, {
78
+ code: 'RUN_HISTORY_FAILED', status: resp.status,
79
+ });
75
80
  }
76
- return;
77
81
  }
78
82
  const data = (await resp.json());
79
83
  spinner.stop();
80
84
  if (options.json) {
81
- console.log(JSON.stringify(data, null, 2));
85
+ console.log(formatSuccessJson('history', data));
82
86
  return;
83
87
  }
84
88
  if (data.runs.length === 0) {
@@ -107,7 +111,7 @@ export class HistoryCommand {
107
111
  }
108
112
  catch (err) {
109
113
  spinner.stop();
110
- this.logger.error(`History error: ${err.message}`);
114
+ throw commandFailure(err, { code: 'RUN_HISTORY_FAILED' });
111
115
  }
112
116
  }
113
117
  }
@@ -8,8 +8,8 @@ import { Config } from '../utils/config.js';
8
8
  import { Logger } from '../utils/logger.js';
9
9
  export declare class HubCommand {
10
10
  private config;
11
- private logger;
12
- constructor(config: Config, logger: Logger);
11
+ constructor(config: Config, _logger: Logger);
12
+ private getAuthToken;
13
13
  /**
14
14
  * Search for modules by natural language query
15
15
  */
@@ -5,13 +5,26 @@
5
5
  * Enables pay-as-you-go module usage directly from the terminal
6
6
  */
7
7
  import chalk from 'chalk';
8
+ import { guardedFetch } from '../utils/network-policy.js';
9
+ import { CliCommandError, commandFailure } from '../utils/command-contract.js';
8
10
  const API_BASE = 'https://api.vigthoria.io';
9
11
  export class HubCommand {
10
12
  config;
11
- logger;
12
- constructor(config, logger) {
13
+ constructor(config, _logger) {
13
14
  this.config = config;
14
- this.logger = logger;
15
+ }
16
+ getAuthToken() {
17
+ const token = String(process.env.VIGTHORIA_TOKEN
18
+ || process.env.VIGTHORIA_AUTH_TOKEN
19
+ || this.config.get('authToken')
20
+ || '');
21
+ if (!token) {
22
+ throw new CliCommandError('Not authenticated. Run: vigthoria login', {
23
+ code: 'AUTH_REQUIRED',
24
+ category: 'authentication',
25
+ });
26
+ }
27
+ return token;
15
28
  }
16
29
  /**
17
30
  * Search for modules by natural language query
@@ -19,13 +32,13 @@ export class HubCommand {
19
32
  async search(query) {
20
33
  console.log(chalk.cyan('\n🔍 Searching Vigthoria Module Hub...\n'));
21
34
  try {
22
- const response = await fetch(`${API_BASE}/api/modules/search`, {
35
+ const response = await guardedFetch(`${API_BASE}/api/modules/search`, {
23
36
  method: 'POST',
24
37
  headers: {
25
38
  'Content-Type': 'application/json'
26
39
  },
27
40
  body: JSON.stringify({ query })
28
- });
41
+ }, { audience: 'hub' });
29
42
  if (!response.ok) {
30
43
  throw new Error(`Search failed: ${response.statusText}`);
31
44
  }
@@ -52,9 +65,7 @@ export class HubCommand {
52
65
  console.log(chalk.gray('Example: vigthoria hub activate music\n'));
53
66
  }
54
67
  catch (error) {
55
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
56
- console.log(chalk.red(`Error: ${errMsg}`));
57
- console.log(chalk.gray('Make sure you have an active internet connection.'));
68
+ throw commandFailure(error, { code: 'HUB_SEARCH_FAILED' });
58
69
  }
59
70
  }
60
71
  /**
@@ -67,7 +78,10 @@ export class HubCommand {
67
78
  if (options.category) {
68
79
  url += `?category=${options.category}`;
69
80
  }
70
- const response = await fetch(url);
81
+ const response = await guardedFetch(url, {}, { audience: 'hub' });
82
+ if (!response.ok) {
83
+ throw new CliCommandError(`Failed to list modules: ${response.statusText}`, { code: 'HUB_LIST_FAILED', status: response.status });
84
+ }
71
85
  const data = await response.json();
72
86
  console.log(chalk.bold.white('Available Modules:\n'));
73
87
  // Group by category
@@ -95,28 +109,23 @@ export class HubCommand {
95
109
  console.log(chalk.gray(' vigthoria hub info <module> - Module details\n'));
96
110
  }
97
111
  catch (error) {
98
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
99
- console.log(chalk.red(`Error: ${errMsg}`));
112
+ throw commandFailure(error, { code: 'HUB_LIST_FAILED' });
100
113
  }
101
114
  }
102
115
  /**
103
116
  * Activate a module for the current user
104
117
  */
105
118
  async activate(moduleId) {
106
- const authToken = this.config.get('authToken');
107
- if (!authToken) {
108
- console.log(chalk.red('\n❌ Not authenticated. Run: vigthoria login\n'));
109
- return;
110
- }
119
+ const authToken = this.getAuthToken();
111
120
  console.log(chalk.cyan(`\n🔌 Activating module: ${moduleId}...\n`));
112
121
  try {
113
- const response = await fetch(`${API_BASE}/api/modules/${moduleId}/activate`, {
122
+ const response = await guardedFetch(`${API_BASE}/api/modules/${moduleId}/activate`, {
114
123
  method: 'POST',
115
124
  headers: {
116
125
  'Authorization': `Bearer ${authToken}`,
117
126
  'Content-Type': 'application/json'
118
127
  }
119
- });
128
+ }, { audience: 'hub' });
120
129
  if (!response.ok) {
121
130
  const errorData = await response.json();
122
131
  throw new Error(errorData.error || response.statusText);
@@ -139,28 +148,21 @@ export class HubCommand {
139
148
  console.log();
140
149
  }
141
150
  catch (error) {
142
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
143
- console.log(chalk.red(`❌ Activation failed: ${errMsg}`));
144
- console.log(chalk.gray('\nMake sure the module ID is correct.'));
145
- console.log(chalk.gray('Run: vigthoria hub list to see available modules.'));
151
+ throw commandFailure(error, { code: 'HUB_ACTIVATION_FAILED' });
146
152
  }
147
153
  }
148
154
  /**
149
155
  * Show currently active modules for user
150
156
  */
151
157
  async active() {
152
- const authToken = this.config.get('authToken');
153
- if (!authToken) {
154
- console.log(chalk.red('\n❌ Not authenticated. Run: vigthoria login\n'));
155
- return;
156
- }
158
+ const authToken = this.getAuthToken();
157
159
  console.log(chalk.cyan('\n📦 Your Active Modules\n'));
158
160
  try {
159
- const response = await fetch(`${API_BASE}/api/modules/active`, {
161
+ const response = await guardedFetch(`${API_BASE}/api/modules/active`, {
160
162
  headers: {
161
163
  'Authorization': `Bearer ${authToken}`
162
164
  }
163
- });
165
+ }, { audience: 'hub' });
164
166
  if (!response.ok) {
165
167
  throw new Error(`Failed to fetch: ${response.statusText}`);
166
168
  }
@@ -185,8 +187,7 @@ export class HubCommand {
185
187
  }
186
188
  }
187
189
  catch (error) {
188
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
189
- console.log(chalk.red(`Error: ${errMsg}`));
190
+ throw commandFailure(error, { code: 'HUB_ACTIVE_FAILED' });
190
191
  }
191
192
  }
192
193
  /**
@@ -195,7 +196,7 @@ export class HubCommand {
195
196
  async info(moduleId) {
196
197
  console.log(chalk.cyan(`\n📖 Module Info: ${moduleId}\n`));
197
198
  try {
198
- const response = await fetch(`${API_BASE}/api/modules/${moduleId}`);
199
+ const response = await guardedFetch(`${API_BASE}/api/modules/${moduleId}`, {}, { audience: 'hub' });
199
200
  if (!response.ok) {
200
201
  throw new Error('Module not found');
201
202
  }
@@ -250,9 +251,7 @@ export class HubCommand {
250
251
  console.log();
251
252
  }
252
253
  catch (error) {
253
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
254
- console.log(chalk.red(`Error: ${errMsg}`));
255
- console.log(chalk.gray('\nAvailable modules: pay, meet, llm, music, voice, images'));
254
+ throw commandFailure(error, { code: 'HUB_INFO_FAILED' });
256
255
  }
257
256
  }
258
257
  /**
@@ -26,7 +26,6 @@ export declare class LegionCommand {
26
26
  private getHyperloopUrls;
27
27
  private getHeaders;
28
28
  private readJsonResponse;
29
- private propagateLegionApiError;
30
29
  run(request: string | undefined, options: LegionOptions): Promise<void>;
31
30
  private runCortex;
32
31
  private isCriticalRoleFailure;
@@ -15,7 +15,9 @@ import * as readline from 'readline/promises';
15
15
  import { exec as execCallback } from 'node:child_process';
16
16
  import { promisify } from 'node:util';
17
17
  import { createSpinner, CH } from '../utils/logger.js';
18
- import { describeUpstreamStatus, isServerRuntime, propagateError } from '../utils/api.js';
18
+ import { describeUpstreamStatus, propagateError } from '../utils/api.js';
19
+ import { hasLoopbackServiceCapability } from '../utils/runtime-capability.js';
20
+ import { CliCommandError } from '../utils/command-contract.js';
19
21
  // Hyper Loop / Legion runs on the Vigthoria backend only. Local user installs
20
22
  // must never even attempt these endpoints, because fetch errors include the
21
23
  // URL we tried (would leak internal infra to the user's terminal).
@@ -31,9 +33,6 @@ function buildServerHyperloopUrls() {
31
33
  `http://${internalHost}:${port}${apiPath}`,
32
34
  ];
33
35
  }
34
- const HYPERLOOP_URLS = isServerRuntime()
35
- ? buildServerHyperloopUrls()
36
- : (process.env.VIGTHORIA_HYPERLOOP_URL ? [process.env.VIGTHORIA_HYPERLOOP_URL] : []);
37
36
  const CORTEX_WARN_BUDGET_USD = 3.5;
38
37
  const CORTEX_HARD_BUDGET_USD = 5.0;
39
38
  const CORTEX_MAX_ROUNDS = 2;
@@ -57,7 +56,7 @@ export class LegionCommand {
57
56
  };
58
57
  // On-server checks must try loopback first. The former configured 10.0.0.2
59
58
  // route could consume the entire timeout while the same node was healthy.
60
- if (isServerRuntime()) {
59
+ if (hasLoopbackServiceCapability()) {
61
60
  for (const internal of buildServerHyperloopUrls()) {
62
61
  const normalized = normalizeBase(internal);
63
62
  if (normalized)
@@ -109,20 +108,6 @@ export class LegionCommand {
109
108
  });
110
109
  }
111
110
  }
112
- propagateLegionApiError(context, endpoint, err) {
113
- const original = err && typeof err === 'object' ? err : { message: String(err) };
114
- propagateError({
115
- ...original,
116
- commandName: 'legion',
117
- endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || endpoint || context,
118
- details: {
119
- ...(original.details && typeof original.details === 'object' ? original.details : {}),
120
- command: 'legion',
121
- endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || endpoint || context,
122
- context,
123
- },
124
- });
125
- }
126
111
  async run(request, options) {
127
112
  if (options.cortex) {
128
113
  await this.runCortex(request, options);
@@ -137,38 +122,32 @@ export class LegionCommand {
137
122
  return;
138
123
  }
139
124
  if (!request) {
140
- console.log(chalk.yellow('Usage: vigthoria legion "<task description>"'));
141
- console.log(chalk.gray(' --workers List available Legion workers'));
142
- console.log(chalk.gray(' --status Show Legion infrastructure status'));
143
- console.log(chalk.gray(' --cortex Vigthoria Cortex: maximum intelligence execution'));
144
- return;
125
+ throw new CliCommandError('Usage: vigthoria legion "<task description>" (or use --workers, --status, or --cortex).', {
126
+ code: 'LEGION_REQUEST_REQUIRED', category: 'usage',
127
+ });
128
+ }
129
+ const execution = await this.planAndExecute(request, options);
130
+ if (execution.status !== 'completed') {
131
+ throw new CliCommandError(execution.finalSummary || 'Legion execution failed.', {
132
+ code: 'LEGION_EXECUTION_FAILED',
133
+ details: execution,
134
+ });
145
135
  }
146
- await this.planAndExecute(request, options);
147
136
  }
148
137
  async runCortex(request, options) {
149
138
  if (!request) {
150
- console.log(chalk.yellow('Usage: vigthoria legion --cortex "<task description>"'));
151
- console.log(chalk.gray(' --plan-only Run calculator only (no execution)'));
152
- console.log(chalk.gray(' --force-budget Allow execution above hard safe-stop budget'));
153
- console.log(chalk.gray(' --ignore-preflight Bypass mandatory preflight checks (no warranty)'));
154
- console.log(chalk.gray(' --speed Speed mode: optional role skip on convergence'));
155
- console.log(chalk.gray(' --tier heavy|lite Model tier: heavy=strongest LLMs (default), lite=efficient+affordable'));
156
- console.log(chalk.gray(' --repro-cmd <cmd> Run a local reproducibility command before spend'));
157
- console.log(chalk.gray(' --expect-repro-fail Require repro command to fail before execution'));
158
- console.log(chalk.gray(' --approve Skip initial confirmation prompt and execute'));
159
- console.log(chalk.gray(' --auto-charge Charge the canonical PAYG wallet when balance is sufficient'));
160
- console.log(chalk.gray(' --timeout <sec> Abort remote execution if no result within timeout (default: 120)'));
161
- return;
139
+ throw new CliCommandError('Usage: vigthoria legion --cortex "<task description>".', {
140
+ code: 'CORTEX_REQUEST_REQUIRED', category: 'usage',
141
+ });
162
142
  }
163
143
  const workspace = options.project || process.cwd();
164
144
  const scan = this.scanProject(workspace);
165
145
  if (!options.ignorePreflight) {
166
146
  const preflight = await this.runMandatoryPreflight(workspace, options.reproCmd, options.expectReproFail === true);
167
147
  if (!preflight.ok) {
168
- console.log(chalk.red('Cortex preflight failed.'));
169
- console.log(chalk.red(` ${preflight.reason}`));
170
- console.log(chalk.yellow('Execution halted before any cloud spend. Use --ignore-preflight to override (no warranty).'));
171
- return;
148
+ throw new CliCommandError(`Cortex preflight failed: ${preflight.reason}`, {
149
+ code: 'CORTEX_PREFLIGHT_FAILED', details: preflight,
150
+ });
172
151
  }
173
152
  console.log(chalk.green('Preflight passed.'));
174
153
  }
@@ -185,9 +164,9 @@ export class LegionCommand {
185
164
  let billingGate = await this.evaluateBillingGate(billingQuote);
186
165
  this.printCortexQuote(workspace, scan, quote, billingQuote, billingGate);
187
166
  if (billingQuote.retryAdjustedUsd > CORTEX_HARD_BUDGET_USD && !options.forceBudget && !billingGate.masterAdminFree) {
188
- console.log(chalk.red(`Estimated spend exceeds hard budget ceiling (${CORTEX_HARD_BUDGET_USD.toFixed(2)}).`));
189
- console.log(chalk.yellow('Re-run with --force-budget to continue.'));
190
- return;
167
+ throw new CliCommandError(`Estimated spend exceeds hard budget ceiling (${CORTEX_HARD_BUDGET_USD.toFixed(2)}). Re-run with --force-budget to continue.`, {
168
+ code: 'CORTEX_BUDGET_EXCEEDED', category: 'authorization', details: billingQuote,
169
+ });
191
170
  }
192
171
  if (options.planOnly) {
193
172
  console.log(chalk.green('Cortex estimator complete (plan-only).'));
@@ -196,8 +175,7 @@ export class LegionCommand {
196
175
  const autoApprove = options.approve === true && options.noApprove !== true;
197
176
  const approved = autoApprove ? true : await this.confirmExecution();
198
177
  if (!approved) {
199
- console.log(chalk.yellow('Cortex cancelled by user.'));
200
- return;
178
+ throw new CliCommandError('Cortex cancelled by user.', { code: 'CORTEX_CANCELLED', category: 'cancelled' });
201
179
  }
202
180
  let round = 1;
203
181
  let cumulativeUsd = 0;
@@ -207,20 +185,23 @@ export class LegionCommand {
207
185
  if (!billingGate.canProceed) {
208
186
  const resolved = await this.resolveBillingInsufficientFunds(currentQuote, billingGate, options);
209
187
  if (!resolved) {
210
- console.log(chalk.yellow('Cortex cancelled due to insufficient PAYG credit balance.'));
211
- return;
188
+ throw new CliCommandError('Cortex blocked by insufficient PAYG credit balance.', {
189
+ code: 'CORTEX_INSUFFICIENT_CREDIT', category: 'authorization', details: billingGate,
190
+ });
212
191
  }
213
192
  billingGate = await this.evaluateBillingGate(currentQuote);
214
193
  if (!billingGate.canProceed) {
215
194
  this.printBillingGateSummary(currentQuote, billingGate);
216
- console.log(chalk.red('Billing gate still blocked after charge attempt.'));
217
- return;
195
+ throw new CliCommandError('Billing gate remains blocked after the charge attempt.', {
196
+ code: 'CORTEX_BILLING_BLOCKED', category: 'authorization', details: billingGate,
197
+ });
218
198
  }
219
199
  }
220
200
  const charged = await this.collectExecutionCharge(currentQuote, billingGate);
221
201
  if (!charged) {
222
- console.log(chalk.yellow('Cortex cancelled because wallet charge was not completed.'));
223
- return;
202
+ throw new CliCommandError('Cortex wallet charge was not completed.', {
203
+ code: 'CORTEX_CHARGE_INCOMPLETE', category: 'authorization',
204
+ });
224
205
  }
225
206
  cumulativeUsd += currentQuote.finalUsd;
226
207
  const enrichedRequest = [
@@ -246,28 +227,32 @@ export class LegionCommand {
246
227
  }
247
228
  const failedRole = String(execution.failedStepId || '').toLowerCase();
248
229
  if (this.isCriticalRoleFailure(failedRole)) {
249
- console.log(chalk.red(`Fail-fast: critical role '${failedRole || 'unknown'}' failed. Manual correction required.`));
250
- return;
230
+ throw new CliCommandError(`Critical Cortex role '${failedRole || 'unknown'}' failed. Manual correction is required.`, {
231
+ code: 'CORTEX_CRITICAL_ROLE_FAILED', details: execution,
232
+ });
251
233
  }
252
234
  if (!this.isOptionalRepairRoleFailure(failedRole)) {
253
- console.log(chalk.red('Execution failed and is not eligible for automatic optional-role repair.'));
254
- return;
235
+ throw new CliCommandError('Cortex execution failed and is not eligible for automatic optional-role repair.', {
236
+ code: 'CORTEX_EXECUTION_FAILED', details: execution,
237
+ });
255
238
  }
256
239
  if (round >= CORTEX_MAX_ROUNDS) {
257
- console.log(chalk.red('Optional-role auto-repair budget exhausted.'));
258
- return;
240
+ throw new CliCommandError('Cortex optional-role auto-repair budget is exhausted.', {
241
+ code: 'CORTEX_REPAIR_EXHAUSTED', details: execution,
242
+ });
259
243
  }
260
244
  const nextQuote = this.estimateAdditionalLoopQuote(currentQuote, execution);
261
245
  const projectedTotal = cumulativeUsd + nextQuote.retryAdjustedUsd;
262
246
  if (projectedTotal > CORTEX_HARD_BUDGET_USD && !options.forceBudget) {
263
- console.log(chalk.red(`Additional loop would exceed hard budget ceiling ($${CORTEX_HARD_BUDGET_USD.toFixed(2)}).`));
264
- console.log(chalk.yellow('Re-run with --force-budget to allow paid continuation.'));
265
- return;
247
+ throw new CliCommandError(`Additional Cortex loop would exceed hard budget ceiling ($${CORTEX_HARD_BUDGET_USD.toFixed(2)}).`, {
248
+ code: 'CORTEX_BUDGET_EXCEEDED', category: 'authorization', details: { projectedTotal },
249
+ });
266
250
  }
267
251
  const continueApproved = await this.confirmAdditionalLoopCharge(nextQuote, round + 1, execution);
268
252
  if (!continueApproved) {
269
- console.log(chalk.yellow('Termination: user declined additional budget for next round.'));
270
- return;
253
+ throw new CliCommandError('Cortex cancelled because the user declined additional budget.', {
254
+ code: 'CORTEX_CANCELLED', category: 'cancelled',
255
+ });
271
256
  }
272
257
  currentQuote = nextQuote;
273
258
  billingGate = await this.evaluateBillingGate(currentQuote);
@@ -1105,7 +1090,7 @@ export class LegionCommand {
1105
1090
  const envOverride = String(process.env.VIGTHORIA_HYPERLOOP_URL || '').trim();
1106
1091
  if (envOverride)
1107
1092
  return `${normalizeBase(envOverride)}/legion/stream`;
1108
- if (isServerRuntime()) {
1093
+ if (hasLoopbackServiceCapability()) {
1109
1094
  return 'http://localhost:8020/api/hyperloop/legion/stream';
1110
1095
  }
1111
1096
  const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').trim();
@@ -1151,27 +1136,26 @@ export class LegionCommand {
1151
1136
  const startTime = Date.now();
1152
1137
  let response;
1153
1138
  try {
1139
+ const executionTimeoutMs = Math.max(1, Number(options.timeoutSec || 120)) * 1000;
1154
1140
  response = await fetch(streamUrl, {
1155
1141
  method: 'POST',
1156
1142
  headers,
1157
1143
  body: JSON.stringify(body),
1144
+ signal: AbortSignal.timeout(executionTimeoutMs),
1158
1145
  });
1159
1146
  }
1160
1147
  catch (connErr) {
1161
1148
  spinner.stop();
1162
- this.logger.error(`Cannot connect to Hyper Loop at ${streamUrl}: ${connErr?.message || connErr}`);
1163
- return { status: 'failed', plannedSteps: 0, completedSteps: 0 };
1149
+ return { status: 'failed', plannedSteps: 0, completedSteps: 0, finalSummary: `Cannot connect to Hyper Loop: ${connErr?.message || connErr}` };
1164
1150
  }
1165
1151
  if (!response.ok) {
1166
1152
  spinner.stop();
1167
1153
  const errBody = await response.text().catch(() => '');
1168
- this.logger.error(`Legion stream ${response.status}: ${describeUpstreamStatus(response.status)} — ${errBody.slice(0, 200)}`);
1169
- return { status: 'failed', plannedSteps: 0, completedSteps: 0 };
1154
+ return { status: 'failed', plannedSteps: 0, completedSteps: 0, finalSummary: `Legion stream ${response.status}: ${describeUpstreamStatus(response.status)} — ${errBody.slice(0, 200)}` };
1170
1155
  }
1171
1156
  if (!response.body) {
1172
1157
  spinner.stop();
1173
- this.logger.error('Legion stream returned no response body');
1174
- return { status: 'failed', plannedSteps: 0, completedSteps: 0 };
1158
+ return { status: 'failed', plannedSteps: 0, completedSteps: 0, finalSummary: 'Legion stream returned no response body' };
1175
1159
  }
1176
1160
  spinner.stop();
1177
1161
  console.log();
@@ -1330,8 +1314,8 @@ export class LegionCommand {
1330
1314
  }
1331
1315
  }
1332
1316
  catch (streamErr) {
1333
- this.logger.error(`Legion stream read error: ${streamErr?.message || streamErr}`);
1334
1317
  finalStatus = 'failed';
1318
+ finalResult = { ...(finalResult || {}), stream_error: String(streamErr?.message || streamErr) };
1335
1319
  }
1336
1320
  const elapsedSec = ((Date.now() - startTime) / 1000).toFixed(1);
1337
1321
  if (stepsTotal > 0 && stepsDone < stepsTotal) {
@@ -1370,7 +1354,7 @@ export class LegionCommand {
1370
1354
  completedSteps: stepsDone,
1371
1355
  failedStepId: failedStepId || undefined,
1372
1356
  failedWorker: failedWorker || undefined,
1373
- finalSummary: summary || undefined,
1357
+ finalSummary: summary || String(finalResult?.stream_error || '') || undefined,
1374
1358
  };
1375
1359
  }
1376
1360
  buildCortexRunReport(base, streamEvents) {
@@ -1499,7 +1483,9 @@ export class LegionCommand {
1499
1483
  }
1500
1484
  }
1501
1485
  spinner.stop();
1502
- this.logger.error(lastError || 'Could not reach Hyper Loop. Is vigthoria-hyper-loop running?');
1486
+ throw new CliCommandError(lastError || 'Could not reach Hyper Loop.', {
1487
+ code: 'LEGION_WORKERS_UNAVAILABLE', category: 'network',
1488
+ });
1503
1489
  }
1504
1490
  async showStatus() {
1505
1491
  const spinner = createSpinner('Checking Legion infrastructure...').start();
@@ -1565,11 +1551,8 @@ export class LegionCommand {
1565
1551
  }
1566
1552
  }
1567
1553
  spinner.stop();
1568
- console.log();
1569
- console.log(chalk.gray(' Hyper Loop: ') + chalk.red('offline'));
1570
- if (lastError) {
1571
- this.logger.error(lastError);
1572
- }
1573
- console.log();
1554
+ throw new CliCommandError(lastError || 'Hyper Loop is offline.', {
1555
+ code: 'LEGION_STATUS_UNAVAILABLE', category: 'network',
1556
+ });
1574
1557
  }
1575
1558
  }
@@ -18,8 +18,7 @@ type MusicStatusOptions = {
18
18
  };
19
19
  export declare class MusicCommand {
20
20
  private config;
21
- private logger;
22
- constructor(config: Config, logger: Logger);
21
+ constructor(config: Config, _logger: Logger);
23
22
  private apiBase;
24
23
  private authHeaders;
25
24
  private request;