@f5xc-salesdemos/xcsh 18.57.0 → 18.58.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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.58.1] - 2026-05-10
6
+
7
+ ### Changed
8
+
9
+ - Regenerated API spec index from catalog v2.1.82: http_loadbalancer CRUD verification corrections (6 new server defaults, corrected minimum configs, cross-field dependencies, default_pool inline pool discovery, 5 composable routing approaches) and tcp_loadbalancer minimum config corrections (listen_port, origin_pools_weights, do_not_advertise format, 9 server defaults, forced hash_policy default) ([#753](https://github.com/f5xc-salesdemos/xcsh/issues/753), [#757](https://github.com/f5xc-salesdemos/xcsh/issues/757))
5
10
  ## [18.53.0] - 2026-05-09
6
11
 
7
12
  ### Changed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.57.0",
4
+ "version": "18.58.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",
@@ -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.57.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.57.0",
53
- "@f5xc-salesdemos/pi-ai": "18.57.0",
54
- "@f5xc-salesdemos/pi-natives": "18.57.0",
55
- "@f5xc-salesdemos/pi-tui": "18.57.0",
56
- "@f5xc-salesdemos/pi-utils": "18.57.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.58.1",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.58.1",
53
+ "@f5xc-salesdemos/pi-ai": "18.58.1",
54
+ "@f5xc-salesdemos/pi-natives": "18.58.1",
55
+ "@f5xc-salesdemos/pi-tui": "18.58.1",
56
+ "@f5xc-salesdemos/pi-utils": "18.58.1",
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.57.0",
21
- "commit": "76523bacae1d5a87ef3c8f1af75423045206e66d",
22
- "shortCommit": "76523ba",
20
+ "version": "18.58.1",
21
+ "commit": "771846c0cfc096fabe25f46e54af5ad4660a650d",
22
+ "shortCommit": "771846c",
23
23
  "branch": "main",
24
- "tag": "v18.57.0",
25
- "commitDate": "2026-05-09T22:22:04Z",
26
- "buildDate": "2026-05-09T22:44:57.315Z",
27
- "dirty": false,
24
+ "tag": "v18.58.1",
25
+ "commitDate": "2026-05-10T05:27:47Z",
26
+ "buildDate": "2026-05-10T05:46:22.190Z",
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/76523bacae1d5a87ef3c8f1af75423045206e66d",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.57.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/771846c0cfc096fabe25f46e54af5ad4660a650d",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.58.1"
33
33
  };
@@ -17,6 +17,7 @@ export const RESERVED_ENV_KEYS: ReadonlySet<string> = new Set([
17
17
  F5XC_API_URL,
18
18
  F5XC_API_TOKEN,
19
19
  F5XC_TENANT,
20
+ F5XC_CONTEXT_NAME,
20
21
  ]);
21
22
 
22
23
  export const RESERVED_ENV_MESSAGES: Readonly<Record<string, string>> = {
@@ -24,6 +25,7 @@ export const RESERVED_ENV_MESSAGES: Readonly<Record<string, string>> = {
24
25
  [F5XC_API_URL]: `${F5XC_API_URL} is managed by apiUrl. It cannot be overridden via env vars.`,
25
26
  [F5XC_API_TOKEN]: `${F5XC_API_TOKEN} is managed by apiToken. It cannot be overridden via env vars.`,
26
27
  [F5XC_TENANT]: `${F5XC_TENANT} is read-only (derived from apiUrl). It cannot be set directly.`,
28
+ [F5XC_CONTEXT_NAME]: `${F5XC_CONTEXT_NAME} is read-only (injected by ContextService on activation). It cannot be set directly.`,
27
29
  };
28
30
 
29
31
  /**
@@ -1,18 +1,29 @@
1
1
  /**
2
2
  * TUI renderer for the xcsh_api tool.
3
3
  *
4
- * Provides context-aware visualization for F5 XC API calls:
4
+ * Provides rich, context-aware visualization for F5 XC API calls:
5
5
  * - renderCall: method badge + path while request is pending
6
- * - renderResult: status code badge + JSON body preview (collapsed/expanded)
6
+ * - renderResult: bordered output block with syntax-highlighted JSON body,
7
+ * request details section, and error guidance section
8
+ *
9
+ * Uses CachedOutputBlock for bordered rendering with state-colored borders
10
+ * (success → dim, error → red, pending → accent). JSON responses are
11
+ * syntax-highlighted via the native pi-natives highlighter with theme colors.
12
+ *
13
+ * Always renders full output — no collapsed mode. This is the primary tool
14
+ * for F5 XC platform operations and benefits from full visibility.
7
15
  */
8
16
  import type { Component } from "@f5xc-salesdemos/pi-tui";
9
17
  import { Text } from "@f5xc-salesdemos/pi-tui";
10
18
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
11
19
  import type { Theme, ThemeColor } from "../modes/theme/theme";
12
- import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
13
- import { formatErrorMessage, PREVIEW_LIMITS, replaceTabs } from "./render-utils";
20
+ import { highlightCode } from "../modes/theme/theme";
21
+ import { CachedOutputBlock, renderStatusLine } from "../tui";
22
+ import { formatErrorMessage, replaceTabs } from "./render-utils";
14
23
  import type { XcshApiToolDetails } from "./xcsh-api";
15
24
 
25
+ const TOOL_TITLE = "XC-API";
26
+
16
27
  interface XcshApiRenderArgs {
17
28
  method?: string;
18
29
  path?: string;
@@ -39,7 +50,54 @@ function statusColor(status: number): ThemeColor {
39
50
  return "error";
40
51
  }
41
52
 
42
- const COLLAPSED_BODY_LINES = PREVIEW_LIMITS.OUTPUT_COLLAPSED;
53
+ /**
54
+ * Split the text content from the tool result into its constituent parts.
55
+ *
56
+ * Tool result text format:
57
+ * - Success: `"200 OK\n\ncompactJSON"`
58
+ * - Error: `"404 Not Found\n\ncompactJSON\n\nguidanceText"`
59
+ *
60
+ * The compact JSON has no internal newlines (produced by `JSON.stringify(JSON.parse(raw))`
61
+ * in xcsh-api.ts), so splitting on `\n\n` is reliable.
62
+ */
63
+ function splitResultContent(textContent: string, isError: boolean): { json?: string; guidance?: string; raw: string } {
64
+ // Strip status line prefix (e.g. "200 OK\n\n")
65
+ const bodyStart = textContent.indexOf("\n\n");
66
+ const body = bodyStart >= 0 ? textContent.slice(bodyStart + 2) : textContent;
67
+
68
+ if (!isError) {
69
+ // Success: entire body is JSON
70
+ try {
71
+ return { json: JSON.stringify(JSON.parse(body.trim()), null, 2), raw: body };
72
+ } catch {
73
+ return { raw: body };
74
+ }
75
+ }
76
+
77
+ // Error: body is "compactJSON\n\nguidanceText"
78
+ const guidanceSplit = body.indexOf("\n\n");
79
+ if (guidanceSplit >= 0) {
80
+ const jsonPart = body.slice(0, guidanceSplit);
81
+ const guidancePart = body.slice(guidanceSplit + 2);
82
+ try {
83
+ return {
84
+ json: JSON.stringify(JSON.parse(jsonPart.trim()), null, 2),
85
+ guidance: guidancePart.trim(),
86
+ raw: body,
87
+ };
88
+ } catch {
89
+ // First part isn't JSON — treat whole body as guidance
90
+ return { guidance: body.trim(), raw: body };
91
+ }
92
+ }
93
+
94
+ // No double newline — might be just JSON or just text
95
+ try {
96
+ return { json: JSON.stringify(JSON.parse(body.trim()), null, 2), raw: body };
97
+ } catch {
98
+ return { guidance: body.trim(), raw: body };
99
+ }
100
+ }
43
101
 
44
102
  export const xcshApiToolRenderer = {
45
103
  renderCall(args: XcshApiRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
@@ -48,7 +106,7 @@ export const xcshApiToolRenderer = {
48
106
  const text = renderStatusLine(
49
107
  {
50
108
  icon: "pending",
51
- title: "API",
109
+ title: TOOL_TITLE,
52
110
  description: apiPath,
53
111
  badge: { label: method, color: methodColor(method) },
54
112
  },
@@ -66,7 +124,9 @@ export const xcshApiToolRenderer = {
66
124
  const details = result.details;
67
125
  const method = details?.method ?? args?.method ?? "???";
68
126
  const url = details?.url;
69
- // Show resolved path (from URL) or the original template path
127
+ const isError = result.isError === true;
128
+
129
+ // Resolve display path: prefer the resolved URL pathname, fall back to template path
70
130
  let displayPath = args?.path ?? "…";
71
131
  if (url) {
72
132
  try {
@@ -75,20 +135,24 @@ export const xcshApiToolRenderer = {
75
135
  // Malformed URL — fall through to args.path
76
136
  }
77
137
  }
138
+
78
139
  const status = details?.status ?? 0;
79
140
  const statusText = status > 0 ? `${status}` : "failed";
80
141
 
81
- if (result.isError && !details) {
142
+ // Fallback: error without structured details (e.g. missing context/credentials)
143
+ if (isError && !details) {
82
144
  const errorText = result.content?.find(c => c.type === "text")?.text;
83
145
  return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
84
146
  }
85
147
 
148
+ // --- Header ---
86
149
  const meta: string[] = [];
87
- if (details?.contextName) meta.push(uiTheme.fg("muted", details.contextName));
150
+ if (details?.contextName) meta.push(uiTheme.fg("statusLineContextF5xcFg", details.contextName));
88
151
  if (details?.durationMs !== undefined) meta.push(uiTheme.fg("dim", `${details.durationMs}ms`));
89
152
  const header = renderStatusLine(
90
153
  {
91
- title: "API",
154
+ title: TOOL_TITLE,
155
+ titleColor: "contentAccent",
92
156
  description: displayPath,
93
157
  badge: { label: `${method} ${statusText}`, color: status > 0 ? statusColor(status) : "error" },
94
158
  meta: meta.length > 0 ? meta : undefined,
@@ -96,52 +160,54 @@ export const xcshApiToolRenderer = {
96
160
  uiTheme,
97
161
  );
98
162
 
163
+ // --- Body sections ---
99
164
  const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
100
- // Split off the status line prefix (e.g. "200 OK\n\n") from the body
101
- const bodyStart = textContent.indexOf("\n\n");
102
- let body = bodyStart >= 0 ? textContent.slice(bodyStart + 2) : textContent;
103
- // Format JSON bodies for readable TUI display (error bodies include guidance text and won't parse)
104
- try {
105
- body = JSON.stringify(JSON.parse(body.trim()), null, 2);
106
- } catch {
107
- // Not valid JSONkeep as-is
165
+ const { json, guidance, raw } = splitResultContent(textContent, isError);
166
+ const sections: Array<{ label?: string; lines: string[] }> = [];
167
+
168
+ // Section 1: Request line (method + full resolved URL)
169
+ const requestLine = url ? `${method} ${url}` : `${method} ${displayPath}`;
170
+ sections.push({ lines: [uiTheme.fg("dim", requestLine)] });
171
+
172
+ // Section 2: Response body syntax-highlighted JSON or plain text
173
+ if (json) {
174
+ const highlighted = highlightCode(json, "json");
175
+ sections.push({
176
+ label: uiTheme.fg("toolTitle", "Response"),
177
+ lines: highlighted.map(line => replaceTabs(line)),
178
+ });
179
+ } else if (raw.trim() && !guidance) {
180
+ // Non-JSON, non-guidance body
181
+ sections.push({
182
+ label: uiTheme.fg("toolTitle", "Response"),
183
+ lines: raw
184
+ .trim()
185
+ .split("\n")
186
+ .map(line => uiTheme.fg("toolOutput", replaceTabs(line))),
187
+ });
108
188
  }
109
- const bodyLines = body.trim() ? replaceTabs(body).split("\n") : [];
110
189
 
111
- let cached: RenderCache | undefined;
190
+ // Section 3: Error guidance (for HTTP error responses)
191
+ if (guidance) {
192
+ sections.push({
193
+ label: uiTheme.fg("toolTitle", "Guidance"),
194
+ lines: [uiTheme.fg("warning", guidance)],
195
+ });
196
+ }
197
+
198
+ // --- Render with CachedOutputBlock ---
199
+ const outputBlock = new CachedOutputBlock();
200
+
112
201
  return {
113
202
  render(width: number): string[] {
114
- const { expanded } = options;
115
- const key = new Hasher().bool(expanded).u32(width).digest();
116
- if (cached?.key === key) return cached.lines;
117
-
118
- const lines: string[] = [header];
119
-
120
- if (bodyLines.length > 0) {
121
- if (expanded) {
122
- for (const line of bodyLines) {
123
- lines.push(truncateToWidth(uiTheme.fg("toolOutput", line), width, Ellipsis.Omit));
124
- }
125
- } else {
126
- const maxLines = COLLAPSED_BODY_LINES;
127
- const display = bodyLines.slice(0, maxLines);
128
- const remaining = bodyLines.length - maxLines;
129
- for (const line of display) {
130
- lines.push(truncateToWidth(uiTheme.fg("toolOutput", line), width, Ellipsis.Omit));
131
- }
132
- if (remaining > 0) {
133
- lines.push(uiTheme.fg("dim", `… (${remaining} more lines) (ctrl+o to expand)`));
134
- }
135
- }
136
- }
137
-
138
- cached = { key, lines };
139
- return lines;
203
+ const state = options.isPartial ? "pending" : isError ? "error" : "success";
204
+ return outputBlock.render({ header, state, sections, width }, uiTheme);
140
205
  },
141
206
  invalidate() {
142
- cached = undefined;
207
+ outputBlock.invalidate();
143
208
  },
144
209
  };
145
210
  },
146
211
  mergeCallAndResult: true,
212
+ inline: true,
147
213
  };
@@ -92,7 +92,7 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
92
92
  case 403:
93
93
  return `Access denied${ctxHint}. The API token may lack the required role or permission for this operation. Check the token's role assignments in the F5 XC console.`;
94
94
  case 404: {
95
- const ns = this.#contextEnv.get("F5XC_NAMESPACE") ?? "default";
95
+ const ns = process.env.F5XC_NAMESPACE ?? this.#contextEnv.get("F5XC_NAMESPACE") ?? "default";
96
96
  return `Resource not found. Verify the resource name exists in namespace \`${ns}\`${ctxHint}. Use a GET list operation to check existing resources.`;
97
97
  }
98
98
  case 409: