@f5xc-salesdemos/xcsh 18.57.0 → 18.58.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.58.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.58.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "18.58.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "18.58.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "18.58.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "18.58.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "18.58.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.58.0",
|
|
21
|
+
"commit": "7885c8aaca7e58809d92aa0c14ba5913a46c2cef",
|
|
22
|
+
"shortCommit": "7885c8a",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-09T22:
|
|
26
|
-
"buildDate": "2026-05-
|
|
24
|
+
"tag": "v18.58.0",
|
|
25
|
+
"commitDate": "2026-05-09T22:49:10Z",
|
|
26
|
+
"buildDate": "2026-05-09T23:10:04.604Z",
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/7885c8aaca7e58809d92aa0c14ba5913a46c2cef",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.58.0"
|
|
33
33
|
};
|
package/src/services/f5xc-env.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
13
|
-
import {
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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:
|
|
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
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
115
|
-
|
|
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
|
-
|
|
207
|
+
outputBlock.invalidate();
|
|
143
208
|
},
|
|
144
209
|
};
|
|
145
210
|
},
|
|
146
211
|
mergeCallAndResult: true,
|
|
212
|
+
inline: true,
|
|
147
213
|
};
|
package/src/tools/xcsh-api.ts
CHANGED
|
@@ -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:
|