vigthoria-cli 1.6.17 → 1.6.19

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.
@@ -8,8 +8,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.AuthCommand = void 0;
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
- const ora_1 = __importDefault(require("ora"));
12
11
  const inquirer_1 = __importDefault(require("inquirer"));
12
+ const logger_js_1 = require("../utils/logger.js");
13
13
  const api_js_1 = require("../utils/api.js");
14
14
  class AuthCommand {
15
15
  config;
@@ -73,7 +73,7 @@ class AuthCommand {
73
73
  validate: (input) => input.length >= 6 || 'Password must be at least 6 characters',
74
74
  },
75
75
  ]);
76
- const spinner = (0, ora_1.default)('Logging in...').start();
76
+ const spinner = (0, logger_js_1.createSpinner)('Logging in...').start();
77
77
  const success = await this.api.login(credentials.email, credentials.password);
78
78
  spinner.stop();
79
79
  if (success) {
@@ -96,7 +96,7 @@ class AuthCommand {
96
96
  await this.loginWithToken(token);
97
97
  }
98
98
  async loginWithToken(token) {
99
- const spinner = (0, ora_1.default)('Validating token...').start();
99
+ const spinner = (0, logger_js_1.createSpinner)('Validating token...').start();
100
100
  const success = await this.api.loginWithToken(token);
101
101
  spinner.stop();
102
102
  if (success) {
@@ -173,7 +173,7 @@ class AuthCommand {
173
173
  });
174
174
  console.log();
175
175
  // API status
176
- const spinner = (0, ora_1.default)('Checking API status...').start();
176
+ const spinner = (0, logger_js_1.createSpinner)('Checking API status...').start();
177
177
  const apiStatus = await this.api.getHealthStatus();
178
178
  spinner.stop();
179
179
  console.log(chalk_1.default.white('API Status:'));
@@ -198,7 +198,7 @@ class AuthCommand {
198
198
  else {
199
199
  console.log(chalk_1.default.gray(' Self-hosted Models: ') + chalk_1.default.gray('disabled'));
200
200
  }
201
- const capabilitySpinner = (0, ora_1.default)('Checking live capability truth...').start();
201
+ const capabilitySpinner = (0, logger_js_1.createSpinner)('Checking live capability truth...').start();
202
202
  const capabilityStatus = await this.api.getCapabilityTruthStatus({
203
203
  workspacePath: process.cwd(),
204
204
  projectPath: process.cwd(),
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BridgeCommand = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const ora_1 = __importDefault(require("ora"));
8
+ const logger_js_1 = require("../utils/logger.js");
9
9
  const api_js_1 = require("../utils/api.js");
10
10
  class BridgeCommand {
11
11
  api;
@@ -13,7 +13,7 @@ class BridgeCommand {
13
13
  this.api = new api_js_1.APIClient(config, logger);
14
14
  }
15
15
  async status() {
16
- const spinner = (0, ora_1.default)('Checking DevTools Bridge...').start();
16
+ const spinner = (0, logger_js_1.createSpinner)('Checking DevTools Bridge...').start();
17
17
  const bridge = await this.api.getDevtoolsBridgeStatus();
18
18
  spinner.stop();
19
19
  console.log();
@@ -38,11 +38,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.ChatCommand = void 0;
40
40
  const chalk_1 = __importDefault(require("chalk"));
41
- const ora_1 = __importDefault(require("ora"));
42
41
  const fs = __importStar(require("fs"));
43
42
  const os = __importStar(require("os"));
44
43
  const path = __importStar(require("path"));
45
44
  const readline = __importStar(require("readline"));
45
+ const logger_js_1 = require("../utils/logger.js");
46
46
  const api_js_1 = require("../utils/api.js");
47
47
  const tools_js_1 = require("../utils/tools.js");
48
48
  const session_js_1 = require("../utils/session.js");
@@ -118,7 +118,13 @@ class ChatCommand {
118
118
  return this.getDefaultChatModel();
119
119
  }
120
120
  isLegacyAgentFallbackAllowed() {
121
- return process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '1';
121
+ // CLI always has local file access, so fallback to local agent loop
122
+ // is safe and should be the default when V3 agent is unreachable or
123
+ // rejects the request (e.g. context too large).
124
+ if (process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '0') {
125
+ return false;
126
+ }
127
+ return true;
122
128
  }
123
129
  resolveAgentExecutionPolicy(prompt) {
124
130
  const explicitModel = this.modelExplicitlySelected;
@@ -543,7 +549,7 @@ class ChatCommand {
543
549
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
544
550
  const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
545
551
  const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
546
- const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
552
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
547
553
  try {
548
554
  const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
549
555
  data: {
@@ -612,7 +618,7 @@ class ChatCommand {
612
618
  return;
613
619
  }
614
620
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
615
- const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
621
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
616
622
  const executionPrompt = this.buildExecutionPrompt(prompt);
617
623
  const workflowType = this.isDiagnosticPrompt(prompt) ? 'analysis_only' : 'full_autonomy';
618
624
  try {
@@ -657,13 +663,34 @@ class ChatCommand {
657
663
  if (spinner) {
658
664
  spinner.fail('Operator workflow failed');
659
665
  }
660
- this.logger.error(error.message);
666
+ const errorMsg = error.message || 'Operator workflow failed with an unknown error.';
667
+ if (this.jsonOutput) {
668
+ process.exitCode = 1;
669
+ console.log(JSON.stringify({
670
+ success: false,
671
+ mode: 'operator',
672
+ content: '',
673
+ error: errorMsg,
674
+ }, null, 2));
675
+ }
676
+ else {
677
+ this.logger.error(errorMsg);
678
+ }
661
679
  }
662
680
  }
663
681
  async runSimplePrompt(prompt) {
664
682
  this.lastActionableUserInput = prompt;
683
+ // In non-agent chat mode the model has no tool access. Inject a
684
+ // grounding constraint so it doesn't fabricate file contents.
685
+ const needsGrounding = /(repo|file|code|project|workspace|source|inspect|analyze|audit|review)/i.test(prompt);
686
+ if (needsGrounding && !this.messages.some(m => m.role === 'system' && m.content.includes('no direct file access'))) {
687
+ this.messages.push({
688
+ role: 'system',
689
+ content: 'You are in simple chat mode with no direct file access. Do not fabricate file contents, search results, or analysis steps. If the user asks about specific files, advise them to use agent mode (vig chat --agent) for grounded repo analysis.',
690
+ });
691
+ }
665
692
  this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
666
- const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: 'Thinking...', spinner: 'clock' }).start();
693
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
667
694
  try {
668
695
  const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
669
696
  if (spinner)
@@ -722,7 +749,7 @@ class ChatCommand {
722
749
  this.saveSession();
723
750
  const maxTurns = 10;
724
751
  for (let turn = 0; turn < maxTurns; turn += 1) {
725
- const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
752
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
726
753
  try {
727
754
  const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
728
755
  if (spinner)
@@ -935,7 +962,7 @@ class ChatCommand {
935
962
  this.v3IterationCount = 0;
936
963
  this.v3ToolCallCount = 0;
937
964
  this.v3LastActivity = Date.now();
938
- const spinner = this.jsonOutput ? null : (0, ora_1.default)({
965
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
939
966
  text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
940
967
  spinner: 'clock',
941
968
  }).start();
@@ -53,7 +53,7 @@ exports.DeployCommand = void 0;
53
53
  const chalk_1 = __importDefault(require("chalk"));
54
54
  const fs = __importStar(require("fs"));
55
55
  const path = __importStar(require("path"));
56
- const ora_1 = __importDefault(require("ora"));
56
+ const logger_js_1 = require("../utils/logger.js");
57
57
  const inquirer_1 = __importDefault(require("inquirer"));
58
58
  class DeployCommand {
59
59
  config;
@@ -127,7 +127,7 @@ class DeployCommand {
127
127
  * Deploy to preview URL (free)
128
128
  */
129
129
  async deployToPreview(projectPath) {
130
- const spinner = (0, ora_1.default)('Deploying to preview...').start();
130
+ const spinner = (0, logger_js_1.createSpinner)('Deploying to preview...').start();
131
131
  try {
132
132
  const projectDir = projectPath || process.cwd();
133
133
  const projectInfo = this.detectProjectInfo(projectDir);
@@ -160,7 +160,7 @@ class DeployCommand {
160
160
  * Deploy to Vigthoria subdomain
161
161
  */
162
162
  async deployToSubdomain(subdomain, projectPath) {
163
- const spinner = (0, ora_1.default)(`Deploying to ${subdomain}.vigthoria.io...`).start();
163
+ const spinner = (0, logger_js_1.createSpinner)(`Deploying to ${subdomain}.vigthoria.io...`).start();
164
164
  try {
165
165
  // Validate subdomain format
166
166
  if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(subdomain) || subdomain.length < 3) {
@@ -213,7 +213,7 @@ class DeployCommand {
213
213
  * Deploy to custom domain
214
214
  */
215
215
  async deployToCustomDomain(domain, projectPath) {
216
- const spinner = (0, ora_1.default)(`Setting up ${domain}...`).start();
216
+ const spinner = (0, logger_js_1.createSpinner)(`Setting up ${domain}...`).start();
217
217
  try {
218
218
  const projectDir = projectPath || process.cwd();
219
219
  const projectInfo = this.detectProjectInfo(projectDir);
@@ -303,7 +303,7 @@ class DeployCommand {
303
303
  * Show hosting plans
304
304
  */
305
305
  async showPlans() {
306
- const spinner = (0, ora_1.default)('Fetching hosting plans...').start();
306
+ const spinner = (0, logger_js_1.createSpinner)('Fetching hosting plans...').start();
307
307
  try {
308
308
  const response = await fetch(`${this.apiBase}/api/hosting/plans`, {
309
309
  headers: this.getAuthHeaders()
@@ -344,7 +344,7 @@ class DeployCommand {
344
344
  */
345
345
  async list() {
346
346
  this.requireAuth();
347
- const spinner = (0, ora_1.default)('Fetching deployments...').start();
347
+ const spinner = (0, logger_js_1.createSpinner)('Fetching deployments...').start();
348
348
  try {
349
349
  const response = await fetch(`${this.apiBase}/api/hosting/domains`, {
350
350
  headers: this.getAuthHeaders()
@@ -384,7 +384,7 @@ class DeployCommand {
384
384
  */
385
385
  async status(domain) {
386
386
  this.requireAuth();
387
- const spinner = (0, ora_1.default)('Checking status...').start();
387
+ const spinner = (0, logger_js_1.createSpinner)('Checking status...').start();
388
388
  try {
389
389
  const endpoint = domain
390
390
  ? `${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}/status`
@@ -411,7 +411,7 @@ class DeployCommand {
411
411
  */
412
412
  async verify(domain) {
413
413
  this.requireAuth();
414
- const spinner = (0, ora_1.default)(`Verifying DNS for ${domain}...`).start();
414
+ const spinner = (0, logger_js_1.createSpinner)(`Verifying DNS for ${domain}...`).start();
415
415
  try {
416
416
  const response = await fetch(`${this.apiBase}/api/hosting/domain/verify`, {
417
417
  method: 'POST',
@@ -454,7 +454,7 @@ class DeployCommand {
454
454
  console.log(chalk_1.default.yellow('\n⚠️ Removal cancelled.\n'));
455
455
  return;
456
456
  }
457
- const spinner = (0, ora_1.default)(`Removing ${domain}...`).start();
457
+ const spinner = (0, logger_js_1.createSpinner)(`Removing ${domain}...`).start();
458
458
  try {
459
459
  const response = await fetch(`${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}`, {
460
460
  method: 'DELETE',
@@ -6,6 +6,7 @@ import { Logger } from '../utils/logger.js';
6
6
  interface EditOptions {
7
7
  instruction?: string;
8
8
  model: string;
9
+ apply?: boolean;
9
10
  }
10
11
  interface FixOptions {
11
12
  type: string;
@@ -8,8 +8,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.EditCommand = void 0;
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
- const ora_1 = __importDefault(require("ora"));
12
11
  const inquirer_1 = __importDefault(require("inquirer"));
12
+ const logger_js_1 = require("../utils/logger.js");
13
13
  const api_js_1 = require("../utils/api.js");
14
14
  const files_js_1 = require("../utils/files.js");
15
15
  class EditCommand {
@@ -52,7 +52,7 @@ class EditCommand {
52
52
  instruction = answer.instruction;
53
53
  }
54
54
  // Generate edit
55
- const spinner = (0, ora_1.default)({
55
+ const spinner = (0, logger_js_1.createSpinner)({
56
56
  text: 'Generating changes...',
57
57
  spinner: 'dots',
58
58
  }).start();
@@ -86,8 +86,13 @@ Return the complete modified code:`,
86
86
  this.logger.error('Failed to generate valid code changes');
87
87
  return;
88
88
  }
89
- // Show diff
90
- await this.showDiffAndConfirm(file.path, file.content, modifiedCode);
89
+ // Show diff and apply
90
+ if (options.apply) {
91
+ await this.applyFix(file.path, file.content, modifiedCode);
92
+ }
93
+ else {
94
+ await this.showDiffAndConfirm(file.path, file.content, modifiedCode);
95
+ }
91
96
  }
92
97
  catch (error) {
93
98
  spinner.stop();
@@ -109,7 +114,7 @@ Return the complete modified code:`,
109
114
  this.logger.section(`Fixing: ${file.relativePath}`);
110
115
  console.log(chalk_1.default.gray(`Fix type: ${options.type} | Language: ${file.language}`));
111
116
  console.log();
112
- const spinner = (0, ora_1.default)({
117
+ const spinner = (0, logger_js_1.createSpinner)({
113
118
  text: `Analyzing for ${options.type} issues...`,
114
119
  spinner: 'dots',
115
120
  }).start();
@@ -8,9 +8,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.ExplainCommand = void 0;
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
- const ora_1 = __importDefault(require("ora"));
12
11
  const marked_1 = require("marked");
13
12
  const marked_terminal_1 = require("marked-terminal");
13
+ const logger_js_1 = require("../utils/logger.js");
14
14
  const api_js_1 = require("../utils/api.js");
15
15
  const files_js_1 = require("../utils/files.js");
16
16
  class ExplainCommand {
@@ -61,7 +61,7 @@ class ExplainCommand {
61
61
  });
62
62
  console.log(chalk_1.default.gray('─'.repeat(60)));
63
63
  console.log();
64
- const spinner = (0, ora_1.default)({
64
+ const spinner = (0, logger_js_1.createSpinner)({
65
65
  text: 'Analyzing code...',
66
66
  spinner: 'dots',
67
67
  }).start();
@@ -10,8 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.GenerateCommand = void 0;
12
12
  const chalk_1 = __importDefault(require("chalk"));
13
- const ora_1 = __importDefault(require("ora"));
14
13
  const inquirer_1 = __importDefault(require("inquirer"));
14
+ const logger_js_1 = require("../utils/logger.js");
15
15
  const api_js_1 = require("../utils/api.js");
16
16
  const files_js_1 = require("../utils/files.js");
17
17
  class GenerateCommand {
@@ -40,7 +40,7 @@ class GenerateCommand {
40
40
  console.log(chalk_1.default.cyan('Pro Mode: Planning → Generating → Quality Check'));
41
41
  }
42
42
  console.log();
43
- const spinner = (0, ora_1.default)({
43
+ const spinner = (0, logger_js_1.createSpinner)({
44
44
  text: proMode ? 'Phase 1: Planning project structure...' : 'Generating code...',
45
45
  spinner: 'dots',
46
46
  }).start();
@@ -54,7 +54,7 @@ exports.RepoCommand = void 0;
54
54
  const chalk_1 = __importDefault(require("chalk"));
55
55
  const fs = __importStar(require("fs"));
56
56
  const path = __importStar(require("path"));
57
- const ora_1 = __importDefault(require("ora"));
57
+ const logger_js_1 = require("../utils/logger.js");
58
58
  const inquirer_1 = __importDefault(require("inquirer"));
59
59
  const archiver_1 = __importDefault(require("archiver"));
60
60
  const fs_1 = require("fs");
@@ -313,7 +313,7 @@ class RepoCommand {
313
313
  console.log(chalk_1.default.red(`\n❌ Path does not exist: ${projectPath}\n`));
314
314
  return;
315
315
  }
316
- const spinner = (0, ora_1.default)('Analyzing project...').start();
316
+ const spinner = (0, logger_js_1.createSpinner)('Analyzing project...').start();
317
317
  try {
318
318
  const projectInfo = this.detectProjectInfo(projectPath);
319
319
  spinner.succeed(`Project detected: ${chalk_1.default.cyan(projectInfo.name)}`);
@@ -363,7 +363,7 @@ class RepoCommand {
363
363
  console.log(chalk_1.default.yellow('\n⚠️ Push cancelled.\n'));
364
364
  return;
365
365
  }
366
- const uploadSpinner = (0, ora_1.default)('Preparing project files...').start();
366
+ const uploadSpinner = (0, logger_js_1.createSpinner)('Preparing project files...').start();
367
367
  const files = this.collectProjectFiles(projectPath);
368
368
  if (files.length === 0) {
369
369
  throw new Error('No readable text files found to push');
@@ -409,7 +409,7 @@ class RepoCommand {
409
409
  */
410
410
  async pull(projectName, options = {}) {
411
411
  this.requireAuth();
412
- const spinner = (0, ora_1.default)(`Fetching project: ${projectName}...`).start();
412
+ const spinner = (0, logger_js_1.createSpinner)(`Fetching project: ${projectName}...`).start();
413
413
  try {
414
414
  const repo = await this.resolveRepoByName(projectName);
415
415
  const response = await this.repoFetch('/api/repo/pull', {
@@ -437,7 +437,7 @@ class RepoCommand {
437
437
  return;
438
438
  }
439
439
  }
440
- const downloadSpinner = (0, ora_1.default)('Downloading project files...').start();
440
+ const downloadSpinner = (0, logger_js_1.createSpinner)('Downloading project files...').start();
441
441
  // Create output directory
442
442
  fs.mkdirSync(outputPath, { recursive: true });
443
443
  // If we have a download URL, fetch and extract
@@ -496,7 +496,7 @@ class RepoCommand {
496
496
  */
497
497
  async list(options = {}) {
498
498
  this.requireAuth();
499
- const spinner = (0, ora_1.default)('Fetching your projects...').start();
499
+ const spinner = (0, logger_js_1.createSpinner)('Fetching your projects...').start();
500
500
  try {
501
501
  const repos = await this.getMyRepos();
502
502
  const filteredRepos = options.visibility
@@ -596,7 +596,7 @@ class RepoCommand {
596
596
  this.requireAuth();
597
597
  const projectPath = process.cwd();
598
598
  const projectInfo = this.detectProjectInfo(projectPath);
599
- const spinner = (0, ora_1.default)('Checking sync status...').start();
599
+ const spinner = (0, logger_js_1.createSpinner)('Checking sync status...').start();
600
600
  try {
601
601
  const response = await fetch(`${this.apiBase}/api/repo/status/${encodeURIComponent(projectInfo.name)}`, {
602
602
  method: 'GET',
@@ -644,7 +644,7 @@ class RepoCommand {
644
644
  */
645
645
  async share(projectName, options = {}) {
646
646
  this.requireAuth();
647
- const spinner = (0, ora_1.default)('Generating share link...').start();
647
+ const spinner = (0, logger_js_1.createSpinner)('Generating share link...').start();
648
648
  try {
649
649
  const response = await fetch(`${this.apiBase}/api/repo/share`, {
650
650
  method: 'POST',
@@ -686,7 +686,7 @@ class RepoCommand {
686
686
  console.log(chalk_1.default.yellow('\n⚠️ Delete cancelled.\n'));
687
687
  return;
688
688
  }
689
- const spinner = (0, ora_1.default)('Deleting project...').start();
689
+ const spinner = (0, logger_js_1.createSpinner)('Deleting project...').start();
690
690
  try {
691
691
  const response = await fetch(`${this.apiBase}/api/repo/projects/${encodeURIComponent(projectName)}`, {
692
692
  method: 'DELETE',
@@ -712,7 +712,7 @@ class RepoCommand {
712
712
  // Parse project URL or name
713
713
  const projectIdMatch = projectUrl.match(/preview\/(\d+)/);
714
714
  const projectId = projectIdMatch ? projectIdMatch[1] : projectUrl;
715
- const spinner = (0, ora_1.default)(`Cloning project...`).start();
715
+ const spinner = (0, logger_js_1.createSpinner)(`Cloning project...`).start();
716
716
  try {
717
717
  const response = await fetch(`${this.apiBase}/api/repo/clone/${projectId}`, {
718
718
  method: 'GET',
@@ -8,9 +8,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.ReviewCommand = void 0;
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
- const ora_1 = __importDefault(require("ora"));
12
11
  const marked_1 = require("marked");
13
12
  const marked_terminal_1 = require("marked-terminal");
13
+ const logger_js_1 = require("../utils/logger.js");
14
14
  const api_js_1 = require("../utils/api.js");
15
15
  const files_js_1 = require("../utils/files.js");
16
16
  class ReviewCommand {
@@ -42,7 +42,7 @@ class ReviewCommand {
42
42
  this.logger.section(`Reviewing: ${file.relativePath}`);
43
43
  console.log(chalk_1.default.gray(`Language: ${file.language} | Lines: ${file.lines}`));
44
44
  console.log();
45
- const spinner = (0, ora_1.default)({
45
+ const spinner = (0, logger_js_1.createSpinner)({
46
46
  text: 'Analyzing code quality...',
47
47
  spinner: 'dots',
48
48
  }).start();
@@ -48,10 +48,23 @@ class WorkflowCommand {
48
48
  }
49
49
  async templates(options) {
50
50
  this.ensureAuthenticated(Boolean(options.json));
51
- const templates = await this.api.listVigFlowTemplates({
52
- category: options.category,
53
- search: options.search,
54
- });
51
+ let templates;
52
+ try {
53
+ templates = await this.api.listVigFlowTemplates({
54
+ category: options.category,
55
+ search: options.search,
56
+ });
57
+ }
58
+ catch (error) {
59
+ const msg = `Workflow service is not reachable: ${error.message}`;
60
+ if (options.json) {
61
+ this.printJson({ success: false, error: msg, templates: [] });
62
+ }
63
+ else {
64
+ this.logger.error(msg);
65
+ }
66
+ return;
67
+ }
55
68
  if (options.json) {
56
69
  this.printJson({ success: true, templates });
57
70
  return;
@@ -70,7 +83,20 @@ class WorkflowCommand {
70
83
  }
71
84
  async list(options) {
72
85
  this.ensureAuthenticated(Boolean(options.json));
73
- const workflows = await this.api.listVigFlowWorkflows();
86
+ let workflows;
87
+ try {
88
+ workflows = await this.api.listVigFlowWorkflows();
89
+ }
90
+ catch (error) {
91
+ const msg = `Workflow service is not reachable: ${error.message}`;
92
+ if (options.json) {
93
+ this.printJson({ success: false, error: msg, workflows: [] });
94
+ }
95
+ else {
96
+ this.logger.error(msg);
97
+ }
98
+ return;
99
+ }
74
100
  if (options.json) {
75
101
  this.printJson({ success: true, workflows });
76
102
  return;
package/dist/index.js CHANGED
@@ -316,6 +316,7 @@ async function main() {
316
316
  .description('Edit a file with AI assistance')
317
317
  .option('-i, --instruction <text>', 'Editing instruction')
318
318
  .option('-m, --model <model>', 'Select AI model', 'code')
319
+ .option('--apply', 'Automatically apply changes without confirmation', false)
319
320
  .action(async (file, options) => {
320
321
  const edit = new edit_js_1.EditCommand(config, logger);
321
322
  await edit.run(file, options);
@@ -346,12 +347,17 @@ async function main() {
346
347
  });
347
348
  // Fix command - Fix code issues
348
349
  program
349
- .command('fix <file>')
350
+ .command('fix [file]')
350
351
  .alias('f')
351
352
  .description('Fix issues in a file')
352
353
  .option('-t, --type <type>', 'Fix type (bugs, style, security, performance)', 'bugs')
353
354
  .option('--apply', 'Automatically apply fixes', false)
354
355
  .action(async (file, options) => {
356
+ if (!file) {
357
+ logger.error('Usage: vigthoria fix <file> [--type bugs|style|security|performance] [--apply]');
358
+ process.exitCode = 1;
359
+ return;
360
+ }
355
361
  const edit = new edit_js_1.EditCommand(config, logger);
356
362
  await edit.fix(file, options);
357
363
  });
@@ -227,7 +227,19 @@ export declare class APIClient {
227
227
  executionOptions?: Record<string, unknown>;
228
228
  }): Promise<VigFlowExecutionResult>;
229
229
  getVigFlowExecutionStatus(executionId: string): Promise<VigFlowExecutionStatus>;
230
+ /** Maximum serialized context length accepted by the V3 server. */
231
+ private static readonly V3_CONTEXT_CHAR_LIMIT;
230
232
  buildV3AgentContext(context?: Record<string, any>): string;
233
+ /**
234
+ * Compact a V3 context payload so the serialized JSON stays under
235
+ * the server's character limit. Progressively sheds bulk:
236
+ * 1. Trim workspaceFiles values to fit budget
237
+ * 2. Drop workspaceFiles entirely
238
+ * 3. Truncate history
239
+ * 4. Truncate file list
240
+ * 5. Drop readmeExcerpt
241
+ */
242
+ private compactV3Context;
231
243
  buildMinimalV3AgentContext(context?: Record<string, any>): string;
232
244
  private extractEmergencyAppName;
233
245
  private materializeEmergencySaaSWorkspace;
@@ -237,6 +249,11 @@ export declare class APIClient {
237
249
  private isLikelyWindowsPath;
238
250
  private resolveServerBindableWorkspacePath;
239
251
  private buildLocalWorkspaceSummary;
252
+ /**
253
+ * Collect text file contents from the workspace for V3 agent hydration.
254
+ * Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
255
+ */
256
+ collectWorkspaceFileContents(rootPath: string, filePaths: string[]): Record<string, string>;
240
257
  hasAgentWorkspaceOutput(context?: Record<string, any>): boolean;
241
258
  getAgentWorkspaceSnapshot(rootPath: string): {
242
259
  fileCount: number;
package/dist/utils/api.js CHANGED
@@ -306,12 +306,14 @@ class APIClient {
306
306
  return [...new Set(urls)];
307
307
  }
308
308
  getVigFlowBaseUrls() {
309
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
309
310
  const urls = [
310
311
  process.env.VIGTHORIA_VIGFLOW_URL,
311
312
  process.env.VIGFLOW_URL,
312
313
  process.env.WORKFLOW_BUILDER_URL,
313
314
  'http://127.0.0.1:5060',
314
315
  'http://127.0.0.1:5050',
316
+ `${configuredApiUrl}/api/vigflow`,
315
317
  ].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
316
318
  return [...new Set(urls)];
317
319
  }
@@ -593,26 +595,37 @@ class APIClient {
593
595
  error: artifacts.error || 'Frontend preview gate could not collect frontend artifacts.',
594
596
  };
595
597
  }
596
- const html = artifacts.html;
597
- const css = artifacts.css || '';
598
- const js = artifacts.js || '';
598
+ // Cap artifact sizes to prevent 413 Payload Too Large
599
+ const PREVIEW_MAX_ARTIFACT_BYTES = 500 * 1024;
600
+ const html = artifacts.html.slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
601
+ const css = (artifacts.css || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
602
+ const js = (artifacts.js || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
599
603
  const errors = [];
600
604
  for (const baseUrl of this.getTemplateServiceBaseUrls()) {
601
605
  try {
606
+ const proofPayload = JSON.stringify({
607
+ vision: String(context.rawPrompt || message || '').slice(0, 2000),
608
+ html,
609
+ css,
610
+ js,
611
+ entryPath: artifacts.htmlPath,
612
+ workspaceName: path_1.default.basename(rootPath),
613
+ });
614
+ // Skip preview proof if payload is still too large (> 4 MB)
615
+ if (Buffer.byteLength(proofPayload, 'utf8') > 4 * 1024 * 1024) {
616
+ return {
617
+ required: true,
618
+ passed: true,
619
+ error: 'Preview proof skipped: payload exceeds size limit.',
620
+ };
621
+ }
602
622
  const response = await fetch(`${baseUrl}/preview-proof`, {
603
623
  method: 'POST',
604
624
  headers: {
605
625
  'Content-Type': 'application/json',
606
626
  Accept: 'application/json',
607
627
  },
608
- body: JSON.stringify({
609
- vision: String(context.rawPrompt || message || ''),
610
- html,
611
- css,
612
- js,
613
- entryPath: artifacts.htmlPath,
614
- workspaceName: path_1.default.basename(rootPath),
615
- }),
628
+ body: proofPayload,
616
629
  });
617
630
  if (!response.ok) {
618
631
  const errorText = await response.text().catch(() => '');
@@ -877,6 +890,8 @@ class APIClient {
877
890
  return payload.execution;
878
891
  });
879
892
  }
893
+ /** Maximum serialized context length accepted by the V3 server. */
894
+ static V3_CONTEXT_CHAR_LIMIT = 95_000;
880
895
  buildV3AgentContext(context = {}) {
881
896
  const resolvedContext = this.ensureExecutionContext(context);
882
897
  const targetPath = resolvedContext.targetPath || resolvedContext.projectPath || resolvedContext.workspacePath || resolvedContext.projectRoot || process.cwd();
@@ -885,7 +900,7 @@ class APIClient {
885
900
  const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
886
901
  const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
887
902
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
888
- return JSON.stringify({
903
+ const payload = {
889
904
  workspace: resolvedContext.workspace || null,
890
905
  activeFile: resolvedContext.activeFile || null,
891
906
  history: resolvedContext.history || [],
@@ -911,7 +926,89 @@ class APIClient {
911
926
  requestStartedAt: resolvedContext.requestStartedAt,
912
927
  subscriptionPlan: this.config.getNormalizedPlan() || null,
913
928
  email: this.config.get('email') || null,
914
- });
929
+ };
930
+ return this.compactV3Context(payload);
931
+ }
932
+ /**
933
+ * Compact a V3 context payload so the serialized JSON stays under
934
+ * the server's character limit. Progressively sheds bulk:
935
+ * 1. Trim workspaceFiles values to fit budget
936
+ * 2. Drop workspaceFiles entirely
937
+ * 3. Truncate history
938
+ * 4. Truncate file list
939
+ * 5. Drop readmeExcerpt
940
+ */
941
+ compactV3Context(payload) {
942
+ const LIMIT = APIClient.V3_CONTEXT_CHAR_LIMIT;
943
+ let json = JSON.stringify(payload);
944
+ if (json.length <= LIMIT)
945
+ return json;
946
+ // Phase 1 — shrink workspaceFiles to fit
947
+ const summary = payload.localWorkspaceSummary;
948
+ if (summary?.workspaceFiles && typeof summary.workspaceFiles === 'object') {
949
+ const fileEntries = Object.entries(summary.workspaceFiles);
950
+ const overhead = json.length - JSON.stringify(summary.workspaceFiles).length;
951
+ const budget = LIMIT - overhead - 512; // reserve a little headroom
952
+ if (budget > 0) {
953
+ const trimmed = {};
954
+ let used = 2; // {}
955
+ for (const [k, v] of fileEntries) {
956
+ const entryLen = JSON.stringify(k).length + 1 + JSON.stringify(v).length + 1;
957
+ if (used + entryLen > budget)
958
+ break;
959
+ trimmed[k] = v;
960
+ used += entryLen;
961
+ }
962
+ summary.workspaceFiles = trimmed;
963
+ }
964
+ else {
965
+ delete summary.workspaceFiles;
966
+ }
967
+ json = JSON.stringify(payload);
968
+ if (json.length <= LIMIT)
969
+ return json;
970
+ }
971
+ // Phase 2 — drop workspaceFiles entirely
972
+ if (summary?.workspaceFiles) {
973
+ delete summary.workspaceFiles;
974
+ json = JSON.stringify(payload);
975
+ if (json.length <= LIMIT)
976
+ return json;
977
+ }
978
+ // Phase 3 — truncate history to last 6 messages
979
+ if (Array.isArray(payload.history) && payload.history.length > 6) {
980
+ payload.history = payload.history.slice(-6);
981
+ json = JSON.stringify(payload);
982
+ if (json.length <= LIMIT)
983
+ return json;
984
+ }
985
+ // Phase 4 — truncate file list
986
+ if (summary?.files && Array.isArray(summary.files) && summary.files.length > 15) {
987
+ summary.files = summary.files.slice(0, 15);
988
+ json = JSON.stringify(payload);
989
+ if (json.length <= LIMIT)
990
+ return json;
991
+ }
992
+ // Phase 5 — drop readmeExcerpt
993
+ if (summary?.readmeExcerpt) {
994
+ delete summary.readmeExcerpt;
995
+ json = JSON.stringify(payload);
996
+ if (json.length <= LIMIT)
997
+ return json;
998
+ }
999
+ // Phase 6 — drop history entirely
1000
+ if (Array.isArray(payload.history) && payload.history.length > 0) {
1001
+ payload.history = [];
1002
+ json = JSON.stringify(payload);
1003
+ if (json.length <= LIMIT)
1004
+ return json;
1005
+ }
1006
+ // Phase 7 — drop localWorkspaceSummary entirely as last resort
1007
+ if (payload.localWorkspaceSummary) {
1008
+ payload.localWorkspaceSummary = { path: summary?.path, name: summary?.name, fileCount: summary?.fileCount };
1009
+ json = JSON.stringify(payload);
1010
+ }
1011
+ return json;
915
1012
  }
916
1013
  buildMinimalV3AgentContext(context = {}) {
917
1014
  const resolvedContext = this.ensureExecutionContext(context);
@@ -1553,6 +1650,9 @@ menu {
1553
1650
  if (fs_1.default.existsSync(readmePath)) {
1554
1651
  summary.readmeExcerpt = fs_1.default.readFileSync(readmePath, 'utf8').slice(0, 2500);
1555
1652
  }
1653
+ // Hydrate workspace: include actual file contents so the V3 server
1654
+ // can populate the remote workspace before the agent starts.
1655
+ summary.workspaceFiles = this.collectWorkspaceFileContents(rootPath, snapshot.paths);
1556
1656
  return summary;
1557
1657
  }
1558
1658
  catch (error) {
@@ -1560,6 +1660,52 @@ menu {
1560
1660
  return null;
1561
1661
  }
1562
1662
  }
1663
+ /**
1664
+ * Collect text file contents from the workspace for V3 agent hydration.
1665
+ * Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
1666
+ */
1667
+ collectWorkspaceFileContents(rootPath, filePaths) {
1668
+ const MAX_TOTAL_BYTES = 2 * 1024 * 1024;
1669
+ const MAX_FILE_BYTES = 200 * 1024;
1670
+ const BINARY_EXTENSIONS = new Set([
1671
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp', '.avif',
1672
+ '.mp3', '.mp4', '.wav', '.ogg', '.webm', '.flac', '.aac',
1673
+ '.zip', '.gz', '.tar', '.rar', '.7z', '.bz2',
1674
+ '.exe', '.dll', '.so', '.dylib', '.bin', '.dat',
1675
+ '.woff', '.woff2', '.ttf', '.eot', '.otf',
1676
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
1677
+ '.db', '.sqlite', '.sqlite3',
1678
+ '.pyc', '.pyo', '.class', '.o', '.obj',
1679
+ '.DS_Store', '.lock',
1680
+ ]);
1681
+ const result = {};
1682
+ let totalBytes = 0;
1683
+ for (const relativePath of filePaths) {
1684
+ if (totalBytes >= MAX_TOTAL_BYTES)
1685
+ break;
1686
+ const ext = path_1.default.extname(relativePath).toLowerCase();
1687
+ if (BINARY_EXTENSIONS.has(ext))
1688
+ continue;
1689
+ if (/(^|[\/\\])\.(git|hg)([\/\\]|$)/.test(relativePath))
1690
+ continue;
1691
+ const absolutePath = path_1.default.join(rootPath, relativePath);
1692
+ try {
1693
+ const stat = fs_1.default.statSync(absolutePath);
1694
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES || stat.size === 0)
1695
+ continue;
1696
+ const content = fs_1.default.readFileSync(absolutePath, 'utf8');
1697
+ // Skip likely binary content (high ratio of non-printable chars)
1698
+ if (/[\x00-\x08\x0e-\x1f]/.test(content.slice(0, 512)))
1699
+ continue;
1700
+ result[relativePath] = content;
1701
+ totalBytes += Buffer.byteLength(content, 'utf8');
1702
+ }
1703
+ catch {
1704
+ // Skip unreadable files
1705
+ }
1706
+ }
1707
+ return result;
1708
+ }
1563
1709
  hasAgentWorkspaceOutput(context = {}) {
1564
1710
  try {
1565
1711
  const root = this.resolveAgentTargetPath(context);
@@ -2231,6 +2377,30 @@ document.addEventListener('DOMContentLoaded', () => {
2231
2377
  serverWorkspaceRoot = event.workspace_root.trim();
2232
2378
  }
2233
2379
  this.captureV3AgentStreamMutation(event, streamedFiles, serverWorkspaceRoot);
2380
+ // Empty workspace guard: if the remote agent lists its root
2381
+ // and finds nothing while our local workspace has files, the
2382
+ // workspace was not hydrated. Abort early with a clear error
2383
+ // instead of letting the agent spin on an empty directory.
2384
+ if (event.type === 'tool_result'
2385
+ && event.name === 'list_directory'
2386
+ && event.success === true
2387
+ && serverWorkspaceRoot
2388
+ && typeof event.output === 'string') {
2389
+ const listOutput = event.output.trim();
2390
+ const looksEmpty = listOutput === `[${serverWorkspaceRoot}]`
2391
+ || listOutput === `[${serverWorkspaceRoot}/]`
2392
+ || listOutput === `[${serverWorkspaceRoot}]\\n`
2393
+ || /^\[\/tmp\/vig-remote-server-[^\]]+\]\s*$/.test(listOutput);
2394
+ if (looksEmpty) {
2395
+ const localPath = this.resolveAgentTargetPath(context);
2396
+ const localHasFiles = localPath && fs_1.default.existsSync(localPath) && this.hasAgentWorkspaceOutput({ ...context, projectPath: localPath, targetPath: localPath });
2397
+ if (localHasFiles) {
2398
+ throw new Error('Remote workspace is empty — the V3 server did not receive your project files. '
2399
+ + 'Your local workspace has files but the remote agent sees an empty directory. '
2400
+ + 'This is a workspace sync failure. Falling back to local agent loop.');
2401
+ }
2402
+ }
2403
+ }
2234
2404
  if (typeof context.onStreamEvent === 'function') {
2235
2405
  try {
2236
2406
  context.onStreamEvent(event);
@@ -2940,8 +3110,11 @@ document.addEventListener('DOMContentLoaded', () => {
2940
3110
  }
2941
3111
  // Code operations - Using Vigthoria Centralized API
2942
3112
  async generateCode(prompt, language, model) {
3113
+ // Prepend scope-enforcement instruction so the model doesn't expand
3114
+ // a small task into an oversized glossy production page.
3115
+ const scopedPrompt = `IMPORTANT: Follow the user's scope literally. If they ask for something small or minimal, produce exactly that — no extra styling, no external dependencies, no landing-page treatment.\n\n${prompt}`;
2943
3116
  const response = await this.client.post('/api/ai/generate', {
2944
- prompt,
3117
+ prompt: scopedPrompt,
2945
3118
  language,
2946
3119
  model: this.resolvePermittedModelId(model),
2947
3120
  });
@@ -2974,7 +3147,12 @@ document.addEventListener('DOMContentLoaded', () => {
2974
3147
  code,
2975
3148
  language,
2976
3149
  });
2977
- return response.data;
3150
+ const raw = response.data ?? {};
3151
+ return {
3152
+ score: typeof raw.score === 'number' ? raw.score : 0,
3153
+ issues: Array.isArray(raw.issues) ? raw.issues : [],
3154
+ suggestions: Array.isArray(raw.suggestions) ? raw.suggestions : [],
3155
+ };
2978
3156
  }
2979
3157
  async fixCode(code, language, fixType) {
2980
3158
  const response = await this.client.post('/api/ai/fix', {
@@ -2982,7 +3160,11 @@ document.addEventListener('DOMContentLoaded', () => {
2982
3160
  language,
2983
3161
  fixType,
2984
3162
  });
2985
- return response.data;
3163
+ const raw = response.data ?? {};
3164
+ return {
3165
+ fixed: typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code),
3166
+ changes: Array.isArray(raw.changes) ? raw.changes : [],
3167
+ };
2986
3168
  }
2987
3169
  // Model resolution - maps Vigthoria model names to internal IDs
2988
3170
  // INTERNAL USE ONLY - users see only Vigthoria branding
@@ -3096,32 +3278,87 @@ document.addEventListener('DOMContentLoaded', () => {
3096
3278
  }
3097
3279
  }
3098
3280
  async getV3AgentHealth() {
3099
- const endpoint = this.getV3AgentRunUrl(this.getV3AgentBaseUrls()[0]).replace('/api/agent/run', '/health');
3100
- try {
3101
- const response = await fetch(endpoint, {
3102
- method: 'GET',
3103
- headers: await this.getV3AgentHeaders(),
3104
- });
3105
- if (!response.ok) {
3281
+ const baseUrl = this.getV3AgentBaseUrls()[0];
3282
+ // Try multiple health endpoint patterns — the V3 backend may expose
3283
+ // different paths depending on whether it's local (8030) or remote.
3284
+ const candidates = [
3285
+ `${baseUrl}/api/v3-agent/health`,
3286
+ `${baseUrl}/api/health`,
3287
+ `${baseUrl}/health`,
3288
+ ];
3289
+ const headers = await this.getV3AgentHeaders();
3290
+ for (const endpoint of candidates) {
3291
+ try {
3292
+ const controller = new AbortController();
3293
+ const timer = setTimeout(() => controller.abort(), 8000);
3294
+ const response = await fetch(endpoint, {
3295
+ method: 'GET',
3296
+ headers,
3297
+ signal: controller.signal,
3298
+ });
3299
+ clearTimeout(timer);
3300
+ if (response.ok) {
3301
+ const data = await response.json().catch(() => ({}));
3302
+ return {
3303
+ name: 'V3 Agent',
3304
+ endpoint,
3305
+ ok: true,
3306
+ details: { health: data },
3307
+ };
3308
+ }
3309
+ // 404 means this path doesn't exist — try next candidate
3310
+ if (response.status === 404)
3311
+ continue;
3312
+ // 405 Method Not Allowed — endpoint exists but rejects GET.
3313
+ // Treat as reachable since the run endpoint (POST) will work.
3314
+ if (response.status === 405) {
3315
+ return {
3316
+ name: 'V3 Agent',
3317
+ endpoint,
3318
+ ok: true,
3319
+ details: { health: { reachable: true, note: 'Health endpoint returned 405 but run endpoint is available' } },
3320
+ };
3321
+ }
3322
+ // Other error
3106
3323
  throw new Error(`V3 health ${response.status}`);
3107
3324
  }
3108
- const data = await response.json();
3109
- const ok = data?.status === 'ok' || data?.healthy === true;
3110
- return {
3111
- name: 'V3 Agent',
3112
- endpoint,
3113
- ok,
3114
- details: { health: data },
3115
- };
3325
+ catch (error) {
3326
+ if (error instanceof Error && error.message.startsWith('V3 health')) {
3327
+ return {
3328
+ name: 'V3 Agent',
3329
+ endpoint,
3330
+ ok: false,
3331
+ error: error.message,
3332
+ };
3333
+ }
3334
+ // Network/timeout error — try next candidate
3335
+ }
3116
3336
  }
3117
- catch (error) {
3118
- return {
3119
- name: 'V3 Agent',
3120
- endpoint,
3121
- ok: false,
3122
- error: error instanceof Error ? error.message : String(error),
3123
- };
3337
+ // Last resort: probe the run endpoint with OPTIONS
3338
+ const runUrl = this.getV3AgentRunUrl(baseUrl);
3339
+ try {
3340
+ const controller = new AbortController();
3341
+ const timer = setTimeout(() => controller.abort(), 5000);
3342
+ const probe = await fetch(runUrl, { method: 'OPTIONS', headers, signal: controller.signal });
3343
+ clearTimeout(timer);
3344
+ if (probe.ok || probe.status === 204 || probe.status === 405) {
3345
+ return {
3346
+ name: 'V3 Agent',
3347
+ endpoint: runUrl,
3348
+ ok: true,
3349
+ details: { health: { reachable: true, method: 'OPTIONS' } },
3350
+ };
3351
+ }
3352
+ }
3353
+ catch {
3354
+ // final attempt failed
3124
3355
  }
3356
+ return {
3357
+ name: 'V3 Agent',
3358
+ endpoint: candidates[0],
3359
+ ok: false,
3360
+ error: 'No V3 agent health endpoint responded.',
3361
+ };
3125
3362
  }
3126
3363
  async getHyperLoopHealth() {
3127
3364
  const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || 'http://127.0.0.1:8020/api/hyperloop/health';
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * Logger utility for Vigthoria CLI
3
3
  */
4
+ import { type Options as OraOptions, type Ora } from 'ora';
5
+ export type { Ora };
6
+ /**
7
+ * Create an ora spinner that writes to stderr so it never
8
+ * pollutes stdout JSON output or triggers PowerShell error styling.
9
+ */
10
+ export declare function createSpinner(textOrOpts: string | OraOptions): Ora;
4
11
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
5
12
  export declare class Logger {
6
13
  private verbose;
@@ -7,7 +7,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.Logger = void 0;
10
+ exports.createSpinner = createSpinner;
10
11
  const chalk_1 = __importDefault(require("chalk"));
12
+ const ora_1 = __importDefault(require("ora"));
13
+ /**
14
+ * Create an ora spinner that writes to stderr so it never
15
+ * pollutes stdout JSON output or triggers PowerShell error styling.
16
+ */
17
+ function createSpinner(textOrOpts) {
18
+ const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
19
+ return (0, ora_1.default)({ ...opts, stream: process.stderr });
20
+ }
11
21
  class Logger {
12
22
  verbose = false;
13
23
  setVerbose(verbose) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.17",
3
+ "version": "1.6.19",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [