@f5xc-salesdemos/xcsh 18.30.2 → 18.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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.30.4] - 2026-05-01
6
+
7
+ ### Added
8
+
9
+ - `/context <name>` direct switch -- type a context name as a positional argument to switch without the `activate` verb, following `kubectx <name>` convention. ([#380](https://github.com/f5xc-salesdemos/xcsh/issues/380))
10
+ - `/context -` previous context switching -- switch back to the last active context, following the `cd -` / `kubectx -` convention. Session-scoped, in-memory only. Calling `/context -` twice returns to the original context. ([#380](https://github.com/f5xc-salesdemos/xcsh/issues/380))
11
+ - Mixed tab completion for `/context <Tab>` -- shows context names first, then `-` (when a previous context exists), then subcommand names. Context names display tenant URL hints. ([#380](https://github.com/f5xc-salesdemos/xcsh/issues/380))
12
+ - Reserved subcommand name guard -- prevents creating, renaming, or importing contexts with names that collide with `/context` subcommands (19 reserved names including `list`, `create`, `delete`, `help`). Case-insensitive. ([#378](https://github.com/f5xc-salesdemos/xcsh/issues/378))
13
+ - Previous context tracking in `ContextService` -- `previousContextName` getter, `activatePrevious()` method, automatic pointer maintenance on delete (cleared) and rename (updated). ([#379](https://github.com/f5xc-salesdemos/xcsh/issues/379))
14
+
5
15
  ## [18.18.4] - 2026-04-26
6
16
 
7
17
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.30.2",
4
+ "version": "18.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",
@@ -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.30.2",
52
- "@f5xc-salesdemos/pi-agent-core": "18.30.2",
53
- "@f5xc-salesdemos/pi-ai": "18.30.2",
54
- "@f5xc-salesdemos/pi-natives": "18.30.2",
55
- "@f5xc-salesdemos/pi-tui": "18.30.2",
56
- "@f5xc-salesdemos/pi-utils": "18.30.2",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.31.0",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.31.0",
53
+ "@f5xc-salesdemos/pi-ai": "18.31.0",
54
+ "@f5xc-salesdemos/pi-natives": "18.31.0",
55
+ "@f5xc-salesdemos/pi-tui": "18.31.0",
56
+ "@f5xc-salesdemos/pi-utils": "18.31.0",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -11,10 +11,6 @@ interface SpecPathOperation {
11
11
  [key: string]: unknown;
12
12
  }
13
13
 
14
- /**
15
- * Finds the operationId schema components (e.g., 'dns_zone', 'views.forward_proxy_policy')
16
- * that correspond to a given resource name by matching path segments.
17
- */
18
14
  function findResourceSchemaComponents(
19
15
  resourceName: string,
20
16
  paths: Record<string, Record<string, SpecPathOperation>>,
@@ -38,11 +34,29 @@ function findResourceSchemaComponents(
38
34
  return [...found];
39
35
  }
40
36
 
37
+ interface IndexEntryResource {
38
+ name: string;
39
+ description: string;
40
+ description_short?: string;
41
+ tier?: string;
42
+ icon?: string;
43
+ supports_logs?: boolean;
44
+ supports_metrics?: boolean;
45
+ dependencies?: { required: string[]; optional: string[] };
46
+ relationship_hints?: string[];
47
+ schema_components?: string[];
48
+ api_paths?: string[];
49
+ }
50
+
41
51
  interface IndexEntry {
42
52
  domain: string;
43
53
  title: string;
44
54
  description: string;
45
55
  "x-f5xc-description-short": string;
56
+ "x-f5xc-description-medium"?: string;
57
+ "x-f5xc-icon"?: string;
58
+ "x-f5xc-is-preview"?: boolean;
59
+ "x-f5xc-requires-tier"?: string;
46
60
  file: string;
47
61
  path_count: number;
48
62
  schema_count: number;
@@ -50,18 +64,23 @@ interface IndexEntry {
50
64
  "x-f5xc-category": string;
51
65
  "x-f5xc-use-cases"?: string[];
52
66
  "x-f5xc-related-domains"?: string[];
53
- "x-f5xc-primary-resources"?: Array<{ name: string; description: string }>;
67
+ "x-f5xc-primary-resources"?: IndexEntryResource[];
54
68
  }
55
69
 
56
70
  interface RawIndex {
57
71
  version: string;
58
72
  timestamp: string;
59
73
  specifications: IndexEntry[];
74
+ "x-f5xc-critical-resources"?: string[];
75
+ "x-f5xc-guided-workflows"?: Record<string, unknown>;
76
+ "x-f5xc-error-resolution"?: Record<string, unknown>;
77
+ "x-f5xc-acronyms"?: Record<string, unknown>;
60
78
  }
61
79
 
62
80
  const REPO = "f5xc-salesdemos/api-specs-enriched";
63
- const PINNED_TAG = "v2.1.62";
81
+ const PINNED_TAG = "v2.1.63";
64
82
  const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/api-spec-index.generated.ts");
83
+ const catalogOutputPath = path.resolve(import.meta.dir, "../src/internal-urls/api-catalog-index.generated.ts");
65
84
 
66
85
  async function downloadFromRelease(): Promise<string> {
67
86
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "api-specs-"));
@@ -114,6 +133,34 @@ async function findSpecsDir(): Promise<string> {
114
133
  return downloadFromRelease();
115
134
  }
116
135
 
136
+ async function downloadCatalog(specsDir: string): Promise<Record<string, unknown> | null> {
137
+ const catalogPath = path.join(specsDir, "api-catalog.json");
138
+ if (fs.existsSync(catalogPath)) {
139
+ return JSON.parse(fs.readFileSync(catalogPath, "utf-8"));
140
+ }
141
+
142
+ const tag = process.env.API_SPECS_TAG ?? PINNED_TAG;
143
+ const catalogUrl = `https://github.com/${REPO}/releases/download/${tag}/api-catalog.json`;
144
+ console.log(`Downloading API catalog from ${catalogUrl}...`);
145
+ try {
146
+ const response = await fetch(catalogUrl, { redirect: "follow" });
147
+ if (!response.ok) {
148
+ console.warn(`api-catalog.json not found (${response.status}), skipping catalog generation`);
149
+ return null;
150
+ }
151
+ const text = await response.text();
152
+ return JSON.parse(text);
153
+ } catch (err) {
154
+ console.warn(`Failed to download api-catalog.json: ${err instanceof Error ? err.message : err}`);
155
+ return null;
156
+ }
157
+ }
158
+
159
+ function serializeEnrichment(key: string, value: unknown): string | undefined {
160
+ if (!value) return undefined;
161
+ return `\t${key}: ${JSON.stringify(value)},`;
162
+ }
163
+
117
164
  let downloadedTmpDir: string | null = null;
118
165
 
119
166
  const specsDir = await findSpecsDir();
@@ -149,9 +196,22 @@ for (const entry of rawIndex.specifications) {
149
196
  const b64 = compressed.toString("base64");
150
197
 
151
198
  const resources = (entry["x-f5xc-primary-resources"] ?? []).map(r => {
152
- const schemaComponents = findResourceSchemaComponents(r.name, specJson.paths ?? {});
153
- const scStr = schemaComponents.length > 0 ? `, schemaComponents: ${JSON.stringify(schemaComponents)}` : "";
154
- return `\t\t\t{ name: ${JSON.stringify(r.name)}, description: ${JSON.stringify(r.description)}${scStr} },`;
199
+ const upstreamSc = r.schema_components ?? [];
200
+ const schemaComponents =
201
+ upstreamSc.length > 0 ? upstreamSc : findResourceSchemaComponents(r.name, specJson.paths ?? {});
202
+ const fields: string[] = [`name: ${JSON.stringify(r.name)}`, `description: ${JSON.stringify(r.description)}`];
203
+ if (schemaComponents.length > 0) fields.push(`schemaComponents: ${JSON.stringify(schemaComponents)}`);
204
+ if (r.api_paths?.length) fields.push(`apiPaths: ${JSON.stringify(r.api_paths)}`);
205
+ if (r.tier) fields.push(`tier: ${JSON.stringify(r.tier)}`);
206
+ if (r.icon) fields.push(`icon: ${JSON.stringify(r.icon)}`);
207
+ if (r.description_short) fields.push(`descriptionShort: ${JSON.stringify(r.description_short)}`);
208
+ if (r.supports_logs != null) fields.push(`supportsLogs: ${r.supports_logs}`);
209
+ if (r.supports_metrics != null) fields.push(`supportsMetrics: ${r.supports_metrics}`);
210
+ if (r.dependencies && (r.dependencies.required.length > 0 || r.dependencies.optional.length > 0)) {
211
+ fields.push(`dependencies: ${JSON.stringify(r.dependencies)}`);
212
+ }
213
+ if (r.relationship_hints?.length) fields.push(`relationshipHints: ${JSON.stringify(r.relationship_hints)}`);
214
+ return `\t\t\t{ ${fields.join(", ")} },`;
155
215
  });
156
216
 
157
217
  const useCases = entry["x-f5xc-use-cases"];
@@ -173,6 +233,14 @@ for (const entry of rawIndex.specifications) {
173
233
  `\t\t\t],`,
174
234
  useCases ? `\t\t\tuseCases: ${JSON.stringify(useCases)},` : undefined,
175
235
  relatedDomains?.length ? `\t\t\trelatedDomains: ${JSON.stringify(relatedDomains)},` : undefined,
236
+ entry["x-f5xc-icon"] ? `\t\t\ticon: ${JSON.stringify(entry["x-f5xc-icon"])},` : undefined,
237
+ entry["x-f5xc-description-medium"]
238
+ ? `\t\t\tdescriptionMedium: ${JSON.stringify(entry["x-f5xc-description-medium"])},`
239
+ : undefined,
240
+ entry["x-f5xc-is-preview"] ? `\t\t\tisPreview: true,` : undefined,
241
+ entry["x-f5xc-requires-tier"]
242
+ ? `\t\t\trequiresTier: ${JSON.stringify(entry["x-f5xc-requires-tier"])},`
243
+ : undefined,
176
244
  "\t\t},",
177
245
  ]
178
246
  .filter(Boolean)
@@ -183,6 +251,11 @@ for (const entry of rawIndex.specifications) {
183
251
  processedCount++;
184
252
  }
185
253
 
254
+ const criticalResources = rawIndex["x-f5xc-critical-resources"];
255
+ const guidedWorkflows = rawIndex["x-f5xc-guided-workflows"];
256
+ const errorResolution = rawIndex["x-f5xc-error-resolution"];
257
+ const acronyms = rawIndex["x-f5xc-acronyms"];
258
+
186
259
  const output = [
187
260
  "// Auto-generated by scripts/generate-api-spec-index.ts - DO NOT EDIT",
188
261
  "",
@@ -196,13 +269,19 @@ const output = [
196
269
  `\tdomains: [`,
197
270
  ...domainEntries,
198
271
  `\t],`,
272
+ serializeEnrichment("criticalResources", criticalResources),
273
+ serializeEnrichment("guidedWorkflows", guidedWorkflows),
274
+ serializeEnrichment("errorResolution", errorResolution),
275
+ serializeEnrichment("acronyms", acronyms),
199
276
  `};`,
200
277
  "",
201
278
  `export const API_SPEC_BLOBS: Readonly<Record<string, string>> = {`,
202
279
  ...blobEntries,
203
280
  `};`,
204
281
  "",
205
- ].join("\n");
282
+ ]
283
+ .filter(l => l !== undefined)
284
+ .join("\n");
206
285
 
207
286
  await Bun.write(outputPath, output);
208
287
 
@@ -211,6 +290,53 @@ console.log(
211
290
  `Generated ${path.relative(process.cwd(), outputPath)} (${processedCount} domains, ${skippedCount} skipped, ${outputSize} MB)`,
212
291
  );
213
292
 
293
+ // Generate API catalog index
294
+ const catalog = await downloadCatalog(specsDir);
295
+ if (catalog) {
296
+ const categories = (catalog.categories ?? []) as Array<{ name: string; displayName: string; operations: unknown[] }>;
297
+ const catalogBlobEntries: string[] = [];
298
+ const catalogIndexEntries: string[] = [];
299
+
300
+ for (const cat of categories) {
301
+ const catJson = JSON.stringify(cat);
302
+ const catCompressed = gzipSync(Buffer.from(catJson));
303
+ catalogBlobEntries.push(`\t${JSON.stringify(cat.name)}: ${JSON.stringify(catCompressed.toString("base64"))},`);
304
+ catalogIndexEntries.push(
305
+ `\t\t{ name: ${JSON.stringify(cat.name)}, displayName: ${JSON.stringify(cat.displayName)}, operationCount: ${cat.operations?.length ?? 0} },`,
306
+ );
307
+ }
308
+
309
+ const catalogOutput = [
310
+ "// Auto-generated by scripts/generate-api-spec-index.ts - DO NOT EDIT",
311
+ "",
312
+ `import type { ApiCatalogCategorySummary, ApiCatalogIndex } from "./api-catalog-types";`,
313
+ "",
314
+ `export const API_CATALOG_INDEX: ApiCatalogIndex = {`,
315
+ `\tversion: ${JSON.stringify(catalog.version ?? "unknown")},`,
316
+ `\tdisplayName: ${JSON.stringify(catalog.displayName ?? "F5 Distributed Cloud")},`,
317
+ `\tservice: ${JSON.stringify(catalog.service ?? "f5xc")},`,
318
+ `\tcategoryCount: ${categories.length},`,
319
+ `\tauth: ${JSON.stringify(catalog.auth ?? {})},`,
320
+ `\tdefaults: ${JSON.stringify(catalog.defaults ?? {})},`,
321
+ `};`,
322
+ "",
323
+ `export const API_CATALOG_CATEGORY_SUMMARIES: ReadonlyArray<ApiCatalogCategorySummary> = [`,
324
+ ...catalogIndexEntries,
325
+ `];`,
326
+ "",
327
+ `export const API_CATALOG_BLOBS: Readonly<Record<string, string>> = {`,
328
+ ...catalogBlobEntries,
329
+ `};`,
330
+ "",
331
+ ].join("\n");
332
+
333
+ await Bun.write(catalogOutputPath, catalogOutput);
334
+ const catalogSize = (Buffer.byteLength(catalogOutput) / 1024 / 1024).toFixed(1);
335
+ console.log(
336
+ `Generated ${path.relative(process.cwd(), catalogOutputPath)} (${categories.length} categories, ${catalogSize} MB)`,
337
+ );
338
+ }
339
+
214
340
  if (downloadedTmpDir) {
215
341
  fs.rmSync(downloadedTmpDir, { recursive: true, force: true });
216
342
  }
@@ -0,0 +1,177 @@
1
+ import { gunzipSync } from "node:zlib";
2
+ import { LRUCache } from "lru-cache";
3
+ import type { ApiCatalogCategory, ApiCatalogCategorySummary, ApiCatalogIndex } from "./api-catalog-types";
4
+ import type { InternalResource, InternalUrl } from "./types";
5
+
6
+ const LRU_CAPACITY = 5;
7
+
8
+ export interface ApiCatalogResolver {
9
+ resolve(url: InternalUrl): Promise<InternalResource>;
10
+ }
11
+
12
+ export function createApiCatalogResolver(
13
+ index: ApiCatalogIndex,
14
+ categorySummaries: readonly ApiCatalogCategorySummary[],
15
+ blobs: Record<string, string>,
16
+ ): ApiCatalogResolver {
17
+ const cache = new LRUCache<string, ApiCatalogCategory>({ max: LRU_CAPACITY });
18
+
19
+ function decompress(category: string): ApiCatalogCategory {
20
+ const cached = cache.get(category);
21
+ if (cached) return cached;
22
+
23
+ const blob = blobs[category];
24
+ if (!blob) throw new Error(`No catalog blob for category: ${category}`);
25
+
26
+ const buffer = Buffer.from(blob, "base64");
27
+ const decompressed = gunzipSync(buffer);
28
+ const cat = JSON.parse(decompressed.toString("utf-8")) as ApiCatalogCategory;
29
+ cache.set(category, cat);
30
+ return cat;
31
+ }
32
+
33
+ return {
34
+ async resolve(url: InternalUrl): Promise<InternalResource> {
35
+ const pathname = url.rawPathname ?? url.pathname;
36
+ const category = pathname.replace(/^\//, "").replace(/\/$/, "");
37
+ const search = url.searchParams.get("search");
38
+
39
+ if (!category) {
40
+ const content = search
41
+ ? renderCatalogSearch(index, categorySummaries, search)
42
+ : renderCatalogIndex(index, categorySummaries);
43
+ return makeResource(url, content);
44
+ }
45
+
46
+ const summary = categorySummaries.find(c => c.name === category);
47
+ if (!summary) {
48
+ return makeResource(url, renderUnknownCategory(category, categorySummaries));
49
+ }
50
+
51
+ try {
52
+ const cat = decompress(category);
53
+ return makeResource(url, renderCatalogDetail(cat, index));
54
+ } catch (err) {
55
+ const message = err instanceof Error ? err.message : String(err);
56
+ return makeResource(url, `# Error loading ${category}\n\n${message}\n`);
57
+ }
58
+ },
59
+ };
60
+ }
61
+
62
+ function makeResource(url: InternalUrl, content: string): InternalResource {
63
+ return {
64
+ url: url.href,
65
+ content,
66
+ contentType: "text/markdown",
67
+ size: Buffer.byteLength(content, "utf-8"),
68
+ sourcePath: `xcsh://${url.rawHost}${url.rawPathname ?? "/"}`,
69
+ };
70
+ }
71
+
72
+ function renderCatalogIndex(index: ApiCatalogIndex, summaries: readonly ApiCatalogCategorySummary[]): string {
73
+ const rows = summaries.map(c => `| ${c.name} | ${c.displayName} | ${c.operationCount} |`);
74
+
75
+ return [
76
+ `# F5 XC API Catalog (v${index.version})`,
77
+ "",
78
+ `${summaries.length} categories. Read \`xcsh://api-catalog/{category}\` for operation details.`,
79
+ "",
80
+ "| Category | Display Name | Operations |",
81
+ "|----------|--------------|------------|",
82
+ ...rows,
83
+ "",
84
+ ].join("\n");
85
+ }
86
+
87
+ function renderCatalogSearch(
88
+ _index: ApiCatalogIndex,
89
+ summaries: readonly ApiCatalogCategorySummary[],
90
+ term: string,
91
+ ): string {
92
+ const lower = term.toLowerCase();
93
+ const matches = summaries.filter(
94
+ c => c.name.toLowerCase().includes(lower) || c.displayName.toLowerCase().includes(lower),
95
+ );
96
+
97
+ if (matches.length === 0) {
98
+ return [
99
+ `# No categories matching "${term}"`,
100
+ "",
101
+ `Use \`xcsh://api-catalog/\` to see all ${summaries.length} categories.`,
102
+ "",
103
+ ].join("\n");
104
+ }
105
+
106
+ const rows = matches.map(c => `| ${c.name} | ${c.displayName} | ${c.operationCount} |`);
107
+
108
+ return [
109
+ `# API Catalog — search: "${term}"`,
110
+ "",
111
+ `${matches.length} matching categories.`,
112
+ "",
113
+ "| Category | Display Name | Operations |",
114
+ "|----------|--------------|------------|",
115
+ ...rows,
116
+ "",
117
+ ].join("\n");
118
+ }
119
+
120
+ function renderCatalogDetail(cat: ApiCatalogCategory, index: ApiCatalogIndex): string {
121
+ const sections: string[] = [`# ${cat.displayName}`, "", `${cat.operations.length} operations.`];
122
+
123
+ for (const op of cat.operations) {
124
+ sections.push("", `## ${op.method.toUpperCase()} ${op.path}`, "");
125
+ sections.push(op.description);
126
+ sections.push(`Danger level: ${op.dangerLevel}`);
127
+
128
+ if (op.parameters.length > 0) {
129
+ sections.push("", "### Parameters", "");
130
+ sections.push("| Name | In | Required | Type | Default |");
131
+ sections.push("|------|-----|----------|------|---------|");
132
+ for (const p of op.parameters) {
133
+ sections.push(`| ${p.name} | ${p.in} | ${p.required ? "yes" : "no"} | ${p.type} | ${p.default ?? ""} |`);
134
+ }
135
+ }
136
+
137
+ if (op.bodySchema) {
138
+ const minConfig = (op.bodySchema as Record<string, unknown>)["x-f5xc-minimum-configuration"] as
139
+ | Record<string, unknown>
140
+ | undefined;
141
+ if (minConfig?.required_fields) {
142
+ sections.push("", "### Minimum Configuration");
143
+ sections.push(`Required fields: ${(minConfig.required_fields as string[]).join(", ")}`);
144
+ }
145
+ }
146
+
147
+ sections.push("", "### Curl Example", "", "```bash");
148
+ const tokenVar = `$${index.auth.tokenSource}`;
149
+ const authHeader = `${index.auth.headerName}: ${index.auth.headerTemplate.replace("$TOKEN", tokenVar).replace("{token}", tokenVar)}`;
150
+ sections.push(`curl -X ${op.method.toUpperCase()} "$${index.auth.baseUrlSource}${op.path}" \\`);
151
+ sections.push(` -H "${authHeader}" \\`);
152
+ if (op.method.toUpperCase() !== "GET" && op.method.toUpperCase() !== "DELETE") {
153
+ sections.push(' -H "Content-Type: application/json" \\');
154
+ sections.push(" -d @payload.json");
155
+ } else {
156
+ const lastLine = sections[sections.length - 1];
157
+ sections[sections.length - 1] = lastLine.replace(/ \\$/, "");
158
+ }
159
+ sections.push("```");
160
+ }
161
+
162
+ sections.push("");
163
+ return sections.join("\n");
164
+ }
165
+
166
+ function renderUnknownCategory(requested: string, summaries: readonly ApiCatalogCategorySummary[]): string {
167
+ const suggestions = summaries
168
+ .filter(c => c.name.includes(requested) || requested.includes(c.name.slice(0, 4)))
169
+ .slice(0, 5);
170
+
171
+ const sections = [`# Category not found: ${requested}`, ""];
172
+ if (suggestions.length > 0) {
173
+ sections.push("Did you mean:", ...suggestions.map(c => `- \`${c.name}\` — ${c.displayName}`), "");
174
+ }
175
+ sections.push("Use `xcsh://api-catalog/` to see all categories.", "");
176
+ return sections.join("\n");
177
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Types for the embedded API catalog index.
3
+ *
4
+ * The catalog provides pre-built curl templates and operation metadata
5
+ * generated at build time from api-catalog.json in the api-specs-enriched repository.
6
+ */
7
+
8
+ export interface ApiCatalogParameter {
9
+ readonly name: string;
10
+ readonly in: string;
11
+ readonly required: boolean;
12
+ readonly type: string;
13
+ readonly default?: string;
14
+ }
15
+
16
+ export interface ApiCatalogOperation {
17
+ readonly name: string;
18
+ readonly description: string;
19
+ readonly method: string;
20
+ readonly path: string;
21
+ readonly dangerLevel: string;
22
+ readonly parameters: readonly ApiCatalogParameter[];
23
+ readonly bodySchema?: Record<string, unknown>;
24
+ readonly responseSchema?: Record<string, unknown>;
25
+ }
26
+
27
+ export interface ApiCatalogCategory {
28
+ readonly name: string;
29
+ readonly displayName: string;
30
+ readonly operations: readonly ApiCatalogOperation[];
31
+ }
32
+
33
+ export interface ApiCatalogAuth {
34
+ readonly type: string;
35
+ readonly headerName: string;
36
+ readonly headerTemplate: string;
37
+ readonly tokenSource: string;
38
+ readonly baseUrlSource: string;
39
+ }
40
+
41
+ export interface ApiCatalogIndex {
42
+ readonly version: string;
43
+ readonly displayName: string;
44
+ readonly service: string;
45
+ readonly categoryCount: number;
46
+ readonly auth: ApiCatalogAuth;
47
+ readonly defaults: Record<string, { readonly source: string }>;
48
+ }
49
+
50
+ export interface ApiCatalogCategorySummary {
51
+ readonly name: string;
52
+ readonly displayName: string;
53
+ readonly operationCount: number;
54
+ }