vigthoria-cli 1.7.0 → 1.8.0

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,7 +12,6 @@ export declare class AuthCommand {
12
12
  private api;
13
13
  constructor(config: Config, logger: Logger);
14
14
  login(options: LoginOptions): Promise<void>;
15
- private askInput;
16
15
  private doCredentialLogin;
17
16
  private loginWithToken;
18
17
  private loginWithBrowser;
@@ -50,27 +50,30 @@ const api_js_1 = require("../utils/api.js");
50
50
  * which triggers ERR_USE_AFTER_CLOSE on Node 20 Windows TTY when a second
51
51
  * prompt is attempted on the same process.stdin. Using raw readline avoids
52
52
  * this entirely.
53
+ *
54
+ * IMPORTANT: We reuse a single readline.Interface for all prompts in the
55
+ * login flow to avoid the triple-rl stdin contention that causes stalls
56
+ * on Windows TTY and piped terminals.
53
57
  */
54
58
  function ask(rl, question) {
55
59
  return new Promise((resolve) => rl.question(question, resolve));
56
60
  }
57
- function askHidden(question) {
58
- // In non-interactive (piped) terminals, fall back to plain readline
61
+ function askHidden(rl, question) {
62
+ // In non-interactive (piped) terminals, fall back to plain rl.question
59
63
  // because raw data events won't fire after stdin reaches EOF.
60
64
  if (!process.stdin.isTTY) {
61
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
62
65
  return new Promise((resolve) => {
63
66
  rl.question(question, (answer) => {
64
- rl.close();
65
67
  resolve(answer.trim());
66
68
  });
67
69
  });
68
70
  }
69
- return new Promise((resolve, reject) => {
70
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
71
+ return new Promise((resolve) => {
71
72
  const stdout = process.stdout;
72
73
  let answer = '';
73
- process.stdout.write(question);
74
+ // Pause rl so it doesn't compete for stdin data events
75
+ rl.pause();
76
+ stdout.write(question);
74
77
  process.stdin.setRawMode(true);
75
78
  process.stdin.resume();
76
79
  const onData = (ch) => {
@@ -79,7 +82,7 @@ function askHidden(question) {
79
82
  process.stdin.setRawMode(false);
80
83
  process.stdin.removeListener('data', onData);
81
84
  stdout.write('\n');
82
- rl.close();
85
+ rl.resume();
83
86
  resolve(answer);
84
87
  }
85
88
  else if (c === '\u007f' || c === '\b') {
@@ -128,17 +131,18 @@ class AuthCommand {
128
131
  console.log(chalk_1.default.white(' 3) Browser Login'));
129
132
  console.log();
130
133
  const choice = (await ask(rl, chalk_1.default.cyan('? ') + 'Choose login method (1/2/3): ')).trim();
131
- rl.close();
132
134
  switch (choice) {
133
135
  case '1':
134
136
  case 'credentials': {
135
- const email = await this.askInput('Email: ');
137
+ const email = (await ask(rl, chalk_1.default.cyan('? ') + 'Email: ')).trim();
136
138
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
137
139
  if (!emailRegex.test(email)) {
138
140
  this.logger.error('Please enter a valid email');
141
+ rl.close();
139
142
  return;
140
143
  }
141
- const password = await askHidden(chalk_1.default.cyan('? ') + 'Password: ');
144
+ const password = await askHidden(rl, chalk_1.default.cyan('? ') + 'Password: ');
145
+ rl.close();
142
146
  if (password.length < 6) {
143
147
  this.logger.error('Password must be at least 6 characters');
144
148
  return;
@@ -148,7 +152,8 @@ class AuthCommand {
148
152
  }
149
153
  case '2':
150
154
  case 'token': {
151
- const token = await askHidden(chalk_1.default.cyan('? ') + 'API Token: ');
155
+ const token = await askHidden(rl, chalk_1.default.cyan('? ') + 'API Token: ');
156
+ rl.close();
152
157
  if (!token) {
153
158
  this.logger.error('Please enter your API token');
154
159
  return;
@@ -158,9 +163,11 @@ class AuthCommand {
158
163
  }
159
164
  case '3':
160
165
  case 'browser':
166
+ rl.close();
161
167
  await this.loginWithBrowser();
162
168
  break;
163
169
  default:
170
+ rl.close();
164
171
  this.logger.error('Invalid choice. Please enter 1, 2, or 3.');
165
172
  break;
166
173
  }
@@ -170,15 +177,6 @@ class AuthCommand {
170
177
  throw err;
171
178
  }
172
179
  }
173
- askInput(label) {
174
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
175
- return new Promise((resolve) => {
176
- rl.question(chalk_1.default.cyan('? ') + label, (answer) => {
177
- rl.close();
178
- resolve(answer.trim());
179
- });
180
- });
181
- }
182
180
  async doCredentialLogin(email, password) {
183
181
  const spinner = (0, logger_js_1.createSpinner)('Logging in...').start();
184
182
  const success = await this.api.login(email, password);
@@ -326,10 +326,19 @@ class ChatCommand {
326
326
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
327
327
  const shortTarget = toolTarget ? ` → ${String(toolTarget).split('/').slice(-2).join('/')}` : '';
328
328
  const stepLabel = chalk_1.default.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
329
- // Print each tool call as a persistent log line so the user sees progress
330
329
  if (spinner.isSpinning)
331
330
  spinner.stop();
332
331
  process.stderr.write(stepLabel + '\n');
332
+ // Show extra detail for key tools
333
+ const args = event.arguments || {};
334
+ const toolName = event.name || event.tool || '';
335
+ if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
336
+ const len = args.content.length;
337
+ process.stderr.write(chalk_1.default.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
338
+ }
339
+ else if (toolName === 'bash' && typeof args.command === 'string') {
340
+ process.stderr.write(chalk_1.default.gray(` $ ${args.command.slice(0, 120)}${args.command.length > 120 ? '…' : ''}\n`));
341
+ }
333
342
  spinner.start();
334
343
  spinner.text = `Running ${toolDesc}...`;
335
344
  return;
@@ -341,6 +350,16 @@ class ChatCommand {
341
350
  if (spinner.isSpinning)
342
351
  spinner.stop();
343
352
  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() : '';
355
+ if (!success && output) {
356
+ const lines = output.split('\n').slice(0, 4);
357
+ process.stderr.write(chalk_1.default.red(` ${lines.join('\n ')}\n`));
358
+ }
359
+ else if (success && output && output.length > 0) {
360
+ const brief = output.split('\n')[0].slice(0, 120);
361
+ process.stderr.write(chalk_1.default.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
362
+ }
344
363
  spinner.start();
345
364
  spinner.text = 'Next step...';
346
365
  return;
@@ -348,11 +367,9 @@ class ChatCommand {
348
367
  if (event.type === 'thinking') {
349
368
  this.v3IterationCount += 1;
350
369
  const iterText = event.content || '';
351
- const iterMatch = iterText.match(/Iteration (\d+)/i);
352
- const iterNum = iterMatch ? iterMatch[1] : String(this.v3IterationCount);
353
370
  if (spinner.isSpinning)
354
371
  spinner.stop();
355
- process.stderr.write(chalk_1.default.cyan(`\n── Iteration ${iterNum} ──\n`));
372
+ process.stderr.write(chalk_1.default.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
356
373
  spinner.start();
357
374
  spinner.text = 'Analyzing...';
358
375
  return;
@@ -385,18 +402,66 @@ class ChatCommand {
385
402
  const tools = event.tool_calls || this.v3ToolCallCount;
386
403
  if (spinner.isSpinning)
387
404
  spinner.stop();
388
- process.stderr.write(chalk_1.default.green(`\n✓ Complete`) + ` — ${iters} iterations, ${tools} tool calls${elapsed ? `, ${elapsed}` : ''}\n`);
405
+ let statLine = `${iters} iterations, ${tools} tool calls`;
406
+ if (elapsed)
407
+ statLine += `, ${elapsed}`;
408
+ if (event.mode === 'planner-executor') {
409
+ const ok = event.tasks_completed ?? '?';
410
+ const fail = event.tasks_failed ?? 0;
411
+ statLine += ` — ${ok} tasks done`;
412
+ if (fail > 0)
413
+ statLine += chalk_1.default.yellow(`, ${fail} failed`);
414
+ }
415
+ process.stderr.write(chalk_1.default.green(`\n✓ Complete`) + ` — ${statLine}\n`);
416
+ // Show seal quality score if available
417
+ if (event.seal_score && typeof event.seal_score.overall === 'number') {
418
+ const score = event.seal_score.overall;
419
+ const tier = event.seal_score.tier || '';
420
+ const scoreColor = score >= 7 ? chalk_1.default.green : score >= 5 ? chalk_1.default.yellow : chalk_1.default.red;
421
+ process.stderr.write(chalk_1.default.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk_1.default.gray(` (${tier})`) : '') + '\n');
422
+ }
389
423
  return;
390
424
  }
391
425
  if (event.type === 'plan') {
392
- const planKind = event.plan?.task_kind || event.task_kind || '';
426
+ const plan = event.plan || {};
427
+ const planKind = plan.task_kind || event.task_kind || '';
428
+ const quality = plan.quality_profile || '';
393
429
  if (spinner.isSpinning)
394
430
  spinner.stop();
395
- process.stderr.write(chalk_1.default.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}...\n`);
431
+ process.stderr.write(chalk_1.default.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
432
+ if (quality)
433
+ process.stderr.write(chalk_1.default.gray(` (${quality})`));
434
+ process.stderr.write('\n');
435
+ // Planner-executor: show task list
436
+ if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
437
+ process.stderr.write(chalk_1.default.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
438
+ for (const t of plan.tasks.slice(0, 10)) {
439
+ const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.join(', ')}` : '';
440
+ process.stderr.write(chalk_1.default.gray(` • ${t.title || t.id}${targets}\n`));
441
+ }
442
+ if (plan.tasks.length > 10) {
443
+ process.stderr.write(chalk_1.default.gray(` ... and ${plan.tasks.length - 10} more\n`));
444
+ }
445
+ }
446
+ // Monolithic: show target files
447
+ if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
448
+ process.stderr.write(chalk_1.default.gray(` Files: ${plan.target_files.join(', ')}\n`));
449
+ }
396
450
  spinner.start();
397
451
  spinner.text = 'Planning...';
398
452
  return;
399
453
  }
454
+ if (event.type === 'file_mutation') {
455
+ const filePath = typeof event.path === 'string' ? event.path.split('/').slice(-2).join('/') : '';
456
+ const action = event.action === 'delete' ? chalk_1.default.red('deleted') : chalk_1.default.green('wrote');
457
+ if (filePath) {
458
+ if (spinner.isSpinning)
459
+ spinner.stop();
460
+ process.stderr.write(chalk_1.default.cyan(' [File] ') + `${action} ${filePath}\n`);
461
+ spinner.start();
462
+ }
463
+ return;
464
+ }
400
465
  if (event.type === 'error') {
401
466
  if (event.checkpointed) {
402
467
  if (spinner.isSpinning)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [