@f5xc-salesdemos/xcsh 18.64.1 → 18.65.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.64.2] - 2026-05-16
6
+
7
+ ### Fixed
8
+
9
+ - Welcome banner cloud provider hints: AWS SSO token expiry now correctly suggests `aws sso login` instead of `aws configure`; Google Cloud check replaced `gcloud auth list` (false positives on expired tokens) with `gcloud auth print-access-token`; F5 XC Context surfaces `errorClass` (network/URL) in hints; GitLab `project_inaccessible` gets its own hint; Salesforce differentiates `session_expired` and `not_configured` hints ([#825](https://github.com/f5xc-salesdemos/xcsh/issues/825))
10
+
5
11
  ## [18.64.0] - 2026-05-13
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.64.1",
4
+ "version": "18.65.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.64.1",
52
- "@f5xc-salesdemos/pi-agent-core": "18.64.1",
53
- "@f5xc-salesdemos/pi-ai": "18.64.1",
54
- "@f5xc-salesdemos/pi-natives": "18.64.1",
55
- "@f5xc-salesdemos/pi-tui": "18.64.1",
56
- "@f5xc-salesdemos/pi-utils": "18.64.1",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.65.0",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.65.0",
53
+ "@f5xc-salesdemos/pi-ai": "18.65.0",
54
+ "@f5xc-salesdemos/pi-natives": "18.65.0",
55
+ "@f5xc-salesdemos/pi-tui": "18.65.0",
56
+ "@f5xc-salesdemos/pi-utils": "18.65.0",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -64,6 +64,16 @@ interface IndexEntry {
64
64
  "x-f5xc-use-cases"?: string[];
65
65
  "x-f5xc-related-domains"?: string[];
66
66
  "x-f5xc-primary-resources"?: IndexEntryResource[];
67
+ "x-f5xc-description-long"?: string;
68
+ "x-f5xc-summary"?: string;
69
+ "x-f5xc-logo-svg"?: string;
70
+ "x-f5xc-cli-domain"?: string;
71
+ "x-f5xc-cli-metadata"?: {
72
+ quick_start: { command: string; description: string; expected_output: string };
73
+ common_workflows: Array<{ name: string; commands: string[] }>;
74
+ troubleshooting: Array<{ symptom: string; fix: string }>;
75
+ icon?: string;
76
+ };
67
77
  }
68
78
 
69
79
  interface RawIndex {
@@ -224,6 +234,8 @@ for (const entry of rawIndex.specifications) {
224
234
  const specContent = fs.readFileSync(specFile, "utf-8");
225
235
  const specJson = JSON.parse(specContent) as {
226
236
  paths?: Record<string, Record<string, SpecPathOperation>>;
237
+ info?: Record<string, unknown>;
238
+ components?: { schemas?: Record<string, Record<string, unknown>> };
227
239
  [k: string]: unknown;
228
240
  };
229
241
 
@@ -255,6 +267,14 @@ for (const entry of rawIndex.specifications) {
255
267
 
256
268
  const useCases = entry["x-f5xc-use-cases"];
257
269
  const relatedDomains = entry["x-f5xc-related-domains"];
270
+ const rawBp = specJson.info?.["x-f5xc-best-practices"] as Record<string, unknown> | undefined;
271
+ const bpData = rawBp
272
+ ? {
273
+ commonErrors: rawBp.common_errors ?? [],
274
+ securityNotes: rawBp.security_notes ?? [],
275
+ performanceTips: rawBp.performance_tips ?? [],
276
+ }
277
+ : undefined;
258
278
 
259
279
  domainEntries.push(
260
280
  [
@@ -280,6 +300,35 @@ for (const entry of rawIndex.specifications) {
280
300
  entry["x-f5xc-requires-tier"]
281
301
  ? `\t\t\trequiresTier: ${JSON.stringify(entry["x-f5xc-requires-tier"])},`
282
302
  : undefined,
303
+ entry["x-f5xc-description-long"]
304
+ ? `\t\t\tdescriptionLong: ${JSON.stringify(entry["x-f5xc-description-long"])},`
305
+ : undefined,
306
+ entry["x-f5xc-summary"] ? `\t\t\tsummary: ${JSON.stringify(entry["x-f5xc-summary"])},` : undefined,
307
+ entry["x-f5xc-logo-svg"] ? `\t\t\tlogoSvg: ${JSON.stringify(entry["x-f5xc-logo-svg"])},` : undefined,
308
+ entry["x-f5xc-cli-domain"] ? `\t\t\tcliDomain: ${JSON.stringify(entry["x-f5xc-cli-domain"])},` : undefined,
309
+ entry["x-f5xc-cli-metadata"]
310
+ ? (() => {
311
+ const raw = entry["x-f5xc-cli-metadata"]!;
312
+ const qs = raw.quick_start;
313
+ return `\t\t\tcliMetadata: ${JSON.stringify({
314
+ quickStart: {
315
+ command: qs.command,
316
+ description: qs.description,
317
+ expectedOutput: qs.expected_output,
318
+ },
319
+ commonWorkflows: (raw.common_workflows ?? []).map((w: { name: string; commands: string[] }) => ({
320
+ name: w.name,
321
+ commands: w.commands,
322
+ })),
323
+ troubleshooting: (raw.troubleshooting ?? []).map((t: { symptom: string; fix: string }) => ({
324
+ symptom: t.symptom,
325
+ fix: t.fix,
326
+ })),
327
+ icon: raw.icon,
328
+ })},`;
329
+ })()
330
+ : undefined,
331
+ bpData ? `\t\t\tbestPractices: ${JSON.stringify(bpData)},` : undefined,
283
332
  "\t\t},",
284
333
  ]
285
334
  .filter(Boolean)
@@ -295,10 +344,85 @@ const guidedWorkflows = rawIndex["x-f5xc-guided-workflows"];
295
344
  const errorResolution = rawIndex["x-f5xc-error-resolution"];
296
345
  const acronyms = rawIndex["x-f5xc-acronyms"];
297
346
 
347
+ // Extract operation-level and schema-level enrichments per domain
348
+ const enrichmentEntries: string[] = [];
349
+
350
+ for (const entry of rawIndex.specifications) {
351
+ const specFile = path.join(specsDir, entry.file);
352
+ if (!fs.existsSync(specFile)) continue;
353
+
354
+ const enrichSpecContent = fs.readFileSync(specFile, "utf-8");
355
+ const enrichSpecJson = JSON.parse(enrichSpecContent) as {
356
+ paths?: Record<string, Record<string, Record<string, unknown>>>;
357
+ components?: { schemas?: Record<string, Record<string, unknown>> };
358
+ };
359
+
360
+ const operationMeta: Record<string, Record<string, unknown>> = {};
361
+ for (const methods of Object.values(enrichSpecJson.paths ?? {})) {
362
+ for (const op of Object.values(methods)) {
363
+ if (typeof op !== "object" || !op) continue;
364
+ const opId = op.operationId as string | undefined;
365
+ if (!opId) continue;
366
+ const enrichment: Record<string, unknown> = {};
367
+ if (op["x-f5xc-danger-level"]) enrichment.dangerLevel = op["x-f5xc-danger-level"];
368
+ if (op["x-f5xc-confirmation-required"] != null)
369
+ enrichment.confirmationRequired = op["x-f5xc-confirmation-required"];
370
+ if (op["x-f5xc-side-effects"]) enrichment.sideEffects = op["x-f5xc-side-effects"];
371
+ if (op["x-f5xc-discovered-response-time"]) {
372
+ const rt = op["x-f5xc-discovered-response-time"] as Record<string, unknown>;
373
+ enrichment.discoveredResponseTime = {
374
+ p50Ms: rt.p50_ms,
375
+ p95Ms: rt.p95_ms,
376
+ p99Ms: rt.p99_ms,
377
+ sampleCount: rt.sample_count,
378
+ source: rt.source,
379
+ };
380
+ }
381
+ if (op["x-f5xc-required-fields"]) enrichment.requiredFields = op["x-f5xc-required-fields"];
382
+ if (op["x-f5xc-operation-metadata"]) {
383
+ const om = op["x-f5xc-operation-metadata"] as Record<string, unknown>;
384
+ const mapped: Record<string, unknown> = { purpose: om.purpose };
385
+ if (om.conditions) {
386
+ const cond = om.conditions as Record<string, unknown>;
387
+ if (cond.prerequisites) mapped.prerequisites = cond.prerequisites;
388
+ if (cond.postconditions) mapped.postconditions = cond.postconditions;
389
+ }
390
+ if (om.common_errors) {
391
+ mapped.commonErrors = (om.common_errors as Array<Record<string, unknown>>).map(e => ({
392
+ code: e.code,
393
+ message: e.message,
394
+ resolution: e.resolution ?? e.solution ?? "",
395
+ }));
396
+ }
397
+ if (om.performance_impact) {
398
+ const pi = om.performance_impact as Record<string, unknown>;
399
+ mapped.performanceImpact = { latency: pi.latency, resourceUsage: pi.resource_usage };
400
+ }
401
+ enrichment.operationMetadata = mapped;
402
+ }
403
+ if (Object.keys(enrichment).length > 0) operationMeta[opId] = enrichment;
404
+ }
405
+ }
406
+
407
+ const schemaEnrichments: Record<string, Record<string, unknown>> = {};
408
+ for (const [schemaName, schemaDef] of Object.entries(enrichSpecJson.components?.schemas ?? {})) {
409
+ const rec = schemaDef["x-f5xc-recommended-oneof-variant"] as Record<string, string> | undefined;
410
+ if (rec) {
411
+ schemaEnrichments[schemaName] = { recommendedOneofVariant: rec };
412
+ }
413
+ }
414
+
415
+ if (Object.keys(operationMeta).length > 0 || Object.keys(schemaEnrichments).length > 0) {
416
+ enrichmentEntries.push(
417
+ `\t${JSON.stringify(entry.domain)}: { operationMeta: ${JSON.stringify(operationMeta)}, schemaEnrichments: ${JSON.stringify(schemaEnrichments)} },`,
418
+ );
419
+ }
420
+ }
421
+
298
422
  const output = [
299
423
  "// Auto-generated by scripts/generate-api-spec-index.ts - DO NOT EDIT",
300
424
  "",
301
- `import type { ApiSpecIndex } from "./api-spec-types";`,
425
+ `import type { ApiSpecDomainEnrichments, ApiSpecIndex } from "./api-spec-types";`,
302
426
  "",
303
427
  `export const API_SPEC_VERSION = ${JSON.stringify(rawIndex.version)};`,
304
428
  "",
@@ -318,6 +442,10 @@ const output = [
318
442
  ...specDataEntries,
319
443
  `};`,
320
444
  "",
445
+ `export const API_SPEC_ENRICHMENTS: Readonly<Record<string, ApiSpecDomainEnrichments>> = {`,
446
+ ...enrichmentEntries,
447
+ `};`,
448
+ "",
321
449
  ]
322
450
  .filter(l => l !== undefined)
323
451
  .join("\n");
@@ -1,4 +1,10 @@
1
- import type { ApiSpecDomainEntry, ApiSpecIndex, OpenAPIPathOperation, OpenAPISpec } from "./api-spec-types";
1
+ import type {
2
+ ApiSpecDomainEnrichments,
3
+ ApiSpecDomainEntry,
4
+ ApiSpecIndex,
5
+ OpenAPIPathOperation,
6
+ OpenAPISpec,
7
+ } from "./api-spec-types";
2
8
  import type { InternalResource, InternalUrl } from "./types";
3
9
 
4
10
  const SCHEMA_RENDER_MAX_DEPTH = 3;
@@ -21,6 +27,7 @@ export interface ApiSpecResolver {
21
27
  export function createApiSpecResolver(
22
28
  index: ApiSpecIndex,
23
29
  data: Readonly<Record<string, OpenAPISpec>>,
30
+ enrichments?: Readonly<Record<string, ApiSpecDomainEnrichments>>,
24
31
  ): ApiSpecResolver {
25
32
  function lookup(domain: string): OpenAPISpec {
26
33
  const spec = data[domain];
@@ -68,7 +75,10 @@ export function createApiSpecResolver(
68
75
  if (Object.keys(matchingPaths).length === 0) {
69
76
  return makeResource(url, renderUnknownResource(resource, entry, spec));
70
77
  }
71
- return makeResource(url, renderResourceSpec(domain, resource, spec, entry, { crudOnly: crud }));
78
+ return makeResource(
79
+ url,
80
+ renderResourceSpec(domain, resource, spec, entry, { crudOnly: crud }, enrichments?.[domain]),
81
+ );
72
82
  }
73
83
 
74
84
  if (pathFilter) {
@@ -138,8 +148,9 @@ function renderDomainDetail(domain: string, entry: ApiSpecDomainEntry, spec: Ope
138
148
  sections.push("", `> ${tags.join(" | ")}`);
139
149
  }
140
150
 
141
- if (entry.descriptionMedium) {
142
- sections.push("", entry.descriptionMedium);
151
+ const desc = entry.descriptionLong ?? entry.descriptionMedium;
152
+ if (desc) {
153
+ sections.push("", desc);
143
154
  }
144
155
 
145
156
  sections.push("", "## Resources", "");
@@ -181,6 +192,52 @@ function renderDomainDetail(domain: string, entry: ApiSpecDomainEntry, spec: Ope
181
192
  sections.push("", "## Related Domains", `- ${entry.relatedDomains.join(", ")}`);
182
193
  }
183
194
 
195
+ if (entry.bestPractices) {
196
+ const bp = entry.bestPractices;
197
+ sections.push("", "## Best Practices");
198
+ if (bp.commonErrors.length > 0) {
199
+ sections.push("", "### Common Errors", "");
200
+ sections.push("| Code | Message | Resolution | Prevention |");
201
+ sections.push("|------|---------|------------|------------|");
202
+ for (const e of bp.commonErrors) {
203
+ sections.push(`| ${e.code} | ${e.message} | ${e.resolution} | ${e.prevention} |`);
204
+ }
205
+ }
206
+ if (bp.securityNotes.length > 0) {
207
+ sections.push("", "### Security Notes", ...bp.securityNotes.map(n => `- ${n}`));
208
+ }
209
+ if (bp.performanceTips.length > 0) {
210
+ sections.push("", "### Performance Tips", ...bp.performanceTips.map(t => `- ${t}`));
211
+ }
212
+ }
213
+
214
+ if (entry.cliMetadata?.quickStart?.command) {
215
+ const cli = entry.cliMetadata;
216
+ sections.push("", "## CLI Quick Start", "");
217
+ sections.push(`\`${cli.quickStart.command}\` — ${cli.quickStart.description}`);
218
+ const validWorkflows = cli.commonWorkflows?.filter(wf => wf.name) ?? [];
219
+ if (validWorkflows.length > 0) {
220
+ sections.push("", "### Common Workflows");
221
+ for (const wf of validWorkflows) {
222
+ if (wf.commands?.length) {
223
+ sections.push("", `**${wf.name}:**`);
224
+ for (const cmd of wf.commands) {
225
+ sections.push(`- \`${cmd}\``);
226
+ }
227
+ } else {
228
+ sections.push(`- ${wf.name}`);
229
+ }
230
+ }
231
+ }
232
+ const validTroubleshooting = cli.troubleshooting?.filter(ts => ts.symptom) ?? [];
233
+ if (validTroubleshooting.length > 0) {
234
+ sections.push("", "### Troubleshooting");
235
+ for (const ts of validTroubleshooting) {
236
+ sections.push(`- **${ts.symptom}:** ${ts.fix}`);
237
+ }
238
+ }
239
+ }
240
+
184
241
  sections.push("", `Read \`xcsh://api-spec/${domain}?resource={name}\` for full endpoint specification.`, "");
185
242
 
186
243
  return sections.join("\n");
@@ -297,6 +354,7 @@ function renderResourceSpec(
297
354
  spec: OpenAPISpec,
298
355
  entry?: ApiSpecDomainEntry,
299
356
  options?: { crudOnly?: boolean },
357
+ domainEnrichments?: ApiSpecDomainEnrichments,
300
358
  ): string {
301
359
  const matchingPaths = filterPathsByResource(spec, resource, entry);
302
360
  const label = options?.crudOnly ? "CRUD Operations" : "Full API Specification";
@@ -313,6 +371,52 @@ function renderResourceSpec(
313
371
  sections.push(`## ${method.toUpperCase()} ${pathKey}`, "");
314
372
  if (operation.summary) sections.push(String(operation.summary), "");
315
373
 
374
+ const opEnrichment = domainEnrichments?.operationMeta[operation.operationId ?? ""];
375
+ if (opEnrichment) {
376
+ const badges: string[] = [];
377
+ if (opEnrichment.dangerLevel) badges.push(`Danger: **${opEnrichment.dangerLevel}**`);
378
+ if (opEnrichment.confirmationRequired) badges.push("**Confirmation required**");
379
+ if (badges.length > 0) sections.push(badges.join(" | "), "");
380
+
381
+ if (opEnrichment.sideEffects) {
382
+ const fx = opEnrichment.sideEffects;
383
+ const parts: string[] = [];
384
+ if (fx.creates?.length) parts.push(`Creates: ${fx.creates.join(", ")}`);
385
+ if (fx.deletes?.length) parts.push(`Deletes: ${fx.deletes.join(", ")}`);
386
+ if (fx.modifies?.length) parts.push(`Modifies: ${fx.modifies.join(", ")}`);
387
+ if (parts.length > 0) sections.push(`Side effects: ${parts.join("; ")}`, "");
388
+ }
389
+
390
+ if (opEnrichment.discoveredResponseTime) {
391
+ const rt = opEnrichment.discoveredResponseTime;
392
+ sections.push(`Response time: p50=${rt.p50Ms}ms, p95=${rt.p95Ms}ms, p99=${rt.p99Ms}ms`, "");
393
+ }
394
+
395
+ if (opEnrichment.requiredFields?.length) {
396
+ sections.push(`Required fields: ${opEnrichment.requiredFields.join(", ")}`, "");
397
+ }
398
+
399
+ if (opEnrichment.operationMetadata) {
400
+ const meta = opEnrichment.operationMetadata;
401
+ if (meta.prerequisites?.length) {
402
+ sections.push("**Prerequisites:**", ...meta.prerequisites.map(p => `- ${p}`), "");
403
+ }
404
+ if (meta.commonErrors?.length) {
405
+ sections.push("**Common Errors:**");
406
+ for (const err of meta.commonErrors) {
407
+ sections.push(`- ${err.code}: ${err.message} — ${err.resolution}`);
408
+ }
409
+ sections.push("");
410
+ }
411
+ if (meta.performanceImpact) {
412
+ sections.push(
413
+ `Performance: ${meta.performanceImpact.latency}, ${meta.performanceImpact.resourceUsage}`,
414
+ "",
415
+ );
416
+ }
417
+ }
418
+ }
419
+
316
420
  const params = operation.parameters;
317
421
  if (params?.length) {
318
422
  sections.push("### Parameters", "");
@@ -333,10 +437,13 @@ function renderResourceSpec(
333
437
  const content = reqBody.content as Record<string, Record<string, unknown>> | undefined;
334
438
  const jsonContent = content?.["application/json"];
335
439
  if (jsonContent?.schema) {
336
- const schema = resolveSchemaRef(jsonContent.schema as Record<string, unknown>, spec);
337
- const oneOfStr = renderOneOfGroups(schema);
338
- if (oneOfStr) sections.push(oneOfStr);
339
- sections.push(renderSchemaAsTable(schema, spec));
440
+ const rawSchema = jsonContent.schema as Record<string, unknown>;
441
+ const schema = resolveSchemaRef(rawSchema, spec);
442
+ const schemaName = extractSchemaName(rawSchema);
443
+ const schemaRec = schemaName
444
+ ? domainEnrichments?.schemaEnrichments[schemaName]?.recommendedOneofVariant
445
+ : undefined;
446
+ sections.push(renderSchemaAsTable(schema, spec, 0, "", schemaRec));
340
447
  }
341
448
  }
342
449
 
@@ -350,7 +457,8 @@ function renderResourceSpec(
350
457
  const jsonResp = respContent?.["application/json"];
351
458
  if (jsonResp?.schema) {
352
459
  sections.push(`### Response ${status}`, "");
353
- const schema = resolveSchemaRef(jsonResp.schema as Record<string, unknown>, spec);
460
+ const rawRespSchema = jsonResp.schema as Record<string, unknown>;
461
+ const schema = resolveSchemaRef(rawRespSchema, spec);
354
462
  sections.push(renderSchemaAsTable(schema, spec));
355
463
  }
356
464
  }
@@ -385,7 +493,17 @@ function renderPathSpec(_domain: string, pathKey: string, spec: OpenAPISpec): st
385
493
  }
386
494
 
387
495
  function resolveSchemaRef(schema: Record<string, unknown>, spec: OpenAPISpec): Record<string, unknown> {
388
- const ref = schema.$ref as string | undefined;
496
+ let ref = schema.$ref as string | undefined;
497
+
498
+ if (!ref && Array.isArray(schema.allOf)) {
499
+ for (const item of schema.allOf as Record<string, unknown>[]) {
500
+ if (typeof item === "object" && item !== null && typeof item.$ref === "string") {
501
+ ref = item.$ref as string;
502
+ break;
503
+ }
504
+ }
505
+ }
506
+
389
507
  if (!ref) return schema;
390
508
 
391
509
  const match = ref.match(/^#\/components\/schemas\/(.+)$/);
@@ -393,7 +511,35 @@ function resolveSchemaRef(schema: Record<string, unknown>, spec: OpenAPISpec): R
393
511
 
394
512
  const schemaName = match[1];
395
513
  const resolved = spec.components?.schemas?.[schemaName];
396
- return (resolved as Record<string, unknown>) ?? schema;
514
+ if (!resolved) return schema;
515
+
516
+ const siblings: Record<string, unknown> = {};
517
+ for (const [key, val] of Object.entries(schema)) {
518
+ if (key !== "$ref" && key !== "allOf") {
519
+ siblings[key] = val;
520
+ }
521
+ }
522
+
523
+ if (Object.keys(siblings).length > 0) {
524
+ return { ...(resolved as Record<string, unknown>), ...siblings };
525
+ }
526
+
527
+ return resolved as Record<string, unknown>;
528
+ }
529
+
530
+ function extractSchemaName(schema: Record<string, unknown>): string | null {
531
+ let ref = schema.$ref as string | undefined;
532
+ if (!ref && Array.isArray(schema.allOf)) {
533
+ for (const item of schema.allOf as Record<string, unknown>[]) {
534
+ if (typeof item === "object" && item !== null && typeof item.$ref === "string") {
535
+ ref = item.$ref as string;
536
+ break;
537
+ }
538
+ }
539
+ }
540
+ if (!ref) return null;
541
+ const match = ref.match(/^#\/components\/schemas\/(.+)$/);
542
+ return match ? match[1] : null;
397
543
  }
398
544
 
399
545
  function formatFieldConstraints(prop: Record<string, unknown>): string {
@@ -434,19 +580,27 @@ function parseOneOfOptions(val: unknown): string[] {
434
580
  return [String(val)];
435
581
  }
436
582
 
437
- function renderOneOfGroups(schema: Record<string, unknown>): string {
583
+ function renderOneOfGroups(schema: Record<string, unknown>, recommended?: Readonly<Record<string, string>>): string {
438
584
  const groups: string[] = [];
439
585
  for (const [key, val] of Object.entries(schema)) {
440
586
  if (!key.startsWith("x-ves-oneof-field-")) continue;
441
587
  const groupName = key.slice("x-ves-oneof-field-".length);
442
588
  const options = parseOneOfOptions(val).join(" | ");
443
- groups.push(`- **${groupName}**: ${options}`);
589
+ const rec = recommended?.[groupName];
590
+ const recStr = rec ? ` (recommended: **${rec}**)` : "";
591
+ groups.push(`- **${groupName}**: ${options}${recStr}`);
444
592
  }
445
593
  if (groups.length === 0) return "";
446
594
  return ["**Mutually exclusive — choose one per group:**", ...groups, ""].join("\n");
447
595
  }
448
596
 
449
- function renderSchemaAsTable(schema: Record<string, unknown>, spec: OpenAPISpec, depth = 0, prefix = ""): string {
597
+ function renderSchemaAsTable(
598
+ schema: Record<string, unknown>,
599
+ spec: OpenAPISpec,
600
+ depth = 0,
601
+ prefix = "",
602
+ schemaRecommended?: Readonly<Record<string, string>>,
603
+ ): string {
450
604
  if (depth > SCHEMA_RENDER_MAX_DEPTH) return "";
451
605
 
452
606
  const resolved = resolveSchemaRef(schema, spec);
@@ -459,11 +613,11 @@ function renderSchemaAsTable(schema: Record<string, unknown>, spec: OpenAPISpec,
459
613
  const required = (resolved.required as string[]) ?? [];
460
614
  const rows: string[] = [];
461
615
 
462
- const oneOfStr = renderOneOfGroups(resolved);
616
+ const oneOfStr = renderOneOfGroups(resolved, schemaRecommended);
463
617
  if (depth === 0) {
464
618
  if (oneOfStr) rows.push(oneOfStr);
465
- rows.push("| Field | Type | Required | Constraints | Example | Description |");
466
- rows.push("|-------|------|----------|-------------|---------|-------------|");
619
+ rows.push("| Field | Type | Required | Default | Constraints | Example | Description |");
620
+ rows.push("|-------|------|----------|---------|-------------|---------|-------------|");
467
621
  }
468
622
 
469
623
  for (const [name, prop] of Object.entries(properties)) {
@@ -476,12 +630,42 @@ function renderSchemaAsTable(schema: Record<string, unknown>, spec: OpenAPISpec,
476
630
  const rawExample = (fieldProp["x-ves-example"] as string) ?? (fieldProp["x-f5xc-example"] as string) ?? "";
477
631
  const example = rawExample.length > 40 ? `${rawExample.slice(0, 37)}…` : rawExample;
478
632
 
479
- rows.push(`| ${fieldName} | ${type} | ${isRequired} | ${constraints} | ${example} | ${desc} |`);
633
+ const serverDefault = fieldProp["x-f5xc-server-default"];
634
+ const recommendedValue = fieldProp["x-f5xc-recommended-value"];
635
+ let defaultCol = "";
636
+ if (serverDefault != null) defaultCol = `${serverDefault} (server)`;
637
+ else if (recommendedValue != null) defaultCol = `${recommendedValue} (rec)`;
638
+
639
+ const reqFor = fieldProp["x-f5xc-required-for"] as Record<string, boolean> | undefined;
640
+ let reqStr = isRequired;
641
+ if (reqFor) {
642
+ const ops = Object.entries(reqFor)
643
+ .filter(([, v]) => v)
644
+ .map(([k]) => k);
645
+ if (ops.length > 0) reqStr = ops.join(", ");
646
+ }
647
+
648
+ rows.push(`| ${fieldName} | ${type} | ${reqStr} | ${defaultCol} | ${constraints} | ${example} | ${desc} |`);
649
+
650
+ const conflictsWith = fieldProp["x-f5xc-conflicts-with"] as string[] | undefined;
651
+ if (conflictsWith?.length) {
652
+ rows.push(`| └─ ${fieldName} | | **conflicts with** ${conflictsWith.join(", ")} | | | | |`);
653
+ }
654
+
655
+ const requires = fieldProp["x-f5xc-requires"] as
656
+ | Array<{ field: string; required?: boolean; reason?: string; min_items?: number }>
657
+ | undefined;
658
+ if (requires?.length) {
659
+ for (const dep of requires) {
660
+ const note = dep.min_items != null ? ` (min: ${dep.min_items})` : "";
661
+ rows.push(`| └─ ${fieldName} | | **requires** ${dep.field}${note} | | ${dep.reason ?? ""} | | |`);
662
+ }
663
+ }
480
664
 
481
665
  if (type === "object" && fieldProp.properties && depth < SCHEMA_RENDER_MAX_DEPTH) {
482
- const nestedOneOf = renderOneOfGroups(fieldProp);
666
+ const nestedOneOf = renderOneOfGroups(fieldProp, schemaRecommended);
483
667
  if (nestedOneOf) rows.push("", nestedOneOf);
484
- const nested = renderSchemaAsTable(fieldProp, spec, depth + 1, fieldName);
668
+ const nested = renderSchemaAsTable(fieldProp, spec, depth + 1, fieldName, schemaRecommended);
485
669
  const nestedLines = nested.split("\n").filter(l => l.startsWith("|") && !l.startsWith("| Field"));
486
670
  rows.push(...nestedLines);
487
671
  }
@@ -23,6 +23,34 @@ export interface ApiSpecDomainResource {
23
23
  readonly catalogCategories?: readonly string[];
24
24
  }
25
25
 
26
+ export interface ApiSpecCliMetadata {
27
+ readonly quickStart: {
28
+ readonly command: string;
29
+ readonly description: string;
30
+ readonly expectedOutput: string;
31
+ };
32
+ readonly commonWorkflows: readonly {
33
+ readonly name: string;
34
+ readonly commands?: readonly string[];
35
+ }[];
36
+ readonly troubleshooting: readonly {
37
+ readonly symptom?: string;
38
+ readonly fix?: string;
39
+ }[];
40
+ readonly icon?: string;
41
+ }
42
+
43
+ export interface ApiSpecBestPractices {
44
+ readonly commonErrors: readonly {
45
+ readonly code: number;
46
+ readonly message: string;
47
+ readonly resolution: string;
48
+ readonly prevention: string;
49
+ }[];
50
+ readonly securityNotes: readonly string[];
51
+ readonly performanceTips: readonly string[];
52
+ }
53
+
26
54
  export interface ApiSpecDomainEntry {
27
55
  readonly domain: string;
28
56
  readonly title: string;
@@ -39,6 +67,12 @@ export interface ApiSpecDomainEntry {
39
67
  readonly descriptionMedium?: string;
40
68
  readonly isPreview?: boolean;
41
69
  readonly requiresTier?: string;
70
+ readonly descriptionLong?: string;
71
+ readonly summary?: string;
72
+ readonly logoSvg?: string;
73
+ readonly cliDomain?: string;
74
+ readonly cliMetadata?: ApiSpecCliMetadata;
75
+ readonly bestPractices?: ApiSpecBestPractices;
42
76
  }
43
77
 
44
78
  export interface ApiSpecGuidedWorkflows {
@@ -111,6 +145,47 @@ export interface ApiSpecAcronyms {
111
145
  readonly acronyms: readonly ApiSpecAcronym[];
112
146
  }
113
147
 
148
+ export interface ApiSpecOperationEnrichment {
149
+ readonly dangerLevel?: "low" | "medium" | "high";
150
+ readonly confirmationRequired?: boolean;
151
+ readonly sideEffects?: {
152
+ readonly creates?: readonly string[];
153
+ readonly deletes?: readonly string[];
154
+ readonly modifies?: readonly string[];
155
+ };
156
+ readonly discoveredResponseTime?: {
157
+ readonly p50Ms: number;
158
+ readonly p95Ms: number;
159
+ readonly p99Ms: number;
160
+ readonly sampleCount: number;
161
+ readonly source: string;
162
+ };
163
+ readonly requiredFields?: readonly string[];
164
+ readonly operationMetadata?: {
165
+ readonly purpose: string;
166
+ readonly prerequisites?: readonly string[];
167
+ readonly postconditions?: readonly string[];
168
+ readonly commonErrors?: readonly {
169
+ readonly code: number;
170
+ readonly message: string;
171
+ readonly resolution: string;
172
+ }[];
173
+ readonly performanceImpact?: {
174
+ readonly latency: string;
175
+ readonly resourceUsage: string;
176
+ };
177
+ };
178
+ }
179
+
180
+ export interface ApiSpecSchemaEnrichment {
181
+ readonly recommendedOneofVariant?: Readonly<Record<string, string>>;
182
+ }
183
+
184
+ export interface ApiSpecDomainEnrichments {
185
+ readonly operationMeta: Readonly<Record<string, ApiSpecOperationEnrichment>>;
186
+ readonly schemaEnrichments: Readonly<Record<string, ApiSpecSchemaEnrichment>>;
187
+ }
188
+
114
189
  export interface ApiSpecIndex {
115
190
  readonly version: string;
116
191
  readonly timestamp: string;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.64.1",
21
- "commit": "899659f36b0f312b3772fe3c1595def03344e62b",
22
- "shortCommit": "899659f",
20
+ "version": "18.65.0",
21
+ "commit": "35c77b050317137efe631106d543647195229000",
22
+ "shortCommit": "35c77b0",
23
23
  "branch": "main",
24
- "tag": "v18.64.1",
25
- "commitDate": "2026-05-16T22:34:30Z",
26
- "buildDate": "2026-05-16T22:55:37.570Z",
24
+ "tag": "v18.65.0",
25
+ "commitDate": "2026-05-17T08:48:25Z",
26
+ "buildDate": "2026-05-17T09:14:22.007Z",
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/899659f36b0f312b3772fe3c1595def03344e62b",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.64.1"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/35c77b050317137efe631106d543647195229000",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.65.0"
33
33
  };
@@ -25,7 +25,7 @@ import type { ContextStatus } from "../services/f5xc-context";
25
25
  import { type ApiCatalogResolver, createApiCatalogResolver } from "./api-catalog-resolve";
26
26
  import type { ApiCatalogCategory, ApiCatalogCategorySummary, ApiCatalogIndex } from "./api-catalog-types";
27
27
  import { type ApiSpecResolver, createApiSpecResolver } from "./api-spec-resolve";
28
- import type { ApiSpecIndex, OpenAPISpec } from "./api-spec-types";
28
+ import type { ApiSpecDomainEnrichments, ApiSpecIndex, OpenAPISpec } from "./api-spec-types";
29
29
  import { getRuntimeBuildInfo, type RuntimeBuildInfo, renderAboutDoc } from "./build-info-runtime";
30
30
  import { loadComputerProfile, renderComputerProfileMarkdown, seedComputerProfile } from "./computer-profile";
31
31
  import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
@@ -51,15 +51,26 @@ const EMPTY_CATALOG_INDEX: ApiCatalogIndex = {
51
51
  defaults: {},
52
52
  };
53
53
 
54
- let _apiSpecCache: { index: ApiSpecIndex; data: Readonly<Record<string, OpenAPISpec>>; version: string } | null = null;
54
+ let _apiSpecCache: {
55
+ index: ApiSpecIndex;
56
+ data: Readonly<Record<string, OpenAPISpec>>;
57
+ enrichments: Readonly<Record<string, ApiSpecDomainEnrichments>>;
58
+ version: string;
59
+ } | null = null;
55
60
 
56
- function loadApiSpecs(): { index: ApiSpecIndex; data: Readonly<Record<string, OpenAPISpec>>; version: string } {
61
+ function loadApiSpecs(): {
62
+ index: ApiSpecIndex;
63
+ data: Readonly<Record<string, OpenAPISpec>>;
64
+ enrichments: Readonly<Record<string, ApiSpecDomainEnrichments>>;
65
+ version: string;
66
+ } {
57
67
  if (_apiSpecCache) return _apiSpecCache;
58
68
  try {
59
69
  const mod = require("./api-spec-index.generated") as {
60
70
  API_SPEC_INDEX?: ApiSpecIndex;
61
71
  API_SPEC_DATA?: Readonly<Record<string, unknown>>;
62
72
  API_SPEC_VERSION?: string;
73
+ API_SPEC_ENRICHMENTS?: Readonly<Record<string, ApiSpecDomainEnrichments>>;
63
74
  };
64
75
  const index = mod.API_SPEC_INDEX ?? EMPTY_INDEX;
65
76
  const version = mod.API_SPEC_VERSION ?? "unknown";
@@ -69,13 +80,14 @@ function loadApiSpecs(): { index: ApiSpecIndex; data: Readonly<Record<string, Op
69
80
  _apiSpecCache = {
70
81
  index,
71
82
  data: (mod.API_SPEC_DATA ?? {}) as Readonly<Record<string, OpenAPISpec>>,
83
+ enrichments: mod.API_SPEC_ENRICHMENTS ?? {},
72
84
  version,
73
85
  };
74
86
  } catch (err) {
75
87
  logger.warn("api-spec index unavailable, embedded specs disabled", {
76
88
  error: err instanceof Error ? err.message : String(err),
77
89
  });
78
- _apiSpecCache = { index: EMPTY_INDEX, data: {}, version: "unavailable" };
90
+ _apiSpecCache = { index: EMPTY_INDEX, data: {}, enrichments: {}, version: "unavailable" };
79
91
  }
80
92
  return _apiSpecCache;
81
93
  }
@@ -141,7 +153,7 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
141
153
  #getApiSpecResolver(): ApiSpecResolver {
142
154
  if (!this.#apiSpecResolver) {
143
155
  const specs = loadApiSpecs();
144
- this.#apiSpecResolver = createApiSpecResolver(specs.index, specs.data);
156
+ this.#apiSpecResolver = createApiSpecResolver(specs.index, specs.data, specs.enrichments);
145
157
  }
146
158
  return this.#apiSpecResolver;
147
159
  }