vigthoria-cli 1.9.8 → 1.9.10

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.
package/README.md CHANGED
@@ -99,7 +99,7 @@ If you see `ENOTFOUND registry.npmjs.org`, try these solutions:
99
99
  4. **Direct tarball download:**
100
100
  ```bash
101
101
  # Download directly
102
- npm install -g https://coder.vigthoria.io/releases/vigthoria-cli-1.9.8.tgz
102
+ npm install -g https://coder.vigthoria.io/releases/vigthoria-cli-1.9.10.tgz
103
103
  ```
104
104
 
105
105
  5. **Use Git clone method (no npm registry needed):**
@@ -200,6 +200,7 @@ export declare class APIClient {
200
200
  private ws;
201
201
  private vigFlowTokens;
202
202
  private _httpsAgent;
203
+ private lastChatTransportErrors;
203
204
  constructor(config: Config, logger: Logger);
204
205
  /**
205
206
  * Destroy keep-alive sockets so the Node.js event loop can drain
@@ -217,12 +218,12 @@ export declare class APIClient {
217
218
  private getAccessToken;
218
219
  /**
219
220
  * Validate the current auth token against the Coder API.
220
- * Returns { valid: true } when the server accepts the token,
221
- * { valid: false, error } when the token is rejected (401/403),
222
- * and { valid: true } when the server is unreachable (network error)
223
- * so that offline/degraded scenarios don't block the user.
221
+ * By default this fails open on network errors to keep offline commands usable.
224
222
  */
225
- validateToken(): Promise<{
223
+ validateToken(options?: {
224
+ allowNetworkFailOpen?: boolean;
225
+ enforceTokenShape?: boolean;
226
+ }): Promise<{
226
227
  valid: boolean;
227
228
  error?: string;
228
229
  }>;
package/dist/utils/api.js CHANGED
@@ -207,6 +207,7 @@ class APIClient {
207
207
  ws = null;
208
208
  vigFlowTokens = new Map();
209
209
  _httpsAgent = null;
210
+ lastChatTransportErrors = [];
210
211
  constructor(config, logger) {
211
212
  this.config = config;
212
213
  this.logger = logger;
@@ -461,17 +462,25 @@ class APIClient {
461
462
  }
462
463
  /**
463
464
  * Validate the current auth token against the Coder API.
464
- * Returns { valid: true } when the server accepts the token,
465
- * { valid: false, error } when the token is rejected (401/403),
466
- * and { valid: true } when the server is unreachable (network error)
467
- * so that offline/degraded scenarios don't block the user.
465
+ * By default this fails open on network errors to keep offline commands usable.
468
466
  */
469
- async validateToken() {
467
+ async validateToken(options = {}) {
468
+ const allowNetworkFailOpen = options.allowNetworkFailOpen !== false;
469
+ const enforceTokenShape = options.enforceTokenShape !== false;
470
+ const explicitEnvToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
470
471
  const token = this.getAccessToken();
471
472
  if (!token) {
472
473
  return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
473
474
  }
474
- const explicitEnvToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
475
+ // Fast-fail obviously malformed ENV override tokens so invalid-token checks
476
+ // don't get masked by unrelated transport outages. Persisted login tokens may
477
+ // be non-JWT in some deployments and must still be gateway-validated server-side.
478
+ if (enforceTokenShape && explicitEnvToken) {
479
+ const looksLikeJwt = token.split('.').length === 3;
480
+ if (!looksLikeJwt || token.length < 40) {
481
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
482
+ }
483
+ }
475
484
  const headers = {
476
485
  Authorization: `Bearer ${token}`,
477
486
  Cookie: `vigthoria-auth-token=${token}`,
@@ -487,18 +496,32 @@ class APIClient {
487
496
  if (r.status === 'fulfilled')
488
497
  return { valid: true };
489
498
  }
490
- for (const r of results) {
491
- if (r.status === 'rejected') {
492
- const err = r.reason;
493
- if (err.response?.status === 401 || err.response?.status === 403) {
494
- return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
495
- }
496
- if (err instanceof CLIError && err.category === 'auth') {
497
- return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
499
+ const sawUnauthorized = results.some((r) => r.status === 'rejected' && ((r.reason?.response?.status === 401) || (r.reason?.response?.status === 403) || (r.reason instanceof CLIError && r.reason.category === 'auth')));
500
+ if (sawUnauthorized) {
501
+ // For persisted CLI sessions, attempt one refresh before failing auth.
502
+ if (!explicitEnvToken) {
503
+ const refreshed = await this.refreshToken();
504
+ if (refreshed) {
505
+ const retryToken = this.getAccessToken();
506
+ if (retryToken) {
507
+ const retryHeaders = {
508
+ Authorization: `Bearer ${retryToken}`,
509
+ Cookie: `vigthoria-auth-token=${retryToken}`,
510
+ };
511
+ const retryResults = await Promise.allSettled([
512
+ axios_1.default.get(`${canonicalBaseUrl}/api/user/profile`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
513
+ axios_1.default.get(`${canonicalBaseUrl}/api/user/subscription`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
514
+ ]);
515
+ for (const rr of retryResults) {
516
+ if (rr.status === 'fulfilled')
517
+ return { valid: true };
518
+ }
519
+ }
498
520
  }
499
521
  }
522
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
500
523
  }
501
- if (explicitEnvToken) {
524
+ if (explicitEnvToken || !allowNetworkFailOpen) {
502
525
  return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
503
526
  }
504
527
  // Both unreachable — don't assume the stored token is bad when running offline.
@@ -958,6 +981,8 @@ class APIClient {
958
981
  if (authToken) {
959
982
  headers.Authorization = `Bearer ${authToken}`;
960
983
  headers.Cookie = `vigthoria-auth-token=${authToken}`;
984
+ headers['X-Vigthoria-Token'] = authToken;
985
+ headers['X-Auth-Token'] = authToken;
961
986
  }
962
987
  return headers;
963
988
  }
@@ -2987,8 +3012,12 @@ document.addEventListener('DOMContentLoaded', () => {
2987
3012
  && !process.env.VIGTHORIA_AUTH_TOKEN
2988
3013
  && Boolean(this.config.get('authToken'));
2989
3014
  if (onlyUnauthorizedErrors && usingStoredConfigToken) {
2990
- this.config.clearAuth();
2991
- throw new Error('V3 agent authentication failed. The stored CLI login token is invalid or expired. Run vigthoria login again.');
3015
+ const gatewayTokenCheck = await this.validateToken({ allowNetworkFailOpen: true, enforceTokenShape: true });
3016
+ if (!gatewayTokenCheck.valid) {
3017
+ this.config.clearAuth();
3018
+ throw new Error('V3 agent authentication failed. The stored CLI login token is invalid or expired. Run vigthoria login again.');
3019
+ }
3020
+ throw new Error('V3 agent authentication failed at the V3 service layer while your gateway login token is still valid. Please retry shortly.');
2992
3021
  }
2993
3022
  if (preferLocalV3
2994
3023
  && !this.hasAgentWorkspaceOutput(executionContext)
@@ -3212,6 +3241,7 @@ document.addEventListener('DOMContentLoaded', () => {
3212
3241
  * NO localhost fallbacks - CLI is for external users, not server-side!
3213
3242
  */
3214
3243
  async chat(messages, model, useLocal = false) {
3244
+ this.lastChatTransportErrors = [];
3215
3245
  const resolvedModel = this.resolveModelId(model);
3216
3246
  const candidateModels = this.isCloudModelId(resolvedModel) && !this.canUseCloudModel()
3217
3247
  ? [this.getSelfHostedFallbackModelId(resolvedModel, model)]
@@ -3230,12 +3260,16 @@ document.addEventListener('DOMContentLoaded', () => {
3230
3260
  }
3231
3261
  }
3232
3262
  // No more localhost fallbacks - CLI is for external users!
3233
- throw new CLIError('AI service unavailable. Please check your internet connection or try again later.', 'model_backend');
3263
+ const detail = this.lastChatTransportErrors.length > 0
3264
+ ? ` Tried routes: ${this.lastChatTransportErrors.slice(0, 4).join(' | ')}`
3265
+ : '';
3266
+ throw new CLIError(`AI service unavailable. Please check your internet connection or try again later.${detail}`, 'model_backend');
3234
3267
  }
3235
3268
  shouldSkipCloudRoutes(resolvedModel) {
3236
3269
  return this.shouldSimulateCloudFailure() && this.isCloudModelId(resolvedModel);
3237
3270
  }
3238
3271
  async tryChatWithModel(messages, resolvedModel, requestedModel) {
3272
+ const routeFailures = [];
3239
3273
  const preferSelfHostedFirst = this.isSelfHostedPreferredModel(resolvedModel, requestedModel);
3240
3274
  if (preferSelfHostedFirst) {
3241
3275
  const selfHostedResponse = await this.trySelfHostedChatWithModel(messages, resolvedModel, requestedModel);
@@ -3276,6 +3310,7 @@ document.addEventListener('DOMContentLoaded', () => {
3276
3310
  catch (error) {
3277
3311
  const errMsg = error.response?.data?.error || error.message || 'Unknown error';
3278
3312
  this.logger.debug(`Direct Vigthoria Models API failed for ${resolvedModel}: ${errMsg}`);
3313
+ routeFailures.push(`models:${String(errMsg).slice(0, 120)}`);
3279
3314
  }
3280
3315
  }
3281
3316
  else {
@@ -3310,6 +3345,7 @@ document.addEventListener('DOMContentLoaded', () => {
3310
3345
  catch (error) {
3311
3346
  const errMsg = error.response?.data?.error || error.message || 'Unknown error';
3312
3347
  this.logger.debug(`Vigthoria Cloud API failed for ${resolvedModel}: ${errMsg}`);
3348
+ routeFailures.push(`coder:${String(errMsg).slice(0, 120)}`);
3313
3349
  }
3314
3350
  try {
3315
3351
  this.logger.debug(`Canonical Vigthoria Cloud fallback: ${resolvedModel}`);
@@ -3339,6 +3375,7 @@ document.addEventListener('DOMContentLoaded', () => {
3339
3375
  catch (error) {
3340
3376
  const errMsg = error.response?.data?.error || error.message || 'Unknown error';
3341
3377
  this.logger.debug(`Canonical Vigthoria Cloud fallback failed for ${resolvedModel}: ${errMsg}`);
3378
+ routeFailures.push(`coder-canonical:${String(errMsg).slice(0, 120)}`);
3342
3379
  }
3343
3380
  }
3344
3381
  if (!preferSelfHostedFirst) {
@@ -3347,6 +3384,9 @@ document.addEventListener('DOMContentLoaded', () => {
3347
3384
  return selfHostedResponse;
3348
3385
  }
3349
3386
  }
3387
+ if (routeFailures.length > 0) {
3388
+ this.lastChatTransportErrors = routeFailures;
3389
+ }
3350
3390
  return null;
3351
3391
  }
3352
3392
  async trySelfHostedChatWithModel(messages, resolvedModel, requestedModel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.9.8",
3
+ "version": "1.9.10",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [