@f5xc-salesdemos/xcsh 18.26.0 → 18.27.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.26.0",
4
+ "version": "18.27.1",
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",
@@ -47,12 +47,12 @@
47
47
  "dependencies": {
48
48
  "@agentclientprotocol/sdk": "0.16.1",
49
49
  "@mozilla/readability": "^0.6",
50
- "@f5xc-salesdemos/xcsh-stats": "18.26.0",
51
- "@f5xc-salesdemos/pi-agent-core": "18.26.0",
52
- "@f5xc-salesdemos/pi-ai": "18.26.0",
53
- "@f5xc-salesdemos/pi-natives": "18.26.0",
54
- "@f5xc-salesdemos/pi-tui": "18.26.0",
55
- "@f5xc-salesdemos/pi-utils": "18.26.0",
50
+ "@f5xc-salesdemos/xcsh-stats": "18.27.1",
51
+ "@f5xc-salesdemos/pi-agent-core": "18.27.1",
52
+ "@f5xc-salesdemos/pi-ai": "18.27.1",
53
+ "@f5xc-salesdemos/pi-natives": "18.27.1",
54
+ "@f5xc-salesdemos/pi-tui": "18.27.1",
55
+ "@f5xc-salesdemos/pi-utils": "18.27.1",
56
56
  "@sinclair/typebox": "^0.34",
57
57
  "@xterm/headless": "^6.0",
58
58
  "ajv": "^8.18",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.26.0",
21
- "commit": "34cc34b4d50f91723ff2936a7755de8158599041",
22
- "shortCommit": "34cc34b",
20
+ "version": "18.27.1",
21
+ "commit": "aa8bc582baa89e73ceb7b76059ac706556123411",
22
+ "shortCommit": "aa8bc58",
23
23
  "branch": "main",
24
- "tag": "v18.26.0",
25
- "commitDate": "2026-04-29T14:53:17Z",
26
- "buildDate": "2026-04-29T15:22:09.660Z",
24
+ "tag": "v18.27.1",
25
+ "commitDate": "2026-04-29T22:53:47Z",
26
+ "buildDate": "2026-04-29T23:15:51.283Z",
27
27
  "dirty": false,
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/34cc34b4d50f91723ff2936a7755de8158599041",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.26.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/aa8bc582baa89e73ceb7b76059ac706556123411",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.27.1"
33
33
  };
@@ -14,7 +14,7 @@ const STARTUP_RETRY_DELAY_MS = 500;
14
14
 
15
15
  type ContextValidator = (opts: {
16
16
  timeoutMs: number;
17
- }) => Promise<{ status: AuthStatus; latencyMs?: number; errorClass?: "network" | "credential" }>;
17
+ }) => Promise<{ status: AuthStatus; latencyMs?: number; errorClass?: "network" | "credential" | "url_not_found" }>;
18
18
 
19
19
  /**
20
20
  * Runs the context validator once with a startup-sized timeout; if the result is `offline`
@@ -28,7 +28,7 @@ export async function validateContextWithStartupRetry(
28
28
  retryTimeoutMs?: number;
29
29
  retryDelayMs?: number;
30
30
  },
31
- ): Promise<{ status: AuthStatus; latencyMs?: number }> {
31
+ ): Promise<{ status: AuthStatus; latencyMs?: number; errorClass?: "network" | "credential" | "url_not_found" }> {
32
32
  const firstTimeoutMs = options?.firstTimeoutMs ?? STARTUP_FIRST_TIMEOUT_MS;
33
33
  const retryTimeoutMs = options?.retryTimeoutMs ?? STARTUP_RETRY_TIMEOUT_MS;
34
34
  const retryDelayMs = options?.retryDelayMs ?? STARTUP_RETRY_DELAY_MS;
@@ -56,6 +56,7 @@ export interface WelcomeContextStatus {
56
56
  state: ContextCheckState;
57
57
  name?: string;
58
58
  latencyMs?: number;
59
+ errorClass?: "network" | "credential" | "url_not_found";
59
60
  }
60
61
 
61
62
  export interface WelcomeCheckResult {
@@ -167,7 +168,7 @@ async function checkContextStatus(): Promise<WelcomeContextStatus> {
167
168
  case "auth_error":
168
169
  return { state: "auth_error", name };
169
170
  case "offline":
170
- return { state: "offline", name };
171
+ return { state: "offline", name, errorClass: result.errorClass };
171
172
  default:
172
173
  return { state: "no_context" };
173
174
  }
@@ -232,6 +232,12 @@ export class WelcomeComponent implements Component {
232
232
  ` ${theme.fg("dim", "Run /context to update")}`,
233
233
  ];
234
234
  case "offline":
235
+ if (this.contextStatus?.errorClass === "url_not_found") {
236
+ return [
237
+ ` ${formatStatusIcon("error")} ${theme.fg("muted", n)} ${theme.fg("error", "\u2014 tenant not found")}`,
238
+ ` ${theme.fg("dim", "Recreate with /context create or check with /context show")}`,
239
+ ];
240
+ }
235
241
  return [
236
242
  ` ${formatStatusIcon("warning")} ${theme.fg("muted", n)} ${theme.fg("warning", "\u2014 unreachable")}`,
237
243
  ` ${theme.fg("dim", "Check network, /context")}`,
@@ -114,7 +114,7 @@ export interface ValidationResult {
114
114
  context: F5XCContext;
115
115
  status: AuthStatus;
116
116
  latencyMs?: number;
117
- errorClass?: "network" | "credential";
117
+ errorClass?: "network" | "credential" | "url_not_found";
118
118
  }
119
119
 
120
120
  export class ContextError extends Error {
@@ -915,7 +915,7 @@ export class ContextService {
915
915
  timeoutMs?: number;
916
916
  apiUrl?: string;
917
917
  apiToken?: string;
918
- }): Promise<{ status: AuthStatus; latencyMs?: number; errorClass?: "network" | "credential" }> {
918
+ }): Promise<{ status: AuthStatus; latencyMs?: number; errorClass?: "network" | "credential" | "url_not_found" }> {
919
919
  // Use explicit credentials if provided (for non-active contexts or env-backed sessions),
920
920
  // otherwise fall back to effective credentials (env override > active context)
921
921
  const effectiveUrl = options?.apiUrl ?? process.env[F5XC_API_URL] ?? this.#activeContext?.apiUrl;
@@ -946,13 +946,23 @@ export class ContextService {
946
946
  method: "GET",
947
947
  headers: { Authorization: `APIToken ${effectiveToken}`, Accept: "application/json" },
948
948
  signal: AbortSignal.timeout(timeout),
949
+ redirect: "manual",
949
950
  });
950
951
  const latencyMs = Math.round(performance.now() - start);
951
952
  if (!adHoc) {
952
953
  this.#lastAuthLatencyMs = latencyMs;
953
954
  this.#lastAuthCheckedAt = checkedAt;
954
955
  }
956
+ if (response.type === "opaqueredirect" || (response.status >= 300 && response.status < 400)) {
957
+ if (!adHoc) this.#authStatus = "offline";
958
+ return { status: "offline", latencyMs, errorClass: "url_not_found" };
959
+ }
955
960
  if (response.ok) {
961
+ const contentType = response.headers.get("content-type") ?? "";
962
+ if (!contentType.includes("application/json")) {
963
+ if (!adHoc) this.#authStatus = "offline";
964
+ return { status: "offline", latencyMs, errorClass: "url_not_found" };
965
+ }
956
966
  if (!adHoc) this.#authStatus = "connected";
957
967
  return { status: "connected", latencyMs };
958
968
  }
@@ -23,7 +23,7 @@ const r = (s: string) => `${F5_RED}${s}${RESET}`;
23
23
  export function formatAuthIndicator(
24
24
  status: AuthStatus,
25
25
  latencyMs?: number,
26
- errorClass?: "network" | "credential",
26
+ errorClass?: "network" | "credential" | "url_not_found",
27
27
  ): string {
28
28
  const ms = latencyMs !== undefined ? ` (${latencyMs}ms)` : "";
29
29
  switch (status) {
@@ -32,6 +32,9 @@ export function formatAuthIndicator(
32
32
  case "auth_error":
33
33
  return `${formatStatusIcon("error")} Auth Error — check token${ms}`;
34
34
  case "offline":
35
+ if (errorClass === "url_not_found") {
36
+ return `${formatStatusIcon("error")} Offline — tenant URL not found${ms}`;
37
+ }
35
38
  return `${formatStatusIcon("warning")} Offline — ${errorClass === "credential" ? "auth issue" : "network issue"}${ms}`;
36
39
  default:
37
40
  return `${formatStatusIcon("unknown")} Unknown`;