@f5xc-salesdemos/xcsh 19.29.5 → 19.30.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "19.29.5",
4
+ "version": "19.30.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",
@@ -50,12 +50,13 @@
50
50
  "dependencies": {
51
51
  "@agentclientprotocol/sdk": "0.16.1",
52
52
  "@mozilla/readability": "^0.6",
53
- "@f5xc-salesdemos/xcsh-stats": "19.29.5",
54
- "@f5xc-salesdemos/pi-agent-core": "19.29.5",
55
- "@f5xc-salesdemos/pi-ai": "19.29.5",
56
- "@f5xc-salesdemos/pi-natives": "19.29.5",
57
- "@f5xc-salesdemos/pi-tui": "19.29.5",
58
- "@f5xc-salesdemos/pi-utils": "19.29.5",
53
+ "@f5xc-salesdemos/xcsh-stats": "19.30.1",
54
+ "@f5xc-salesdemos/pi-agent-core": "19.30.1",
55
+ "@f5xc-salesdemos/pi-ai": "19.30.1",
56
+ "@f5xc-salesdemos/pi-natives": "19.30.1",
57
+ "@f5xc-salesdemos/pi-resource-management": "19.30.1",
58
+ "@f5xc-salesdemos/pi-tui": "19.30.1",
59
+ "@f5xc-salesdemos/pi-utils": "19.30.1",
59
60
  "@sinclair/typebox": "^0.34",
60
61
  "@xterm/headless": "^6.0",
61
62
  "ajv": "^8.20",
@@ -67,6 +68,7 @@
67
68
  "lru-cache": "11.3.1",
68
69
  "markit-ai": "0.5.0",
69
70
  "puppeteer": "^24.37",
71
+ "yaml": "2.9.0",
70
72
  "zod": "4.3.6"
71
73
  },
72
74
  "devDependencies": {
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.29.5",
21
- "commit": "01fdddd1aa0fe33bc912d65cd02a4bf36bd51c57",
22
- "shortCommit": "01fdddd",
20
+ "version": "19.30.1",
21
+ "commit": "b1150372a182f6653cf5a62a53edb7d1c04c277a",
22
+ "shortCommit": "b115037",
23
23
  "branch": "main",
24
- "tag": "v19.29.5",
25
- "commitDate": "2026-06-14T14:50:32Z",
26
- "buildDate": "2026-06-14T15:12:53.627Z",
24
+ "tag": "v19.30.1",
25
+ "commitDate": "2026-06-14T23:46:38Z",
26
+ "buildDate": "2026-06-15T00:08:14.358Z",
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/01fdddd1aa0fe33bc912d65cd02a4bf36bd51c57",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.29.5"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/b1150372a182f6653cf5a62a53edb7d1c04c277a",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.30.1"
33
33
  };
@@ -0,0 +1,42 @@
1
+ import { createKindResolver } from "@f5xc-salesdemos/pi-resource-management";
2
+ import { API_SPEC_INDEX, API_VALIDATION_DATA } from "../internal-urls/api-spec-index.generated";
3
+
4
+ export const kindResolver = createKindResolver(API_SPEC_INDEX, API_VALIDATION_DATA);
5
+
6
+ export type {
7
+ ApiSpecDomainEntry,
8
+ ApiSpecDomainResource,
9
+ ApiSpecIndex,
10
+ ApiSpecValidationResourceEntry,
11
+ DiffEntry,
12
+ KindResolver,
13
+ ManifestValidationResult,
14
+ OperationResult,
15
+ ParsedResourceArgs,
16
+ ResolvedKind,
17
+ ResourceClientOptions,
18
+ ResourceDiff,
19
+ ResourceError,
20
+ ResourceManifest,
21
+ ValidationError,
22
+ ValidationWarning,
23
+ } from "@f5xc-salesdemos/pi-resource-management";
24
+ export {
25
+ computeResourceDiff,
26
+ createKindResolver,
27
+ formatDiff,
28
+ formatMultiOperationSummary,
29
+ formatOperationResult,
30
+ formatResourceDetail,
31
+ formatResourceList,
32
+ formatValidationErrors,
33
+ KindResolutionError,
34
+ ManifestFileError,
35
+ ManifestParseError,
36
+ parseManifests,
37
+ parseResourceArgs,
38
+ ResourceClient,
39
+ readManifestFiles,
40
+ validateManifest,
41
+ validateManifests,
42
+ } from "@f5xc-salesdemos/pi-resource-management";
@@ -1382,6 +1382,99 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1382
1382
  description: t("commands.quit.description"),
1383
1383
  handle: shutdownHandler,
1384
1384
  },
1385
+ {
1386
+ name: "apply",
1387
+ description: "Apply a configuration to a resource from a file (create or update)",
1388
+ inlineHint: "-f <file.json|file.yaml> [-n namespace] [--dry-run=client|server]",
1389
+ allowArgs: true,
1390
+ getArgumentCompletions(_prefix: string) {
1391
+ return null;
1392
+ },
1393
+ handle: async (command, runtime) => {
1394
+ const { handleResourceCommand } = await import("./resource-commands");
1395
+ await handleResourceCommand("apply", command, runtime.ctx);
1396
+ },
1397
+ },
1398
+ {
1399
+ name: "create",
1400
+ description: "Create a resource from a file (fail if exists)",
1401
+ inlineHint: "-f <file.json|file.yaml> [-n namespace] [--dry-run=client|server]",
1402
+ allowArgs: true,
1403
+ getArgumentCompletions(_prefix: string) {
1404
+ return null;
1405
+ },
1406
+ handle: async (command, runtime) => {
1407
+ const { handleResourceCommand } = await import("./resource-commands");
1408
+ await handleResourceCommand("create", command, runtime.ctx);
1409
+ },
1410
+ },
1411
+ {
1412
+ name: "delete",
1413
+ description: "Delete a resource by file or by kind and name",
1414
+ inlineHint: "<kind> <name> | -f <file> [-n namespace] [--force]",
1415
+ allowArgs: true,
1416
+ getArgumentCompletions(prefix: string) {
1417
+ try {
1418
+ const { getKindCompletions } = require("./resource-commands") as typeof import("./resource-commands");
1419
+ return getKindCompletions(prefix);
1420
+ } catch {
1421
+ return null;
1422
+ }
1423
+ },
1424
+ handle: async (command, runtime) => {
1425
+ const { handleResourceCommand } = await import("./resource-commands");
1426
+ await handleResourceCommand("delete", command, runtime.ctx);
1427
+ },
1428
+ },
1429
+ {
1430
+ name: "describe",
1431
+ description: "Show detailed information about a resource",
1432
+ inlineHint: "<kind> <name> [-n namespace] [-o json|yaml]",
1433
+ allowArgs: true,
1434
+ getArgumentCompletions(prefix: string) {
1435
+ try {
1436
+ const { getKindCompletions } = require("./resource-commands") as typeof import("./resource-commands");
1437
+ return getKindCompletions(prefix);
1438
+ } catch {
1439
+ return null;
1440
+ }
1441
+ },
1442
+ handle: async (command, runtime) => {
1443
+ const { handleResourceCommand } = await import("./resource-commands");
1444
+ await handleResourceCommand("describe", command, runtime.ctx);
1445
+ },
1446
+ },
1447
+ {
1448
+ name: "diff",
1449
+ description: "Preview what changes would be applied from a file",
1450
+ inlineHint: "-f <file.json|file.yaml> [-n namespace]",
1451
+ allowArgs: true,
1452
+ getArgumentCompletions(_prefix: string) {
1453
+ return null;
1454
+ },
1455
+ handle: async (command, runtime) => {
1456
+ const { handleResourceCommand } = await import("./resource-commands");
1457
+ await handleResourceCommand("diff", command, runtime.ctx);
1458
+ },
1459
+ },
1460
+ {
1461
+ name: "get",
1462
+ description: "List or fetch F5 XC resources",
1463
+ inlineHint: "<kind> [name] [-n namespace] [-o json|yaml|table]",
1464
+ allowArgs: true,
1465
+ getArgumentCompletions(prefix: string) {
1466
+ try {
1467
+ const { getKindCompletions } = require("./resource-commands") as typeof import("./resource-commands");
1468
+ return getKindCompletions(prefix);
1469
+ } catch {
1470
+ return null;
1471
+ }
1472
+ },
1473
+ handle: async (command, runtime) => {
1474
+ const { handleResourceCommand } = await import("./resource-commands");
1475
+ await handleResourceCommand("get", command, runtime.ctx);
1476
+ },
1477
+ },
1385
1478
  {
1386
1479
  name: "context",
1387
1480
  description: t("commands.context.description"),
@@ -0,0 +1,258 @@
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 getKindCompletions(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 handleResourceCommand(
26
+ commandName: string,
27
+ command: ParsedBuiltinSlashCommand,
28
+ ctx: InteractiveModeContext,
29
+ ): Promise<void> {
30
+ ctx.editor.addToHistory(command.text);
31
+ ctx.editor.setText("");
32
+
33
+ const {
34
+ parseResourceArgs,
35
+ ResourceClient,
36
+ readManifestFiles,
37
+ ManifestFileError,
38
+ parseManifests,
39
+ ManifestParseError,
40
+ KindResolutionError,
41
+ validateManifest,
42
+ formatValidationErrors,
43
+ formatOperationResult,
44
+ formatResourceList,
45
+ formatResourceDetail,
46
+ formatMultiOperationSummary,
47
+ formatDiff,
48
+ } = await import("@f5xc-salesdemos/pi-resource-management");
49
+ const { kindResolver } = await import("../resource-management/index");
50
+
51
+ const parsed = parseResourceArgs(command.args);
52
+ if ("error" in parsed) {
53
+ ctx.showStatus(parsed.error);
54
+ return;
55
+ }
56
+
57
+ const { ContextService } = await import("../services/f5xc-context");
58
+ const { createContextEnv } = await import("../services/context-env");
59
+ const { settings } = await import("../config/settings");
60
+
61
+ let svc: typeof ContextService.prototype;
62
+ try {
63
+ svc = ContextService.instance;
64
+ } catch {
65
+ ctx.showStatus("No F5 XC context active. Run /context create to configure one first.");
66
+ return;
67
+ }
68
+
69
+ const status = svc.getStatus();
70
+ if (!status.isConfigured) {
71
+ ctx.showStatus("No F5 XC context active. Run /context create to configure one first.");
72
+ return;
73
+ }
74
+
75
+ const contextEnv = createContextEnv(settings);
76
+ const apiUrl = contextEnv.get("F5XC_API_URL");
77
+ const apiToken = contextEnv.get("F5XC_API_TOKEN");
78
+ const defaultNamespace = contextEnv.get("F5XC_NAMESPACE") ?? "";
79
+
80
+ if (!apiUrl || !apiToken) {
81
+ ctx.showStatus("Missing API credentials. Check your context configuration.");
82
+ return;
83
+ }
84
+
85
+ const client = new ResourceClient({
86
+ apiUrl,
87
+ apiToken,
88
+ namespace: parsed.namespace ?? defaultNamespace,
89
+ resolvePayloadVars: (json: string) => contextEnv.resolvePayloadVars(json),
90
+ });
91
+
92
+ const ns = parsed.namespace ?? defaultNamespace;
93
+
94
+ try {
95
+ switch (commandName) {
96
+ case "apply":
97
+ case "create": {
98
+ if (parsed.filenames.length === 0) {
99
+ ctx.showStatus(
100
+ `Usage: /${commandName} -f <file.json|file.yaml|dir/> [-n namespace] [--dry-run=client|server]`,
101
+ );
102
+ return;
103
+ }
104
+ const fileResults = await readManifestFiles(parsed.filenames, parsed.recursive);
105
+ const allObjects = fileResults.flatMap(r => r.objects);
106
+ if (allObjects.length === 0) {
107
+ ctx.showStatus("No resources found in the specified file(s).");
108
+ return;
109
+ }
110
+ const manifests = parseManifests(allObjects, fileResults[0]?.sourcePath ?? "input");
111
+ const results = [];
112
+ for (const manifest of manifests) {
113
+ const { result: valResult, resolved } = validateManifest(manifest, kindResolver, ns);
114
+ if (!valResult.valid) {
115
+ ctx.showStatus(formatValidationErrors(manifest, valResult));
116
+ results.push({
117
+ status: "error" as const,
118
+ error: { kind: "validation" as const, message: "Validation failed" },
119
+ });
120
+ continue;
121
+ }
122
+ if (!resolved) continue;
123
+
124
+ if (parsed.dryRun === "client") {
125
+ results.push({ status: "dry-run" as const, action: "create" as const });
126
+ ctx.showStatus(
127
+ formatOperationResult({ status: "dry-run", action: "create" }, manifest, parsed.outputFormat),
128
+ );
129
+ continue;
130
+ }
131
+
132
+ const opResult =
133
+ commandName === "apply"
134
+ ? await client.apply(manifest, resolved, ns, parsed.dryRun)
135
+ : await client.create(manifest, resolved, ns, parsed.dryRun);
136
+
137
+ results.push(opResult);
138
+ ctx.showStatus(formatOperationResult(opResult, manifest, parsed.outputFormat));
139
+ }
140
+
141
+ if (manifests.length > 1) {
142
+ ctx.showStatus(formatMultiOperationSummary(results as any, manifests));
143
+ }
144
+ break;
145
+ }
146
+
147
+ case "delete": {
148
+ let deleteTargets: { kind: string; name: string }[] = [];
149
+
150
+ if (parsed.filenames.length > 0) {
151
+ const fileResults = await readManifestFiles(parsed.filenames, parsed.recursive);
152
+ const allObjects = fileResults.flatMap(r => r.objects);
153
+ const manifests = parseManifests(allObjects, fileResults[0]?.sourcePath ?? "input");
154
+ deleteTargets = manifests.map(m => ({ kind: m.kind, name: m.metadata.name }));
155
+ } else if (parsed.kind && parsed.name) {
156
+ deleteTargets = [{ kind: parsed.kind, name: parsed.name }];
157
+ } else {
158
+ ctx.showStatus("Usage: /delete -f <file> or /delete <kind> <name> [-n namespace] [--force]");
159
+ return;
160
+ }
161
+
162
+ for (const target of deleteTargets) {
163
+ const resolved = kindResolver.resolveKind(target.kind);
164
+ const result = await client.delete(target.kind, target.name, resolved, ns);
165
+ ctx.showStatus(
166
+ formatOperationResult(
167
+ result,
168
+ { kind: target.kind, metadata: { name: target.name }, spec: {}, rawObject: {} } as any,
169
+ parsed.outputFormat,
170
+ ),
171
+ );
172
+ }
173
+ break;
174
+ }
175
+
176
+ case "describe": {
177
+ if (!parsed.kind) {
178
+ ctx.showStatus("Usage: /describe <kind> <name> [-n namespace] [-o json|yaml]");
179
+ return;
180
+ }
181
+ const kind = parsed.kind;
182
+ const name = parsed.name;
183
+ if (!name) {
184
+ ctx.showStatus("Usage: /describe <kind> <name> [-n namespace] [-o json|yaml]");
185
+ return;
186
+ }
187
+ const resolved = kindResolver.resolveKind(kind);
188
+ const result = await client.get(resolved, name, ns);
189
+ if (result.error) {
190
+ ctx.showStatus(`Error: ${result.error.message}`);
191
+ return;
192
+ }
193
+ if (result.resource) {
194
+ ctx.showStatus(formatResourceDetail(result.resource, kind, parsed.outputFormat));
195
+ }
196
+ break;
197
+ }
198
+
199
+ case "diff": {
200
+ if (parsed.filenames.length === 0) {
201
+ ctx.showStatus("Usage: /diff -f <file.json|file.yaml> [-n namespace]");
202
+ return;
203
+ }
204
+ const fileResults = await readManifestFiles(parsed.filenames, parsed.recursive);
205
+ const allObjects = fileResults.flatMap(r => r.objects);
206
+ const manifests = parseManifests(allObjects, fileResults[0]?.sourcePath ?? "input");
207
+
208
+ for (const manifest of manifests) {
209
+ const { resolved } = validateManifest(manifest, kindResolver, ns);
210
+ if (!resolved) {
211
+ ctx.showStatus(`Unknown kind: "${manifest.kind}"`);
212
+ continue;
213
+ }
214
+ const diffResult = await client.diff(manifest, resolved, ns);
215
+ if (diffResult.error) {
216
+ ctx.showStatus(`Error: ${diffResult.error.message}`);
217
+ continue;
218
+ }
219
+ if (diffResult.isNew) {
220
+ ctx.showStatus(`${manifest.kind}/${manifest.metadata.name} does not exist yet — will be created.`);
221
+ continue;
222
+ }
223
+ if (diffResult.diff) {
224
+ ctx.showStatus(formatDiff(diffResult.diff, manifest.kind, manifest.metadata.name));
225
+ }
226
+ }
227
+ break;
228
+ }
229
+
230
+ case "get": {
231
+ if (!parsed.kind) {
232
+ ctx.showStatus("Usage: /get <kind> [name] [-n namespace] [-o json|yaml|table]");
233
+ return;
234
+ }
235
+ const resolved = kindResolver.resolveKind(parsed.kind);
236
+ const result = await client.get(resolved, parsed.name, ns);
237
+ if (result.error) {
238
+ ctx.showStatus(`Error: ${result.error.message}`);
239
+ return;
240
+ }
241
+ if (result.items) {
242
+ ctx.showStatus(formatResourceList(result.items, parsed.kind, parsed.outputFormat));
243
+ } else if (result.resource) {
244
+ ctx.showStatus(formatResourceDetail(result.resource, parsed.kind, parsed.outputFormat));
245
+ }
246
+ break;
247
+ }
248
+ }
249
+ } catch (err) {
250
+ if (err instanceof ManifestFileError || err instanceof ManifestParseError || err instanceof KindResolutionError) {
251
+ ctx.showStatus(`Error: ${err.message}`);
252
+ } else {
253
+ ctx.showStatus(`Unexpected error: ${(err as Error).message}`);
254
+ }
255
+ }
256
+ }
257
+
258
+ export { getKindCompletions };