@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.
|
|
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.
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
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.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.60.0",
|
|
21
|
+
"commit": "f941cc11a5e3bdc300351bb68621137b5e72b76f",
|
|
22
|
+
"shortCommit": "f941cc1",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
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
|
|
116
|
+
/** Build a compact identity summary for a single F5 XC resource. */
|
|
119
117
|
function buildResourceSummary(
|
|
120
118
|
parsed: Record<string, unknown>,
|
|
121
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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:
|
|
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
|
|
266
|
-
const methodBadge =
|
|
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:
|
|
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
|
|
306
|
-
if (
|
|
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
|
|
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)
|
|
387
|
-
|
|
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 =
|
|
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 });
|
package/src/tools/xcsh-api.ts
CHANGED
|
@@ -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
|
-
/**
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
247
|
+
resolvedPayload,
|
|
257
248
|
};
|
|
258
249
|
|
|
259
250
|
// Context-aware CRUD error guidance for common HTTP status codes
|