@f5xc-salesdemos/xcsh 18.75.3 → 18.77.3

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.75.3",
4
+ "version": "18.77.3",
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": "workspace:*",
52
- "@f5xc-salesdemos/pi-agent-core": "workspace:*",
53
- "@f5xc-salesdemos/pi-ai": "workspace:*",
54
- "@f5xc-salesdemos/pi-natives": "workspace:*",
55
- "@f5xc-salesdemos/pi-tui": "workspace:*",
56
- "@f5xc-salesdemos/pi-utils": "workspace:*",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.77.3",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.77.3",
53
+ "@f5xc-salesdemos/pi-ai": "18.77.3",
54
+ "@f5xc-salesdemos/pi-natives": "18.77.3",
55
+ "@f5xc-salesdemos/pi-tui": "18.77.3",
56
+ "@f5xc-salesdemos/pi-utils": "18.77.3",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -10,6 +10,12 @@ interface SpecPathOperation {
10
10
  [key: string]: unknown;
11
11
  }
12
12
 
13
+ const DEDUP_SUFFIX_RE = /_(get|post|put|delete|patch)(_\d+)?$/;
14
+
15
+ function normalizeOperationId(opId: string): string {
16
+ return opId.replace(DEDUP_SUFFIX_RE, "");
17
+ }
18
+
13
19
  function findResourceSchemaComponents(
14
20
  resourceName: string,
15
21
  paths: Record<string, Record<string, SpecPathOperation>>,
@@ -25,7 +31,7 @@ function findResourceSchemaComponents(
25
31
  for (const op of Object.values(methods)) {
26
32
  const opId = op?.operationId;
27
33
  if (!opId) continue;
28
- const match = opId.match(/^ves\.io\.schema\.(.+?)\.(?:API|CustomAPI)\./);
34
+ const match = normalizeOperationId(opId).match(/^ves\.io\.schema\.(.+?)\.(?:API|CustomAPI)\./);
29
35
  if (match) found.add(match[1]);
30
36
  }
31
37
  }
@@ -214,6 +220,29 @@ async function downloadCatalog(specsDir: string): Promise<Record<string, unknown
214
220
  }
215
221
  }
216
222
 
223
+ async function downloadValidation(specsDir: string): Promise<Record<string, unknown> | null> {
224
+ const validationPath = path.join(specsDir, "validation.json");
225
+ if (fs.existsSync(validationPath)) {
226
+ return JSON.parse(fs.readFileSync(validationPath, "utf-8"));
227
+ }
228
+
229
+ const validationTag = process.env.API_SPECS_TAG ?? (await resolveLatestTag());
230
+ const validationUrl = `https://github.com/${REPO}/releases/download/${validationTag}/validation.json`;
231
+ console.log(`Downloading validation.json from ${validationUrl}...`);
232
+ try {
233
+ const response = await fetchWithRetry(validationUrl, { redirect: "follow" });
234
+ if (!response.ok) {
235
+ console.warn(`validation.json not found (${response.status}), skipping validation data generation`);
236
+ return null;
237
+ }
238
+ const text = await response.text();
239
+ return JSON.parse(text);
240
+ } catch (err) {
241
+ console.warn(`Failed to download validation.json: ${err instanceof Error ? err.message : err}`);
242
+ return null;
243
+ }
244
+ }
245
+
217
246
  function serializeEnrichment(key: string, value: unknown): string | undefined {
218
247
  if (!value) return undefined;
219
248
  return `\t${key}: ${JSON.stringify(value)},`;
@@ -238,6 +267,7 @@ if (!fs.existsSync(indexPath)) {
238
267
  const rawIndex: RawIndex = JSON.parse(fs.readFileSync(indexPath, "utf-8"));
239
268
 
240
269
  const catalog = await downloadCatalog(specsDir);
270
+ const validation = await downloadValidation(specsDir);
241
271
 
242
272
  const pathToCatalogCategories = new Map<string, string[]>();
243
273
  if (catalog) {
@@ -441,8 +471,12 @@ for (const entry of rawIndex.specifications) {
441
471
  const schemaEnrichments: Record<string, Record<string, unknown>> = {};
442
472
  for (const [schemaName, schemaDef] of Object.entries(enrichSpecJson.components?.schemas ?? {})) {
443
473
  const rec = schemaDef["x-f5xc-recommended-oneof-variant"] as Record<string, string> | undefined;
444
- if (rec) {
445
- schemaEnrichments[schemaName] = { recommendedOneofVariant: rec };
474
+ const minConfig = schemaDef["x-f5xc-minimum-configuration"] as Record<string, unknown> | undefined;
475
+ if (rec || minConfig) {
476
+ schemaEnrichments[schemaName] = {
477
+ ...(rec ? { recommendedOneofVariant: rec } : {}),
478
+ ...(minConfig ? { minimumConfiguration: minConfig } : {}),
479
+ };
446
480
  }
447
481
  }
448
482
 
@@ -456,7 +490,7 @@ for (const entry of rawIndex.specifications) {
456
490
  const output = [
457
491
  "// Auto-generated by scripts/generate-api-spec-index.ts - DO NOT EDIT",
458
492
  "",
459
- `import type { ApiSpecDomainEnrichments, ApiSpecIndex } from "./api-spec-types";`,
493
+ `import type { ApiSpecDomainEnrichments, ApiSpecIndex, ApiSpecValidationResourceEntry } from "./api-spec-types";`,
460
494
  "",
461
495
  `export const API_SPEC_VERSION = ${JSON.stringify(rawIndex.version)};`,
462
496
  "",
@@ -480,6 +514,12 @@ const output = [
480
514
  ...enrichmentEntries,
481
515
  `};`,
482
516
  "",
517
+ ...(validation
518
+ ? [
519
+ `export const API_VALIDATION_DATA: Readonly<Record<string, ApiSpecValidationResourceEntry>> = ${JSON.stringify((validation as { required_fields?: { resources?: Record<string, unknown> } }).required_fields?.resources ?? {})};`,
520
+ "",
521
+ ]
522
+ : [`export const API_VALIDATION_DATA: Readonly<Record<string, ApiSpecValidationResourceEntry>> = {};`, ""]),
483
523
  ]
484
524
  .filter(l => l !== undefined)
485
525
  .join("\n");
@@ -1199,6 +1199,16 @@ export const SETTINGS_SCHEMA = {
1199
1199
  },
1200
1200
  },
1201
1201
 
1202
+ "todo.verbose": {
1203
+ type: "boolean",
1204
+ default: false,
1205
+ ui: {
1206
+ tab: "tools",
1207
+ label: "Show Todo Tool Output",
1208
+ description: "Display todo_write tool calls and reminders in the chat transcript",
1209
+ },
1210
+ },
1211
+
1202
1212
  // Search and AST tools
1203
1213
  "find.enabled": {
1204
1214
  type: "boolean",
@@ -2,6 +2,8 @@ import type {
2
2
  ApiSpecDomainEnrichments,
3
3
  ApiSpecDomainEntry,
4
4
  ApiSpecIndex,
5
+ ApiSpecMinimumConfiguration,
6
+ ApiSpecValidationResourceEntry,
5
7
  OpenAPIPathOperation,
6
8
  OpenAPISpec,
7
9
  } from "./api-spec-types";
@@ -10,6 +12,12 @@ import type { InternalResource, InternalUrl } from "./types";
10
12
  const SCHEMA_RENDER_MAX_DEPTH = 3;
11
13
  const CRUD_OPERATION_SUFFIXES = [".API.Create", ".API.Replace", ".API.Get", ".API.List", ".API.Delete"];
12
14
 
15
+ const DEDUP_SUFFIX_RE = /_(get|post|put|delete|patch)(_\d+)?$/;
16
+
17
+ function normalizeOperationId(opId: string): string {
18
+ return opId.replace(DEDUP_SUFFIX_RE, "");
19
+ }
20
+
13
21
  const groupsCache = new WeakMap<OpenAPISpec, Map<string, Record<string, Record<string, OpenAPIPathOperation>>>>();
14
22
 
15
23
  function getCachedGroups(spec: OpenAPISpec): Map<string, Record<string, Record<string, OpenAPIPathOperation>>> {
@@ -28,6 +36,7 @@ export function createApiSpecResolver(
28
36
  index: ApiSpecIndex,
29
37
  data: Readonly<Record<string, OpenAPISpec>>,
30
38
  enrichments?: Readonly<Record<string, ApiSpecDomainEnrichments>>,
39
+ validationData?: Readonly<Record<string, ApiSpecValidationResourceEntry>>,
31
40
  ): ApiSpecResolver {
32
41
  function lookup(domain: string): OpenAPISpec {
33
42
  const spec = data[domain];
@@ -59,6 +68,16 @@ export function createApiSpecResolver(
59
68
  return makeResource(url, renderGlossary(index));
60
69
  }
61
70
 
71
+ if (domain === "validation" || domain.startsWith("validation/")) {
72
+ const resourceKey = domain.replace(/^validation\/?/, "");
73
+ return makeResource(
74
+ url,
75
+ resourceKey
76
+ ? renderValidationDetail(resourceKey, validationData)
77
+ : renderValidationIndex(validationData),
78
+ );
79
+ }
80
+
62
81
  const entry = index.domains.find(d => d.domain === domain);
63
82
  if (!entry) {
64
83
  return makeResource(url, renderUnknownDomain(domain, index));
@@ -77,7 +96,15 @@ export function createApiSpecResolver(
77
96
  }
78
97
  return makeResource(
79
98
  url,
80
- renderResourceSpec(domain, resource, spec, entry, { crudOnly: crud }, enrichments?.[domain]),
99
+ renderResourceSpec(
100
+ domain,
101
+ resource,
102
+ spec,
103
+ entry,
104
+ { crudOnly: crud },
105
+ enrichments?.[domain],
106
+ validationData,
107
+ ),
81
108
  );
82
109
  }
83
110
 
@@ -244,7 +271,7 @@ function renderDomainDetail(domain: string, entry: ApiSpecDomainEntry, spec: Ope
244
271
  }
245
272
 
246
273
  function extractSchemaComponent(operationId: string): string | null {
247
- const match = operationId.match(/^ves\.io\.schema\.(.+?)\.(?:API|CustomAPI)\./);
274
+ const match = normalizeOperationId(operationId).match(/^ves\.io\.schema\.(.+?)\.(?:API|CustomAPI)\./);
248
275
  return match ? match[1] : null;
249
276
  }
250
277
 
@@ -348,6 +375,78 @@ function filterPathsByResource(
348
375
  return result;
349
376
  }
350
377
 
378
+ function lookupMinConfig(
379
+ schemaKey: string,
380
+ enrichments: ApiSpecDomainEnrichments,
381
+ ): ApiSpecMinimumConfiguration | undefined {
382
+ const direct = enrichments.schemaEnrichments[schemaKey]?.minimumConfiguration;
383
+ if (direct) return direct as ApiSpecMinimumConfiguration;
384
+ const createReq = enrichments.schemaEnrichments[`${schemaKey}CreateRequest`]?.minimumConfiguration;
385
+ if (createReq) return createReq as ApiSpecMinimumConfiguration;
386
+ const leaf = schemaKey.includes(".") ? schemaKey.split(".").at(-1) : undefined;
387
+ if (leaf) {
388
+ const leafDirect = enrichments.schemaEnrichments[leaf]?.minimumConfiguration;
389
+ if (leafDirect) return leafDirect as ApiSpecMinimumConfiguration;
390
+ const leafCreateReq = enrichments.schemaEnrichments[`${leaf}CreateRequest`]?.minimumConfiguration;
391
+ if (leafCreateReq) return leafCreateReq as ApiSpecMinimumConfiguration;
392
+ }
393
+ return undefined;
394
+ }
395
+
396
+ function findResourceMinConfig(
397
+ resource: string,
398
+ entry: ApiSpecDomainEntry | undefined,
399
+ matchingPaths: Record<string, Record<string, OpenAPIPathOperation>>,
400
+ domainEnrichments?: ApiSpecDomainEnrichments,
401
+ ): ApiSpecMinimumConfiguration | undefined {
402
+ if (!domainEnrichments) return undefined;
403
+
404
+ if (entry) {
405
+ const indexedResource = entry.resources.find(r => r.name === resource);
406
+ if (indexedResource?.schemaComponents?.length) {
407
+ const mc = lookupMinConfig(indexedResource.schemaComponents[0], domainEnrichments);
408
+ if (mc) return mc;
409
+ }
410
+ }
411
+
412
+ for (const methods of Object.values(matchingPaths)) {
413
+ for (const op of Object.values(methods)) {
414
+ if (typeof op !== "object" || !op) continue;
415
+ const opId = op.operationId;
416
+ if (!opId) continue;
417
+ const schema = extractSchemaComponent(opId);
418
+ if (schema) {
419
+ const mc = lookupMinConfig(schema, domainEnrichments);
420
+ if (mc) return mc;
421
+ }
422
+ }
423
+ }
424
+
425
+ return undefined;
426
+ }
427
+
428
+ function renderMinimumConfigSection(mc: ApiSpecMinimumConfiguration): string {
429
+ const sections: string[] = ["## Quick Start — Minimum Configuration", ""];
430
+
431
+ if (mc.required_fields.length > 0) {
432
+ sections.push(`**Required fields:** ${mc.required_fields.join(", ")}`, "");
433
+ }
434
+
435
+ if (mc.example_json) {
436
+ sections.push("### JSON Example", "", "```json", mc.example_json, "```", "");
437
+ }
438
+
439
+ if (mc.example_yaml) {
440
+ sections.push("### YAML Example", "", "```yaml", mc.example_yaml, "```", "");
441
+ }
442
+
443
+ if (mc.example_curl) {
444
+ sections.push("### curl Example", "", "```bash", mc.example_curl, "```", "");
445
+ }
446
+
447
+ return sections.join("\n");
448
+ }
449
+
351
450
  function renderResourceSpec(
352
451
  _domain: string,
353
452
  resource: string,
@@ -355,6 +454,7 @@ function renderResourceSpec(
355
454
  entry?: ApiSpecDomainEntry,
356
455
  options?: { crudOnly?: boolean },
357
456
  domainEnrichments?: ApiSpecDomainEnrichments,
457
+ validationData?: Readonly<Record<string, ApiSpecValidationResourceEntry>>,
358
458
  ): string {
359
459
  const matchingPaths = filterPathsByResource(spec, resource, entry);
360
460
  const label = options?.crudOnly ? "CRUD Operations" : "Full API Specification";
@@ -365,13 +465,16 @@ function renderResourceSpec(
365
465
  if (typeof op !== "object" || !op) continue;
366
466
  if (options?.crudOnly) {
367
467
  const opId = op.operationId ?? "";
368
- if (!CRUD_OPERATION_SUFFIXES.some(s => opId.endsWith(s))) continue;
468
+ if (!CRUD_OPERATION_SUFFIXES.some(s => normalizeOperationId(opId).endsWith(s))) continue;
369
469
  }
370
470
  const operation = op;
371
471
  sections.push(`## ${method.toUpperCase()} ${pathKey}`, "");
372
472
  if (operation.summary) sections.push(String(operation.summary), "");
373
473
 
374
- const opEnrichment = domainEnrichments?.operationMeta[operation.operationId ?? ""];
474
+ const rawOpId = operation.operationId ?? "";
475
+ const opEnrichment =
476
+ domainEnrichments?.operationMeta[rawOpId] ??
477
+ domainEnrichments?.operationMeta[normalizeOperationId(rawOpId)];
375
478
  if (opEnrichment) {
376
479
  const badges: string[] = [];
377
480
  if (opEnrichment.dangerLevel) badges.push(`Danger: **${opEnrichment.dangerLevel}**`);
@@ -468,6 +571,25 @@ function renderResourceSpec(
468
571
  }
469
572
  }
470
573
 
574
+ const minConfig = findResourceMinConfig(resource, entry, matchingPaths, domainEnrichments);
575
+ if (minConfig) {
576
+ sections.push(renderMinimumConfigSection(minConfig));
577
+ }
578
+
579
+ const validationEntry = validationData?.[resource];
580
+ if (validationEntry) {
581
+ sections.push("## Field Requirements", "");
582
+ if (validationEntry.create?.length) {
583
+ sections.push(`**Create:** ${validationEntry.create.join(", ")}`, "");
584
+ }
585
+ if (validationEntry.update?.length) {
586
+ sections.push(`**Update:** ${validationEntry.update.join(", ")}`, "");
587
+ }
588
+ if (validationEntry.minimum_config?.length) {
589
+ sections.push(`**Minimum config:** ${validationEntry.minimum_config.join(", ")}`, "");
590
+ }
591
+ }
592
+
471
593
  return sections.join("\n");
472
594
  }
473
595
 
@@ -817,6 +939,71 @@ function renderGlossary(index: ApiSpecIndex): string {
817
939
  return sections.join("\n");
818
940
  }
819
941
 
942
+ function renderValidationIndex(validationData?: Readonly<Record<string, ApiSpecValidationResourceEntry>>): string {
943
+ if (!validationData || Object.keys(validationData).length === 0) {
944
+ return "# API Field Requirements\n\nNo validation data available.\n";
945
+ }
946
+
947
+ const rows = Object.entries(validationData)
948
+ .sort(([a], [b]) => a.localeCompare(b))
949
+ .map(([name, entry]) => {
950
+ const createFields = entry.create?.join(", ") ?? "—";
951
+ const updateFields = entry.update?.join(", ") ?? "—";
952
+ const minFields = entry.minimum_config?.join(", ") ?? "—";
953
+ return `| ${name} | ${createFields} | ${updateFields} | ${minFields} |`;
954
+ });
955
+
956
+ return [
957
+ "# API Field Requirements",
958
+ "",
959
+ `${Object.keys(validationData).length} resources. Read \`xcsh://api-spec/validation/{resource}\` for details.`,
960
+ "",
961
+ "| Resource | Create Fields | Update Fields | Minimum Config Fields |",
962
+ "|----------|--------------|---------------|----------------------|",
963
+ ...rows,
964
+ "",
965
+ ].join("\n");
966
+ }
967
+
968
+ function renderValidationDetail(
969
+ resource: string,
970
+ validationData?: Readonly<Record<string, ApiSpecValidationResourceEntry>>,
971
+ ): string {
972
+ if (!validationData || Object.keys(validationData).length === 0) {
973
+ return `# ${resource} — Field Requirements\n\nNo validation data available.\n`;
974
+ }
975
+
976
+ const entry = validationData[resource];
977
+ if (!entry) {
978
+ const available = Object.keys(validationData).sort().slice(0, 20);
979
+ return [
980
+ `# Resource not found: ${resource}`,
981
+ "",
982
+ "Available resources:",
983
+ ...available.map(r => `- \`${r}\``),
984
+ "",
985
+ "Use `xcsh://api-spec/validation/{resource}` with one of the above.",
986
+ "",
987
+ ].join("\n");
988
+ }
989
+
990
+ const sections = [`# ${resource} — Field Requirements`, ""];
991
+
992
+ if (entry.create?.length) {
993
+ sections.push("## Create (full)", "", ...entry.create.map(f => `- ${f}`), "");
994
+ }
995
+
996
+ if (entry.update?.length) {
997
+ sections.push("## Update", "", ...entry.update.map(f => `- ${f}`), "");
998
+ }
999
+
1000
+ if (entry.minimum_config?.length) {
1001
+ sections.push("## Minimum Configuration", "", ...entry.minimum_config.map(f => `- ${f}`), "");
1002
+ }
1003
+
1004
+ return sections.join("\n");
1005
+ }
1006
+
820
1007
  function renderUnknownDomain(requested: string, index: ApiSpecIndex): string {
821
1008
  const suggestions = index.domains
822
1009
  .filter(d => d.domain.includes(requested) || requested.includes(d.domain.slice(0, 3)))
@@ -177,8 +177,19 @@ export interface ApiSpecOperationEnrichment {
177
177
  };
178
178
  }
179
179
 
180
+ export interface ApiSpecMinimumConfiguration {
181
+ readonly required_fields: readonly string[];
182
+ readonly example_yaml?: string;
183
+ readonly example_json?: string;
184
+ readonly example_curl?: string;
185
+ readonly description?: string;
186
+ readonly mutually_exclusive_groups?: readonly unknown[];
187
+ readonly [key: string]: unknown;
188
+ }
189
+
180
190
  export interface ApiSpecSchemaEnrichment {
181
191
  readonly recommendedOneofVariant?: Readonly<Record<string, string>>;
192
+ readonly minimumConfiguration?: ApiSpecMinimumConfiguration;
182
193
  }
183
194
 
184
195
  export interface ApiSpecDomainEnrichments {
@@ -186,6 +197,13 @@ export interface ApiSpecDomainEnrichments {
186
197
  readonly schemaEnrichments: Readonly<Record<string, ApiSpecSchemaEnrichment>>;
187
198
  }
188
199
 
200
+ export interface ApiSpecValidationResourceEntry {
201
+ readonly create?: readonly string[];
202
+ readonly update?: readonly string[];
203
+ readonly minimum_config?: readonly string[];
204
+ readonly [key: string]: unknown;
205
+ }
206
+
189
207
  export interface ApiSpecIndex {
190
208
  readonly version: string;
191
209
  readonly timestamp: string;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.75.3",
21
- "commit": "6856457e251f8583b237e72702ac395bc7173f42",
22
- "shortCommit": "6856457",
20
+ "version": "18.77.3",
21
+ "commit": "97623491edfb9ed7a9da8c71c074d24d51a17a9d",
22
+ "shortCommit": "9762349",
23
23
  "branch": "main",
24
- "tag": "v18.75.3",
25
- "commitDate": "2026-05-23T08:23:41Z",
26
- "buildDate": "2026-05-23T17:15:18.019Z",
27
- "dirty": false,
24
+ "tag": "v18.77.3",
25
+ "commitDate": "2026-05-23T22:55:12Z",
26
+ "buildDate": "2026-05-23T23:14:49.552Z",
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/6856457e251f8583b237e72702ac395bc7173f42",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.75.3"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/97623491edfb9ed7a9da8c71c074d24d51a17a9d",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.77.3"
33
33
  };
@@ -25,7 +25,12 @@ 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 { ApiSpecDomainEnrichments, ApiSpecIndex, OpenAPISpec } from "./api-spec-types";
28
+ import type {
29
+ ApiSpecDomainEnrichments,
30
+ ApiSpecIndex,
31
+ ApiSpecValidationResourceEntry,
32
+ OpenAPISpec,
33
+ } from "./api-spec-types";
29
34
  import { getRuntimeBuildInfo, type RuntimeBuildInfo, renderAboutDoc } from "./build-info-runtime";
30
35
  import { loadComputerProfile, renderComputerProfileMarkdown, seedComputerProfile } from "./computer-profile";
31
36
  import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
@@ -55,6 +60,7 @@ let _apiSpecCache: {
55
60
  index: ApiSpecIndex;
56
61
  data: Readonly<Record<string, OpenAPISpec>>;
57
62
  enrichments: Readonly<Record<string, ApiSpecDomainEnrichments>>;
63
+ validationData: Readonly<Record<string, ApiSpecValidationResourceEntry>>;
58
64
  version: string;
59
65
  } | null = null;
60
66
 
@@ -62,6 +68,7 @@ function loadApiSpecs(): {
62
68
  index: ApiSpecIndex;
63
69
  data: Readonly<Record<string, OpenAPISpec>>;
64
70
  enrichments: Readonly<Record<string, ApiSpecDomainEnrichments>>;
71
+ validationData: Readonly<Record<string, ApiSpecValidationResourceEntry>>;
65
72
  version: string;
66
73
  } {
67
74
  if (_apiSpecCache) return _apiSpecCache;
@@ -71,6 +78,7 @@ function loadApiSpecs(): {
71
78
  API_SPEC_DATA?: Readonly<Record<string, unknown>>;
72
79
  API_SPEC_VERSION?: string;
73
80
  API_SPEC_ENRICHMENTS?: Readonly<Record<string, ApiSpecDomainEnrichments>>;
81
+ API_VALIDATION_DATA?: Readonly<Record<string, ApiSpecValidationResourceEntry>>;
74
82
  };
75
83
  const index = mod.API_SPEC_INDEX ?? EMPTY_INDEX;
76
84
  const version = mod.API_SPEC_VERSION ?? "unknown";
@@ -81,13 +89,14 @@ function loadApiSpecs(): {
81
89
  index,
82
90
  data: (mod.API_SPEC_DATA ?? {}) as Readonly<Record<string, OpenAPISpec>>,
83
91
  enrichments: mod.API_SPEC_ENRICHMENTS ?? {},
92
+ validationData: (mod.API_VALIDATION_DATA ?? {}) as Readonly<Record<string, ApiSpecValidationResourceEntry>>,
84
93
  version,
85
94
  };
86
95
  } catch (err) {
87
96
  logger.warn("api-spec index unavailable, embedded specs disabled", {
88
97
  error: err instanceof Error ? err.message : String(err),
89
98
  });
90
- _apiSpecCache = { index: EMPTY_INDEX, data: {}, enrichments: {}, version: "unavailable" };
99
+ _apiSpecCache = { index: EMPTY_INDEX, data: {}, enrichments: {}, validationData: {}, version: "unavailable" };
91
100
  }
92
101
  return _apiSpecCache;
93
102
  }
@@ -153,7 +162,12 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
153
162
  #getApiSpecResolver(): ApiSpecResolver {
154
163
  if (!this.#apiSpecResolver) {
155
164
  const specs = loadApiSpecs();
156
- this.#apiSpecResolver = createApiSpecResolver(specs.index, specs.data, specs.enrichments);
165
+ this.#apiSpecResolver = createApiSpecResolver(
166
+ specs.index,
167
+ specs.data,
168
+ specs.enrichments,
169
+ specs.validationData,
170
+ );
157
171
  }
158
172
  return this.#apiSpecResolver;
159
173
  }
@@ -338,6 +338,9 @@ export class EventController {
338
338
  }
339
339
 
340
340
  this.#resetReadGroup();
341
+ if (event.toolName === "todo_write" && !settings.get("todo.verbose")) {
342
+ break;
343
+ }
341
344
  const tool = this.ctx.session.getToolByName(event.toolName);
342
345
  const component = new ToolExecutionComponent(
343
346
  event.toolName,
@@ -655,9 +658,11 @@ export class EventController {
655
658
  }
656
659
 
657
660
  case "todo_reminder": {
658
- const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
659
- this.ctx.chatContainer.addChild(createSystemGutter(this.ctx.ui, component));
660
- this.ctx.ui.requestRender();
661
+ if (settings.get("todo.verbose")) {
662
+ const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
663
+ this.ctx.chatContainer.addChild(createSystemGutter(this.ctx.ui, component));
664
+ this.ctx.ui.requestRender();
665
+ }
661
666
  break;
662
667
  }
663
668
 
@@ -629,6 +629,9 @@ export class InteractiveMode implements InteractiveModeContext {
629
629
 
630
630
  #renderTodoList(): void {
631
631
  this.todoContainer.clear();
632
+ if (!settings.get("todo.verbose")) {
633
+ return;
634
+ }
632
635
  const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
633
636
  if (phases.length === 0) {
634
637
  return;
@@ -333,6 +333,9 @@ export class UiHelpers {
333
333
 
334
334
  // Non-read tool call breaks the group.
335
335
  finalizeReadGroup();
336
+ if (content.name === "todo_write" && !settings.get("todo.verbose")) {
337
+ continue;
338
+ }
336
339
  const tool = this.ctx.session.getToolByName(content.name);
337
340
  const renderArgs =
338
341
  "partialJson" in content