vigthoria-cli 1.6.57 → 1.6.59

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.
@@ -12,8 +12,7 @@ export declare class AuthCommand {
12
12
  private api;
13
13
  constructor(config: Config, logger: Logger);
14
14
  login(options: LoginOptions): Promise<void>;
15
- private loginWithCredentials;
16
- private loginWithTokenPrompt;
15
+ private doCredentialLogin;
17
16
  private loginWithToken;
18
17
  private loginWithBrowser;
19
18
  logout(): Promise<void>;
@@ -29,7 +29,11 @@ class AuthCommand {
29
29
  console.log();
30
30
  console.log(chalk_1.default.cyan(`${logger_js_1.CH.hDouble.repeat(3)} Vigthoria Login ${logger_js_1.CH.hDouble.repeat(3)}`));
31
31
  console.log();
32
- // Prompt for credentials
32
+ // Collect ALL interactive input in a single inquirer.prompt() call.
33
+ // inquirer 9.x creates and destroys a readline interface per prompt()
34
+ // call; on Node 20 TTY the second readline on the same process.stdin
35
+ // throws ERR_USE_AFTER_CLOSE. Using `when` conditionals keeps one
36
+ // readline alive for the entire login flow.
33
37
  const answers = await inquirer_1.default.prompt([
34
38
  {
35
39
  type: 'list',
@@ -41,25 +45,11 @@ class AuthCommand {
41
45
  { name: 'Browser Login', value: 'browser' },
42
46
  ],
43
47
  },
44
- ]);
45
- switch (answers.method) {
46
- case 'credentials':
47
- await this.loginWithCredentials();
48
- break;
49
- case 'token':
50
- await this.loginWithTokenPrompt();
51
- break;
52
- case 'browser':
53
- await this.loginWithBrowser();
54
- break;
55
- }
56
- }
57
- async loginWithCredentials() {
58
- const credentials = await inquirer_1.default.prompt([
59
48
  {
60
49
  type: 'input',
61
50
  name: 'email',
62
51
  message: 'Email:',
52
+ when: (ans) => ans.method === 'credentials',
63
53
  validate: (input) => {
64
54
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
65
55
  return emailRegex.test(input) || 'Please enter a valid email';
@@ -70,11 +60,33 @@ class AuthCommand {
70
60
  name: 'password',
71
61
  message: 'Password:',
72
62
  mask: '*',
63
+ when: (ans) => ans.method === 'credentials',
73
64
  validate: (input) => input.length >= 6 || 'Password must be at least 6 characters',
74
65
  },
66
+ {
67
+ type: 'password',
68
+ name: 'token',
69
+ message: 'API Token:',
70
+ mask: '*',
71
+ when: (ans) => ans.method === 'token',
72
+ validate: (input) => input.length > 0 || 'Please enter your API token',
73
+ },
75
74
  ]);
75
+ switch (answers.method) {
76
+ case 'credentials':
77
+ await this.doCredentialLogin(answers.email, answers.password);
78
+ break;
79
+ case 'token':
80
+ await this.loginWithToken(answers.token);
81
+ break;
82
+ case 'browser':
83
+ await this.loginWithBrowser();
84
+ break;
85
+ }
86
+ }
87
+ async doCredentialLogin(email, password) {
76
88
  const spinner = (0, logger_js_1.createSpinner)('Logging in...').start();
77
- const success = await this.api.login(credentials.email, credentials.password);
89
+ const success = await this.api.login(email, password);
78
90
  spinner.stop();
79
91
  if (success) {
80
92
  this.printLoginSuccess();
@@ -83,18 +95,6 @@ class AuthCommand {
83
95
  this.logger.error('Login failed. Please check your credentials.');
84
96
  }
85
97
  }
86
- async loginWithTokenPrompt() {
87
- const { token } = await inquirer_1.default.prompt([
88
- {
89
- type: 'password',
90
- name: 'token',
91
- message: 'API Token:',
92
- mask: '*',
93
- validate: (input) => input.length > 0 || 'Please enter your API token',
94
- },
95
- ]);
96
- await this.loginWithToken(token);
97
- }
98
98
  async loginWithToken(token) {
99
99
  const spinner = (0, logger_js_1.createSpinner)('Validating token...').start();
100
100
  const success = await this.api.loginWithToken(token);
@@ -74,6 +74,7 @@ export declare class ChatCommand {
74
74
  private v3IterationCount;
75
75
  private v3ToolCallCount;
76
76
  private v3LastActivity;
77
+ private v3StreamingStarted;
77
78
  private describeV3AgentTool;
78
79
  private updateV3AgentSpinner;
79
80
  private updateOperatorSpinner;
@@ -298,6 +298,7 @@ class ChatCommand {
298
298
  v3IterationCount = 0;
299
299
  v3ToolCallCount = 0;
300
300
  v3LastActivity = Date.now();
301
+ v3StreamingStarted = false;
301
302
  describeV3AgentTool(toolName) {
302
303
  const normalized = String(toolName || '').toLowerCase();
303
304
  if (/read|grep|search|list|find|glob/.test(normalized)) {
@@ -324,6 +325,9 @@ class ChatCommand {
324
325
  const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
325
326
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
326
327
  const shortTarget = toolTarget ? ` → ${String(toolTarget).split('/').slice(-2).join('/')}` : '';
328
+ if (this.v3StreamingStarted && !spinner.isSpinning) {
329
+ spinner.start();
330
+ }
327
331
  spinner.text = chalk_1.default.cyan(`[${this.v3IterationCount}/${this.v3ToolCallCount}] `) + `${toolDesc}${shortTarget}`;
328
332
  return;
329
333
  }
@@ -331,6 +335,9 @@ class ChatCommand {
331
335
  const success = event.success !== false;
332
336
  const toolName = event.name || event.tool || '';
333
337
  const indicator = success ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
338
+ if (this.v3StreamingStarted && !spinner.isSpinning) {
339
+ spinner.start();
340
+ }
334
341
  spinner.text = `${indicator} ${toolName} complete — next step...`;
335
342
  return;
336
343
  }
@@ -339,12 +346,32 @@ class ChatCommand {
339
346
  const iterText = event.content || '';
340
347
  const iterMatch = iterText.match(/Iteration (\d+)/i);
341
348
  const iterNum = iterMatch ? iterMatch[1] : String(this.v3IterationCount);
349
+ if (this.v3StreamingStarted && !spinner.isSpinning) {
350
+ spinner.start();
351
+ }
342
352
  spinner.text = chalk_1.default.cyan(`[Iteration ${iterNum}] `) + 'Analyzing...';
343
353
  return;
344
354
  }
345
- if (event.type === 'message') {
346
- const preview = String(event.content || '').slice(0, 80).replace(/\n/g, ' ');
347
- spinner.text = chalk_1.default.cyan('[Response] ') + (preview || 'Writing response...');
355
+ if (event.type === 'message' || event.type === 'assistant' || event.type === 'text' || event.type === 'content_block_delta') {
356
+ const text = event.type === 'content_block_delta'
357
+ ? (event.delta?.text || '')
358
+ : (event.content || '');
359
+ if (text) {
360
+ if (!this.v3StreamingStarted) {
361
+ this.v3StreamingStarted = true;
362
+ spinner.stop();
363
+ if (!this.directPromptMode) {
364
+ console.log();
365
+ }
366
+ }
367
+ else {
368
+ spinner.stop();
369
+ }
370
+ process.stdout.write(text);
371
+ }
372
+ else {
373
+ spinner.text = chalk_1.default.cyan('[Response] ') + 'Writing response...';
374
+ }
348
375
  return;
349
376
  }
350
377
  if (event.type === 'complete') {
@@ -828,13 +855,19 @@ class ChatCommand {
828
855
  await this.runOperatorDirectAnswer(prompt);
829
856
  return;
830
857
  }
831
- // ── Repo-grounded operator path: use the agent loop with tools ──
858
+ // ── Repo-grounded operator path: try V3 agent first, then fall back ──
832
859
  // Prompts that reference files, code symbols, or analysis verbs need
833
860
  // tool access to read actual file contents. Route these through the
834
- // local agent loop (which has read_file, grep, etc.) instead of the
835
- // toolless BMAD SSE workflow which cannot read files and will fabricate.
861
+ // V3 agent (which archives runs for history/replay/fork and supports
862
+ // workspace hydration) first. Falls back to local agent loop if V3
863
+ // is unreachable.
836
864
  if (this.isRepoGroundedPrompt(prompt) && this.tools) {
837
865
  this.operatorMode = true;
866
+ const handledByV3 = await this.tryV3AgentWorkflow(prompt);
867
+ if (handledByV3) {
868
+ this.saveSession();
869
+ return;
870
+ }
838
871
  await this.runLocalAgentLoop(prompt);
839
872
  return;
840
873
  }
@@ -1431,6 +1464,7 @@ class ChatCommand {
1431
1464
  this.v3IterationCount = 0;
1432
1465
  this.v3ToolCallCount = 0;
1433
1466
  this.v3LastActivity = Date.now();
1467
+ this.v3StreamingStarted = false;
1434
1468
  const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
1435
1469
  text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
1436
1470
  spinner: 'clock',
@@ -1484,6 +1518,10 @@ class ChatCommand {
1484
1518
  if (spinner) {
1485
1519
  spinner.stop();
1486
1520
  }
1521
+ // End streaming line if we streamed text inline
1522
+ if (this.v3StreamingStarted) {
1523
+ process.stdout.write('\n');
1524
+ }
1487
1525
  const previewGate = (response.metadata?.previewGate || null);
1488
1526
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
1489
1527
  const success = previewGate?.required === true
@@ -1536,6 +1574,12 @@ class ChatCommand {
1536
1574
  metadata: response.metadata || {},
1537
1575
  }, null, 2));
1538
1576
  }
1577
+ else if (this.v3StreamingStarted) {
1578
+ // Content was already streamed to stdout in real-time; skip duplicate print.
1579
+ if (!this.directPromptMode) {
1580
+ console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
1581
+ }
1582
+ }
1539
1583
  else if (response.content) {
1540
1584
  if (!this.directPromptMode) {
1541
1585
  console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
@@ -1976,10 +2020,11 @@ class ChatCommand {
1976
2020
  if (!this.directPromptMode) {
1977
2021
  return false;
1978
2022
  }
1979
- if (this.isServerBindableWorkspace(this.currentProjectPath)) {
1980
- return false;
1981
- }
1982
- return /(analyse|analyze|audit|overview|inspect|explain|review|debug|diagnos|read|summari[sz]e|investigate|list|show|find|search|check|scan|count|what|describe)/i.test(prompt);
2023
+ // Always prefer V3 agent first — it supports workspace hydration for
2024
+ // non-server-bindable workspaces (Windows paths, remote machines).
2025
+ // The V3 path will fall back to local-agent-loop if V3 is unreachable.
2026
+ // This ensures runs get archived for history/replay/fork.
2027
+ return false;
1983
2028
  }
1984
2029
  shouldRequireV3AgentWorkflow(prompt) {
1985
2030
  if (!this.directPromptMode) {
@@ -127,6 +127,7 @@ class DeployCommand {
127
127
  * Deploy to preview URL (free)
128
128
  */
129
129
  async deployToPreview(projectPath) {
130
+ this.requireAuth();
130
131
  const spinner = (0, logger_js_1.createSpinner)('Deploying to preview...').start();
131
132
  try {
132
133
  const projectDir = projectPath || process.cwd();
@@ -155,6 +156,7 @@ class DeployCommand {
155
156
  this.logger.error('Deploy failed');
156
157
  const errMsg = error instanceof Error ? error.message : 'Unknown error';
157
158
  console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
159
+ process.exitCode = 1;
158
160
  }
159
161
  }
160
162
  /**
@@ -11,6 +11,7 @@ export declare class ForkCommand {
11
11
  constructor(config: Config, logger: Logger);
12
12
  private getHeaders;
13
13
  private getBaseUrl;
14
+ private resolveWorkspaceRoot;
14
15
  run(runId: string, message: string, options: ForkOptions): Promise<void>;
15
16
  }
16
17
  export {};
@@ -31,18 +31,35 @@ class ForkCommand {
31
31
  return headers;
32
32
  }
33
33
  getBaseUrl() {
34
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
35
+ const allowLocal = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
34
36
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
35
37
  process.env.V3_AGENT_URL ||
36
- 'http://127.0.0.1:8030');
38
+ (allowLocal ? 'http://127.0.0.1:8030' : null) ||
39
+ configuredApiUrl);
40
+ }
41
+ resolveWorkspaceRoot(project) {
42
+ if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
43
+ return '';
44
+ if (typeof require !== 'undefined') {
45
+ try {
46
+ const path = require('path');
47
+ if (!path.isAbsolute(project))
48
+ return '';
49
+ }
50
+ catch { }
51
+ }
52
+ return project;
37
53
  }
38
54
  async run(runId, message, options) {
39
55
  const project = options.project || process.cwd();
56
+ const workspace = this.resolveWorkspaceRoot(project);
40
57
  const eventIndex = options.eventIndex || 0;
41
58
  const spinner = (0, logger_js_1.createSpinner)(`Forking run ${runId}...`).start();
42
59
  try {
43
60
  const baseUrl = this.getBaseUrl();
44
61
  const body = {
45
- workspace_root: project,
62
+ workspace_root: workspace,
46
63
  from_event_index: eventIndex,
47
64
  new_request: message || '',
48
65
  stream: true,
@@ -58,6 +75,9 @@ class ForkCommand {
58
75
  if (resp.status === 404) {
59
76
  this.logger.error(`Run ${runId} not found or has no event log.`);
60
77
  }
78
+ else if (resp.status === 502 || resp.status === 503) {
79
+ this.logger.error(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`);
80
+ }
61
81
  else {
62
82
  this.logger.error(`Fork failed: ${resp.status} ${resp.statusText}`);
63
83
  }
@@ -11,6 +11,7 @@ export declare class HistoryCommand {
11
11
  constructor(config: Config, logger: Logger);
12
12
  private getHeaders;
13
13
  private getBaseUrl;
14
+ private resolveWorkspaceRoot;
14
15
  run(options: HistoryOptions): Promise<void>;
15
16
  }
16
17
  export {};
@@ -31,26 +31,49 @@ class HistoryCommand {
31
31
  return headers;
32
32
  }
33
33
  getBaseUrl() {
34
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
35
+ const allowLocal = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
34
36
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
35
37
  process.env.V3_AGENT_URL ||
36
- 'http://127.0.0.1:8030');
38
+ (allowLocal ? 'http://127.0.0.1:8030' : null) ||
39
+ configuredApiUrl);
40
+ }
41
+ resolveWorkspaceRoot(project) {
42
+ // On Windows or non-absolute paths, send empty so the server uses its fallback root
43
+ if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
44
+ return '';
45
+ if (typeof require !== 'undefined') {
46
+ try {
47
+ const path = require('path');
48
+ if (!path.isAbsolute(project))
49
+ return '';
50
+ }
51
+ catch { }
52
+ }
53
+ return project;
37
54
  }
38
55
  async run(options) {
39
56
  const limit = options.limit || 20;
40
57
  const project = options.project || process.cwd();
58
+ const workspace = this.resolveWorkspaceRoot(project);
41
59
  const spinner = (0, logger_js_1.createSpinner)('Loading run history...').start();
42
60
  try {
43
61
  const baseUrl = this.getBaseUrl();
44
62
  const params = new URLSearchParams({
45
63
  limit: String(limit),
46
- workspace_root: project,
64
+ workspace_root: workspace,
47
65
  });
48
66
  const resp = await fetch(`${baseUrl}/api/runs?${params}`, {
49
67
  headers: this.getHeaders(),
50
68
  });
51
69
  if (!resp.ok) {
52
70
  spinner.stop();
53
- this.logger.error(`Failed to fetch runs: ${resp.status} ${resp.statusText}`);
71
+ if (resp.status === 404 || resp.status === 502 || resp.status === 503) {
72
+ this.logger.error(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`);
73
+ }
74
+ else {
75
+ this.logger.error(`Failed to fetch runs: ${resp.status} ${resp.statusText}`);
76
+ }
54
77
  return;
55
78
  }
56
79
  const data = (await resp.json());
@@ -11,6 +11,7 @@ export declare class ReplayCommand {
11
11
  constructor(config: Config, logger: Logger);
12
12
  private getHeaders;
13
13
  private getBaseUrl;
14
+ private resolveWorkspaceRoot;
14
15
  private sleep;
15
16
  run(runId: string, options: ReplayOptions): Promise<void>;
16
17
  }
@@ -31,9 +31,25 @@ class ReplayCommand {
31
31
  return headers;
32
32
  }
33
33
  getBaseUrl() {
34
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
35
+ const allowLocal = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
34
36
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
35
37
  process.env.V3_AGENT_URL ||
36
- 'http://127.0.0.1:8030');
38
+ (allowLocal ? 'http://127.0.0.1:8030' : null) ||
39
+ configuredApiUrl);
40
+ }
41
+ resolveWorkspaceRoot(project) {
42
+ if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
43
+ return '';
44
+ if (typeof require !== 'undefined') {
45
+ try {
46
+ const path = require('path');
47
+ if (!path.isAbsolute(project))
48
+ return '';
49
+ }
50
+ catch { }
51
+ }
52
+ return project;
37
53
  }
38
54
  sleep(ms) {
39
55
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -41,10 +57,11 @@ class ReplayCommand {
41
57
  async run(runId, options) {
42
58
  const speed = options.speed || 200;
43
59
  const project = options.project || process.cwd();
60
+ const workspace = this.resolveWorkspaceRoot(project);
44
61
  const spinner = (0, logger_js_1.createSpinner)(`Loading events for run ${runId}...`).start();
45
62
  try {
46
63
  const baseUrl = this.getBaseUrl();
47
- const params = new URLSearchParams({ workspace_root: project });
64
+ const params = new URLSearchParams({ workspace_root: workspace });
48
65
  const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
49
66
  headers: this.getHeaders(),
50
67
  });
@@ -53,6 +70,9 @@ class ReplayCommand {
53
70
  if (resp.status === 404) {
54
71
  this.logger.error(`No event log found for run ${runId}. Events are only recorded for runs made after the replay system was deployed.`);
55
72
  }
73
+ else if (resp.status === 502 || resp.status === 503) {
74
+ this.logger.error(`V3 run-history route is not available on ${baseUrl}. Backend may not expose /api/runs.`);
75
+ }
56
76
  else {
57
77
  this.logger.error(`Failed to fetch events: ${resp.status} ${resp.statusText}`);
58
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.57",
3
+ "version": "1.6.59",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [