vigthoria-cli 1.8.4 → 1.8.6

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.
@@ -75,6 +75,11 @@ export declare class ChatCommand {
75
75
  private v3ToolCallCount;
76
76
  private v3LastActivity;
77
77
  private v3StreamingStarted;
78
+ /**
79
+ * Strip server-internal path prefixes from tool output strings.
80
+ * Prevents exposing paths like /var/www/V3-Code-Agent/... to end users.
81
+ */
82
+ private sanitizeServerPath;
78
83
  private describeV3AgentTool;
79
84
  private updateV3AgentSpinner;
80
85
  private updateOperatorSpinner;
@@ -299,6 +299,20 @@ class ChatCommand {
299
299
  v3ToolCallCount = 0;
300
300
  v3LastActivity = Date.now();
301
301
  v3StreamingStarted = false;
302
+ /**
303
+ * Strip server-internal path prefixes from tool output strings.
304
+ * Prevents exposing paths like /var/www/V3-Code-Agent/... to end users.
305
+ */
306
+ sanitizeServerPath(text) {
307
+ if (!text)
308
+ return text;
309
+ // Strip common server path prefixes from output
310
+ return text
311
+ .replace(/\/var\/www\/V3-Code-Agent\//g, '')
312
+ .replace(/\/var\/www\/[a-zA-Z0-9_-]+\//g, '')
313
+ .replace(/\/tmp\/vig-remote-[a-zA-Z0-9_-]+\//g, '')
314
+ .replace(/\/opt\/vigthoria[a-zA-Z0-9_/-]*\//g, '');
315
+ }
302
316
  describeV3AgentTool(toolName) {
303
317
  const normalized = String(toolName || '').toLowerCase();
304
318
  if (/read|grep|search|list|find|glob/.test(normalized)) {
@@ -324,7 +338,8 @@ class ChatCommand {
324
338
  this.v3ToolCallCount += 1;
325
339
  const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
326
340
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
327
- const shortTarget = toolTarget ? ` → ${String(toolTarget).split('/').slice(-2).join('/')}` : '';
341
+ const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
342
+ const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
328
343
  const stepLabel = chalk_1.default.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
329
344
  if (spinner.isSpinning)
330
345
  spinner.stop();
@@ -350,10 +365,12 @@ class ChatCommand {
350
365
  if (spinner.isSpinning)
351
366
  spinner.stop();
352
367
  process.stderr.write(`${indicator} ${toolName}\n`);
353
- // Show output for failures, or brief summary for successes
354
- const output = typeof event.output === 'string' ? event.output.trim() : '';
368
+ // Show output for failures, or brief summary for successes — sanitize server paths
369
+ const rawOutput = typeof event.output === 'string' ? event.output.trim() : '';
370
+ const output = this.sanitizeServerPath(rawOutput);
355
371
  if (!success && output) {
356
- const lines = output.split('\n').slice(0, 4);
372
+ const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
373
+ const lines = sanitizedError.split('\n').slice(0, 4);
357
374
  process.stderr.write(chalk_1.default.red(` ${lines.join('\n ')}\n`));
358
375
  }
359
376
  else if (success && output && output.length > 0) {
@@ -452,7 +469,8 @@ class ChatCommand {
452
469
  return;
453
470
  }
454
471
  if (event.type === 'file_mutation') {
455
- const filePath = typeof event.path === 'string' ? event.path.split('/').slice(-2).join('/') : '';
472
+ const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
473
+ const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
456
474
  const action = event.action === 'delete' ? chalk_1.default.red('deleted') : chalk_1.default.green('wrote');
457
475
  if (filePath) {
458
476
  if (spinner.isSpinning)
@@ -1606,7 +1624,7 @@ class ChatCommand {
1606
1624
  clientSurface: 'cli',
1607
1625
  localMachineCapable: true,
1608
1626
  agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
1609
- agentIdleTimeoutMs: 0,
1627
+ agentIdleTimeoutMs: 90000,
1610
1628
  model: routingPolicy.selectedModel,
1611
1629
  requestedModel: this.currentModel,
1612
1630
  agentExecutionPolicy: routingPolicy,
@@ -1616,9 +1634,14 @@ class ChatCommand {
1616
1634
  ...runtimeContext,
1617
1635
  onStreamEvent: spinner ? (event) => this.updateV3AgentSpinner(spinner, event) : undefined,
1618
1636
  });
1619
- // No hard timeout — SSE stream stays open until the server
1620
- // sends complete/error/[DONE]. We never cut off a working workflow.
1621
- const response = await workflowPromise;
1637
+ const response = await (rescueEligible
1638
+ ? Promise.race([
1639
+ workflowPromise,
1640
+ new Promise((_, reject) => {
1641
+ setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')), 240000);
1642
+ }),
1643
+ ])
1644
+ : workflowPromise);
1622
1645
  if (spinner) {
1623
1646
  spinner.stop();
1624
1647
  }
@@ -81,6 +81,14 @@ export declare class RepoCommand {
81
81
  * Delete a project from Vigthoria Repository
82
82
  */
83
83
  delete(projectName: string): Promise<void>;
84
+ /**
85
+ * Open a project in a Vigthoria engine (shop, visual, game).
86
+ * Pushes to community if not yet there, then triggers engine import.
87
+ */
88
+ openIn(engine: 'shop' | 'visual' | 'game', projectName?: string, options?: {
89
+ browser?: boolean;
90
+ shopId?: string;
91
+ }): Promise<void>;
84
92
  /**
85
93
  * Clone a public project from Vigthoria Repository
86
94
  */
@@ -714,6 +714,64 @@ class RepoCommand {
714
714
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
715
715
  }
716
716
  }
717
+ /**
718
+ * Open a project in a Vigthoria engine (shop, visual, game).
719
+ * Pushes to community if not yet there, then triggers engine import.
720
+ */
721
+ async openIn(engine, projectName, options = {}) {
722
+ this.requireAuth();
723
+ const engineLabels = {
724
+ shop: 'Shop Engine',
725
+ visual: 'Visual Editor',
726
+ game: 'Gaming Engine'
727
+ };
728
+ const label = engineLabels[engine] || engine;
729
+ // Resolve project name from arg or current directory
730
+ const resolvedName = projectName || this.detectProjectInfo(process.cwd()).name;
731
+ const spinner = (0, logger_js_1.createSpinner)(`Opening "${resolvedName}" in ${label}...`).start();
732
+ try {
733
+ const response = await fetch(`${this.apiBase}/api/engine-import`, {
734
+ method: 'POST',
735
+ headers: this.getAuthHeaders(),
736
+ body: JSON.stringify({
737
+ engine,
738
+ projectName: resolvedName,
739
+ shopId: options.shopId || 'default',
740
+ source: 'repo'
741
+ })
742
+ });
743
+ if (!response.ok) {
744
+ const err = await response.json().catch(async () => ({ error: await response.text() }));
745
+ throw new Error(err.error || `Engine import failed (${response.status})`);
746
+ }
747
+ const data = await response.json();
748
+ spinner.succeed(chalk_1.default.green(`Project imported into ${label}!`));
749
+ console.log(chalk_1.default.cyan(`\n🚀 Open in browser:`));
750
+ console.log(chalk_1.default.bold(` ${data.url}\n`));
751
+ if (options.browser) {
752
+ try {
753
+ const { execSync } = await import('child_process');
754
+ const platform = process.platform;
755
+ if (platform === 'win32')
756
+ execSync(`start "" "${data.url}"`, { stdio: 'ignore' });
757
+ else if (platform === 'darwin')
758
+ execSync(`open "${data.url}"`, { stdio: 'ignore' });
759
+ else
760
+ execSync(`xdg-open "${data.url}"`, { stdio: 'ignore' });
761
+ }
762
+ catch { /* browser open is optional */ }
763
+ }
764
+ if (data.message) {
765
+ console.log(chalk_1.default.gray(` ${data.message}\n`));
766
+ }
767
+ }
768
+ catch (error) {
769
+ spinner.stop();
770
+ const errMsg = error instanceof Error ? error.message : 'Unknown error';
771
+ console.log(chalk_1.default.red(`\n❌ ${label} import failed: ${errMsg}\n`));
772
+ console.log(chalk_1.default.gray('Tip: Make sure your project is pushed first with `vigthoria repo push`\n'));
773
+ }
774
+ }
717
775
  /**
718
776
  * Clone a public project from Vigthoria Repository
719
777
  */
package/dist/index.js CHANGED
@@ -514,6 +514,8 @@ async function main() {
514
514
  .option('-d, --description <text>', 'Project description')
515
515
  .option('-f, --force', 'Overwrite existing project', false)
516
516
  .option('-y, --yes', 'Run non-interactively with provided/default values', false)
517
+ .option('--open-in <engine>', 'After push, open project in engine: shop, visual, game')
518
+ .option('--browser', 'Open result URL in default browser', false)
517
519
  .action(async (pathArg, options) => {
518
520
  const repo = new repo_js_1.RepoCommand(config, logger);
519
521
  await repo.push({
@@ -524,6 +526,15 @@ async function main() {
524
526
  force: options.force,
525
527
  yes: options.yes
526
528
  });
529
+ if (options.openIn) {
530
+ const engine = options.openIn;
531
+ if (!['shop', 'visual', 'game'].includes(engine)) {
532
+ logger.warn(`Unknown engine "${engine}". Use: shop, visual, game`);
533
+ }
534
+ else {
535
+ await repo.openIn(engine, options.name || undefined, { browser: options.browser });
536
+ }
537
+ }
527
538
  });
528
539
  repoCommand
529
540
  .command('pull <name>')
@@ -582,6 +593,27 @@ async function main() {
582
593
  force: options.force
583
594
  });
584
595
  });
596
+ repoCommand
597
+ .command('open-in <engine> [projectName]')
598
+ .description('Open a Vigthoria Repo project in the Shop Engine, Visual Editor, or Gaming Engine')
599
+ .option('--browser', 'Open result URL in default browser', false)
600
+ .option('--shop-id <id>', 'Target shop ID (for shop engine)', 'default')
601
+ .addHelpText('after', `
602
+ Examples:
603
+ vigthoria repo open-in shop my-store
604
+ vigthoria repo open-in visual my-website
605
+ vigthoria repo open-in game my-game --browser`)
606
+ .action(async (engine, projectName, options) => {
607
+ if (!['shop', 'visual', 'game'].includes(engine)) {
608
+ logger.error(`Unknown engine "${engine}". Valid options: shop, visual, game`);
609
+ process.exit(1);
610
+ }
611
+ const repo = new repo_js_1.RepoCommand(config, logger);
612
+ await repo.openIn(engine, projectName, {
613
+ browser: options.browser,
614
+ shopId: options.shopId
615
+ });
616
+ });
585
617
  // Default repo action shows help with available subcommands
586
618
  repoCommand.action(() => {
587
619
  repoCommand.outputHelp();
package/dist/utils/api.js CHANGED
@@ -89,24 +89,20 @@ function formatCLIError(err) {
89
89
  return `${tag} ${err.message}`;
90
90
  }
91
91
  }
92
- // SSE-based architecture: NO hard timeouts. Streams stay open until
93
- // complete/error/disconnect. Liveness is checked via heartbeat events.
94
- // Set to 0 (disabled) by default. Only use a timeout as an emergency
95
- // safety net when explicitly configured via env vars.
96
92
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
97
- const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS || '0';
93
+ const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS || '1200000';
98
94
  const parsed = Number.parseInt(rawValue, 10);
99
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
95
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1200000;
100
96
  })();
101
97
  const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
102
- const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS || '0';
98
+ const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS || '90000';
103
99
  const parsed = Number.parseInt(rawValue, 10);
104
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
100
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
105
101
  })();
106
102
  const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
107
- const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS || '0';
103
+ const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS || '300000';
108
104
  const parsed = Number.parseInt(rawValue, 10);
109
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
105
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
110
106
  })();
111
107
  class APIClient {
112
108
  client;
@@ -2736,6 +2732,7 @@ document.addEventListener('DOMContentLoaded', () => {
2736
2732
  && context.localMachineCapable !== false;
2737
2733
  const rescueEligibleSaaS = preferLocalV3
2738
2734
  && /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
2735
+ const timeoutMs = rescueEligibleSaaS ? Math.min(baseTimeoutMs, 210000) : baseTimeoutMs;
2739
2736
  const maxAttempts = preferLocalV3 ? 2 : 1;
2740
2737
  let lastErrors = [];
2741
2738
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -2768,9 +2765,8 @@ document.addEventListener('DOMContentLoaded', () => {
2768
2765
  stream: true,
2769
2766
  };
2770
2767
  for (const baseUrl of this.getV3AgentBaseUrls(preferLocalV3)) {
2771
- // No hard timeout — SSE stream stays alive until server sends
2772
- // complete/error/[DONE] or the TCP connection drops.
2773
2768
  const controller = new AbortController();
2769
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2774
2770
  try {
2775
2771
  const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
2776
2772
  if (!response.ok) {
@@ -2802,6 +2798,7 @@ document.addEventListener('DOMContentLoaded', () => {
2802
2798
  stream: true,
2803
2799
  };
2804
2800
  const continueController = new AbortController();
2801
+ const continueTimeoutId = setTimeout(() => continueController.abort(), timeoutMs);
2805
2802
  try {
2806
2803
  const continueHeaders = await this.getV3AgentHeaders();
2807
2804
  const continueResponse = await fetch(this.getV3AgentContinueUrl(baseUrl), {
@@ -2819,7 +2816,7 @@ document.addEventListener('DOMContentLoaded', () => {
2819
2816
  break; // Fall through to normal completion with partial data
2820
2817
  }
2821
2818
  finally {
2822
- // no timeout to clear — SSE stream ends naturally
2819
+ clearTimeout(continueTimeoutId);
2823
2820
  }
2824
2821
  }
2825
2822
  // Use the final continuation data for workspace recovery
@@ -2875,6 +2872,9 @@ document.addEventListener('DOMContentLoaded', () => {
2875
2872
  }
2876
2873
  errors.push(`${baseUrl}: ${error?.message || String(error)}`);
2877
2874
  }
2875
+ finally {
2876
+ clearTimeout(timeoutId);
2877
+ }
2878
2878
  }
2879
2879
  lastErrors = errors;
2880
2880
  const shouldRetry = attempt < maxAttempts
@@ -2940,6 +2940,7 @@ document.addEventListener('DOMContentLoaded', () => {
2940
2940
  }
2941
2941
  async runOperatorWorkflow(message, context = {}) {
2942
2942
  const executionContext = await this.bindExecutionContext(context);
2943
+ const timeoutMs = context.operatorTimeoutMs || DEFAULT_OPERATOR_TIMEOUT_MS;
2943
2944
  const errors = [];
2944
2945
  const authToken = this.config.get('authToken');
2945
2946
  // Collect a lightweight workspace file listing so the operator can
@@ -2947,10 +2948,8 @@ document.addEventListener('DOMContentLoaded', () => {
2947
2948
  const workspacePath = executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd();
2948
2949
  const workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath);
2949
2950
  for (const baseUrl of this.getOperatorBaseUrls()) {
2950
- // No hard timeout — SSE stream stays open until result/error/disconnect.
2951
- // BMAD sends heartbeat events every 15s; if the TCP connection drops,
2952
- // the fetch will throw naturally.
2953
2951
  const controller = new AbortController();
2952
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2954
2953
  try {
2955
2954
  const response = await fetch(this.getOperatorStreamUrl(baseUrl), {
2956
2955
  method: 'POST',
@@ -3089,8 +3088,16 @@ document.addEventListener('DOMContentLoaded', () => {
3089
3088
  };
3090
3089
  }
3091
3090
  catch (error) {
3091
+ const isAbort = error?.name === 'AbortError' || error?.code === 'ABORT_ERR';
3092
+ if (isAbort) {
3093
+ const mins = Math.round(timeoutMs / 60000);
3094
+ throw new CLIError(`Operator workflow timed out after ${mins} minute(s). You can increase the timeout with VIGTHORIA_OPERATOR_TIMEOUT_MS.`, 'timeout', error);
3095
+ }
3092
3096
  errors.push(`${baseUrl}: ${error?.message || String(error)}`);
3093
3097
  }
3098
+ finally {
3099
+ clearTimeout(timeoutId);
3100
+ }
3094
3101
  }
3095
3102
  throw new CLIError(`Operator workflow failed on all endpoints: ${errors.join(' | ')}`, 'model_backend');
3096
3103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -43,7 +43,8 @@
43
43
  "test:repo:live": "npm run build && node scripts/test-live-repo-e2e.js",
44
44
  "test:game:live": "npm run build && node scripts/test-live-game-push.js",
45
45
  "proof:agent": "npm run build && node scripts/proof-agent-mode.js",
46
- "test:fresh-install": "node scripts/test-fresh-install.js"
46
+ "test:fresh-install": "node scripts/test-fresh-install.js",
47
+ "prepublishOnly": "npm run build && node scripts/verify-package-hygiene.js && node scripts/test-fresh-install.js"
47
48
  },
48
49
  "keywords": [
49
50
  "ai",