@f5xc-salesdemos/xcsh 19.30.3 → 19.31.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": "19.
|
|
4
|
+
"version": "19.31.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",
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
52
52
|
"@mozilla/readability": "^0.6",
|
|
53
|
-
"@f5xc-salesdemos/xcsh-stats": "19.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-resource-management": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
59
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "19.31.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.31.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.31.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.31.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-resource-management": "19.31.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-tui": "19.31.0",
|
|
59
|
+
"@f5xc-salesdemos/pi-utils": "19.31.0",
|
|
60
60
|
"@sinclair/typebox": "^0.34",
|
|
61
61
|
"@xterm/headless": "^6.0",
|
|
62
62
|
"ajv": "^8.20",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.31.0",
|
|
21
|
+
"commit": "f162b28f59b6db856a5c429de1dc9041767fc341",
|
|
22
|
+
"shortCommit": "f162b28",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.31.0",
|
|
25
|
+
"commitDate": "2026-06-15T02:42:18Z",
|
|
26
|
+
"buildDate": "2026-06-15T03:07:55.063Z",
|
|
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/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/f162b28f59b6db856a5c429de1dc9041767fc341",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.31.0"
|
|
33
33
|
};
|
|
@@ -185,6 +185,32 @@ Use these values when constructing API payloads and resource names.
|
|
|
185
185
|
Available F5 XC documentation topics: {{knowledgeTopics}}.
|
|
186
186
|
{{/if}}
|
|
187
187
|
{{/if}}
|
|
188
|
+
|
|
189
|
+
## Resource Manifest Format
|
|
190
|
+
|
|
191
|
+
When a user asks you to write, export, or save a resource manifest, produce a clean `{kind, metadata, spec}` JSON file that is compatible with the `/apply` slash command.
|
|
192
|
+
|
|
193
|
+
**Format:**
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"kind": "<resource_kind>",
|
|
197
|
+
"metadata": {
|
|
198
|
+
"name": "<resource_name>",
|
|
199
|
+
"namespace": "<namespace>"
|
|
200
|
+
},
|
|
201
|
+
"spec": { ... }
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Rules:**
|
|
206
|
+
- `kind` — the resource type in snake_case (e.g., `http_loadbalancer`, `origin_pool`, `app_firewall`)
|
|
207
|
+
- `metadata` — include only: `name`, `namespace`, `labels`, `annotations`, `description`, `disable`
|
|
208
|
+
- `spec` — include the full spec from the API response
|
|
209
|
+
- **Exclude:** `system_metadata`, `status`, and any other server-managed fields
|
|
210
|
+
- The output must round-trip through `/apply -f` without errors
|
|
211
|
+
|
|
212
|
+
When fetching a resource from the API, strip the server-added metadata fields and inject the `kind` field (which the API response does not include). The `/manifest` slash command does this automatically.
|
|
213
|
+
|
|
188
214
|
{{#if userProfile}}
|
|
189
215
|
## Primary Human
|
|
190
216
|
|
|
@@ -1475,6 +1475,24 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
1475
1475
|
await handleResourceCommand("get", command, runtime.ctx);
|
|
1476
1476
|
},
|
|
1477
1477
|
},
|
|
1478
|
+
{
|
|
1479
|
+
name: "manifest",
|
|
1480
|
+
description: "Export live F5 XC resources as {kind, metadata, spec} manifest files",
|
|
1481
|
+
inlineHint: "<kind> [name] [-n namespace] [-o json|yaml] [-f output-path] [--all]",
|
|
1482
|
+
allowArgs: true,
|
|
1483
|
+
getArgumentCompletions(prefix: string) {
|
|
1484
|
+
try {
|
|
1485
|
+
const { getExportKindCompletions } = require("./export-command") as typeof import("./export-command");
|
|
1486
|
+
return getExportKindCompletions(prefix);
|
|
1487
|
+
} catch {
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
handle: async (command, runtime) => {
|
|
1492
|
+
const { handleExportResourceCommand } = await import("./export-command");
|
|
1493
|
+
await handleExportResourceCommand(command, runtime.ctx);
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1478
1496
|
{
|
|
1479
1497
|
name: "context",
|
|
1480
1498
|
description: t("commands.context.description"),
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { AutocompleteItem } from "@f5xc-salesdemos/pi-tui";
|
|
2
|
+
import type { InteractiveModeContext } from "../modes/types";
|
|
3
|
+
|
|
4
|
+
interface ParsedBuiltinSlashCommand {
|
|
5
|
+
name: string;
|
|
6
|
+
args: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getExportKindCompletions(prefix: string): AutocompleteItem[] | null {
|
|
11
|
+
try {
|
|
12
|
+
const { kindResolver } = require("../resource-management/index") as typeof import("../resource-management/index");
|
|
13
|
+
const kinds = kindResolver.getKindsWithApiPaths();
|
|
14
|
+
const lower = prefix.toLowerCase();
|
|
15
|
+
const items = kinds
|
|
16
|
+
.filter(k => k.toLowerCase().startsWith(lower))
|
|
17
|
+
.slice(0, 20)
|
|
18
|
+
.map(k => ({ value: `${k} `, label: k }));
|
|
19
|
+
return items.length > 0 ? items : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function handleExportResourceCommand(
|
|
26
|
+
command: ParsedBuiltinSlashCommand,
|
|
27
|
+
ctx: InteractiveModeContext,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
ctx.editor.addToHistory(command.text);
|
|
30
|
+
ctx.editor.setText("");
|
|
31
|
+
|
|
32
|
+
const { parseExportArgs, ResourceClient, KindResolutionError, toManifest, formatManifestOutput } = await import(
|
|
33
|
+
"@f5xc-salesdemos/pi-resource-management"
|
|
34
|
+
);
|
|
35
|
+
const { kindResolver } = await import("../resource-management/index");
|
|
36
|
+
|
|
37
|
+
const parsed = parseExportArgs(command.args);
|
|
38
|
+
if ("error" in parsed) {
|
|
39
|
+
ctx.showStatus(parsed.error);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { ContextService } = await import("../services/f5xc-context");
|
|
44
|
+
const { createContextEnv } = await import("../services/context-env");
|
|
45
|
+
const { settings } = await import("../config/settings");
|
|
46
|
+
|
|
47
|
+
let svc: typeof ContextService.prototype;
|
|
48
|
+
try {
|
|
49
|
+
svc = ContextService.instance;
|
|
50
|
+
} catch {
|
|
51
|
+
ctx.showStatus("No F5 XC context active. Run /context create to configure one first.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const status = svc.getStatus();
|
|
56
|
+
if (!status.isConfigured) {
|
|
57
|
+
ctx.showStatus("No F5 XC context active. Run /context create to configure one first.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const contextEnv = createContextEnv(settings);
|
|
62
|
+
const apiUrl = contextEnv.get("F5XC_API_URL");
|
|
63
|
+
const apiToken = contextEnv.get("F5XC_API_TOKEN");
|
|
64
|
+
const defaultNamespace = contextEnv.get("F5XC_NAMESPACE") ?? "";
|
|
65
|
+
|
|
66
|
+
if (!apiUrl || !apiToken) {
|
|
67
|
+
ctx.showStatus("Missing API credentials. Check your context configuration.");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ns = parsed.namespace ?? defaultNamespace;
|
|
72
|
+
const client = new ResourceClient({
|
|
73
|
+
apiUrl,
|
|
74
|
+
apiToken,
|
|
75
|
+
namespace: ns,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (parsed.outputFormat === "hcl") {
|
|
79
|
+
ctx.showStatus(
|
|
80
|
+
'HCL export requires AI-assisted transformation. Use a conversational request instead:\n "Export my-lb as Terraform HCL"',
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fmt = parsed.outputFormat as "json" | "yaml";
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const manifests: Array<{ kind: string; metadata: Record<string, unknown>; spec: Record<string, unknown> }> = [];
|
|
89
|
+
|
|
90
|
+
if (parsed.all) {
|
|
91
|
+
const result = await client.exportAll(kindResolver, ns, (kind, count) => {
|
|
92
|
+
ctx.showStatus(`Exporting ${kind} (${count} found)...`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
for (const err of result.errors) {
|
|
96
|
+
ctx.showStatus(`Warning: ${err.kind}: ${err.error.message}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
manifests.push(...result.manifests);
|
|
100
|
+
} else if (parsed.kind && parsed.name) {
|
|
101
|
+
const resolved = kindResolver.resolveKind(parsed.kind);
|
|
102
|
+
const result = await client.exportOne(parsed.kind, resolved, parsed.name, ns);
|
|
103
|
+
if (result.error) {
|
|
104
|
+
ctx.showStatus(`Error: ${result.error.message}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
manifests.push(result.manifest!);
|
|
108
|
+
} else if (parsed.kind) {
|
|
109
|
+
const resolved = kindResolver.resolveKind(parsed.kind);
|
|
110
|
+
const getResult = await client.get(resolved, undefined, ns);
|
|
111
|
+
if (getResult.error) {
|
|
112
|
+
ctx.showStatus(`Error: ${getResult.error.message}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!getResult.items || getResult.items.length === 0) {
|
|
116
|
+
ctx.showStatus(`No ${parsed.kind} resources found.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const item of getResult.items) {
|
|
120
|
+
manifests.push(toManifest(item, parsed.kind));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (manifests.length === 0) {
|
|
125
|
+
ctx.showStatus("No resources found to export.");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const output = formatManifestOutput(manifests, fmt);
|
|
130
|
+
|
|
131
|
+
if (parsed.outputFile) {
|
|
132
|
+
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
133
|
+
const { dirname, resolve, sep } = await import("node:path");
|
|
134
|
+
const isDir = parsed.outputFile.endsWith("/");
|
|
135
|
+
|
|
136
|
+
if (isDir) {
|
|
137
|
+
await mkdir(parsed.outputFile, { recursive: true });
|
|
138
|
+
const baseDir = resolve(parsed.outputFile);
|
|
139
|
+
const ext = fmt === "yaml" ? "yaml" : "json";
|
|
140
|
+
for (const m of manifests) {
|
|
141
|
+
const safeKind = m.kind.replace(/[/\\]/g, "_");
|
|
142
|
+
const safeName = ((m.metadata.name as string) ?? "unknown").replace(/[/\\]/g, "_");
|
|
143
|
+
const filename = `${safeKind}-${safeName}.${ext}`;
|
|
144
|
+
const filepath = resolve(parsed.outputFile, filename);
|
|
145
|
+
if (!filepath.startsWith(baseDir + sep) && filepath !== baseDir) {
|
|
146
|
+
ctx.showStatus(`Skipping unsafe resource name: ${m.metadata.name}`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const content = formatManifestOutput([m], fmt);
|
|
150
|
+
await writeFile(filepath, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
ctx.showStatus(`Exported ${manifests.length} resource(s) to ${parsed.outputFile}`);
|
|
153
|
+
} else {
|
|
154
|
+
await mkdir(dirname(parsed.outputFile), { recursive: true });
|
|
155
|
+
await writeFile(parsed.outputFile, output, "utf-8");
|
|
156
|
+
ctx.showStatus(`Exported ${manifests.length} resource(s) to ${parsed.outputFile}`);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
ctx.showStatus(output);
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err instanceof KindResolutionError) {
|
|
163
|
+
ctx.showStatus(`Error: ${err.message}`);
|
|
164
|
+
} else {
|
|
165
|
+
ctx.showStatus(`Unexpected error: ${(err as Error).message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { getExportKindCompletions };
|