@adaptabletools/adaptable-mcp 0.0.0-stage → 0.1.0-canary.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.
Files changed (75) hide show
  1. package/README.md +90 -2
  2. package/data/manifest.snapshot.json +184 -0
  3. package/dist/bin.js +16 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/cli/build-commands.js +200 -0
  6. package/dist/cli/build-commands.js.map +1 -0
  7. package/dist/cli/index.js +86 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/render.js +57 -0
  10. package/dist/cli/render.js.map +1 -0
  11. package/dist/core/capabilities.js +99 -0
  12. package/dist/core/capabilities.js.map +1 -0
  13. package/dist/core/compat/capabilities.js +127 -0
  14. package/dist/core/compat/capabilities.js.map +1 -0
  15. package/dist/core/compat/manifest.js +203 -0
  16. package/dist/core/compat/manifest.js.map +1 -0
  17. package/dist/core/constants.js +38 -0
  18. package/dist/core/constants.js.map +1 -0
  19. package/dist/core/context/capabilities.js +106 -0
  20. package/dist/core/context/capabilities.js.map +1 -0
  21. package/dist/core/context/detect.js +370 -0
  22. package/dist/core/context/detect.js.map +1 -0
  23. package/dist/core/context/projects.js +159 -0
  24. package/dist/core/context/projects.js.map +1 -0
  25. package/dist/core/context/resolve.js +85 -0
  26. package/dist/core/context/resolve.js.map +1 -0
  27. package/dist/core/docs/areas.js +22 -0
  28. package/dist/core/docs/areas.js.map +1 -0
  29. package/dist/core/docs/cache.js +133 -0
  30. package/dist/core/docs/cache.js.map +1 -0
  31. package/dist/core/docs/capabilities.js +381 -0
  32. package/dist/core/docs/capabilities.js.map +1 -0
  33. package/dist/core/docs/corpus.js +114 -0
  34. package/dist/core/docs/corpus.js.map +1 -0
  35. package/dist/core/docs/demos.js +105 -0
  36. package/dist/core/docs/demos.js.map +1 -0
  37. package/dist/core/docs/get-page.js +182 -0
  38. package/dist/core/docs/get-page.js.map +1 -0
  39. package/dist/core/docs/hosts.js +74 -0
  40. package/dist/core/docs/hosts.js.map +1 -0
  41. package/dist/core/docs/search.js +92 -0
  42. package/dist/core/docs/search.js.map +1 -0
  43. package/dist/core/envelope.js +79 -0
  44. package/dist/core/envelope.js.map +1 -0
  45. package/dist/core/index.js +13 -0
  46. package/dist/core/index.js.map +1 -0
  47. package/dist/core/packages/cache.js +138 -0
  48. package/dist/core/packages/cache.js.map +1 -0
  49. package/dist/core/packages/capabilities.js +95 -0
  50. package/dist/core/packages/capabilities.js.map +1 -0
  51. package/dist/core/packages/entry-types.js +13 -0
  52. package/dist/core/packages/entry-types.js.map +1 -0
  53. package/dist/core/packages/exec.js +37 -0
  54. package/dist/core/packages/exec.js.map +1 -0
  55. package/dist/core/packages/load-entry.js +123 -0
  56. package/dist/core/packages/load-entry.js.map +1 -0
  57. package/dist/core/packages/prepare.js +122 -0
  58. package/dist/core/packages/prepare.js.map +1 -0
  59. package/dist/core/packages/registry.js +75 -0
  60. package/dist/core/packages/registry.js.map +1 -0
  61. package/dist/core/packages/resolve.js +94 -0
  62. package/dist/core/packages/resolve.js.map +1 -0
  63. package/dist/core/packages/versions.js +71 -0
  64. package/dist/core/packages/versions.js.map +1 -0
  65. package/dist/core/ping.js +22 -0
  66. package/dist/core/ping.js.map +1 -0
  67. package/dist/core/register.js +10 -0
  68. package/dist/core/register.js.map +1 -0
  69. package/dist/core/state/rules.js +417 -0
  70. package/dist/core/state/rules.js.map +1 -0
  71. package/dist/core/state/type-check.js +142 -0
  72. package/dist/core/state/type-check.js.map +1 -0
  73. package/dist/mcp/index.js +21 -0
  74. package/dist/mcp/index.js.map +1 -0
  75. package/package.json +50 -4
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Context resolution (plan §7.2). Precedence, first hit wins:
3
+ * 1. explicit `--major` / `--framework`
4
+ * 2. session pin — reserved for a future remote transport; never set by the CLI or the stdio server
5
+ * 3. detection — the nearest project declaring `@adaptabletools/*` (workspace-aware)
6
+ * 4. `latest` from the docs manifest (or the snapshot offline), with a mandatory warning
7
+ */
8
+ import { resolve as resolvePath } from 'node:path';
9
+ import { MIGRATION_FLOOR } from '../constants.js';
10
+ import { docsHostFor } from '../docs/hosts.js';
11
+ import { CapabilityError } from '../envelope.js';
12
+ import { findProjects } from './projects.js';
13
+ const docsHostInfo = (latest) => ({
14
+ latestMajor: latest?.adaptableMajor,
15
+ docsHosts: latest?.docsHosts,
16
+ });
17
+ /** A0.2 placeholder; A0.6 replaces it with the hosted-manifest / snapshot lookup. */
18
+ export const noLatest = async () => undefined;
19
+ function assertSupportedMajor(major) {
20
+ if (!Number.isInteger(major) || major < MIGRATION_FLOOR) {
21
+ throw new CapabilityError('VERSION_NOT_SUPPORTED', `AdapTable ${major} is not a supported major version.`, `Pass --major with a value of ${MIGRATION_FLOOR} or later.`);
22
+ }
23
+ }
24
+ export async function resolveContext(options, deps = {}) {
25
+ const cwd = resolvePath(options.cwd ?? process.cwd());
26
+ const warnings = [];
27
+ if (options.major !== undefined) {
28
+ assertSupportedMajor(options.major);
29
+ }
30
+ const projects = findProjects(cwd);
31
+ const project = projects.length === 1 ? projects[0] : undefined;
32
+ let source;
33
+ let major;
34
+ let version;
35
+ let latest;
36
+ if (options.major !== undefined) {
37
+ source = 'explicit';
38
+ major = options.major;
39
+ version = project?.adaptable.major === major ? project.adaptable.installed : undefined;
40
+ }
41
+ else if (deps.session) {
42
+ source = 'session';
43
+ major = deps.session.adaptableMajor;
44
+ version = project?.adaptable.major === major ? project.adaptable.installed : undefined;
45
+ }
46
+ else if (projects.length > 1) {
47
+ throw new CapabilityError('MULTIPLE_PROJECTS', `${projects.length} projects at or below ${cwd} depend on AdapTable: ${projects.map((p) => p.path).join(', ')}.`, 'Run from one project folder, or pass --cwd <folder> (or --major to skip detection).');
48
+ }
49
+ else if (project?.adaptable.major !== undefined) {
50
+ source = 'detected';
51
+ major = project.adaptable.major;
52
+ version = project.adaptable.installed;
53
+ if (!version && project.adaptable.range) {
54
+ warnings.push(`${project.adaptable.name} is declared (${project.adaptable.range}) but not installed at ${project.path}; using major ${major} from the declared range. Install dependencies so checks use the exact version.`);
55
+ }
56
+ }
57
+ else {
58
+ source = 'latest';
59
+ latest = await (deps.latest ?? noLatest)();
60
+ major = latest?.adaptableMajor;
61
+ version = latest?.adaptableVersion;
62
+ const where = project
63
+ ? `${project.adaptable.name} at ${project.path} declares "${project.adaptable.range}", which names no major`
64
+ : `No @adaptabletools/* dependency found at or above ${cwd}`;
65
+ warnings.push(major !== undefined
66
+ ? `${where}; assuming AdapTable ${major}, the latest release. Pass --major to override.`
67
+ : `${where}, and the latest release could not be determined (offline without a manifest snapshot). Pass --major.`);
68
+ }
69
+ const framework = options.framework ?? deps.session?.framework ?? project?.framework;
70
+ if (!options.docsHost && !latest) {
71
+ latest = await (deps.latest ?? noLatest)();
72
+ }
73
+ const docsHost = docsHostFor(major, docsHostInfo(latest), options.docsHost ?? process.env.ADAPTABLE_MCP_DOCS_HOST);
74
+ const context = {
75
+ ...(version !== undefined ? { adaptableVersion: version } : {}),
76
+ ...(major !== undefined ? { adaptableMajor: major } : {}),
77
+ ...(project?.agGrid?.installed ? { agGridVersion: project.agGrid.installed } : {}),
78
+ ...(framework ? { framework } : {}),
79
+ source,
80
+ docsHost,
81
+ codeSource: version !== undefined && project?.adaptable.installed === version ? 'installed' : 'none',
82
+ };
83
+ return { context, warnings, project };
84
+ }
85
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/context/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /** `--area` of `docs list|search` (plan §8.3) → the `pages.json` categories it covers */
2
+ export const AREAS = ['handbook', 'dev-guide', 'reference', 'tutorial', 'support', 'all'];
3
+ const AREA_CATEGORIES = {
4
+ handbook: ['handbook', 'ui', 'adaptable-ql', 'framework', 'integrations', 'adaptable-server'],
5
+ 'dev-guide': ['dev-guide'],
6
+ reference: ['api', 'technical-reference'],
7
+ tutorial: ['getting-started', 'demos', 'documentation'],
8
+ support: ['support', 'release-notes'],
9
+ };
10
+ /** Categories for an area; undefined for `all` */
11
+ export function categoriesFor(area) {
12
+ return area && area !== 'all' ? AREA_CATEGORIES[area] : undefined;
13
+ }
14
+ export function areaOf(category) {
15
+ for (const [area, categories] of Object.entries(AREA_CATEGORIES)) {
16
+ if (categories.includes(category)) {
17
+ return area;
18
+ }
19
+ }
20
+ return 'handbook';
21
+ }
22
+ //# sourceMappingURL=areas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"areas.js","sourceRoot":"","sources":["../../../src/core/docs/areas.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC;AAGnG,MAAM,eAAe,GAA2C;IAC9D,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,CAAC;IAC7F,WAAW,EAAE,CAAC,WAAW,CAAC;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACzC,QAAQ,EAAE,CAAC,iBAAiB,EAAE,OAAO,EAAE,eAAe,CAAC;IACvD,OAAO,EAAE,CAAC,SAAS,EAAE,eAAe,CAAC;CACtC,CAAC;AAEF,kDAAkD;AAClD,MAAM,UAAU,aAAa,CAAC,IAAsB;IAClD,OAAO,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,QAAgB;IACrC,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAuC,EAAE,CAAC;QACvG,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The docs corpus cache (plan §8.3 "Freshness"): `pages.json` and `llms-full.txt`
3
+ * live under `docs/<host>/<files hash>/`, where the hash is taken from the
4
+ * manifest's `files` block, so a deploy that changes the docs changes the folder
5
+ * and nothing else triggers a download. Offline, the newest cached corpus for the
6
+ * host is used with a warning.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { hostCacheDir } from '../compat/manifest.js';
12
+ import { splitPages, splitSections } from './corpus.js';
13
+ import { docsNotAvailable, resolveDocsSource } from './hosts.js';
14
+ import { SectionIndex } from './search.js';
15
+ const CORPUS_FILES = { full: 'llms-full.txt', pages: 'pages.json' };
16
+ const FETCH_TIMEOUT_MS = 60_000;
17
+ /** Short hash of the manifest's file hashes: the corpus folder name */
18
+ export function corpusKey(manifest) {
19
+ const hashes = Object.keys(manifest.files)
20
+ .sort()
21
+ .map((name) => `${name}:${manifest.files[name].sha256}`)
22
+ .join('\n');
23
+ return createHash('sha256').update(hashes).digest('hex').slice(0, 16);
24
+ }
25
+ export function corpusDir(host, manifest) {
26
+ return join(hostCacheDir(host), corpusKey(manifest));
27
+ }
28
+ const isComplete = (dir) => Object.values(CORPUS_FILES).every((file) => existsSync(join(dir, file)));
29
+ /** The newest complete corpus folder cached for a host, by modification time */
30
+ function newestCachedCorpus(host) {
31
+ const root = hostCacheDir(host);
32
+ let entries;
33
+ try {
34
+ entries = readdirSync(root);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ return entries
40
+ .map((name) => join(root, name))
41
+ .filter((dir) => statSync(dir).isDirectory() && isComplete(dir))
42
+ .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)[0];
43
+ }
44
+ async function download(host, path) {
45
+ const url = `${host}${path}`;
46
+ const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
47
+ if (!response.ok) {
48
+ throw new Error(`${url} returned ${response.status}`);
49
+ }
50
+ return response.text();
51
+ }
52
+ function buildCorpus(source, origin, dir) {
53
+ const pagesJson = readFileSync(join(dir, CORPUS_FILES.pages), 'utf8');
54
+ const llmsFull = readFileSync(join(dir, CORPUS_FILES.full), 'utf8');
55
+ const pageMeta = new Map(JSON.parse(pagesJson).map((meta) => [meta.routePath, meta]));
56
+ const pages = new Map();
57
+ const sections = [];
58
+ for (const page of splitPages(llmsFull, pageMeta)) {
59
+ pages.set(page.routePath, page);
60
+ sections.push(...splitSections(page, sections.length));
61
+ }
62
+ return {
63
+ manifest: source.manifest,
64
+ host: source.host,
65
+ origin,
66
+ pages,
67
+ pageMeta,
68
+ sections,
69
+ index: new SectionIndex(sections),
70
+ };
71
+ }
72
+ async function loadUncached(source, options) {
73
+ const warnings = [...source.warnings];
74
+ const { host, manifest } = source;
75
+ const files = manifest.files ?? {};
76
+ if (!files.pages?.path || !files.llmsFullTxt?.path) {
77
+ throw docsNotAvailable(host, 'The docs manifest lists no pages.json / llms-full.txt');
78
+ }
79
+ const dir = corpusDir(host, manifest);
80
+ if (isComplete(dir)) {
81
+ return { corpus: buildCorpus(source, 'cache', dir), warnings };
82
+ }
83
+ if (options.offline) {
84
+ const fallback = newestCachedCorpus(host);
85
+ if (!fallback) {
86
+ throw docsNotAvailable(host, '--offline and no docs corpus is cached');
87
+ }
88
+ warnings.push(`--offline: using the most recent docs corpus cached for ${host}, which predates the current manifest.`);
89
+ return { corpus: buildCorpus(source, 'cache', fallback), warnings };
90
+ }
91
+ try {
92
+ const [pagesJson, llmsFull] = await Promise.all([download(host, files.pages.path), download(host, files.llmsFullTxt.path)]);
93
+ mkdirSync(dir, { recursive: true });
94
+ writeFileSync(join(dir, CORPUS_FILES.pages), pagesJson);
95
+ writeFileSync(join(dir, CORPUS_FILES.full), llmsFull);
96
+ writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2));
97
+ }
98
+ catch (error) {
99
+ const fallback = newestCachedCorpus(host);
100
+ const reason = error instanceof Error ? error.message : String(error);
101
+ if (!fallback) {
102
+ throw docsNotAvailable(host, `Could not download the docs corpus: ${reason}`);
103
+ }
104
+ warnings.push(`Could not download the docs corpus (${reason}); using the most recent one cached for ${host}.`);
105
+ return { corpus: buildCorpus(source, 'cache', fallback), warnings };
106
+ }
107
+ return { corpus: buildCorpus(source, 'host', dir), warnings };
108
+ }
109
+ const corpora = new Map();
110
+ /**
111
+ * The corpus for a major (or the current host): host resolution, manifest
112
+ * re-validation, cache lookup or download. Memoised per host + manifest hash
113
+ * within the process; the manifest re-validation is governed by `revalidateTtlMs`.
114
+ */
115
+ export async function loadCorpus(options = {}) {
116
+ const source = await resolveDocsSource(options);
117
+ const key = `${source.host}/${corpusKey(source.manifest)}`;
118
+ let loaded = corpora.get(key);
119
+ if (!loaded) {
120
+ loaded = loadUncached(source, options);
121
+ loaded.catch(() => corpora.delete(key));
122
+ corpora.set(key, loaded);
123
+ }
124
+ const result = await loaded;
125
+ // the memoised load carries its own warnings; the fresh source resolution may add new ones
126
+ const warnings = [...new Set([...result.warnings, ...source.warnings])];
127
+ return { corpus: result.corpus, warnings };
128
+ }
129
+ /** Test hook: forget memoised corpora */
130
+ export function resetCorpusCache() {
131
+ corpora.clear();
132
+ }
133
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/core/docs/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,381 @@
1
+ /**
2
+ * `adaptable docs list|search|get|demos|demo` · MCP `search_docs`, `get_doc`, `get_demo` (plan §8.3).
3
+ */
4
+ import { z } from 'zod';
5
+ import { registerCapability } from '../capabilities.js';
6
+ import { CapabilityError, ok } from '../envelope.js';
7
+ import { AREAS, areaOf, categoriesFor } from './areas.js';
8
+ import { loadCorpus } from './cache.js';
9
+ import { filterFramework, FRAMEWORKS } from './corpus.js';
10
+ import { estimateTokens, extractHeading, findPage, outline, slugOf, suggestSlugs, trimDemos, truncateAtHeadings, } from './get-page.js';
11
+ import { closestDemoNames, demosOnPage, loadDemoMarkdown, loadDemosIndex, parseDemoMarkdown, selectFiles } from './demos.js';
12
+ import { pageUrl } from './hosts.js';
13
+ const DEFAULT_MAX_TOKENS = 8_000;
14
+ const EXCERPT_CHARS = 240;
15
+ async function corpusFor(ctx) {
16
+ const { context, warnings } = await ctx.resolveContext();
17
+ const loaded = await loadCorpus({
18
+ major: context.adaptableMajor,
19
+ offline: ctx.globals.offline,
20
+ docsHost: ctx.globals.docsHost,
21
+ });
22
+ return { corpus: loaded.corpus, context, warnings: [...warnings, ...loaded.warnings] };
23
+ }
24
+ const frameworkOf = (ctx, explicit, detected) => explicit ?? ctx.globals.framework ?? detected;
25
+ // ---------------------------------------------------------------- docs list
26
+ const ListPage = z.object({
27
+ slug: z.string(),
28
+ title: z.string(),
29
+ area: z.enum(AREAS),
30
+ category: z.string(),
31
+ description: z.string().optional(),
32
+ /** `technical-reference-*` page for a feature, when one exists */
33
+ technicalReference: z.string().optional(),
34
+ demos: z.array(z.string()).optional(),
35
+ children: z.array(z.string()).optional(),
36
+ });
37
+ const ListOutput = z.object({
38
+ /** `section` when pages.json carries the sidebar sections, else `category` */
39
+ groupedBy: z.enum(['section', 'category']),
40
+ sections: z.array(z.object({ name: z.string(), pages: z.array(ListPage) })),
41
+ pageCount: z.number(),
42
+ });
43
+ function featureKey(meta) {
44
+ // handbook-layouts → layouts; technical-reference-layouts → layouts
45
+ const match = meta.routePath.match(/^\/docs\/(?:handbook|technical-reference|dev-guide|ui)-([a-z0-9-]+)$/);
46
+ return match?.[1];
47
+ }
48
+ export function buildList(corpus, options) {
49
+ const metas = [...corpus.pageMeta.values()];
50
+ const hasSections = metas.some((m) => m.section);
51
+ const categories = categoriesFor(options.area);
52
+ const technicalRefs = new Map();
53
+ for (const meta of metas) {
54
+ if (meta.category === 'technical-reference') {
55
+ const key = featureKey(meta);
56
+ if (key) {
57
+ technicalRefs.set(key, slugOf(meta.routePath));
58
+ }
59
+ }
60
+ }
61
+ const children = new Map();
62
+ for (const meta of metas) {
63
+ if (meta.parent) {
64
+ children.set(meta.parent, [...(children.get(meta.parent) ?? []), slugOf(meta.routePath)]);
65
+ }
66
+ }
67
+ const groups = new Map();
68
+ for (const meta of metas) {
69
+ if (categories && !categories.includes(meta.category)) {
70
+ continue;
71
+ }
72
+ // reference entries (700+ generated pages) are listed only when asked for explicitly
73
+ if (!options.area && meta.category === 'api') {
74
+ continue;
75
+ }
76
+ const group = hasSections ? (meta.section ?? meta.category) : meta.category;
77
+ if (options.section && group.toLowerCase() !== options.section.toLowerCase()) {
78
+ continue;
79
+ }
80
+ const key = featureKey(meta);
81
+ const page = {
82
+ slug: slugOf(meta.routePath),
83
+ title: meta.title,
84
+ area: areaOf(meta.category),
85
+ category: meta.category,
86
+ ...(meta.description ? { description: meta.description } : {}),
87
+ ...(key && meta.category !== 'technical-reference' && technicalRefs.has(key)
88
+ ? { technicalReference: technicalRefs.get(key) }
89
+ : {}),
90
+ ...(meta.demos?.length ? { demos: meta.demos } : {}),
91
+ ...(children.has(meta.routePath) ? { children: children.get(meta.routePath) } : {}),
92
+ };
93
+ groups.set(group, [...(groups.get(group) ?? []), page]);
94
+ }
95
+ const sections = [...groups.entries()]
96
+ .map(([name, pages]) => ({ name, pages: pages.sort((a, b) => a.slug.localeCompare(b.slug)) }))
97
+ .sort((a, b) => a.name.localeCompare(b.name));
98
+ return {
99
+ groupedBy: hasSections ? 'section' : 'category',
100
+ sections,
101
+ pageCount: sections.reduce((n, s) => n + s.pages.length, 0),
102
+ };
103
+ }
104
+ registerCapability({
105
+ name: 'docs-list',
106
+ description: 'Lists the docs pages for the project’s AdapTable version, grouped by sidebar section: the feature index.',
107
+ cli: { command: ['docs', 'list'] },
108
+ mcp: null,
109
+ input: z.object({
110
+ section: z.string().optional().describe('Only this sidebar section (or category)'),
111
+ area: z.enum(AREAS).optional().describe('handbook | dev-guide | reference | tutorial | support | all; reference pages are listed only when asked for'),
112
+ }),
113
+ output: ListOutput,
114
+ run: async (input, ctx) => {
115
+ const { corpus, context, warnings } = await corpusFor(ctx);
116
+ return ok(buildList(corpus, input), { resolvedContext: context, warnings });
117
+ },
118
+ markdown: (data) => data.sections
119
+ .map((section) => `## ${section.name} (${section.pages.length})\n\n${section.pages
120
+ .map((p) => `- \`${p.slug}\` — ${p.title}${p.technicalReference ? ` · ref \`${p.technicalReference}\`` : ''}${p.demos?.length ? ` · demos: ${p.demos.join(', ')}` : ''}`)
121
+ .join('\n')}`)
122
+ .join('\n\n'),
123
+ });
124
+ // ---------------------------------------------------------------- docs search
125
+ const SearchResult = z.object({
126
+ slug: z.string(),
127
+ title: z.string(),
128
+ section: z.string(),
129
+ heading: z.string(),
130
+ url: z.string(),
131
+ tokens: z.number(),
132
+ excerpt: z.string(),
133
+ score: z.number(),
134
+ });
135
+ const SearchOutput = z.object({ query: z.string(), results: z.array(SearchResult) });
136
+ export function runSearch(corpus, query, options) {
137
+ const categories = categoriesFor(options.area);
138
+ const hits = corpus.index.search(query, {
139
+ framework: options.framework,
140
+ categories,
141
+ // release notes describe old syntax; they are searched only when asked for
142
+ excludeCategories: categories ? undefined : ['release-notes'],
143
+ limit: options.limit ?? 8,
144
+ });
145
+ return {
146
+ query,
147
+ results: hits.map(({ section, score }) => {
148
+ const meta = corpus.pageMeta.get(section.routePath);
149
+ const text = section.text.replace(/\s+/g, ' ').trim();
150
+ return {
151
+ slug: slugOf(section.routePath),
152
+ title: section.pageTitle,
153
+ section: meta?.section ?? section.category,
154
+ heading: section.headings.join(' > '),
155
+ url: pageUrl(corpus.host, section.routePath, options.framework),
156
+ tokens: estimateTokens(section.text),
157
+ excerpt: text.length > EXCERPT_CHARS ? `${text.slice(0, EXCERPT_CHARS)}…` : text,
158
+ score: Math.round(score * 100) / 100,
159
+ };
160
+ }),
161
+ };
162
+ }
163
+ registerCapability({
164
+ name: 'docs-search',
165
+ description: 'Searches the docs for the project’s AdapTable version and framework; returns matching sections with slugs and URLs.',
166
+ cli: { command: ['docs', 'search'], positionals: ['query'] },
167
+ mcp: { tool: 'search_docs' },
168
+ input: z.object({
169
+ query: z.string().min(1).describe('What to look for: exact API names (LayoutApi, GROUP_BY) or a plain question'),
170
+ limit: z.number().int().min(1).max(50).optional().describe('Results to return (default 8)'),
171
+ area: z.enum(AREAS).optional().describe('Restrict to one docs area; release notes are searched only with support or all'),
172
+ framework: z.enum(FRAMEWORKS).optional().describe('Defaults to the detected framework'),
173
+ }),
174
+ output: SearchOutput,
175
+ run: async (input, ctx) => {
176
+ const { corpus, context, warnings } = await corpusFor(ctx);
177
+ const framework = frameworkOf(ctx, input.framework, context.framework);
178
+ return ok(runSearch(corpus, input.query, { limit: input.limit, area: input.area, framework }), {
179
+ resolvedContext: context,
180
+ warnings,
181
+ });
182
+ },
183
+ markdown: (data) => data.results.length
184
+ ? data.results
185
+ .map((r, i) => `${i + 1}. **${r.title}**${r.heading ? ` › ${r.heading}` : ''} — \`${r.slug}\` (${r.section}, ~${r.tokens} tokens)\n ${r.url}\n ${r.excerpt}`)
186
+ .join('\n\n')
187
+ : `No matches for "${data.query}".`,
188
+ });
189
+ // ---------------------------------------------------------------- docs get
190
+ const GotPage = z.object({
191
+ slug: z.string(),
192
+ title: z.string(),
193
+ url: z.string(),
194
+ markdown: z.string(),
195
+ tokens: z.number(),
196
+ truncated: z.boolean(),
197
+ /** headings cut by --max-tokens; pass one to --section to read it */
198
+ omitted: z.array(z.string()),
199
+ /** demo blocks replaced by a one-line summary (use `docs demo` or --full) */
200
+ demosTrimmed: z.number(),
201
+ outline: z.array(z.string()),
202
+ });
203
+ const GetOutput = z.object({
204
+ pages: z.array(GotPage),
205
+ /** requested pages that do not exist, each with the closest slugs */
206
+ missing: z.array(z.object({ requested: z.string(), suggestions: z.array(z.string()) })),
207
+ });
208
+ export function getPages(corpus, requested, options) {
209
+ const pages = [];
210
+ const missing = [];
211
+ for (const request of requested) {
212
+ const page = findPage(corpus, request);
213
+ if (!page) {
214
+ missing.push({ requested: request, suggestions: suggestSlugs(corpus, request) });
215
+ continue;
216
+ }
217
+ let markdown = filterFramework(page.markdown, options.framework);
218
+ let demosTrimmed = 0;
219
+ if (!options.full) {
220
+ const trimmed = trimDemos(markdown);
221
+ markdown = trimmed.markdown;
222
+ demosTrimmed = trimmed.trimmed;
223
+ }
224
+ const fullOutline = outline(markdown);
225
+ if (options.section) {
226
+ const section = extractHeading(markdown, options.section);
227
+ if (section === undefined) {
228
+ throw new CapabilityError('NOT_FOUND', `No heading matching "${options.section}" in ${slugOf(page.routePath)}.`, `Headings on this page: ${fullOutline.map((h) => h.replace(/^#+\s*/, '')).join(' · ') || '(none)'}`);
229
+ }
230
+ markdown = section;
231
+ }
232
+ const truncation = options.full
233
+ ? { markdown, truncated: false, omitted: [] }
234
+ : truncateAtHeadings(markdown, options.maxTokens ?? DEFAULT_MAX_TOKENS);
235
+ pages.push({
236
+ slug: slugOf(page.routePath),
237
+ title: page.title,
238
+ url: pageUrl(corpus.host, page.routePath, options.framework),
239
+ markdown: truncation.markdown,
240
+ tokens: estimateTokens(truncation.markdown),
241
+ truncated: truncation.truncated,
242
+ omitted: truncation.omitted,
243
+ demosTrimmed,
244
+ outline: fullOutline,
245
+ });
246
+ }
247
+ if (!pages.length && missing.length) {
248
+ const [first] = missing;
249
+ throw new CapabilityError('NOT_FOUND', `No docs page "${first.requested}"${missing.length > 1 ? ` (and ${missing.length - 1} more)` : ''}.`, first.suggestions.length
250
+ ? `Closest pages: ${first.suggestions.join(', ')}. Use \`adaptable docs search\` to find others.`
251
+ : 'Use `adaptable docs search "<topic>"` or `adaptable docs list` to find the slug.');
252
+ }
253
+ return { pages, missing };
254
+ }
255
+ registerCapability({
256
+ name: 'docs-get',
257
+ description: 'Reads docs pages as Markdown for the project’s version and framework; one section with --section, demo code trimmed unless --full.',
258
+ cli: { command: ['docs', 'get'], positionals: ['pages'] },
259
+ mcp: { tool: 'get_doc' },
260
+ input: z.object({
261
+ pages: z
262
+ .array(z.string().min(1))
263
+ .min(1)
264
+ .describe('Slugs (handbook-layouts), reference names (LayoutApi), routes (/docs/…) or docs URLs'),
265
+ framework: z.enum(FRAMEWORKS).optional().describe('Defaults to the detected framework'),
266
+ section: z.string().optional().describe('Return only the section under this heading'),
267
+ maxTokens: z.number().int().min(200).optional().describe(`Cut at a heading boundary above this many tokens (default ${DEFAULT_MAX_TOKENS})`),
268
+ full: z.boolean().optional().describe('Inline demo code and never truncate'),
269
+ }),
270
+ output: GetOutput,
271
+ run: async (input, ctx) => {
272
+ const { corpus, context, warnings } = await corpusFor(ctx);
273
+ const framework = frameworkOf(ctx, input.framework, context.framework);
274
+ const data = getPages(corpus, input.pages, { ...input, framework });
275
+ for (const miss of data.missing) {
276
+ warnings.push(`No docs page "${miss.requested}"${miss.suggestions.length ? `; closest: ${miss.suggestions.join(', ')}` : ''}.`);
277
+ }
278
+ return ok(data, { resolvedContext: context, warnings });
279
+ },
280
+ markdown: (data) => data.pages
281
+ .map((p) => {
282
+ const notes = [];
283
+ if (p.truncated) {
284
+ notes.push(`truncated at a heading boundary; omitted: ${p.omitted.map((h) => h.replace(/^#+\s*/, '')).join(' · ')} — pass --section "<heading>" or --max-tokens`);
285
+ }
286
+ if (p.demosTrimmed) {
287
+ notes.push(`${p.demosTrimmed} demo${p.demosTrimmed > 1 ? 's' : ''} trimmed — \`docs demo <name>\` or --full`);
288
+ }
289
+ return `<!-- ${p.slug} · ${p.url}${notes.length ? ` · ${notes.join(' · ')}` : ''} -->\n\n${p.markdown}`;
290
+ })
291
+ .join('\n\n---\n\n'),
292
+ });
293
+ // ---------------------------------------------------------------- docs demos | docs demo
294
+ const DemoSummary = z.object({
295
+ name: z.string(),
296
+ title: z.string(),
297
+ description: z.string(),
298
+ framework: z.string(),
299
+ files: z.array(z.string()),
300
+ });
301
+ registerCapability({
302
+ name: 'docs-demos',
303
+ description: 'Lists the demos embedded in a docs page, with the files each demo owns.',
304
+ cli: { command: ['docs', 'demos'], positionals: ['page'] },
305
+ mcp: null,
306
+ input: z.object({
307
+ page: z.string().min(1).describe('Slug, route or URL of the page'),
308
+ }),
309
+ output: z.object({ page: z.string(), demos: z.array(DemoSummary) }),
310
+ run: async (input, ctx) => {
311
+ const { corpus, context, warnings } = await corpusFor(ctx);
312
+ const page = findPage(corpus, input.page);
313
+ if (!page) {
314
+ const suggestions = suggestSlugs(corpus, input.page);
315
+ throw new CapabilityError('NOT_FOUND', `No docs page "${input.page}".`, suggestions.length ? `Closest pages: ${suggestions.join(', ')}.` : 'Use `adaptable docs search` to find the slug.');
316
+ }
317
+ const index = await loadDemosIndex(corpus, { offline: ctx.globals.offline });
318
+ const demos = demosOnPage(index, corpus, page.routePath).map(({ name, title, description, framework, files }) => ({
319
+ name,
320
+ title,
321
+ description,
322
+ framework,
323
+ files,
324
+ }));
325
+ return ok({ page: slugOf(page.routePath), demos }, { resolvedContext: context, warnings });
326
+ },
327
+ markdown: (data) => data.demos.length
328
+ ? data.demos
329
+ .map((d) => `- \`${d.name}\` — ${d.title}${d.description ? `: ${d.description}` : ''} (${d.framework}; files: ${d.files.join(', ') || 'none of its own'})`)
330
+ .join('\n')
331
+ : `No demos on \`${data.page}\`.`,
332
+ });
333
+ registerCapability({
334
+ name: 'docs-demo',
335
+ description: 'Returns one demo’s own files (adaptableOptions.ts by default; --files for others). Template files are never returned.',
336
+ cli: { command: ['docs', 'demo'], positionals: ['name'] },
337
+ mcp: { tool: 'get_demo' },
338
+ input: z.object({
339
+ name: z.string().min(1).describe('Demo name, as listed by `docs demos` or in the page'),
340
+ files: z.array(z.string()).optional().describe('File names or paths to return, e.g. columnDefs.ts rowData.ts'),
341
+ }),
342
+ output: z.object({
343
+ name: z.string(),
344
+ title: z.string(),
345
+ page: z.string().nullable(),
346
+ url: z.string().nullable(),
347
+ framework: z.string(),
348
+ files: z.array(z.object({ path: z.string(), language: z.string(), code: z.string() })),
349
+ /** every file the demo owns, for a follow-up --files */
350
+ available: z.array(z.string()),
351
+ }),
352
+ run: async (input, ctx) => {
353
+ const { corpus, context, warnings } = await corpusFor(ctx);
354
+ const index = await loadDemosIndex(corpus, { offline: ctx.globals.offline });
355
+ const demo = index.find((d) => d.name === input.name) ?? index.find((d) => d.name.toLowerCase() === input.name.toLowerCase());
356
+ if (!demo) {
357
+ const closest = closestDemoNames(index, input.name);
358
+ throw new CapabilityError('NOT_FOUND', `No demo named "${input.name}".`, closest.length ? `Closest demos: ${closest.join(', ')}.` : 'List a page’s demos with `adaptable docs demos <page>`.');
359
+ }
360
+ const markdown = await loadDemoMarkdown(corpus, demo, { offline: ctx.globals.offline });
361
+ const parsed = parseDemoMarkdown(markdown);
362
+ const { selected, unknown } = selectFiles(parsed, input.files);
363
+ for (const name of unknown) {
364
+ warnings.push(`Demo "${demo.name}" has no file "${name}"; it owns: ${parsed.map((f) => f.path).join(', ') || 'none'}.`);
365
+ }
366
+ return ok({
367
+ name: demo.name,
368
+ title: demo.title,
369
+ page: demo.page ? slugOf(demo.page) : null,
370
+ url: demo.page ? pageUrl(corpus.host, demo.page) : null,
371
+ framework: demo.framework,
372
+ files: selected,
373
+ available: parsed.map((f) => f.path),
374
+ }, { resolvedContext: context, warnings });
375
+ },
376
+ markdown: (data) => [
377
+ `<!-- demo ${data.name} (${data.framework})${data.url ? ` · ${data.url}` : ''} · files: ${data.available.join(', ')} -->`,
378
+ ...data.files.map((f) => `**\`${f.path}\`**\n\n\`\`\`${f.language}\n${f.code}\n\`\`\``),
379
+ ].join('\n\n'),
380
+ });
381
+ //# sourceMappingURL=capabilities.js.map