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
@@ -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 registerCodingCommands(program: Command, config: Config, logger: Logger): void;
@@ -0,0 +1,24 @@
1
+ import { EditCommand } from './edit.js';
2
+ import { ExplainCommand } from './explain.js';
3
+ import { GenerateCommand } from './generate.js';
4
+ import { ReviewCommand } from './review.js';
5
+ export function registerCodingCommands(program, config, logger) {
6
+ program.command('edit <file>').alias('e').description('Edit a file with AI assistance')
7
+ .option('-i, --instruction <text>', 'Editing instruction').option('-m, --model <model>', 'Select AI model', 'code')
8
+ .option('--apply', 'Automatically apply changes without confirmation', false)
9
+ .action(async (file, options) => new EditCommand(config, logger).run(file, options));
10
+ program.command('generate <description>').alias('g').description('Generate code from description')
11
+ .option('-l, --language <lang>', 'Target language (auto-detected from description, or specify: javascript, typescript, python, html, etc.)')
12
+ .option('-o, --output <file>', 'Output file path').option('-m, --model <model>', 'Select AI model', 'code')
13
+ .option('-p, --pro', 'Senior Developer Mode: plan, generate, quality check (recommended)', false)
14
+ .action(async (description, options) => new GenerateCommand(config, logger).run(description, options));
15
+ program.command('explain <file>').alias('x').description('Explain code in a file')
16
+ .option('-l, --lines <range>', 'Line range (e.g., 1-50)').option('-d, --detail <level>', 'Detail level (brief, normal, detailed)', 'normal')
17
+ .action(async (file, options) => new ExplainCommand(config, logger).run(file, options));
18
+ program.command('fix <file>').alias('f').description('Fix issues in a file')
19
+ .option('-t, --type <type>', 'Fix type (bugs, style, security, performance)', 'bugs').option('--apply', 'Automatically apply fixes', false)
20
+ .action(async (file, options) => new EditCommand(config, logger).fix(file, options));
21
+ program.command('review <file>').alias('r').description('Review code quality')
22
+ .option('-f, --format <format>', 'Output format (text, json, markdown)', 'text')
23
+ .action(async (file, options) => new ReviewCommand(config, logger).run(file, options));
24
+ }
@@ -6,7 +6,9 @@ import inquirer from 'inquirer';
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { normalizePersonaMode } from '../utils/persona.js';
9
+ import { assertTrustedEndpoint, assertTrustedWebSocketEndpoint } from '../utils/network-policy.js';
9
10
  import { CH } from '../utils/logger.js';
11
+ import { CliCommandError } from '../utils/command-contract.js';
10
12
  export class ConfigCommand {
11
13
  config;
12
14
  logger;
@@ -49,8 +51,9 @@ export class ConfigCommand {
49
51
  if (fs.existsSync(configFile)) {
50
52
  if (nonInteractive) {
51
53
  if (!options.yes) {
52
- this.logger.error('.vigthoria.json already exists. Re-run with --yes to overwrite in non-interactive mode.');
53
- return;
54
+ throw new CliCommandError('.vigthoria.json already exists. Re-run with --yes to overwrite in non-interactive mode.', {
55
+ code: 'PROJECT_ALREADY_INITIALIZED', category: 'usage',
56
+ });
54
57
  }
55
58
  }
56
59
  else {
@@ -79,9 +82,9 @@ export class ConfigCommand {
79
82
  const defaults = profileDefaults[profile];
80
83
  const model = options.model || defaults.defaultModel;
81
84
  if (!allowedModels.has(model)) {
82
- this.logger.error(`Invalid --model: ${model}`);
83
- this.logger.info('Allowed values: architect, code, assistant, code-35b, code-9b, balanced, balanced-4b');
84
- return;
85
+ throw new CliCommandError(`Invalid --model: ${model}. Allowed values: architect, code, assistant, code-35b, code-9b, balanced, balanced-4b.`, {
86
+ code: 'INVALID_MODEL', category: 'usage',
87
+ });
85
88
  }
86
89
  settings = {
87
90
  defaultModel: model,
@@ -149,8 +152,9 @@ export class ConfigCommand {
149
152
  const [key, ...valueParts] = keyValue.split('=');
150
153
  const value = valueParts.join('=');
151
154
  if (!key || value === undefined) {
152
- this.logger.error('Invalid format. Use: vigthoria config --set key=value');
153
- return;
155
+ throw new CliCommandError('Invalid format. Use: vigthoria config --set key=value', {
156
+ code: 'INVALID_CONFIG_ASSIGNMENT', category: 'usage',
157
+ });
154
158
  }
155
159
  const configMap = {
156
160
  'model': (v) => this.config.set('preferences', { ...this.config.get('preferences'), defaultModel: v }),
@@ -158,10 +162,10 @@ export class ConfigCommand {
158
162
  'autoApply': (v) => this.config.set('preferences', { ...this.config.get('preferences'), autoApplyFixes: v === 'true' }),
159
163
  'showDiffs': (v) => this.config.set('preferences', { ...this.config.get('preferences'), showDiffs: v === 'true' }),
160
164
  'maxTokens': (v) => this.config.set('preferences', { ...this.config.get('preferences'), maxTokens: parseInt(v, 10) }),
161
- 'apiUrl': (v) => this.config.set('apiUrl', v),
162
- 'modelsApiUrl': (v) => this.config.set('modelsApiUrl', v),
163
- 'wsUrl': (v) => this.config.set('wsUrl', v),
164
- 'selfHostedModelsApiUrl': (v) => this.config.set('selfHostedModelsApiUrl', v === 'null' || v === 'off' ? null : v),
165
+ 'apiUrl': (v) => this.config.set('apiUrl', assertTrustedEndpoint(v, { audience: 'coder' }).toString().replace(/\/$/, '')),
166
+ 'modelsApiUrl': (v) => this.config.set('modelsApiUrl', assertTrustedEndpoint(v, { audience: 'models' }).toString().replace(/\/$/, '')),
167
+ 'wsUrl': (v) => this.config.set('wsUrl', assertTrustedWebSocketEndpoint(v, 'coder').toString().replace(/\/$/, '')),
168
+ 'selfHostedModelsApiUrl': (v) => this.config.set('selfHostedModelsApiUrl', v === 'null' || v === 'off' ? null : assertTrustedEndpoint(v, { audience: 'models' }).toString().replace(/\/$/, '')),
165
169
  'persona': (v) => {
166
170
  const mode = normalizePersonaMode(v);
167
171
  if (!mode)
@@ -174,8 +178,9 @@ export class ConfigCommand {
174
178
  this.logger.success(`Set ${key} = ${this.formatConfigValueForDisplay(key, value)}`);
175
179
  }
176
180
  else {
177
- this.logger.error(`Unknown config key: ${key}`);
178
- console.log(chalk.gray('Available keys: model, theme, autoApply, showDiffs, maxTokens, persona, apiUrl, modelsApiUrl, wsUrl, selfHostedModelsApiUrl'));
181
+ throw new CliCommandError(`Unknown config key: ${key}. Available keys: model, theme, autoApply, showDiffs, maxTokens, persona, apiUrl, modelsApiUrl, wsUrl, selfHostedModelsApiUrl.`, {
182
+ code: 'UNKNOWN_CONFIG_KEY', category: 'usage',
183
+ });
179
184
  }
180
185
  }
181
186
  formatConfigValueForDisplay(key, value) {
@@ -224,7 +229,7 @@ export class ConfigCommand {
224
229
  console.log(flatConfig[key]);
225
230
  }
226
231
  else {
227
- this.logger.error(`Unknown config key: ${key}`);
232
+ throw new CliCommandError(`Unknown config key: ${key}`, { code: 'UNKNOWN_CONFIG_KEY', category: 'usage' });
228
233
  }
229
234
  }
230
235
  listConfig() {
@@ -262,21 +267,10 @@ export class ConfigCommand {
262
267
  },
263
268
  ]);
264
269
  if (confirm) {
265
- // Save auth before reset
266
- const authToken = this.config.get('authToken');
267
- const refreshToken = this.config.get('refreshToken');
268
- const userId = this.config.get('userId');
269
- const email = this.config.get('email');
270
- const subscription = this.config.get('subscription');
271
- this.config.reset();
272
- // Restore auth
273
- if (authToken) {
274
- this.config.set('authToken', authToken);
275
- this.config.set('refreshToken', refreshToken);
276
- this.config.set('userId', userId);
277
- this.config.set('email', email);
278
- this.config.set('subscription', subscription);
279
- }
270
+ // Reset preferences and endpoints in one durable transaction while
271
+ // preserving the complete credential bundle. Separate reset/set writes
272
+ // can leave a partially restored login after interruption.
273
+ this.config.resetPreservingAuth();
280
274
  this.logger.success('Configuration reset to defaults');
281
275
  }
282
276
  }
@@ -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 registerConversationCommands(program: Command, config: Config, logger: Logger): void;
@@ -0,0 +1,122 @@
1
+ import { ChatCommand } from './chat.js';
2
+ function projectWasProvided(project) {
3
+ return typeof project === 'string' && project.trim().length > 0;
4
+ }
5
+ export function registerConversationCommands(program, config, logger) {
6
+ program
7
+ .command('chat')
8
+ .alias('c')
9
+ .description('Start interactive chat with Vigthoria AI')
10
+ .option('-m, --model <model>', 'Select AI model (agent, code, code-35b, code-9b, balanced, balanced-4b, cloud, ultra)')
11
+ .option('-p, --project <path>', 'Set project context path')
12
+ .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
13
+ .option('-a, --agent', 'Enable agentic mode (default: true for best quality)', true)
14
+ .option('--no-agent', 'Disable agentic mode (simple chat only)')
15
+ .option('-r, --resume', 'Resume last session for this project', false)
16
+ .option('--prompt <text>', 'Run a single prompt directly and exit')
17
+ .option('-w, --workflow <selector>', 'Route prompts through a named or explicit VigFlow workflow target')
18
+ .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
19
+ .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
20
+ .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
21
+ .action(async (options) => {
22
+ await new ChatCommand(config, logger, program).run({
23
+ model: options.model,
24
+ project: options.project,
25
+ projectProvided: projectWasProvided(options.project),
26
+ newProject: options.newProject,
27
+ agent: options.agent,
28
+ workflow: options.workflow,
29
+ json: options.json,
30
+ autoApprove: options.autoApprove,
31
+ resume: options.resume,
32
+ prompt: options.prompt,
33
+ bridge: options.bridge,
34
+ });
35
+ });
36
+ program
37
+ .command('chat-resume')
38
+ .description('Resume the latest chat session for the current or specified project')
39
+ .option('-m, --model <model>', 'Select AI model (agent, code, code-35b, code-9b, balanced, balanced-4b, cloud, ultra)')
40
+ .option('-p, --project <path>', 'Set project context path')
41
+ .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
42
+ .option('-a, --agent', 'Enable agentic mode (default: true for best quality)', true)
43
+ .option('--no-agent', 'Disable agentic mode (simple chat only)')
44
+ .option('--prompt <text>', 'Run a single prompt directly and exit after resuming context')
45
+ .option('-w, --workflow <selector>', 'Route prompts through a named or explicit VigFlow workflow target')
46
+ .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
47
+ .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
48
+ .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
49
+ .action(async (options) => {
50
+ await new ChatCommand(config, logger, program).run({
51
+ model: options.model,
52
+ project: options.project,
53
+ projectProvided: projectWasProvided(options.project),
54
+ newProject: options.newProject,
55
+ agent: options.agent,
56
+ workflow: options.workflow,
57
+ json: options.json,
58
+ autoApprove: options.autoApprove,
59
+ resume: true,
60
+ prompt: options.prompt,
61
+ bridge: options.bridge,
62
+ });
63
+ });
64
+ program
65
+ .command('agent')
66
+ .alias('a')
67
+ .description('Start agentic mode - AI can read/write files, run commands')
68
+ .option('-m, --model <model>', 'Select AI model (agent, code, cloud, ultra)')
69
+ .option('-p, --project <path>', 'Set project context path')
70
+ .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
71
+ .option('--prompt <text>', 'Run a single agent prompt directly and exit')
72
+ .option('-w, --workflow <selector>', 'Run the prompt through a named or explicit VigFlow workflow target')
73
+ .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
74
+ .option('--retry', 'Retry failed tasks from the last agent run in this workspace session', false)
75
+ .option('--continue', 'Continue unfinished tasks from the last agent run in this workspace session', false)
76
+ .option('--auto-approve', 'Auto-approve all actions (dangerous!)', false)
77
+ .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
78
+ .action(async (options) => {
79
+ await new ChatCommand(config, logger, program).run({
80
+ model: options.model,
81
+ project: options.project,
82
+ projectProvided: projectWasProvided(options.project),
83
+ newProject: options.newProject,
84
+ agent: true,
85
+ operator: false,
86
+ workflow: options.workflow,
87
+ json: options.json,
88
+ autoApprove: options.autoApprove,
89
+ prompt: options.prompt,
90
+ bridge: options.bridge,
91
+ retry: options.retry,
92
+ continue: options.continue,
93
+ });
94
+ });
95
+ program
96
+ .command('operator')
97
+ .alias('op')
98
+ .description('Start BMAD operator mode for infrastructure and system workflows')
99
+ .option('-m, --model <model>', 'Select operator model (code, agent, cloud)')
100
+ .option('-p, --project <path>', 'Set project context path')
101
+ .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
102
+ .option('--prompt <text>', 'Run a single operator prompt directly and exit')
103
+ .option('-w, --workflow <selector>', 'Run the prompt through a named or explicit VigFlow workflow target')
104
+ .option('--save-plan', 'Save the completed BMAD plan into VigFlow for rerun and audit', false)
105
+ .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
106
+ .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
107
+ .action(async (options) => {
108
+ await new ChatCommand(config, logger, program).run({
109
+ model: options.model,
110
+ project: options.project,
111
+ projectProvided: projectWasProvided(options.project),
112
+ newProject: options.newProject,
113
+ agent: false,
114
+ operator: true,
115
+ workflow: options.workflow,
116
+ savePlan: options.savePlan,
117
+ json: options.json,
118
+ prompt: options.prompt,
119
+ bridge: options.bridge,
120
+ });
121
+ });
122
+ }
@@ -21,12 +21,18 @@ interface DeployOptions {
21
21
  }
22
22
  export declare class DeployCommand {
23
23
  private config;
24
- private logger;
25
24
  private apiBase;
26
- constructor(config: Config, logger: Logger);
25
+ constructor(config: Config, _logger: Logger);
27
26
  private getAuthHeaders;
28
27
  private isAuthenticated;
29
28
  private requireAuth;
29
+ /**
30
+ * Deployment is a Coder-server operation. A client-local absolute path is
31
+ * neither portable nor a valid remote workspace binding, so resolve the
32
+ * validated local project to an existing server-owned project identifier
33
+ * before starting a deployment mutation.
34
+ */
35
+ private resolveServerProject;
30
36
  /**
31
37
  * Interactive deploy wizard
32
38
  */
@@ -15,33 +15,67 @@ import chalk from 'chalk';
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
17
  import { createSpinner, CH } from '../utils/logger.js';
18
+ import { validateExtractedWorkspace } from '../utils/workspace-boundary.js';
19
+ import { guardedFetch } from '../utils/network-policy.js';
20
+ import { CliCommandError, commandFailure } from '../utils/command-contract.js';
18
21
  import inquirer from 'inquirer';
19
22
  export class DeployCommand {
20
23
  config;
21
- logger;
22
24
  apiBase;
23
- constructor(config, logger) {
25
+ constructor(config, _logger) {
24
26
  this.config = config;
25
- this.logger = logger;
26
27
  this.apiBase = this.config.get('apiUrl') || 'https://coder.vigthoria.io';
27
28
  }
28
29
  getAuthHeaders() {
29
- const token = this.config.get('authToken');
30
- return {
31
- 'Authorization': `Bearer ${token}`,
32
- 'Content-Type': 'application/json'
33
- };
30
+ const token = process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken');
31
+ return token
32
+ ? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
33
+ : { 'Content-Type': 'application/json' };
34
34
  }
35
35
  isAuthenticated() {
36
- return !!this.config.get('authToken');
36
+ return !!(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken'));
37
37
  }
38
38
  requireAuth() {
39
39
  if (!this.isAuthenticated()) {
40
- console.log(chalk.red('\nāŒ Authentication required'));
41
- console.log(chalk.gray(' Run `vig login` to authenticate first.\n'));
42
- process.exit(1);
40
+ throw new CliCommandError('Authentication required. Run `vigthoria login` first.', {
41
+ code: 'AUTH_REQUIRED', category: 'authentication',
42
+ });
43
43
  }
44
44
  }
45
+ /**
46
+ * Deployment is a Coder-server operation. A client-local absolute path is
47
+ * neither portable nor a valid remote workspace binding, so resolve the
48
+ * validated local project to an existing server-owned project identifier
49
+ * before starting a deployment mutation.
50
+ */
51
+ async resolveServerProject(projectPath) {
52
+ const projectDir = fs.realpathSync(projectPath || process.cwd());
53
+ validateExtractedWorkspace(projectDir, { ignoreNames: ['node_modules', '.git', 'dist', 'build'] });
54
+ const projectInfo = this.detectProjectInfo(projectDir);
55
+ const response = await guardedFetch(`${this.apiBase}/api/projects`, {
56
+ headers: this.getAuthHeaders(),
57
+ }, { audience: 'coder', maxRedirects: 0 });
58
+ if (!response.ok) {
59
+ throw new Error('Unable to resolve the project in Coder before deployment');
60
+ }
61
+ const data = await response.json();
62
+ const localNames = new Set([
63
+ projectInfo.name.trim().toLowerCase(),
64
+ path.basename(projectDir).trim().toLowerCase(),
65
+ ].filter(Boolean));
66
+ const match = (data.projects || []).find((project) => {
67
+ const remoteName = String(project.project_name || project.name || '').trim().toLowerCase();
68
+ return localNames.has(remoteName);
69
+ });
70
+ const projectId = match?.id ?? match?.project_id;
71
+ if (projectId === undefined || projectId === null || String(projectId).trim() === '') {
72
+ throw new Error(`Project "${projectInfo.name}" is not synchronized with Coder. Upload or create it in Coder before deploying.`);
73
+ }
74
+ return {
75
+ projectId: typeof projectId === 'number' ? projectId : String(projectId),
76
+ projectName: String(match?.project_name || match?.name || projectInfo.name),
77
+ };
78
+ }
45
79
  /**
46
80
  * Interactive deploy wizard
47
81
  */
@@ -91,16 +125,15 @@ export class DeployCommand {
91
125
  this.requireAuth();
92
126
  const spinner = createSpinner('Deploying to preview...').start();
93
127
  try {
94
- const projectDir = projectPath || process.cwd();
95
- const projectInfo = this.detectProjectInfo(projectDir);
96
- const response = await fetch(`${this.apiBase}/api/hosting/deploy/preview`, {
128
+ const project = await this.resolveServerProject(projectPath);
129
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/preview`, {
97
130
  method: 'POST',
98
131
  headers: this.getAuthHeaders(),
99
132
  body: JSON.stringify({
100
- projectName: projectInfo.name,
101
- projectPath: projectDir
133
+ projectId: project.projectId,
134
+ projectName: project.projectName,
102
135
  })
103
- });
136
+ }, { audience: 'coder', maxRedirects: 0 });
104
137
  if (!response.ok) {
105
138
  const error = await response.json();
106
139
  throw new Error(error.error || 'Failed to deploy');
@@ -114,49 +147,40 @@ export class DeployCommand {
114
147
  }
115
148
  catch (error) {
116
149
  spinner.stop();
117
- this.logger.error('Deploy failed');
118
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
119
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
120
- process.exitCode = 1;
150
+ throw commandFailure(error, { code: 'DEPLOY_PREVIEW_FAILED' });
121
151
  }
122
152
  }
123
153
  /**
124
154
  * Deploy to Vigthoria subdomain
125
155
  */
126
156
  async deployToSubdomain(subdomain, projectPath) {
157
+ this.requireAuth();
127
158
  const spinner = createSpinner(`Deploying to ${subdomain}.vigthoria.io...`).start();
128
159
  try {
129
160
  // Validate subdomain format
130
161
  if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(subdomain) || subdomain.length < 3) {
131
- throw new Error('Subdomain must be 3+ chars, lowercase alphanumeric with hyphens');
162
+ throw new CliCommandError('Subdomain must be 3+ chars, lowercase alphanumeric with hyphens', {
163
+ code: 'SUBDOMAIN_INVALID', category: 'usage',
164
+ });
132
165
  }
133
- const projectDir = projectPath || process.cwd();
134
- const projectInfo = this.detectProjectInfo(projectDir);
135
- const response = await fetch(`${this.apiBase}/api/hosting/deploy/subdomain`, {
166
+ const project = await this.resolveServerProject(projectPath);
167
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/subdomain`, {
136
168
  method: 'POST',
137
169
  headers: this.getAuthHeaders(),
138
170
  body: JSON.stringify({
139
171
  subdomain,
140
- projectName: projectInfo.name,
141
- projectPath: projectDir
172
+ projectId: project.projectId,
173
+ projectName: project.projectName,
142
174
  })
143
- });
175
+ }, { audience: 'coder', maxRedirects: 0 });
144
176
  const data = await response.json();
145
177
  if (!response.ok || !data.success) {
146
178
  if (data.requiresSubscription) {
147
179
  spinner.stop();
148
- console.log(chalk.yellow(`\n${CH.warnEmoji} Subdomain hosting requires a subscription (€4.99/mo)`));
149
- const { proceed } = await inquirer.prompt([{
150
- type: 'confirm',
151
- name: 'proceed',
152
- message: 'Would you like to subscribe now?',
153
- default: true
154
- }]);
155
- if (proceed && data.checkoutUrl) {
156
- console.log(chalk.cyan(`\nšŸ”— Opening checkout: ${data.checkoutUrl}`));
157
- console.log(chalk.gray('Please open this URL in your browser to subscribe.\n'));
158
- }
159
- return;
180
+ throw new CliCommandError('Subdomain hosting requires a subscription (€4.99/mo)', {
181
+ code: 'DEPLOY_SUBSCRIPTION_REQUIRED', category: 'authorization',
182
+ details: data.checkoutUrl ? { checkoutUrl: data.checkoutUrl } : undefined,
183
+ });
160
184
  }
161
185
  throw new Error(data.error || 'Failed to deploy');
162
186
  }
@@ -169,44 +193,34 @@ export class DeployCommand {
169
193
  }
170
194
  catch (error) {
171
195
  spinner.stop();
172
- this.logger.error('Deploy failed');
173
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
174
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
196
+ throw commandFailure(error, { code: 'DEPLOY_SUBDOMAIN_FAILED' });
175
197
  }
176
198
  }
177
199
  /**
178
200
  * Deploy to custom domain
179
201
  */
180
202
  async deployToCustomDomain(domain, projectPath) {
203
+ this.requireAuth();
181
204
  const spinner = createSpinner(`Setting up ${domain}...`).start();
182
205
  try {
183
- const projectDir = projectPath || process.cwd();
184
- const projectInfo = this.detectProjectInfo(projectDir);
185
- const response = await fetch(`${this.apiBase}/api/hosting/deploy/custom`, {
206
+ const project = await this.resolveServerProject(projectPath);
207
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/custom`, {
186
208
  method: 'POST',
187
209
  headers: this.getAuthHeaders(),
188
210
  body: JSON.stringify({
189
211
  domain,
190
- projectName: projectInfo.name,
191
- projectPath: projectDir
212
+ projectId: project.projectId,
213
+ projectName: project.projectName,
192
214
  })
193
- });
215
+ }, { audience: 'coder', maxRedirects: 0 });
194
216
  const data = await response.json();
195
217
  if (!response.ok || !data.success) {
196
218
  if (data.requiresSubscription) {
197
219
  spinner.stop();
198
- console.log(chalk.yellow(`\n${CH.warnEmoji} Custom domain hosting requires a subscription (€9.99/mo)`));
199
- const { proceed } = await inquirer.prompt([{
200
- type: 'confirm',
201
- name: 'proceed',
202
- message: 'Would you like to subscribe now?',
203
- default: true
204
- }]);
205
- if (proceed && data.checkoutUrl) {
206
- console.log(chalk.cyan(`\nšŸ”— Checkout URL: ${data.checkoutUrl}`));
207
- console.log(chalk.gray('Please open this URL in your browser to subscribe.\n'));
208
- }
209
- return;
220
+ throw new CliCommandError('Custom domain hosting requires a subscription (€9.99/mo)', {
221
+ code: 'DEPLOY_SUBSCRIPTION_REQUIRED', category: 'authorization',
222
+ details: data.checkoutUrl ? { checkoutUrl: data.checkoutUrl } : undefined,
223
+ });
210
224
  }
211
225
  throw new Error(data.error || 'Failed to deploy');
212
226
  }
@@ -224,9 +238,7 @@ export class DeployCommand {
224
238
  }
225
239
  catch (error) {
226
240
  spinner.stop();
227
- this.logger.error('Deploy failed');
228
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
229
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
241
+ throw commandFailure(error, { code: 'DEPLOY_CUSTOM_DOMAIN_FAILED' });
230
242
  }
231
243
  }
232
244
  /**
@@ -271,9 +283,9 @@ export class DeployCommand {
271
283
  async showPlans() {
272
284
  const spinner = createSpinner('Fetching hosting plans...').start();
273
285
  try {
274
- const response = await fetch(`${this.apiBase}/api/hosting/plans`, {
286
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/plans`, {
275
287
  headers: this.getAuthHeaders()
276
- });
288
+ }, { audience: 'coder', maxRedirects: 0 });
277
289
  if (!response.ok) {
278
290
  throw new Error('Failed to fetch plans');
279
291
  }
@@ -301,9 +313,7 @@ export class DeployCommand {
301
313
  }
302
314
  catch (error) {
303
315
  spinner.stop();
304
- this.logger.error('Failed to fetch plans');
305
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
306
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
316
+ throw commandFailure(error, { code: 'DEPLOY_PLANS_FAILED' });
307
317
  }
308
318
  }
309
319
  /**
@@ -313,9 +323,9 @@ export class DeployCommand {
313
323
  this.requireAuth();
314
324
  const spinner = createSpinner('Fetching deployments...').start();
315
325
  try {
316
- const response = await fetch(`${this.apiBase}/api/hosting/domains`, {
326
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/domains`, {
317
327
  headers: this.getAuthHeaders()
318
- });
328
+ }, { audience: 'coder', maxRedirects: 0 });
319
329
  if (!response.ok) {
320
330
  throw new Error('Failed to fetch deployments');
321
331
  }
@@ -342,9 +352,7 @@ export class DeployCommand {
342
352
  }
343
353
  catch (error) {
344
354
  spinner.stop();
345
- this.logger.error('List failed');
346
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
347
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
355
+ throw commandFailure(error, { code: 'DEPLOY_LIST_FAILED' });
348
356
  }
349
357
  }
350
358
  /**
@@ -357,9 +365,9 @@ export class DeployCommand {
357
365
  const endpoint = domain
358
366
  ? `${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}/status`
359
367
  : `${this.apiBase}/api/hosting/status`;
360
- const response = await fetch(endpoint, {
368
+ const response = await guardedFetch(endpoint, {
361
369
  headers: this.getAuthHeaders()
362
- });
370
+ }, { audience: 'coder', maxRedirects: 0 });
363
371
  if (!response.ok) {
364
372
  throw new Error('Failed to fetch status');
365
373
  }
@@ -370,9 +378,7 @@ export class DeployCommand {
370
378
  }
371
379
  catch (error) {
372
380
  spinner.stop();
373
- this.logger.error('Status check failed');
374
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
375
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
381
+ throw commandFailure(error, { code: 'DEPLOY_STATUS_FAILED' });
376
382
  }
377
383
  }
378
384
  /**
@@ -382,11 +388,11 @@ export class DeployCommand {
382
388
  this.requireAuth();
383
389
  const spinner = createSpinner(`Verifying DNS for ${domain}...`).start();
384
390
  try {
385
- const response = await fetch(`${this.apiBase}/api/hosting/domain/verify`, {
391
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/domain/verify`, {
386
392
  method: 'POST',
387
393
  headers: this.getAuthHeaders(),
388
394
  body: JSON.stringify({ domain })
389
- });
395
+ }, { audience: 'coder', maxRedirects: 0 });
390
396
  const data = await response.json();
391
397
  if (!response.ok || !data.success) {
392
398
  throw new Error(data.error || 'Verification failed');
@@ -404,9 +410,7 @@ export class DeployCommand {
404
410
  }
405
411
  catch (error) {
406
412
  spinner.stop();
407
- this.logger.error('Verification failed');
408
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
409
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
413
+ throw commandFailure(error, { code: 'DEPLOY_VERIFY_FAILED' });
410
414
  }
411
415
  }
412
416
  /**
@@ -426,10 +430,10 @@ export class DeployCommand {
426
430
  }
427
431
  const spinner = createSpinner(`Removing ${domain}...`).start();
428
432
  try {
429
- const response = await fetch(`${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}`, {
433
+ const response = await guardedFetch(`${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}`, {
430
434
  method: 'DELETE',
431
435
  headers: this.getAuthHeaders()
432
- });
436
+ }, { audience: 'coder', maxRedirects: 0 });
433
437
  if (!response.ok) {
434
438
  const error = await response.json();
435
439
  throw new Error(error.error || 'Failed to remove');
@@ -439,9 +443,7 @@ export class DeployCommand {
439
443
  }
440
444
  catch (error) {
441
445
  spinner.stop();
442
- this.logger.error('Remove failed');
443
- const errMsg = error instanceof Error ? error.message : 'Unknown error';
444
- console.log(chalk.red(`\nāŒ Error: ${errMsg}\n`));
446
+ throw commandFailure(error, { code: 'DEPLOY_REMOVE_FAILED' });
445
447
  }
446
448
  }
447
449
  /**
@@ -9,8 +9,7 @@ type DeviceOptions = {
9
9
  port?: string;
10
10
  };
11
11
  export declare class DeviceCommand {
12
- private logger;
13
- constructor(_config: Config, logger: Logger);
12
+ constructor(_config: Config, _logger: Logger);
14
13
  status(options?: DeviceOptions): Promise<void>;
15
14
  list(options?: DeviceOptions): Promise<void>;
16
15
  screenshot(options?: DeviceOptions): Promise<void>;