@f5xc-salesdemos/xcsh 18.64.0 → 18.64.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.64.2] - 2026-05-16
6
+
7
+ ### Fixed
8
+
9
+ - Welcome banner cloud provider hints: AWS SSO token expiry now correctly suggests `aws sso login` instead of `aws configure`; Google Cloud check replaced `gcloud auth list` (false positives on expired tokens) with `gcloud auth print-access-token`; F5 XC Context surfaces `errorClass` (network/URL) in hints; GitLab `project_inaccessible` gets its own hint; Salesforce differentiates `session_expired` and `not_configured` hints ([#825](https://github.com/f5xc-salesdemos/xcsh/issues/825))
10
+
5
11
  ## [18.64.0] - 2026-05-13
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.64.0",
4
+ "version": "18.64.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -48,12 +48,12 @@
48
48
  "dependencies": {
49
49
  "@agentclientprotocol/sdk": "0.16.1",
50
50
  "@mozilla/readability": "^0.6",
51
- "@f5xc-salesdemos/xcsh-stats": "18.64.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.64.0",
53
- "@f5xc-salesdemos/pi-ai": "18.64.0",
54
- "@f5xc-salesdemos/pi-natives": "18.64.0",
55
- "@f5xc-salesdemos/pi-tui": "18.64.0",
56
- "@f5xc-salesdemos/pi-utils": "18.64.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.64.2",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.64.2",
53
+ "@f5xc-salesdemos/pi-ai": "18.64.2",
54
+ "@f5xc-salesdemos/pi-natives": "18.64.2",
55
+ "@f5xc-salesdemos/pi-tui": "18.64.2",
56
+ "@f5xc-salesdemos/pi-utils": "18.64.2",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.64.0",
21
- "commit": "9928b0103e54c3d9c17c7b1b58efd631c4a20ed5",
22
- "shortCommit": "9928b01",
20
+ "version": "18.64.2",
21
+ "commit": "192cc2f2ed25a353be4c27164699e162d96fd540",
22
+ "shortCommit": "192cc2f",
23
23
  "branch": "main",
24
- "tag": "v18.64.0",
25
- "commitDate": "2026-05-13T20:48:46Z",
26
- "buildDate": "2026-05-13T21:13:33.444Z",
24
+ "tag": "v18.64.2",
25
+ "commitDate": "2026-05-16T23:00:39Z",
26
+ "buildDate": "2026-05-16T23:20:31.262Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
30
30
  "repoSlug": "f5xc-salesdemos/xcsh",
31
- "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/9928b0103e54c3d9c17c7b1b58efd631c4a20ed5",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.64.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/192cc2f2ed25a353be4c27164699e162d96fd540",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.64.2"
33
33
  };
@@ -364,8 +364,16 @@ export function mapContextStatus(status: WelcomeContextStatus): ServiceStatus {
364
364
  case "no_context":
365
365
  return { name: "F5 XC Context", state: "unauthenticated", hint: "run: /context create" };
366
366
  case "auth_error":
367
- case "offline":
368
367
  return { name: "F5 XC Context", state: "unauthenticated", hint: "run: /context" };
368
+ case "offline":
369
+ switch (status.errorClass) {
370
+ case "network":
371
+ return { name: "F5 XC Context", state: "unauthenticated", hint: "network unreachable" };
372
+ case "url_not_found":
373
+ return { name: "F5 XC Context", state: "unauthenticated", hint: "tenant URL not found" };
374
+ default:
375
+ return { name: "F5 XC Context", state: "unauthenticated", hint: "run: /context" };
376
+ }
369
377
  }
370
378
  }
371
379
 
@@ -376,6 +384,8 @@ export function mapGitLabStatus(status: WelcomeGitLabStatus | undefined): Servic
376
384
  return { name: "GitLab", state: "connected" };
377
385
  case "not_installed":
378
386
  return { name: "GitLab", state: "unavailable", hint: "not installed" };
387
+ case "project_inaccessible":
388
+ return { name: "GitLab", state: "unauthenticated", hint: "check project access" };
379
389
  default:
380
390
  return { name: "GitLab", state: "unauthenticated", hint: "run: glab auth login" };
381
391
  }
@@ -386,6 +396,10 @@ export function mapSalesforceStatus(status: WelcomeSalesforceStatus | undefined)
386
396
  switch (status.state) {
387
397
  case "connected":
388
398
  return { name: "Salesforce", state: "connected" };
399
+ case "session_expired":
400
+ return { name: "Salesforce", state: "unauthenticated", hint: "session expired, run: sf org login web" };
401
+ case "not_configured":
402
+ return { name: "Salesforce", state: "unauthenticated", hint: "run: sf org login web --set-default" };
389
403
  default:
390
404
  return { name: "Salesforce", state: "unauthenticated", hint: "run: sf org login web" };
391
405
  }
@@ -428,17 +442,52 @@ export function mapAzureStatus(status: WelcomeAzureStatus | undefined): ServiceS
428
442
  }
429
443
  }
430
444
 
431
- export type AwsCheckState = "connected" | "auth_error";
445
+ export type AwsCheckState =
446
+ | "connected"
447
+ | "sso_expired"
448
+ | "not_configured"
449
+ | "profile_not_found"
450
+ | "network_error"
451
+ | "auth_error";
432
452
 
433
453
  export interface WelcomeAwsStatus {
434
454
  state: AwsCheckState;
435
455
  }
436
456
 
457
+ /** Classify AWS CLI stderr into a specific failure state. Exported for testing. */
458
+ export function classifyAwsError(stderr: string): AwsCheckState {
459
+ const s = stderr.toLowerCase();
460
+ // SSO session/token expiry
461
+ if (
462
+ s.includes("sso session") ||
463
+ s.includes("sso token") ||
464
+ s.includes("expiredtokenexception") ||
465
+ s.includes("expiredtoken")
466
+ ) {
467
+ return "sso_expired";
468
+ }
469
+ // No credentials configured at all
470
+ if (s.includes("unable to locate credentials")) {
471
+ return "not_configured";
472
+ }
473
+ // AWS_PROFILE points to a non-existent profile
474
+ if (s.includes("config profile") && s.includes("could not be found")) {
475
+ return "profile_not_found";
476
+ }
477
+ // Network/connectivity errors
478
+ if (s.includes("could not connect") || s.includes("connection error") || s.includes("endpoint url")) {
479
+ return "network_error";
480
+ }
481
+ return "auth_error";
482
+ }
483
+
437
484
  export async function checkAwsStatus(): Promise<WelcomeAwsStatus | undefined> {
438
485
  try {
439
486
  if (!$which("aws")) return undefined;
440
487
  const result = await $`aws sts get-caller-identity --output json`.quiet().nothrow();
441
- return { state: result.exitCode === 0 ? "connected" : "auth_error" };
488
+ if (result.exitCode === 0) return { state: "connected" };
489
+ const stderr = result.stderr.toString();
490
+ return { state: classifyAwsError(stderr) };
442
491
  } catch (err) {
443
492
  logger.warn("AWS startup check failed", { error: String(err) });
444
493
  return { state: "auth_error" };
@@ -450,23 +499,43 @@ export function mapAwsStatus(status: WelcomeAwsStatus | undefined): ServiceStatu
450
499
  switch (status.state) {
451
500
  case "connected":
452
501
  return { name: "AWS", state: "connected" };
502
+ case "sso_expired":
503
+ return { name: "AWS", state: "unauthenticated", hint: "SSO session expired, run: aws sso login" };
504
+ case "not_configured":
505
+ return { name: "AWS", state: "unauthenticated", hint: "run: aws configure" };
506
+ case "profile_not_found":
507
+ return { name: "AWS", state: "unauthenticated", hint: "check AWS_PROFILE env var" };
508
+ case "network_error":
509
+ return { name: "AWS", state: "unauthenticated", hint: "network unreachable" };
453
510
  case "auth_error":
454
511
  return { name: "AWS", state: "unauthenticated", hint: "run: aws configure" };
455
512
  }
456
513
  }
457
514
 
458
- export type GcloudCheckState = "connected" | "auth_error";
515
+ export type GcloudCheckState = "connected" | "token_expired" | "auth_error";
459
516
 
460
517
  export interface WelcomeGcloudStatus {
461
518
  state: GcloudCheckState;
462
519
  }
463
520
 
521
+ /** Classify gcloud stderr into a specific failure state. Exported for testing. */
522
+ export function classifyGcloudError(stderr: string): GcloudCheckState {
523
+ const s = stderr.toLowerCase();
524
+ if (s.includes("valid credentials") || s.includes("refreshing your current auth tokens")) {
525
+ return "token_expired";
526
+ }
527
+ return "auth_error";
528
+ }
529
+
464
530
  export async function checkGcloudStatus(): Promise<WelcomeGcloudStatus | undefined> {
465
531
  try {
466
532
  if (!$which("gcloud")) return undefined;
467
- const result = await $`gcloud auth list --format=value(account)`.quiet().nothrow();
468
- const hasAccount = result.text().trim().length > 0;
469
- return { state: hasAccount ? "connected" : "auth_error" };
533
+ const result = await $`gcloud auth print-access-token --quiet`.quiet().nothrow();
534
+ if (result.exitCode === 0 && result.text().trim().length > 0) {
535
+ return { state: "connected" };
536
+ }
537
+ const stderr = result.stderr.toString();
538
+ return { state: classifyGcloudError(stderr) };
470
539
  } catch (err) {
471
540
  logger.warn("Google Cloud startup check failed", { error: String(err) });
472
541
  return { state: "auth_error" };
@@ -478,6 +547,8 @@ export function mapGcloudStatus(status: WelcomeGcloudStatus | undefined): Servic
478
547
  switch (status.state) {
479
548
  case "connected":
480
549
  return { name: "Google Cloud", state: "connected" };
550
+ case "token_expired":
551
+ return { name: "Google Cloud", state: "unauthenticated", hint: "token expired, run: gcloud auth login" };
481
552
  case "auth_error":
482
553
  return { name: "Google Cloud", state: "unauthenticated", hint: "run: gcloud auth login" };
483
554
  }