vigthoria-cli 1.6.29 → 1.6.30

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.
@@ -240,10 +240,14 @@ class AuthCommand {
240
240
  if (capabilityStatus.devtoolsBridge.error) {
241
241
  console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.gray(capabilityStatus.devtoolsBridge.error));
242
242
  }
243
- // Auth scope summary
243
+ // Auth scope summary — use a real server-side probe for Model Auth
244
+ const tokenValidation = await this.api.validateToken();
244
245
  console.log();
245
246
  console.log(chalk_1.default.white('Auth Scopes:'));
246
- console.log(chalk_1.default.gray(' Model Auth: ') + (this.config.isAuthenticated() ? chalk_1.default.green('Active') : chalk_1.default.red('Missing')) + chalk_1.default.gray(' (used by chat, agent, review, explain, generate, fix)'));
247
+ console.log(chalk_1.default.gray(' Model Auth: ') + (tokenValidation.valid ? chalk_1.default.green('Valid') : chalk_1.default.red('Invalid')) + chalk_1.default.gray(' (used by chat, agent, review, explain, generate, fix)'));
248
+ if (!tokenValidation.valid && tokenValidation.error) {
249
+ console.log(chalk_1.default.gray(' ') + chalk_1.default.red(tokenValidation.error));
250
+ }
247
251
  console.log(chalk_1.default.gray(' Repo Auth: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.yellow('Inactive')) + chalk_1.default.gray(' (used by repo push/pull/list only)'));
248
252
  console.log(chalk_1.default.gray(' Bridge Auth: ') + (capabilityStatus.devtoolsBridge.ok ? chalk_1.default.green('Connected') : chalk_1.default.gray('N/A')) + chalk_1.default.gray(' (used by --bridge flag only)'));
249
253
  console.log();
@@ -29,6 +29,12 @@ class EditCommand {
29
29
  this.logger.error('Not authenticated. Run: vigthoria login');
30
30
  return;
31
31
  }
32
+ // Server-side token validation — fail fast instead of waiting for 401
33
+ const tokenCheck = await this.api.validateToken();
34
+ if (!tokenCheck.valid) {
35
+ this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
36
+ return;
37
+ }
32
38
  // Read file
33
39
  const file = this.fileUtils.readFile(filePath);
34
40
  if (!file) {
@@ -46,6 +52,11 @@ class EditCommand {
46
52
  this.logger.error('The --apply flag requires --instruction. Example: vigthoria edit file.ts --apply --instruction "fix the bug"');
47
53
  return;
48
54
  }
55
+ // Non-TTY stdin cannot prompt interactively
56
+ if (!process.stdin.isTTY) {
57
+ this.logger.error('No --instruction provided and stdin is not interactive. Use: vigthoria edit file.ts --instruction "..."');
58
+ return;
59
+ }
49
60
  const answer = await inquirer_1.default.prompt([
50
61
  {
51
62
  type: 'input',
@@ -93,11 +104,14 @@ Return the complete modified file content:`,
93
104
  ], options.model);
94
105
  spinner.stop();
95
106
  // Extract code from response
96
- const modifiedCode = this.extractCode(response.message, file.language);
107
+ let modifiedCode = this.extractCode(response.message, file.language);
97
108
  if (!modifiedCode) {
98
109
  this.logger.error('Failed to generate valid code changes');
99
110
  return;
100
111
  }
112
+ // Always deduplicate — extractCode only dedupes on the no-fence
113
+ // fallback path, so fenced responses (the common case) need this.
114
+ modifiedCode = this.deduplicateCode(modifiedCode);
101
115
  // Show diff and apply
102
116
  if (options.apply) {
103
117
  await this.applyFix(file.path, file.content, modifiedCode);
@@ -118,6 +132,12 @@ Return the complete modified file content:`,
118
132
  this.logger.error('Not authenticated. Run: vigthoria login');
119
133
  return;
120
134
  }
135
+ // Server-side token validation — fail fast instead of waiting for 401
136
+ const tokenCheck = await this.api.validateToken();
137
+ if (!tokenCheck.valid) {
138
+ this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
139
+ return;
140
+ }
121
141
  // Read file
122
142
  const file = this.fileUtils.readFile(filePath);
123
143
  if (!file) {
@@ -238,11 +258,15 @@ Return the complete modified file content:`,
238
258
  runEnd++;
239
259
  }
240
260
  const runLen = runEnd - i;
261
+ // Check if this run is "effectively at end" — only trailing empty
262
+ // lines follow the duplicate pair.
263
+ const isAtEffectiveEnd = runEnd === lines.length
264
+ || lines.slice(runEnd).every(l => l.trim() === '');
241
265
  if (runLen >= 3) {
242
266
  // 3+ identical lines is almost certainly stutter — keep one
243
267
  deduped.push(lines[i]);
244
268
  }
245
- else if (runLen === 2 && runEnd === lines.length) {
269
+ else if (runLen === 2 && isAtEffectiveEnd) {
246
270
  // Exactly 2 identical lines at the very end — trailing stutter
247
271
  deduped.push(lines[i]);
248
272
  }
@@ -297,6 +321,11 @@ Return the complete modified file content:`,
297
321
  await this.applyFix(filePath, original, modified);
298
322
  return;
299
323
  }
324
+ // Non-TTY: show the diff but don't try to prompt — re-run with --apply
325
+ if (!process.stdin.isTTY) {
326
+ this.logger.info('Non-interactive mode. Re-run with --apply to apply changes.');
327
+ return;
328
+ }
300
329
  const { action } = await inquirer_1.default.prompt([
301
330
  {
302
331
  type: 'list',
@@ -33,6 +33,12 @@ class ExplainCommand {
33
33
  this.logger.error('Not authenticated. Run: vigthoria login');
34
34
  return;
35
35
  }
36
+ // Server-side token validation — fail fast instead of waiting for 401
37
+ const tokenCheck = await this.api.validateToken();
38
+ if (!tokenCheck.valid) {
39
+ this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
40
+ return;
41
+ }
36
42
  // Read file
37
43
  const file = this.fileUtils.readFile(filePath);
38
44
  if (!file) {
@@ -31,6 +31,12 @@ class GenerateCommand {
31
31
  this.logger.error('Not authenticated. Run: vigthoria login');
32
32
  return;
33
33
  }
34
+ // Server-side token validation — fail fast instead of waiting for 401
35
+ const tokenCheck = await this.api.validateToken();
36
+ if (!tokenCheck.valid) {
37
+ this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
38
+ return;
39
+ }
34
40
  // Determine mode
35
41
  const proMode = options.pro === true;
36
42
  // Auto-detect language from description if not explicitly specified
@@ -33,6 +33,12 @@ class ReviewCommand {
33
33
  this.logger.error('Not authenticated. Run: vigthoria login');
34
34
  return;
35
35
  }
36
+ // Server-side token validation — fail fast instead of waiting for 401
37
+ const tokenCheck = await this.api.validateToken();
38
+ if (!tokenCheck.valid) {
39
+ this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
40
+ return;
41
+ }
36
42
  // Read file
37
43
  const file = this.fileUtils.readFile(filePath);
38
44
  if (!file) {
@@ -201,6 +201,17 @@ export declare class APIClient {
201
201
  private refreshToken;
202
202
  getSubscriptionStatus(): Promise<void>;
203
203
  private getAccessToken;
204
+ /**
205
+ * Validate the current auth token against the Coder API.
206
+ * Returns { valid: true } when the server accepts the token,
207
+ * { valid: false, error } when the token is rejected (401/403),
208
+ * and { valid: true } when the server is unreachable (network error)
209
+ * so that offline/degraded scenarios don't block the user.
210
+ */
211
+ validateToken(): Promise<{
212
+ valid: boolean;
213
+ error?: string;
214
+ }>;
204
215
  getV3AgentBaseUrls(preferLocal?: boolean): string[];
205
216
  getV3AgentRunUrl(baseUrl: string): string;
206
217
  getV3AgentContinueUrl(baseUrl: string): string;
package/dist/utils/api.js CHANGED
@@ -340,6 +340,34 @@ class APIClient {
340
340
  || this.config.get('authToken')
341
341
  || null;
342
342
  }
343
+ /**
344
+ * Validate the current auth token against the Coder API.
345
+ * Returns { valid: true } when the server accepts the token,
346
+ * { valid: false, error } when the token is rejected (401/403),
347
+ * and { valid: true } when the server is unreachable (network error)
348
+ * so that offline/degraded scenarios don't block the user.
349
+ */
350
+ async validateToken() {
351
+ const token = this.getAccessToken();
352
+ if (!token) {
353
+ return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
354
+ }
355
+ try {
356
+ await this.client.get('/api/user/profile', { timeout: 10000 });
357
+ return { valid: true };
358
+ }
359
+ catch (error) {
360
+ if (error instanceof CLIError && error.category === 'auth') {
361
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
362
+ }
363
+ const axErr = error;
364
+ if (axErr.response?.status === 401 || axErr.response?.status === 403) {
365
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
366
+ }
367
+ // Network/timeout errors — don't assume token is bad
368
+ return { valid: true };
369
+ }
370
+ }
343
371
  getV3AgentBaseUrls(preferLocal = false) {
344
372
  const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
345
373
  const allowLocalV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1' || preferLocal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.29",
3
+ "version": "1.6.30",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [