vigthoria-cli 1.6.19 → 1.6.21

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.
@@ -46,6 +46,11 @@ export declare class ChatCommand {
46
46
  private resolveAgentExecutionPolicy;
47
47
  private getMessagesForModel;
48
48
  private isDiagnosticPrompt;
49
+ /**
50
+ * Returns true when the prompt is a simple lookup / analysis / read-only
51
+ * question — these should use analysis_only workflow, not full_autonomy.
52
+ */
53
+ private isAnalysisLookupPrompt;
49
54
  private isBrowserTaskPrompt;
50
55
  private inferAgentTaskType;
51
56
  private buildTaskShapingInstructions;
@@ -195,11 +195,22 @@ class ChatCommand {
195
195
  isDiagnosticPrompt(prompt) {
196
196
  return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
197
197
  }
198
+ /**
199
+ * Returns true when the prompt is a simple lookup / analysis / read-only
200
+ * question — these should use analysis_only workflow, not full_autonomy.
201
+ */
202
+ isAnalysisLookupPrompt(prompt) {
203
+ return /^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(prompt.trim());
204
+ }
198
205
  isBrowserTaskPrompt(prompt) {
199
206
  return /(browser|chrome|devtools|console|dom|network tab|network request|frontend runtime|client-side|client side|rendering|page load|websocket|ui bug|inspect element)/i.test(prompt);
200
207
  }
201
208
  inferAgentTaskType(prompt) {
202
- return this.isDiagnosticPrompt(prompt) ? 'debugging' : 'implementation';
209
+ if (this.isDiagnosticPrompt(prompt))
210
+ return 'debugging';
211
+ if (/^(what|which|how many|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|find|count|read|look at|tell me)/i.test(prompt.trim()))
212
+ return 'analysis';
213
+ return 'implementation';
203
214
  }
204
215
  buildTaskShapingInstructions(prompt) {
205
216
  const instructions = [];
@@ -356,7 +367,13 @@ class ChatCommand {
356
367
  }
357
368
  async run(options) {
358
369
  if (!this.config.isAuthenticated()) {
359
- this.logger.error('Not authenticated. Run: vigthoria login');
370
+ if (options.json) {
371
+ process.exitCode = 1;
372
+ console.log(JSON.stringify({ success: false, error: 'Not authenticated. Run: vigthoria login' }, null, 2));
373
+ }
374
+ else {
375
+ this.logger.error('Not authenticated. Run: vigthoria login');
376
+ }
360
377
  return;
361
378
  }
362
379
  this.agentMode = options.agent === true;
@@ -607,7 +624,8 @@ class ChatCommand {
607
624
  }
608
625
  catch (error) {
609
626
  if (spinner) {
610
- spinner.fail('Workflow target execution failed');
627
+ spinner.stop();
628
+ this.logger.error('Workflow target execution failed');
611
629
  }
612
630
  throw error;
613
631
  }
@@ -620,7 +638,9 @@ class ChatCommand {
620
638
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
621
639
  const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
622
640
  const executionPrompt = this.buildExecutionPrompt(prompt);
623
- const workflowType = this.isDiagnosticPrompt(prompt) ? 'analysis_only' : 'full_autonomy';
641
+ const workflowType = this.isDiagnosticPrompt(prompt) || this.isAnalysisLookupPrompt(prompt)
642
+ ? 'analysis_only'
643
+ : 'full_autonomy';
624
644
  try {
625
645
  const response = await this.api.runOperatorWorkflow(executionPrompt, {
626
646
  workspacePath: this.currentProjectPath,
@@ -661,9 +681,12 @@ class ChatCommand {
661
681
  }
662
682
  catch (error) {
663
683
  if (spinner) {
664
- spinner.fail('Operator workflow failed');
684
+ spinner.stop();
665
685
  }
666
686
  const errorMsg = error.message || 'Operator workflow failed with an unknown error.';
687
+ if (!this.jsonOutput) {
688
+ this.logger.error('Operator workflow failed');
689
+ }
667
690
  if (this.jsonOutput) {
668
691
  process.exitCode = 1;
669
692
  console.log(JSON.stringify({
@@ -682,11 +705,26 @@ class ChatCommand {
682
705
  this.lastActionableUserInput = prompt;
683
706
  // In non-agent chat mode the model has no tool access. Inject a
684
707
  // grounding constraint so it doesn't fabricate file contents.
708
+ // Also inject a real file listing so the model can reference actual
709
+ // paths instead of guessing.
685
710
  const needsGrounding = /(repo|file|code|project|workspace|source|inspect|analyze|audit|review)/i.test(prompt);
686
711
  if (needsGrounding && !this.messages.some(m => m.role === 'system' && m.content.includes('no direct file access'))) {
712
+ let groundingContent = 'You are in simple chat mode with no direct file access or tools. Do not fabricate file contents, search results, or analysis steps. If the user asks you to modify, edit, or deeply inspect files, advise them to use agent mode (vig chat --agent) for grounded repo analysis.';
713
+ // Inject a workspace file listing so the model has real paths
714
+ try {
715
+ const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
716
+ if (snapshot && snapshot.paths.length > 0) {
717
+ const listing = snapshot.paths.slice(0, 60).join('\n');
718
+ groundingContent += `\n\nWorkspace: ${this.currentProjectPath}\nFile listing (${snapshot.fileCount} files total):\n${listing}`;
719
+ if (snapshot.fileCount > 60) {
720
+ groundingContent += `\n... and ${snapshot.fileCount - 60} more files.`;
721
+ }
722
+ }
723
+ }
724
+ catch { /* ignore snapshot errors */ }
687
725
  this.messages.push({
688
726
  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.',
727
+ content: groundingContent,
690
728
  });
691
729
  }
692
730
  this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
@@ -695,17 +733,43 @@ class ChatCommand {
695
733
  const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
696
734
  if (spinner)
697
735
  spinner.stop();
698
- const finalText = response.message.trim();
699
- if (finalText) {
736
+ const finalText = (response.message || '').trim();
737
+ if (this.jsonOutput) {
738
+ console.log(JSON.stringify({
739
+ success: true,
740
+ mode: 'chat',
741
+ model: this.currentModel,
742
+ content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
743
+ }, null, 2));
744
+ }
745
+ else if (finalText) {
700
746
  console.log(finalText);
701
747
  }
702
- this.messages.push({ role: 'assistant', content: response.message });
748
+ else {
749
+ console.log(chalk_1.default.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
750
+ }
751
+ this.messages.push({ role: 'assistant', content: response.message || '' });
703
752
  this.saveSession();
704
753
  }
705
754
  catch (error) {
706
755
  if (spinner)
707
- spinner.fail('Failed to get response');
708
- this.logger.error(error.message);
756
+ spinner.stop();
757
+ if (!this.jsonOutput)
758
+ this.logger.error('Failed to get response');
759
+ const errorMsg = error.message;
760
+ if (this.jsonOutput) {
761
+ process.exitCode = 1;
762
+ console.log(JSON.stringify({
763
+ success: false,
764
+ mode: 'chat',
765
+ model: this.currentModel,
766
+ content: '',
767
+ error: errorMsg,
768
+ }, null, 2));
769
+ }
770
+ else {
771
+ this.logger.error(errorMsg);
772
+ }
709
773
  }
710
774
  }
711
775
  async runAgentTurn(prompt) {
@@ -802,7 +866,9 @@ class ChatCommand {
802
866
  }
803
867
  catch (error) {
804
868
  if (spinner)
805
- spinner.fail('Agent request failed');
869
+ spinner.stop();
870
+ if (!this.jsonOutput)
871
+ this.logger.error('Agent request failed');
806
872
  if (this.jsonOutput) {
807
873
  process.exitCode = 1;
808
874
  console.log(JSON.stringify({
@@ -1012,14 +1078,15 @@ class ChatCommand {
1012
1078
  if (!success) {
1013
1079
  if (this.isLegacyAgentFallbackAllowed()) {
1014
1080
  if (spinner) {
1015
- spinner.warn('Falling back to legacy CLI agent loop');
1081
+ spinner.stop();
1016
1082
  }
1083
+ this.logger.warn('Falling back to legacy CLI agent loop');
1017
1084
  this.logger.debug(`V3 agent workflow returned an incomplete result: ${previewGate?.error || 'workspace changes were not fully validated'}`);
1018
1085
  return false;
1019
1086
  }
1020
1087
  const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
1021
1088
  if (spinner) {
1022
- spinner.fail('V3 agent workflow rejected the result');
1089
+ spinner.stop();
1023
1090
  }
1024
1091
  this.logger.error(errorMessage);
1025
1092
  this.messages.push({ role: 'assistant', content: errorMessage });
@@ -1081,13 +1148,14 @@ class ChatCommand {
1081
1148
  }
1082
1149
  if (this.isLegacyAgentFallbackAllowed()) {
1083
1150
  if (spinner) {
1084
- spinner.warn('Falling back to legacy CLI agent loop');
1151
+ spinner.stop();
1085
1152
  }
1153
+ this.logger.warn('Falling back to legacy CLI agent loop');
1086
1154
  this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
1087
1155
  return false;
1088
1156
  }
1089
1157
  if (spinner) {
1090
- spinner.fail('V3 agent workflow failed');
1158
+ spinner.stop();
1091
1159
  }
1092
1160
  const errorMessage = `Agent mode requires the V3 workflow and will not fall back to the legacy CLI loop. ${error.message}`;
1093
1161
  this.logger.error(errorMessage);
@@ -151,7 +151,8 @@ class DeployCommand {
151
151
  console.log(chalk_1.default.gray(' Upgrade to a subdomain for permanent hosting.\n'));
152
152
  }
153
153
  catch (error) {
154
- spinner.fail('Deploy failed');
154
+ spinner.stop();
155
+ this.logger.error('Deploy failed');
155
156
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
156
157
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
157
158
  }
@@ -204,7 +205,8 @@ class DeployCommand {
204
205
  console.log(chalk_1.default.gray(' ✓ Unlimited traffic included\n'));
205
206
  }
206
207
  catch (error) {
207
- spinner.fail('Deploy failed');
208
+ spinner.stop();
209
+ this.logger.error('Deploy failed');
208
210
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
209
211
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
210
212
  }
@@ -258,7 +260,8 @@ class DeployCommand {
258
260
  }
259
261
  }
260
262
  catch (error) {
261
- spinner.fail('Deploy failed');
263
+ spinner.stop();
264
+ this.logger.error('Deploy failed');
262
265
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
263
266
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
264
267
  }
@@ -334,7 +337,8 @@ class DeployCommand {
334
337
  console.log(chalk_1.default.cyan('\n Subscribe: vig deploy --subdomain myapp\n'));
335
338
  }
336
339
  catch (error) {
337
- spinner.fail('Failed to fetch plans');
340
+ spinner.stop();
341
+ this.logger.error('Failed to fetch plans');
338
342
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
339
343
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
340
344
  }
@@ -374,7 +378,8 @@ class DeployCommand {
374
378
  }
375
379
  }
376
380
  catch (error) {
377
- spinner.fail('List failed');
381
+ spinner.stop();
382
+ this.logger.error('List failed');
378
383
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
379
384
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
380
385
  }
@@ -401,7 +406,8 @@ class DeployCommand {
401
406
  console.log(JSON.stringify(data, null, 2));
402
407
  }
403
408
  catch (error) {
404
- spinner.fail('Status check failed');
409
+ spinner.stop();
410
+ this.logger.error('Status check failed');
405
411
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
406
412
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
407
413
  }
@@ -434,7 +440,8 @@ class DeployCommand {
434
440
  }
435
441
  }
436
442
  catch (error) {
437
- spinner.fail('Verification failed');
443
+ spinner.stop();
444
+ this.logger.error('Verification failed');
438
445
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
439
446
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
440
447
  }
@@ -468,7 +475,8 @@ class DeployCommand {
468
475
  console.log(chalk_1.default.gray('\n Your project files are still in your repository.\n'));
469
476
  }
470
477
  catch (error) {
471
- spinner.fail('Remove failed');
478
+ spinner.stop();
479
+ this.logger.error('Remove failed');
472
480
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
473
481
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
474
482
  }
@@ -41,6 +41,11 @@ class EditCommand {
41
41
  // Get instruction
42
42
  let instruction = options.instruction;
43
43
  if (!instruction) {
44
+ // --apply requires --instruction to avoid hanging on non-interactive terminals
45
+ if (options.apply) {
46
+ this.logger.error('The --apply flag requires --instruction. Example: vigthoria edit file.ts --apply --instruction "fix the bug"');
47
+ return;
48
+ }
44
49
  const answer = await inquirer_1.default.prompt([
45
50
  {
46
51
  type: 'input',
@@ -148,12 +153,17 @@ Return the complete modified code:`,
148
153
  }
149
154
  }
150
155
  extractCode(response, language) {
151
- // Try to extract code block
156
+ // Try to extract code block (language-specific first, then any)
152
157
  const codeBlockRegex = new RegExp(`\`\`\`(?:${language})?\\n([\\s\\S]*?)\`\`\``, 'i');
153
158
  const match = response.match(codeBlockRegex);
154
159
  if (match) {
155
160
  return match[1].trim();
156
161
  }
162
+ // Try generic code block
163
+ const genericMatch = response.match(/```\w*\n([\s\S]*?)```/);
164
+ if (genericMatch) {
165
+ return genericMatch[1].trim();
166
+ }
157
167
  // If no code block, check if response looks like code
158
168
  const trimmed = response.trim();
159
169
  if (!trimmed.startsWith('```') && !trimmed.includes('Here') && !trimmed.includes('I ')) {
@@ -399,7 +399,8 @@ class RepoCommand {
399
399
  console.log(chalk_1.default.gray('\nTip: Use `vigthoria repo pull <name>` to restore this project anywhere.\n'));
400
400
  }
401
401
  catch (error) {
402
- spinner.fail('Push failed');
402
+ spinner.stop();
403
+ this.logger.error('Push failed');
403
404
  const errMsg = this.formatRepoError(error);
404
405
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
405
406
  }
@@ -486,7 +487,8 @@ class RepoCommand {
486
487
  console.log();
487
488
  }
488
489
  catch (error) {
489
- spinner.fail('Pull failed');
490
+ spinner.stop();
491
+ this.logger.error('Pull failed');
490
492
  const errMsg = this.formatRepoError(error);
491
493
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
492
494
  }
@@ -584,7 +586,8 @@ class RepoCommand {
584
586
  console.log(chalk_1.default.gray(' vigthoria repo share <name> - Share a project\n'));
585
587
  }
586
588
  catch (error) {
587
- spinner.fail('List failed');
589
+ spinner.stop();
590
+ this.logger.error('List failed');
588
591
  const errMsg = this.formatRepoError(error);
589
592
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
590
593
  }
@@ -634,7 +637,8 @@ class RepoCommand {
634
637
  }
635
638
  }
636
639
  catch (error) {
637
- spinner.fail('Status check failed');
640
+ spinner.stop();
641
+ this.logger.error('Status check failed');
638
642
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
639
643
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
640
644
  }
@@ -666,7 +670,8 @@ class RepoCommand {
666
670
  console.log(chalk_1.default.gray('\n Anyone with this link can view/download the project.\n'));
667
671
  }
668
672
  catch (error) {
669
- spinner.fail('Share failed');
673
+ spinner.stop();
674
+ this.logger.error('Share failed');
670
675
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
671
676
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
672
677
  }
@@ -700,7 +705,8 @@ class RepoCommand {
700
705
  console.log(chalk_1.default.gray('\nNote: Your local files are not affected.\n'));
701
706
  }
702
707
  catch (error) {
703
- spinner.fail('Delete failed');
708
+ spinner.stop();
709
+ this.logger.error('Delete failed');
704
710
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
705
711
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
706
712
  }
@@ -728,7 +734,8 @@ class RepoCommand {
728
734
  await this.pull(data.project.project_name, options);
729
735
  }
730
736
  catch (error) {
731
- spinner.fail('Clone failed');
737
+ spinner.stop();
738
+ this.logger.error('Clone failed');
732
739
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
733
740
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
734
741
  }
@@ -195,6 +195,11 @@ export declare class APIClient {
195
195
  getVigFlowBaseUrls(): string[];
196
196
  getTemplateServiceBaseUrls(): string[];
197
197
  private isFrontendTask;
198
+ /**
199
+ * Returns true when the prompt describes a read-only / analysis task.
200
+ * Used to suppress preview gate and other write-oriented side effects.
201
+ */
202
+ isAnalysisOnlyTask(message?: string, context?: Record<string, any>): boolean;
198
203
  private normalizeWorkspaceRelativePath;
199
204
  private listFrontendWorkspaceFiles;
200
205
  private chooseFrontendPreviewEntry;
@@ -212,6 +217,13 @@ export declare class APIClient {
212
217
  private getVigFlowAccessToken;
213
218
  private getVigFlowHeaders;
214
219
  private withVigFlow;
220
+ /**
221
+ * Build the correct sub-path for VigFlow endpoints.
222
+ * Local servers (e.g. localhost:5060) need `/api/…` prefix.
223
+ * The remote gateway URL already ends with `/api/vigflow`, so appending
224
+ * another `/api/…` would double the prefix and cause 404s.
225
+ */
226
+ private vigFlowEndpoint;
215
227
  listVigFlowTemplates(options?: {
216
228
  category?: string;
217
229
  search?: string;
@@ -273,6 +285,11 @@ export declare class APIClient {
273
285
  private buildFallbackFrontendJs;
274
286
  private replaceMissingLocalAssetReferences;
275
287
  formatV3AgentResponse(data: any): string;
288
+ /**
289
+ * Build a human-readable answer from the tool results in a V3 event
290
+ * stream when the server didn't emit a proper complete/message event.
291
+ */
292
+ private synthesizeAnswerFromV3Events;
276
293
  collectV3AgentStream(response: Response, context?: Record<string, any>): Promise<any>;
277
294
  runV3AgentWorkflow(message: string, context?: Record<string, any>): Promise<V3AgentWorkflowResponse>;
278
295
  private formatOperatorResponse;
@@ -329,6 +346,11 @@ export declare class APIClient {
329
346
  }[];
330
347
  suggestions: string[];
331
348
  }>;
349
+ /**
350
+ * Lightweight client-side heuristic scan: catches common code smells
351
+ * so review never returns "score 30, no issues".
352
+ */
353
+ private heuristicCodeIssues;
332
354
  fixCode(code: string, language: string, fixType: string): Promise<{
333
355
  fixed: string;
334
356
  changes: {
@@ -338,6 +360,11 @@ export declare class APIClient {
338
360
  reason: string;
339
361
  }[];
340
362
  }>;
363
+ /**
364
+ * Lightweight client-side syntax error detection.
365
+ * Returns a human-readable description of obvious errors, or empty string.
366
+ */
367
+ private detectSyntaxErrors;
341
368
  private resolveModelId;
342
369
  private getCoderHealth;
343
370
  private getModelsHealth;
package/dist/utils/api.js CHANGED
@@ -328,11 +328,29 @@ class APIClient {
328
328
  return [...new Set(urls)];
329
329
  }
330
330
  isFrontendTask(message = '', context = {}) {
331
+ // Never treat analysis-only tasks as frontend tasks — preview gate
332
+ // should not fire for read-only inspection prompts.
333
+ if (this.isAnalysisOnlyTask(message, context)) {
334
+ return false;
335
+ }
331
336
  const prompt = String(message || '');
332
337
  const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
333
338
  return /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|hero|responsive)/i.test(prompt)
334
339
  || expectedFiles.some((filePath) => /\.(html|css|js)$/i.test(filePath));
335
340
  }
341
+ /**
342
+ * Returns true when the prompt describes a read-only / analysis task.
343
+ * Used to suppress preview gate and other write-oriented side effects.
344
+ */
345
+ isAnalysisOnlyTask(message = '', context = {}) {
346
+ const prompt = String(message || '').toLowerCase();
347
+ const taskType = String(context.agentTaskType || '').toLowerCase();
348
+ if (taskType === 'debugging' || taskType === 'analysis' || taskType === 'analysis_only')
349
+ return true;
350
+ if (context.workflowType === 'analysis_only')
351
+ return true;
352
+ return /^(what|which|how many|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|find|count|read|look at|tell me|diagnos)/i.test(prompt.trim());
353
+ }
336
354
  normalizeWorkspaceRelativePath(filePath) {
337
355
  return String(filePath || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
338
356
  }
@@ -596,7 +614,7 @@ class APIClient {
596
614
  };
597
615
  }
598
616
  // Cap artifact sizes to prevent 413 Payload Too Large
599
- const PREVIEW_MAX_ARTIFACT_BYTES = 500 * 1024;
617
+ const PREVIEW_MAX_ARTIFACT_BYTES = 250 * 1024;
600
618
  const html = artifacts.html.slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
601
619
  const css = (artifacts.css || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
602
620
  const js = (artifacts.js || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
@@ -775,6 +793,19 @@ class APIClient {
775
793
  }
776
794
  throw lastError || new Error(`No VigFlow backend available for ${operation}.`);
777
795
  }
796
+ /**
797
+ * Build the correct sub-path for VigFlow endpoints.
798
+ * Local servers (e.g. localhost:5060) need `/api/…` prefix.
799
+ * The remote gateway URL already ends with `/api/vigflow`, so appending
800
+ * another `/api/…` would double the prefix and cause 404s.
801
+ */
802
+ vigFlowEndpoint(baseUrl, subPath) {
803
+ if (/\/api\/vigflow\/?$/i.test(baseUrl)) {
804
+ // Remote gateway – subPath like '/templates' is enough
805
+ return `${baseUrl.replace(/\/$/, '')}${subPath}`;
806
+ }
807
+ return `${baseUrl}/api${subPath}`;
808
+ }
778
809
  async listVigFlowTemplates(options = {}) {
779
810
  return this.withVigFlow('list templates', async (baseUrl, headers) => {
780
811
  const query = new URLSearchParams();
@@ -784,7 +815,7 @@ class APIClient {
784
815
  if (options.search) {
785
816
  query.set('search', options.search);
786
817
  }
787
- const url = `${baseUrl}/api/templates${query.size > 0 ? `?${query.toString()}` : ''}`;
818
+ const url = `${this.vigFlowEndpoint(baseUrl, '/templates')}${query.size > 0 ? `?${query.toString()}` : ''}`;
788
819
  const response = await axios_1.default.get(url, {
789
820
  headers,
790
821
  timeout: 30000,
@@ -795,7 +826,7 @@ class APIClient {
795
826
  }
796
827
  async listVigFlowWorkflows() {
797
828
  return this.withVigFlow('list workflows', async (baseUrl, headers) => {
798
- const response = await axios_1.default.get(`${baseUrl}/api/workflows`, {
829
+ const response = await axios_1.default.get(this.vigFlowEndpoint(baseUrl, '/workflows'), {
799
830
  headers,
800
831
  timeout: 30000,
801
832
  });
@@ -847,7 +878,7 @@ class APIClient {
847
878
  }
848
879
  async useVigFlowTemplate(templateId, options = {}) {
849
880
  return this.withVigFlow('use template', async (baseUrl, headers) => {
850
- const response = await axios_1.default.post(`${baseUrl}/api/templates/${encodeURIComponent(templateId)}/use`, {
881
+ const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`)}`, {
851
882
  name: options.name,
852
883
  variables: options.variables || {},
853
884
  }, {
@@ -863,7 +894,7 @@ class APIClient {
863
894
  }
864
895
  async runVigFlowWorkflow(workflowId, options = {}) {
865
896
  return this.withVigFlow('run workflow', async (baseUrl, headers) => {
866
- const response = await axios_1.default.post(`${baseUrl}/api/executions/run/${encodeURIComponent(workflowId)}`, {
897
+ const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`)}`, {
867
898
  data: options.data || {},
868
899
  options: options.executionOptions || {},
869
900
  }, {
@@ -879,7 +910,7 @@ class APIClient {
879
910
  }
880
911
  async getVigFlowExecutionStatus(executionId) {
881
912
  return this.withVigFlow('execution status', async (baseUrl, headers) => {
882
- const response = await axios_1.default.get(`${baseUrl}/api/executions/${encodeURIComponent(executionId)}`, {
913
+ const response = await axios_1.default.get(`${this.vigFlowEndpoint(baseUrl, `/executions/${encodeURIComponent(executionId)}`)}`, {
883
914
  headers,
884
915
  timeout: 30000,
885
916
  });
@@ -900,6 +931,11 @@ class APIClient {
900
931
  const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
901
932
  const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
902
933
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
934
+ // When the server cannot directly access the workspace (e.g. Windows
935
+ // client), use the local path as a hint and flag that the workspace
936
+ // files are provided inline in localWorkspaceSummary.workspaceFiles.
937
+ const effectiveWorkspacePath = serverWorkspacePath || localWorkspacePath || null;
938
+ const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
903
939
  const payload = {
904
940
  workspace: resolvedContext.workspace || null,
905
941
  activeFile: resolvedContext.activeFile || null,
@@ -913,12 +949,16 @@ class APIClient {
913
949
  executionSurface: resolvedContext.executionSurface || 'cli',
914
950
  clientSurface: resolvedContext.clientSurface || 'cli',
915
951
  localMachineCapable: resolvedContext.localMachineCapable !== false,
916
- workspacePath: serverWorkspacePath || null,
917
- projectPath: serverWorkspacePath || null,
918
- targetPath: serverWorkspacePath || null,
952
+ workspacePath: effectiveWorkspacePath,
953
+ projectPath: effectiveWorkspacePath,
954
+ targetPath: effectiveWorkspacePath,
919
955
  localWorkspacePath: localWorkspacePath || null,
920
956
  localWorkspaceName: localWorkspacePath ? path_1.default.basename(localWorkspacePath) : null,
921
957
  localWorkspaceSummary,
958
+ // Signal to the server that the workspace filesystem is not locally
959
+ // accessible — it must hydrate a temp directory from the provided
960
+ // workspaceFiles before the agent starts using tools.
961
+ workspaceHydrationRequired: needsHydration,
922
962
  contextId: resolvedContext.contextId,
923
963
  traceId: resolvedContext.traceId,
924
964
  mcpContextId: resolvedContext.mcpContextId || null,
@@ -2292,10 +2332,80 @@ document.addEventListener('DOMContentLoaded', () => {
2292
2332
  if (messageEvent) {
2293
2333
  return messageEvent.content;
2294
2334
  }
2335
+ // Synthesize a grounded answer from the tool-call evidence the
2336
+ // agent produced, rather than dumping the raw event trace.
2337
+ const answer = this.synthesizeAnswerFromV3Events(data.events);
2338
+ if (answer) {
2339
+ return answer;
2340
+ }
2341
+ }
2342
+ // Last resort: if data has files written, report them.
2343
+ if (data?.files && typeof data.files === 'object' && Object.keys(data.files).length > 0) {
2344
+ const fileList = Object.keys(data.files).join(', ');
2345
+ return `Agent wrote workspace files: ${fileList}`;
2295
2346
  }
2296
2347
  const text = JSON.stringify(data, null, 2);
2297
2348
  return text.length > 12000 ? `${text.slice(0, 12000)}\n\n[V3 agent output truncated]` : text;
2298
2349
  }
2350
+ /**
2351
+ * Build a human-readable answer from the tool results in a V3 event
2352
+ * stream when the server didn't emit a proper complete/message event.
2353
+ */
2354
+ synthesizeAnswerFromV3Events(events) {
2355
+ const toolResults = [];
2356
+ const filesRead = [];
2357
+ const filesWritten = [];
2358
+ const assistantFragments = [];
2359
+ for (const event of events) {
2360
+ if (!event)
2361
+ continue;
2362
+ if (event.type === 'tool_result' && event.success && typeof event.output === 'string') {
2363
+ const name = event.name || 'unknown_tool';
2364
+ if (name === 'read_file' && typeof event.target === 'string') {
2365
+ filesRead.push(event.target);
2366
+ }
2367
+ else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
2368
+ filesWritten.push(event.target);
2369
+ }
2370
+ else {
2371
+ // Keep last ~300 chars of output for context
2372
+ const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
2373
+ toolResults.push(`[${name}] ${excerpt}`);
2374
+ }
2375
+ }
2376
+ if (event.type === 'assistant' && typeof event.content === 'string' && event.content.trim()) {
2377
+ assistantFragments.push(event.content.trim());
2378
+ }
2379
+ // Some servers emit 'text' events for incremental assistant text
2380
+ if (event.type === 'text' && typeof event.content === 'string' && event.content.trim()) {
2381
+ assistantFragments.push(event.content.trim());
2382
+ }
2383
+ // Some servers emit content_block_delta for streamed text
2384
+ if (event.type === 'content_block_delta' && typeof event.delta?.text === 'string' && event.delta.text.trim()) {
2385
+ assistantFragments.push(event.delta.text.trim());
2386
+ }
2387
+ }
2388
+ // Concatenate ALL assistant text fragments in order — keeps full
2389
+ // multi-turn reasoning instead of only the last fragment.
2390
+ const fullAssistantText = assistantFragments.join('\n\n').trim();
2391
+ if (fullAssistantText.length > 20) {
2392
+ return fullAssistantText;
2393
+ }
2394
+ // Otherwise build a summary from tool evidence
2395
+ const sections = [];
2396
+ if (filesRead.length > 0) {
2397
+ sections.push(`Files inspected: ${filesRead.join(', ')}`);
2398
+ }
2399
+ if (filesWritten.length > 0) {
2400
+ sections.push(`Files written: ${filesWritten.join(', ')}`);
2401
+ }
2402
+ if (toolResults.length > 0) {
2403
+ sections.push(toolResults.slice(-5).join('\n'));
2404
+ }
2405
+ return sections.length > 0
2406
+ ? sections.join('\n\n')
2407
+ : '';
2408
+ }
2299
2409
  async collectV3AgentStream(response, context = {}) {
2300
2410
  if (!response.body || typeof response.body.getReader !== 'function') {
2301
2411
  return response.json();
@@ -2660,6 +2770,10 @@ document.addEventListener('DOMContentLoaded', () => {
2660
2770
  const timeoutMs = context.operatorTimeoutMs || DEFAULT_OPERATOR_TIMEOUT_MS;
2661
2771
  const errors = [];
2662
2772
  const authToken = this.config.get('authToken');
2773
+ // Collect a lightweight workspace file listing so the operator can
2774
+ // ground its analysis in real files rather than returning files_analyzed: 0.
2775
+ const workspacePath = executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd();
2776
+ const workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath);
2663
2777
  for (const baseUrl of this.getOperatorBaseUrls()) {
2664
2778
  const controller = new AbortController();
2665
2779
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -2683,8 +2797,9 @@ document.addEventListener('DOMContentLoaded', () => {
2683
2797
  trace_id: executionContext.traceId,
2684
2798
  mcp_context_id: executionContext.mcpContextId || null,
2685
2799
  context: {
2686
- workspace: { path: executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd() },
2687
- workspace_path: executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd(),
2800
+ workspace: { path: workspacePath },
2801
+ workspace_path: workspacePath,
2802
+ workspace_summary: workspaceSummary,
2688
2803
  model: this.resolveModelId(executionContext.model || 'code-8b'),
2689
2804
  history: executionContext.history || [],
2690
2805
  executionSurface: executionContext.executionSurface || 'cli',
@@ -3110,9 +3225,25 @@ document.addEventListener('DOMContentLoaded', () => {
3110
3225
  }
3111
3226
  // Code operations - Using Vigthoria Centralized API
3112
3227
  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}`;
3228
+ // Prepend a forceful scope-enforcement instruction so the model
3229
+ // doesn't expand a small task into an oversized glossy page.
3230
+ const scopedPrompt = [
3231
+ 'IMPORTANT — MANDATORY SCOPE CONSTRAINTS (violation = failure):',
3232
+ '1. Output ONLY what the user explicitly asked for. Nothing more.',
3233
+ '2. If the prompt is ≤ 15 words, produce ≤ 80 lines of code maximum.',
3234
+ '3. If the user says "tiny", "small", "simple", "minimal", or "basic", produce ≤ 50 lines.',
3235
+ '4. NEVER add any of these unless the user EXPLICITLY requests them:',
3236
+ ' - Hero sections, CTAs, testimonials, pricing tables, footers, navbars',
3237
+ ' - CSS animations, gradients, neon effects, glass-morphism, particles',
3238
+ ' - Google Fonts, Font Awesome, external CDN links, icon libraries',
3239
+ ' - Responsive breakpoints, media queries (unless asked)',
3240
+ ' - Multiple pages or components when one was requested',
3241
+ '5. Prefer inline styles or a small <style> block. No CSS frameworks.',
3242
+ '6. Return raw code only — no markdown fences, no explanations, no comments about what could be added.',
3243
+ '7. Match the complexity of the request: a "hello world" is 1-5 lines, a "button" is 5-15 lines.',
3244
+ '',
3245
+ prompt,
3246
+ ].join('\n');
3116
3247
  const response = await this.client.post('/api/ai/generate', {
3117
3248
  prompt: scopedPrompt,
3118
3249
  language,
@@ -3146,25 +3277,184 @@ document.addEventListener('DOMContentLoaded', () => {
3146
3277
  const response = await this.client.post('/api/ai/review', {
3147
3278
  code,
3148
3279
  language,
3280
+ instructions: 'Return concrete, line-specific issues with severity. Every issue MUST reference a line number. If the score is below 50, you MUST list at least 2 specific issues.',
3149
3281
  });
3150
3282
  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
- };
3283
+ const score = typeof raw.score === 'number' ? raw.score : 0;
3284
+ const issues = Array.isArray(raw.issues) ? raw.issues : [];
3285
+ const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
3286
+ // Prevent contradictory output: low score but zero issues.
3287
+ // Run lightweight client-side heuristics to produce concrete findings.
3288
+ if (score < 50 && issues.length === 0) {
3289
+ const heuristic = this.heuristicCodeIssues(code, language);
3290
+ if (heuristic.length > 0) {
3291
+ issues.push(...heuristic);
3292
+ }
3293
+ else {
3294
+ issues.push({
3295
+ type: 'quality',
3296
+ line: 1,
3297
+ message: `The analysis returned a low quality score (${score}/100) but did not enumerate specific issues. Re-run the review or inspect the file manually.`,
3298
+ severity: 'warning',
3299
+ });
3300
+ }
3301
+ }
3302
+ return { score, issues, suggestions };
3303
+ }
3304
+ /**
3305
+ * Lightweight client-side heuristic scan: catches common code smells
3306
+ * so review never returns "score 30, no issues".
3307
+ */
3308
+ heuristicCodeIssues(code, language) {
3309
+ const issues = [];
3310
+ const lines = code.split('\n');
3311
+ for (let i = 0; i < lines.length; i++) {
3312
+ const line = lines[i];
3313
+ const lineNum = i + 1;
3314
+ // console.log left in production code
3315
+ if (/\bconsole\.(log|debug|info)\b/.test(line) && !/\/\//.test(line.slice(0, line.indexOf('console')))) {
3316
+ issues.push({ type: 'quality', line: lineNum, message: 'console.log/debug statement — remove or replace with proper logging.', severity: 'warning' });
3317
+ }
3318
+ // TODO/FIXME/HACK comments
3319
+ if (/\b(TODO|FIXME|HACK|XXX)\b/.test(line)) {
3320
+ issues.push({ type: 'maintainability', line: lineNum, message: `Unresolved ${line.match(/\b(TODO|FIXME|HACK|XXX)\b/)?.[0]} comment.`, severity: 'info' });
3321
+ }
3322
+ // Empty catch blocks
3323
+ if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
3324
+ issues.push({ type: 'error-handling', line: lineNum, message: 'Empty catch block — errors are silently swallowed.', severity: 'warning' });
3325
+ }
3326
+ // Very long lines (> 200 chars)
3327
+ if (line.length > 200) {
3328
+ issues.push({ type: 'style', line: lineNum, message: `Line exceeds 200 characters (${line.length}).`, severity: 'info' });
3329
+ }
3330
+ // Limit to 10 heuristic issues
3331
+ if (issues.length >= 10)
3332
+ break;
3333
+ }
3334
+ return issues;
3156
3335
  }
3157
3336
  async fixCode(code, language, fixType) {
3337
+ // Client-side syntax pre-check: detect obvious errors and include
3338
+ // them in the request so the model has concrete signals.
3339
+ const syntaxHints = this.detectSyntaxErrors(code, language);
3340
+ const augmentedCode = syntaxHints
3341
+ ? `// SYNTAX ERRORS DETECTED BY CLIENT:\n// ${syntaxHints}\n\n${code}`
3342
+ : code;
3158
3343
  const response = await this.client.post('/api/ai/fix', {
3159
- code,
3344
+ code: augmentedCode,
3160
3345
  language,
3161
3346
  fixType,
3162
3347
  });
3163
3348
  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
- };
3349
+ let fixed = typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code);
3350
+ let changes = Array.isArray(raw.changes) ? raw.changes : [];
3351
+ // If server returned no changes but we found syntax errors, strip
3352
+ // our injected comment prefix from the returned code and attempt
3353
+ // a basic client-side repair.
3354
+ if (changes.length === 0 && syntaxHints && fixed === augmentedCode) {
3355
+ fixed = code; // restore original
3356
+ }
3357
+ // Strip the injected comment block if it leaked into the output
3358
+ if (fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT:')) {
3359
+ const idx = fixed.indexOf('\n\n');
3360
+ if (idx !== -1) {
3361
+ fixed = fixed.slice(idx + 2);
3362
+ }
3363
+ }
3364
+ // If there are still no changes but the fixed code differs, build a
3365
+ // synthetic change entry so the user sees something actionable.
3366
+ if (changes.length === 0 && fixed !== code) {
3367
+ changes = [{ line: 1, before: '(original code)', after: '(fixed code)', reason: syntaxHints || 'AI-suggested fix' }];
3368
+ }
3369
+ return { fixed, changes };
3370
+ }
3371
+ /**
3372
+ * Lightweight client-side syntax error detection.
3373
+ * Returns a human-readable description of obvious errors, or empty string.
3374
+ */
3375
+ detectSyntaxErrors(code, language) {
3376
+ const lang = language.toLowerCase();
3377
+ const errors = [];
3378
+ if (lang === 'javascript' || lang === 'typescript' || lang === 'js' || lang === 'ts') {
3379
+ // Bracket matching
3380
+ let braces = 0, brackets = 0, parens = 0;
3381
+ // Track string context (simple heuristic — not a full parser)
3382
+ let inString = null;
3383
+ let inLineComment = false;
3384
+ let inBlockComment = false;
3385
+ for (let i = 0; i < code.length; i++) {
3386
+ const ch = code[i];
3387
+ const next = code[i + 1] || '';
3388
+ if (inLineComment) {
3389
+ if (ch === '\n')
3390
+ inLineComment = false;
3391
+ continue;
3392
+ }
3393
+ if (inBlockComment) {
3394
+ if (ch === '*' && next === '/') {
3395
+ inBlockComment = false;
3396
+ i++;
3397
+ }
3398
+ continue;
3399
+ }
3400
+ if (inString) {
3401
+ if (ch === inString && code[i - 1] !== '\\')
3402
+ inString = null;
3403
+ continue;
3404
+ }
3405
+ if (ch === '/' && next === '/') {
3406
+ inLineComment = true;
3407
+ continue;
3408
+ }
3409
+ if (ch === '/' && next === '*') {
3410
+ inBlockComment = true;
3411
+ continue;
3412
+ }
3413
+ if (ch === '"' || ch === "'" || ch === '`') {
3414
+ inString = ch;
3415
+ continue;
3416
+ }
3417
+ if (ch === '{')
3418
+ braces++;
3419
+ else if (ch === '}')
3420
+ braces--;
3421
+ else if (ch === '[')
3422
+ brackets++;
3423
+ else if (ch === ']')
3424
+ brackets--;
3425
+ else if (ch === '(')
3426
+ parens++;
3427
+ else if (ch === ')')
3428
+ parens--;
3429
+ }
3430
+ if (braces !== 0)
3431
+ errors.push(`Mismatched braces (balance: ${braces > 0 ? '+' : ''}${braces})`);
3432
+ if (brackets !== 0)
3433
+ errors.push(`Mismatched brackets (balance: ${brackets > 0 ? '+' : ''}${brackets})`);
3434
+ if (parens !== 0)
3435
+ errors.push(`Mismatched parentheses (balance: ${parens > 0 ? '+' : ''}${parens})`);
3436
+ // Detect obvious keyword typos
3437
+ const lines = code.split('\n');
3438
+ for (let li = 0; li < lines.length; li++) {
3439
+ const line = lines[li];
3440
+ if (/\bfuncion\b|\bfuction\b|\bfuntion\b/i.test(line))
3441
+ errors.push(`Line ${li + 1}: Misspelled 'function'`);
3442
+ if (/\bretrun\b/i.test(line))
3443
+ errors.push(`Line ${li + 1}: Misspelled 'return'`);
3444
+ if (/\bconts\b|\bcosnt\b/i.test(line))
3445
+ errors.push(`Line ${li + 1}: Misspelled 'const'`);
3446
+ }
3447
+ }
3448
+ if (lang === 'python' || lang === 'py') {
3449
+ const lines = code.split('\n');
3450
+ for (let li = 0; li < lines.length; li++) {
3451
+ const line = lines[li];
3452
+ if (/^\s*(def|class|if|elif|else|for|while|try|except|with)\b/.test(line) && !line.trimEnd().endsWith(':') && !line.trimEnd().endsWith('\\')) {
3453
+ errors.push(`Line ${li + 1}: Missing colon after '${line.trim().split(/\s/)[0]}'`);
3454
+ }
3455
+ }
3456
+ }
3457
+ return errors.join('; ');
3168
3458
  }
3169
3459
  // Model resolution - maps Vigthoria model names to internal IDs
3170
3460
  // INTERNAL USE ONLY - users see only Vigthoria branding
@@ -5,7 +5,15 @@ import { type Options as OraOptions, type Ora } from 'ora';
5
5
  export type { Ora };
6
6
  /**
7
7
  * Create an ora spinner that writes to stderr so it never
8
- * pollutes stdout JSON output or triggers PowerShell error styling.
8
+ * pollutes stdout JSON output.
9
+ *
10
+ * IMPORTANT: On Windows PowerShell, any stderr output triggers
11
+ * NativeCommandError styling. The spinner animation itself is
12
+ * tolerable, but `spinner.fail(msg)` writes the message to stderr
13
+ * which produces ugly red PowerShell errors.
14
+ *
15
+ * Prefer: spinner.stop() then Logger.error(msg) — which writes to
16
+ * stdout — instead of spinner.fail(msg).
9
17
  */
10
18
  export declare function createSpinner(textOrOpts: string | OraOptions): Ora;
11
19
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
@@ -12,7 +12,15 @@ const chalk_1 = __importDefault(require("chalk"));
12
12
  const ora_1 = __importDefault(require("ora"));
13
13
  /**
14
14
  * Create an ora spinner that writes to stderr so it never
15
- * pollutes stdout JSON output or triggers PowerShell error styling.
15
+ * pollutes stdout JSON output.
16
+ *
17
+ * IMPORTANT: On Windows PowerShell, any stderr output triggers
18
+ * NativeCommandError styling. The spinner animation itself is
19
+ * tolerable, but `spinner.fail(msg)` writes the message to stderr
20
+ * which produces ugly red PowerShell errors.
21
+ *
22
+ * Prefer: spinner.stop() then Logger.error(msg) — which writes to
23
+ * stdout — instead of spinner.fail(msg).
16
24
  */
17
25
  function createSpinner(textOrOpts) {
18
26
  const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
@@ -35,7 +43,10 @@ class Logger {
35
43
  console.log(chalk_1.default.yellow('⚠'), ...args);
36
44
  }
37
45
  error(...args) {
38
- console.error(chalk_1.default.red('✗'), ...args);
46
+ // Write error messages to stdout (not stderr) to avoid triggering
47
+ // PowerShell NativeCommandError styling. The red ✗ prefix already
48
+ // signals an error visually; stderr redirection is unnecessary.
49
+ console.log(chalk_1.default.red('✗'), ...args);
39
50
  }
40
51
  success(...args) {
41
52
  console.log(chalk_1.default.green('✓'), ...args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.19",
3
+ "version": "1.6.21",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [