vigthoria-cli 1.13.20 → 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 +63 -128
  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 +3 -42
  12. package/dist/commands/chat.js +378 -1123
  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 +23 -29
  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 -1589
  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 +44 -266
  71. package/dist/utils/api.js +647 -3180
  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 +220 -94
  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 +5 -6
  106. package/dist/utils/fastAgentRouter.js +17 -25
  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 +17 -3
  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 +85 -142
  167. package/install.sh +75 -80
  168. package/package.json +43 -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 -174
  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
@@ -1,17 +1,16 @@
1
- import { isServerRuntime } from '../utils/api.js';
1
+ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
2
2
  /**
3
3
  * fork.ts — Fork from an existing V3 agent run and stream the result.
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 ForkCommand {
10
11
  config;
11
- logger;
12
- constructor(config, logger) {
12
+ constructor(config, _logger) {
13
13
  this.config = config;
14
- this.logger = logger;
15
14
  }
16
15
  getHeaders() {
17
16
  const headers = { 'Content-Type': 'application/json' };
@@ -29,7 +28,7 @@ export class ForkCommand {
29
28
  }
30
29
  getBaseUrl() {
31
30
  const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
32
- const allowLocal = isServerRuntime() && process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
31
+ const allowLocal = hasLocalV3AgentCapability();
33
32
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
34
33
  process.env.V3_AGENT_URL ||
35
34
  (allowLocal ? 'http://127.0.0.1:8030' : null) ||
@@ -53,6 +52,13 @@ export class ForkCommand {
53
52
  const workspace = this.resolveWorkspaceRoot(project);
54
53
  const eventIndex = options.eventIndex || 0;
55
54
  const spinner = createSpinner(`Forking run ${runId}...`).start();
55
+ const streamController = new AbortController();
56
+ let interrupted = false;
57
+ const onInterrupt = () => {
58
+ interrupted = true;
59
+ streamController.abort();
60
+ };
61
+ process.once('SIGINT', onInterrupt);
56
62
  try {
57
63
  const baseUrl = this.getBaseUrl();
58
64
  const body = {
@@ -66,33 +72,57 @@ export class ForkCommand {
66
72
  method: 'POST',
67
73
  headers: this.getHeaders(),
68
74
  body: JSON.stringify(body),
75
+ signal: streamController.signal,
69
76
  });
70
77
  if (!resp.ok) {
71
78
  spinner.stop();
72
79
  if (resp.status === 404) {
73
- this.logger.error(`Run ${runId} not found or has no event log.`);
80
+ throw new CliCommandError(`Run ${runId} not found or has no event log.`, {
81
+ code: 'RUN_NOT_FOUND', status: resp.status,
82
+ });
74
83
  }
75
84
  else if (resp.status === 502 || resp.status === 503) {
76
- this.logger.error(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`);
85
+ throw new CliCommandError(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`, {
86
+ code: 'RUN_HISTORY_UNAVAILABLE', status: resp.status, category: 'network',
87
+ });
77
88
  }
78
89
  else {
79
- this.logger.error(`Fork failed: ${resp.status} ${resp.statusText}`);
90
+ throw new CliCommandError(`Fork failed: ${resp.status} ${resp.statusText}`, {
91
+ code: 'RUN_FORK_FAILED', status: resp.status,
92
+ });
80
93
  }
81
- return;
82
94
  }
83
95
  spinner.stop();
84
- console.log(chalk.bold(`\n${CH.success} Forked from ${chalk.cyan(runId)} at event ${eventIndex}\n`));
96
+ if (!options.json)
97
+ console.log(chalk.bold(`\n${CH.success} Forked from ${chalk.cyan(runId)} at event ${eventIndex}\n`));
85
98
  if (!resp.body) {
86
- this.logger.error('No response body');
87
- return;
99
+ throw new CliCommandError('Fork response did not contain an event stream', { code: 'RUN_FORK_STREAM_MISSING' });
88
100
  }
89
101
  // Stream SSE events
90
102
  const reader = resp.body.getReader();
91
103
  const decoder = new TextDecoder();
92
104
  let buffer = '';
93
105
  let toolCallNum = 0;
94
- while (true) {
95
- const { done, value } = await reader.read();
106
+ let completed = false;
107
+ const events = [];
108
+ const configuredIdle = Number.parseInt(process.env.VIGTHORIA_FORK_IDLE_TIMEOUT_MS || '', 10);
109
+ const idleTimeoutMs = Number.isFinite(configuredIdle) && configuredIdle > 0 ? configuredIdle : 60_000;
110
+ stream: while (true) {
111
+ let timer;
112
+ const { done, value } = await Promise.race([
113
+ reader.read(),
114
+ new Promise((_resolve, reject) => {
115
+ timer = setTimeout(() => {
116
+ streamController.abort();
117
+ reject(new CliCommandError(`Fork event stream was idle for ${idleTimeoutMs}ms.`, {
118
+ code: 'RUN_FORK_IDLE_TIMEOUT', category: 'network',
119
+ }));
120
+ }, idleTimeoutMs);
121
+ }),
122
+ ]).finally(() => {
123
+ if (timer)
124
+ clearTimeout(timer);
125
+ });
96
126
  if (done)
97
127
  break;
98
128
  buffer += decoder.decode(value, { stream: true });
@@ -103,14 +133,14 @@ export class ForkCommand {
103
133
  continue;
104
134
  const payload = line.slice(6).trim();
105
135
  if (payload === '[DONE]') {
106
- console.log(chalk.bold(`\n${CH.success} Fork run complete\n`));
107
- return;
136
+ completed = true;
137
+ break stream;
108
138
  }
109
139
  try {
110
140
  const evt = JSON.parse(payload);
111
141
  const type = evt.type || 'unknown';
142
+ events.push(evt);
112
143
  if (options.json) {
113
- console.log(JSON.stringify(evt));
114
144
  continue;
115
145
  }
116
146
  switch (type) {
@@ -144,21 +174,41 @@ export class ForkCommand {
144
174
  break;
145
175
  }
146
176
  case 'error':
147
- console.log(chalk.red(` āŒ ERROR: `) + (evt.message || '').substring(0, 200));
148
- break;
177
+ throw new CliCommandError(String(evt.message || 'Fork stream reported an error.'), {
178
+ code: 'RUN_FORK_STREAM_ERROR', details: evt,
179
+ });
149
180
  default:
150
181
  console.log(chalk.dim(` ${type}: ${JSON.stringify(evt).substring(0, 80)}`));
151
182
  }
152
183
  }
153
- catch {
154
- // skip unparseable lines
184
+ catch (error) {
185
+ if (error instanceof CliCommandError)
186
+ throw error;
187
+ throw new CliCommandError('Fork event stream contained malformed JSON.', {
188
+ code: 'RUN_FORK_STREAM_MALFORMED', details: { payload: payload.slice(0, 200) }, cause: error,
189
+ });
155
190
  }
156
191
  }
157
192
  }
193
+ if (!completed) {
194
+ throw new CliCommandError('Fork event stream ended without a completion marker.', { code: 'RUN_FORK_STREAM_INCOMPLETE' });
195
+ }
196
+ if (options.json) {
197
+ console.log(formatSuccessJson('fork', { runId, eventIndex, events }, { stream: true, eventCount: events.length }));
198
+ }
199
+ else {
200
+ console.log(chalk.bold(`\n${CH.success} Fork run complete\n`));
201
+ }
158
202
  }
159
203
  catch (err) {
160
204
  spinner.stop();
161
- this.logger.error(`Fork error: ${err.message}`);
205
+ if (interrupted) {
206
+ throw new CliCommandError('Fork cancelled by user.', { code: 'RUN_FORK_CANCELLED', category: 'cancelled', cause: err });
207
+ }
208
+ throw commandFailure(err, { code: 'RUN_FORK_FAILED' });
209
+ }
210
+ finally {
211
+ process.removeListener('SIGINT', onInterrupt);
162
212
  }
163
213
  }
164
214
  }
@@ -18,7 +18,7 @@ export declare class GameCommand {
18
18
  }): Promise<void>;
19
19
  validate(o: CommonOptions & {
20
20
  json?: boolean;
21
- }): Promise<boolean>;
21
+ }): Promise<true>;
22
22
  private project;
23
23
  private sources;
24
24
  private finish;
@@ -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;