@f5xc-salesdemos/xcsh 18.59.1 → 18.60.0

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.59.1",
4
+ "version": "18.60.0",
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.59.1",
52
- "@f5xc-salesdemos/pi-agent-core": "18.59.1",
53
- "@f5xc-salesdemos/pi-ai": "18.59.1",
54
- "@f5xc-salesdemos/pi-natives": "18.59.1",
55
- "@f5xc-salesdemos/pi-tui": "18.59.1",
56
- "@f5xc-salesdemos/pi-utils": "18.59.1",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.60.0",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.60.0",
53
+ "@f5xc-salesdemos/pi-ai": "18.60.0",
54
+ "@f5xc-salesdemos/pi-natives": "18.60.0",
55
+ "@f5xc-salesdemos/pi-tui": "18.60.0",
56
+ "@f5xc-salesdemos/pi-utils": "18.60.0",
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.59.1",
21
- "commit": "fa007898365b1a63e38748e06d2c86c13779ff53",
22
- "shortCommit": "fa00789",
20
+ "version": "18.60.0",
21
+ "commit": "f941cc11a5e3bdc300351bb68621137b5e72b76f",
22
+ "shortCommit": "f941cc1",
23
23
  "branch": "main",
24
- "tag": "v18.59.1",
25
- "commitDate": "2026-05-10T17:48:40Z",
26
- "buildDate": "2026-05-10T18:15:56.886Z",
24
+ "tag": "v18.60.0",
25
+ "commitDate": "2026-05-10T19:14:41Z",
26
+ "buildDate": "2026-05-10T19:41:53.757Z",
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/fa007898365b1a63e38748e06d2c86c13779ff53",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.59.1"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/f941cc11a5e3bdc300351bb68621137b5e72b76f",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.60.0"
33
33
  };
@@ -59,13 +59,6 @@ function statusColor(status: number): ThemeColor {
59
59
  return "error";
60
60
  }
61
61
 
62
- /** Format byte size to human-readable string (e.g. "1.2 KB", "3.4 MB"). */
63
- function formatBytes(bytes: number): string {
64
- if (bytes < 1024) return `${bytes} B`;
65
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
66
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
67
- }
68
-
69
62
  /**
70
63
  * Strip null, empty string, and empty array fields recursively.
71
64
  * Reduces JSON noise from F5 XC API responses which contain many null/empty defaults.
@@ -98,6 +91,11 @@ function formatTimestamp(iso: string): string {
98
91
  .replace(/:\d{2}Z$/, " UTC");
99
92
  }
100
93
 
94
+ /** Strip internal protobuf type prefixes from F5 XC error messages. */
95
+ function stripProtobufPrefix(message: string): string {
96
+ return message.replace(/^ves\.io\.schema\.\S+:\s*/i, "");
97
+ }
98
+
101
99
  /** Push a labeled section with optional line truncation. */
102
100
  function pushSection(
103
101
  sections: Array<{ label?: string; lines: string[] }>,
@@ -115,10 +113,10 @@ function pushSection(
115
113
  }
116
114
  }
117
115
 
118
- /** Build a compact summary section for a single F5 XC resource (identity + spec). */
116
+ /** Build a compact identity summary for a single F5 XC resource. */
119
117
  function buildResourceSummary(
120
118
  parsed: Record<string, unknown>,
121
- pathParts: string[],
119
+ _pathParts: string[],
122
120
  method: string,
123
121
  uiTheme: Theme,
124
122
  ): { label: string; lines: string[] } | null {
@@ -132,30 +130,10 @@ function buildResourceSummary(
132
130
  if (typeof sysMeta?.uid === "string") lines.push(uiTheme.fg("dim", ` uid: ${sysMeta.uid}`));
133
131
  const createdAt = sysMeta?.creation_timestamp;
134
132
  if (typeof createdAt === "string") lines.push(uiTheme.fg("dim", ` created: ${formatTimestamp(createdAt)}`));
133
+ const creatorId = sysMeta?.creator_id;
134
+ if (typeof creatorId === "string") lines.push(uiTheme.fg("dim", ` creator: ${creatorId}`));
135
135
  if (metadata.disable === true) lines.push(uiTheme.fg("warning", ` status: DISABLED`));
136
136
 
137
- // Compact spec line: resource type from path + key config values
138
- const spec = parsed.spec;
139
- if (spec && typeof spec === "object") {
140
- const specEntries = Object.entries(spec as Record<string, unknown>);
141
- // Only show spec line when there are actual entries (skip empty spec: {})
142
- if (specEntries.length > 0) {
143
- const specScalars = specEntries
144
- .filter(
145
- ([, v]) =>
146
- typeof v === "number" ||
147
- typeof v === "boolean" ||
148
- (typeof v === "string" && v.length > 0 && v.length <= 30),
149
- )
150
- .slice(0, 4)
151
- .map(([k, v]) => `${k}=${v}`)
152
- .join(", ");
153
- const resourceType = (pathParts.at(-2) ?? "config").replace(/_/g, " ").replace(/s$/, "");
154
- const specLine = specScalars ? `${resourceType} (${specScalars})` : resourceType;
155
- lines.push(uiTheme.fg("dim", ` spec: ${specLine}`));
156
- }
157
- }
158
-
159
137
  const isMutating = method === "POST" || method === "PUT" || method === "PATCH";
160
138
  const label = isMutating ? (method === "POST" ? "Created" : "Updated") : "Summary";
161
139
  return { label: uiTheme.fg("toolTitle", label), lines };
@@ -214,15 +192,15 @@ export const xcshApiToolRenderer = {
214
192
  renderCall(args: XcshApiRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
215
193
  const method = args.method ?? "???";
216
194
  const apiPath = args.path ?? "…";
217
- // Compact long paths for pending state consistency with result header
218
- const parts = apiPath.split("/").filter(Boolean);
219
- const pendingPath = parts.length > 3 ? `…/${parts.slice(-3).join("/")}` : apiPath;
195
+ const methodBadge = uiTheme.fg(
196
+ methodColor(method),
197
+ `${uiTheme.format.bracketLeft}${method}${uiTheme.format.bracketRight}`,
198
+ );
220
199
  const text = renderStatusLine(
221
200
  {
222
201
  icon: "pending",
223
202
  title: TOOL_TITLE,
224
- description: pendingPath,
225
- badge: { label: method, color: methodColor(method) },
203
+ description: `${methodBadge} ${uiTheme.fg("muted", apiPath)}`,
226
204
  },
227
205
  uiTheme,
228
206
  );
@@ -262,36 +240,24 @@ export const xcshApiToolRenderer = {
262
240
  return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
263
241
  }
264
242
 
265
- // --- Header with separate method and status badges ---
266
- const methodBadge = { label: method, color: methodColor(method) };
243
+ // --- Header: METHOD [STATUS] full-path ---
244
+ const methodBadge = uiTheme.fg(
245
+ methodColor(method),
246
+ `${uiTheme.format.bracketLeft}${method}${uiTheme.format.bracketRight}`,
247
+ );
267
248
  const errorLabel = details?.errorCodeLabel;
268
249
  const statusDisplay = errorLabel ? `${statusText} ${errorLabel}` : statusText;
269
250
  const statusBadge = uiTheme.fg(status > 0 ? statusColor(status) : "error", `[${statusDisplay}]`);
270
251
 
271
252
  const meta: string[] = [];
272
- meta.push(statusBadge);
273
- if (details?.contextName) meta.push(uiTheme.fg("statusLineContextF5xcFg", details.contextName));
274
- if (details?.durationMs !== undefined) {
275
- const durationColor: ThemeColor =
276
- details.durationMs < 200 ? "success" : details.durationMs > 1000 ? "warning" : "dim";
277
- meta.push(uiTheme.fg(durationColor, `${details.durationMs}ms`));
278
- }
279
- if (details?.itemCount !== undefined) meta.push(uiTheme.fg("dim", `${details.itemCount} items`));
280
- if (details?.bodySize !== undefined) meta.push(uiTheme.fg("dim", formatBytes(details.bodySize)));
281
- if (details?.contentType && !details.contentType.includes("json"))
282
- meta.push(uiTheme.fg("dim", details.contentType));
283
- // Show requestId for errors (useful for support/debugging)
284
253
  if (isError && details?.requestId) meta.push(uiTheme.fg("dim", `req:${details.requestId.slice(0, 8)}`));
285
254
  if (details?.retried) meta.push(uiTheme.fg("warning", "retried"));
286
255
 
287
- const compactPath = pathParts.length > 3 ? `…/${pathParts.slice(-3).join("/")}` : displayPath;
288
-
289
256
  const header = renderStatusLine(
290
257
  {
291
258
  title: TOOL_TITLE,
292
259
  titleColor: "contentAccent",
293
- description: compactPath,
294
- badge: methodBadge,
260
+ description: `${methodBadge} ${statusBadge} ${uiTheme.fg("muted", displayPath)}`,
295
261
  meta: meta.length > 0 ? meta : undefined,
296
262
  },
297
263
  uiTheme,
@@ -302,19 +268,13 @@ export const xcshApiToolRenderer = {
302
268
  const { json, guidance, raw } = splitResultContent(textContent, isError);
303
269
  const sections: Array<{ label?: string; lines: string[] }> = [];
304
270
 
305
- // Section 2: Request payload (for mutating methods with a body)
306
- if (args?.payload && method !== "GET") {
271
+ // Section: Request payload — show resolved body (actual JSON sent to API)
272
+ if (method !== "GET" && (details?.resolvedPayload || args?.payload)) {
307
273
  try {
308
- const prettyPayload = JSON.stringify(args.payload, null, 2);
274
+ const payloadSource = details?.resolvedPayload ? JSON.parse(details.resolvedPayload) : args?.payload;
275
+ const prettyPayload = JSON.stringify(payloadSource, null, 2);
309
276
  const payloadLines = highlightCode(prettyPayload, "json");
310
277
  const sanitized = payloadLines.map(line => replaceTabs(line));
311
- // Show expanded variable substitutions
312
- if (details?.expandedVars && details.expandedVars.length > 0) {
313
- const varLines = details.expandedVars.map(({ variable, value }) =>
314
- uiTheme.fg("dim", ` ${variable} → ${value}`),
315
- );
316
- sanitized.push(...varLines);
317
- }
318
278
  pushSection(sections, uiTheme.fg("toolTitle", "Request"), sanitized, MAX_PAYLOAD_LINES, uiTheme);
319
279
  } catch {
320
280
  // Payload not serializable — skip section
@@ -383,8 +343,10 @@ export const xcshApiToolRenderer = {
383
343
 
384
344
  // Determine if JSON body should be suppressed
385
345
  let apiErrorMessage: string | undefined;
386
- if (isError && typeof parsed.message === "string" && parsed.message) apiErrorMessage = parsed.message;
387
- const skipJsonBody = (summary && isMutating) || (isError && (guidance || apiErrorMessage));
346
+ if (isError && typeof parsed.message === "string" && parsed.message) {
347
+ apiErrorMessage = stripProtobufPrefix(parsed.message);
348
+ }
349
+ const skipJsonBody = (summary && isMutating) || (isError && (apiErrorMessage || guidance));
388
350
 
389
351
  // Show extracted API error for errors without statusGuidance (400, 422, etc.)
390
352
  if (apiErrorMessage && !guidance) {
@@ -430,7 +392,8 @@ export const xcshApiToolRenderer = {
430
392
  if (guidance) {
431
393
  // Extract the API's specific error message from JSON body for prominent display
432
394
  const guidanceLines: string[] = [];
433
- const apiMessage = parsed && typeof parsed.message === "string" ? parsed.message : undefined;
395
+ const apiMessage =
396
+ parsed && typeof parsed.message === "string" ? stripProtobufPrefix(parsed.message) : undefined;
434
397
  if (apiMessage) guidanceLines.push(uiTheme.fg("error", apiMessage));
435
398
  guidanceLines.push(uiTheme.fg("warning", guidance));
436
399
  sections.push({ label: uiTheme.fg("toolTitle", "Guidance"), lines: guidanceLines });
@@ -42,8 +42,8 @@ export interface XcshApiToolDetails {
42
42
  errorCodeLabel?: string;
43
43
  /** Whether the request was automatically retried after a transient error (429/503). */
44
44
  retried?: boolean;
45
- /** Payload variables that were expanded (e.g. $F5XC_NAMESPACE → r-mordasiewicz). */
46
- expandedVars?: Array<{ variable: string; value: string }>;
45
+ /** The resolved JSON body string sent to the API (after $F5XC_* expansion). */
46
+ resolvedPayload?: string;
47
47
  }
48
48
 
49
49
  type XcshApiResult = AgentToolResult<XcshApiToolDetails> & { isError?: boolean };
@@ -179,22 +179,13 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
179
179
  signal: fetchSignal,
180
180
  };
181
181
 
182
- let expandedVars: Array<{ variable: string; value: string }> | undefined;
182
+ let resolvedPayload: string | undefined;
183
183
  if (params.payload && params.method !== "GET") {
184
184
  headers["Content-Type"] = "application/json";
185
185
  const payloadJson = JSON.stringify(params.payload);
186
186
  const resolved = this.#contextEnv.resolvePayloadVars(payloadJson);
187
187
  init.body = resolved;
188
- // Track which $F5XC_* variables were expanded
189
- if (resolved !== payloadJson) {
190
- expandedVars = [];
191
- const env = this.#contextEnv;
192
- for (const match of payloadJson.matchAll(/\$F5XC_([A-Z0-9_]+)/g)) {
193
- const key = `F5XC_${match[1]}`;
194
- const value = env.get(key) ?? process.env[key];
195
- if (value) expandedVars.push({ variable: `$${key}`, value });
196
- }
197
- }
188
+ resolvedPayload = resolved;
198
189
  }
199
190
 
200
191
  const startMs = performance.now();
@@ -253,7 +244,7 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
253
244
  itemCount,
254
245
  contentType: contentType || undefined,
255
246
  retried: retried || undefined,
256
- expandedVars,
247
+ resolvedPayload,
257
248
  };
258
249
 
259
250
  // Context-aware CRUD error guidance for common HTTP status codes