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
@@ -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;
@@ -1,29 +1,30 @@
1
1
  import chalk from 'chalk';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
+ import { guardedFetch } from '../utils/network-policy.js';
5
+ import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
6
+ import { setTimeout as delay } from 'node:timers/promises';
4
7
  export class MusicCommand {
5
8
  config;
6
- logger;
7
- constructor(config, logger) {
9
+ constructor(config, _logger) {
8
10
  this.config = config;
9
- this.logger = logger;
10
11
  }
11
12
  apiBase() {
12
13
  const configured = String(this.config.getAll().musicApiUrl || '').trim();
13
14
  return (process.env.VIGTHORIA_MUSIC_API_URL || configured || 'https://music.vigthoria.io').replace(/\/+$/, '');
14
15
  }
15
16
  authHeaders() {
16
- const token = this.config.get('authToken');
17
+ const token = process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken');
17
18
  return token ? { Authorization: `Bearer ${token}` } : {};
18
19
  }
19
20
  async request(endpoint, init = {}) {
20
- const response = await fetch(`${this.apiBase()}${endpoint}`, {
21
+ const response = await guardedFetch(`${this.apiBase()}${endpoint}`, {
21
22
  ...init,
22
23
  headers: {
23
24
  ...this.authHeaders(),
24
25
  ...init.headers,
25
26
  },
26
- });
27
+ }, { audience: 'music' });
27
28
  const text = await response.text();
28
29
  let data = {};
29
30
  try {
@@ -58,7 +59,7 @@ export class MusicCommand {
58
59
  async generate(promptArg, options) {
59
60
  const prompt = String(promptArg || options.prompt || '').trim();
60
61
  if (!prompt) {
61
- throw new Error('Music generation requires a prompt.');
62
+ throw new CliCommandError('Music generation requires a prompt.', { code: 'MUSIC_PROMPT_REQUIRED', category: 'usage' });
62
63
  }
63
64
  let referenceAudioPath = '';
64
65
  if (options.voice) {
@@ -88,10 +89,7 @@ export class MusicCommand {
88
89
  body: JSON.stringify(payload),
89
90
  });
90
91
  const taskId = String(data.task_id || data.session_id || '').trim();
91
- if (options.json) {
92
- console.log(JSON.stringify({ ...data, task_id: taskId || data.task_id }, null, 2));
93
- }
94
- else {
92
+ if (!options.json) {
95
93
  console.log(chalk.green('Music generation queued.'));
96
94
  console.log(chalk.cyan('Task ID: ') + (taskId || '(pending)'));
97
95
  if (data.queue_position)
@@ -100,13 +98,18 @@ export class MusicCommand {
100
98
  console.log(chalk.gray(`Estimated wait: ${data.estimated_wait_seconds}s`));
101
99
  }
102
100
  if (options.wait && taskId) {
103
- await this.waitForStatus(taskId, Number(options.pollInterval || 5), Boolean(options.json));
101
+ const terminal = await this.waitForStatus(taskId, Number(options.pollInterval || 5), options.json === true);
102
+ if (options.json)
103
+ console.log(formatSuccessJson('music generate', terminal, { stage: 'terminal' }));
104
+ return;
104
105
  }
106
+ if (options.json)
107
+ console.log(formatSuccessJson('music generate', { ...data, task_id: taskId || data.task_id }, { stage: 'queued' }));
105
108
  }
106
109
  async status(taskId, options = {}) {
107
110
  const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
108
111
  if (options.json) {
109
- console.log(JSON.stringify(data, null, 2));
112
+ console.log(formatSuccessJson('music status', data));
110
113
  return;
111
114
  }
112
115
  console.log(chalk.cyan(`Music task ${taskId}`));
@@ -117,21 +120,53 @@ export class MusicCommand {
117
120
  if (downloadUrl)
118
121
  console.log(`Download: ${downloadUrl}`);
119
122
  }
120
- async waitForStatus(taskId, intervalSeconds, json) {
121
- const delay = Math.max(1, intervalSeconds) * 1000;
122
- for (;;) {
123
- await new Promise((resolve) => setTimeout(resolve, delay));
124
- const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
125
- const status = String(data.status || '').toLowerCase();
126
- if (!json) {
127
- const progress = data.progress !== undefined ? ` (${data.progress}%)` : '';
128
- console.log(chalk.gray(`Status: ${status || 'unknown'}${progress}`));
129
- }
130
- if (['completed', 'complete', 'finished', 'failed', 'error', 'cancelled'].includes(status)) {
131
- if (json)
132
- console.log(JSON.stringify(data, null, 2));
133
- return;
123
+ async waitForStatus(taskId, intervalSeconds, quiet) {
124
+ const pollDelayMs = Math.max(1, intervalSeconds) * 1000;
125
+ const configuredTimeout = Number.parseInt(process.env.VIGTHORIA_MUSIC_WAIT_TIMEOUT_MS || '', 10);
126
+ const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 30 * 60_000;
127
+ const deadline = Date.now() + timeoutMs;
128
+ const cancellation = new AbortController();
129
+ let interrupted = false;
130
+ const onInterrupt = () => {
131
+ interrupted = true;
132
+ cancellation.abort();
133
+ };
134
+ process.once('SIGINT', onInterrupt);
135
+ try {
136
+ for (;;) {
137
+ if (Date.now() >= deadline) {
138
+ throw new CliCommandError(`Music generation did not finish within ${timeoutMs}ms.`, {
139
+ code: 'MUSIC_WAIT_TIMEOUT', category: 'network', details: { taskId, timeoutMs },
140
+ });
141
+ }
142
+ await delay(Math.min(pollDelayMs, Math.max(1, deadline - Date.now())), undefined, { signal: cancellation.signal });
143
+ const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`, { signal: cancellation.signal });
144
+ const status = String(data.status || '').toLowerCase();
145
+ if (!quiet) {
146
+ const progress = data.progress !== undefined ? ` (${data.progress}%)` : '';
147
+ console.log(chalk.gray(`Status: ${status || 'unknown'}${progress}`));
148
+ }
149
+ if (['failed', 'error'].includes(status)) {
150
+ throw new CliCommandError(String(data.error || data.message || 'Music generation failed.'), {
151
+ code: 'MUSIC_GENERATION_FAILED', details: data,
152
+ });
153
+ }
154
+ if (status === 'cancelled') {
155
+ throw new CliCommandError('Music generation was cancelled.', {
156
+ code: 'MUSIC_GENERATION_CANCELLED', category: 'cancelled', details: data,
157
+ });
158
+ }
159
+ if (['completed', 'complete', 'finished'].includes(status))
160
+ return data;
134
161
  }
135
162
  }
163
+ catch (error) {
164
+ if (interrupted)
165
+ throw new CliCommandError('Music wait cancelled by user.', { code: 'MUSIC_WAIT_CANCELLED', category: 'cancelled', cause: error });
166
+ throw commandFailure(error, { code: 'MUSIC_WAIT_FAILED' });
167
+ }
168
+ finally {
169
+ process.removeListener('SIGINT', onInterrupt);
170
+ }
136
171
  }
137
172
  }
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { Config } from '../utils/config.js';
3
+ import type { Logger } from '../utils/logger.js';
4
+ export declare function registerPlatformCommands(program: Command, config: Config, logger: Logger): void;
@@ -0,0 +1,45 @@
1
+ import { BridgeCommand } from './bridge.js';
2
+ import { DeviceCommand } from './device.js';
3
+ import { SecurityCommand } from './security.js';
4
+ export function registerPlatformCommands(program, config, logger) {
5
+ program.command('bridge').description('Inspect local DevTools Bridge availability for browser debugging tasks')
6
+ .command('status').description('Show live DevTools Bridge status')
7
+ .action(async () => new BridgeCommand(config, logger).status());
8
+ const device = program.command('device').alias('adb').description('Android Developer Bridge commands via local ADB');
9
+ device.command('status').description('Show ADB availability and connected Android devices').option('--json', 'Emit JSON output', false)
10
+ .action(async (options) => new DeviceCommand(config, logger).status(options));
11
+ device.command('list').description('List connected Android devices').option('--json', 'Emit JSON output', false)
12
+ .action(async (options) => new DeviceCommand(config, logger).list(options));
13
+ device.command('screenshot').description('Capture a screenshot from an Android device')
14
+ .option('-d, --device <id>', 'ADB device id').option('-o, --output <path>', 'Output PNG path')
15
+ .action(async (options) => new DeviceCommand(config, logger).screenshot(options));
16
+ device.command('install <apk>').description('Install or update an APK on an Android device')
17
+ .option('-d, --device <id>', 'ADB device id').option('--json', 'Emit JSON output', false)
18
+ .action(async (apk, options) => new DeviceCommand(config, logger).install(apk, options));
19
+ device.command('launch <package>').description('Launch an Android app by package name')
20
+ .option('-d, --device <id>', 'ADB device id').option('-a, --activity <activity>', 'Activity class, for example .MainActivity')
21
+ .option('--json', 'Emit JSON output', false)
22
+ .action(async (packageName, options) => new DeviceCommand(config, logger).launch(packageName, options));
23
+ device.command('logs').description('Read Android logcat output').option('-d, --device <id>', 'ADB device id')
24
+ .option('-n, --lines <count>', 'Number of log lines for snapshot mode', '250').option('-f, --follow', 'Follow logcat until interrupted', false)
25
+ .option('--json', 'Emit JSON output for snapshot mode', false)
26
+ .action(async (options) => new DeviceCommand(config, logger).logs(options));
27
+ device.command('tcpip').description('Enable wireless ADB tcpip mode on the selected USB-connected device')
28
+ .option('-d, --device <id>', 'ADB device id').option('-p, --port <port>', 'Wireless ADB port', '5555')
29
+ .option('--json', 'Emit JSON output', false).action(async (options) => new DeviceCommand(config, logger).tcpip(options));
30
+ device.command('connect <address>').description('Connect to a wireless ADB device, for example 192.168.1.30:5555')
31
+ .option('--json', 'Emit JSON output', false).action(async (address, options) => new DeviceCommand(config, logger).connect(address, options));
32
+ device.command('disconnect [address]').description('Disconnect a wireless ADB device')
33
+ .option('--json', 'Emit JSON output', false).action(async (address, options) => new DeviceCommand(config, logger).disconnect(address, options));
34
+ device.action(async () => new DeviceCommand(config, logger).status({}));
35
+ const security = program.command('security').alias('vsec')
36
+ .description('Run security scans, scores, and fix plans via Vigthoria MCP security tools');
37
+ security.command('scan').description('Scan project for security issues').option('-d, --dir <path>', 'Directory to scan (default: current directory)')
38
+ .option('--json', 'Emit JSON output', false).action(async (options) => new SecurityCommand(config, logger).scan({ dir: options.dir, json: options.json }));
39
+ security.command('score').description('Calculate project security score').option('-d, --dir <path>', 'Directory to score (default: current directory)')
40
+ .option('--json', 'Emit JSON output', false).action(async (options) => new SecurityCommand(config, logger).score({ dir: options.dir, json: options.json }));
41
+ security.command('fix').description('Generate security fix plan').option('-d, --dir <path>', 'Directory to inspect (default: current directory)')
42
+ .option('-i, --issue-id <id>', 'Single issue id to target').option('--apply', 'Request auto-apply mode (safe mode still requires explicit patching)', false)
43
+ .option('--json', 'Emit JSON output', false)
44
+ .action(async (options) => new SecurityCommand(config, logger).fix({ dir: options.dir, issueId: options.issueId, apply: options.apply, json: options.json }));
45
+ }
@@ -21,7 +21,6 @@ interface PreviewOptions {
21
21
  screenshot?: boolean;
22
22
  }
23
23
  export declare class PreviewCommand {
24
- private config;
25
24
  private logger;
26
25
  private api;
27
26
  private server;