@amcharts/amcharts5-mcp 1.2.0 → 1.4.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/README.md +98 -0
- package/content/SKILL.md +84 -6
- package/content/gantt.md +3 -3
- package/content/map.md +3 -1
- package/content/stock.md +45 -4
- package/content/timeline.md +29 -25
- package/content/ui-elements.md +5 -3
- package/content/wordcloud.md +91 -5
- package/content/xy.md +51 -1
- package/extended/charts/word-cloud.md +19 -3
- package/extended/concepts/colors-gradients-and-patterns/patterns.md +53 -0
- package/extended/concepts/events.md +18 -0
- package/extended/concepts/themes.md +91 -0
- package/extended/reference/irectanglepatternsettings.md +1 -0
- package/extended/reference/iseriessettings.md +2 -0
- package/extended/reference/iserpentinechartsettings.md +1 -0
- package/extended/reference/ispiralchartsettings.md +1 -0
- package/extended/reference/istarpatternsettings.md +38 -0
- package/extended/reference/itrianglepatternsettings.md +37 -0
- package/extended/reference/iwordcloudsettings.md +24 -8
- package/extended/reference/ixychartsettings.md +2 -0
- package/extended/reference/ixycursorsettings.md +1 -0
- package/extended/reference/starpattern.md +51 -0
- package/extended/reference/trianglepattern.md +49 -0
- package/package.json +10 -1
- package/src/content-fs.js +84 -0
- package/src/content.js +214 -0
- package/src/index.js +6 -629
- package/src/tools.js +391 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Node-only disk loader.
|
|
3
|
+
//
|
|
4
|
+
// Reads raw markdown off the filesystem into the { skillFiles, extendedFiles }
|
|
5
|
+
// shape consumed by buildContent(). Used by the stdio server (src/index.js) and
|
|
6
|
+
// by the Worker content build script (scripts/build-worker-content.js).
|
|
7
|
+
//
|
|
8
|
+
// NOTE: imports `fs` — must NOT be imported by the Worker bundle.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { buildContent } from "./content.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const ROOT = join(__dirname, "..");
|
|
18
|
+
|
|
19
|
+
// Submodule path (local dev) or content/ fallback (npm install)
|
|
20
|
+
const SUBMODULE_DIR = join(ROOT, "amcharts5-skill", "amcharts5-skill");
|
|
21
|
+
const CONTENT_DIR = join(ROOT, "content");
|
|
22
|
+
const SKILL_DIR = existsSync(SUBMODULE_DIR) ? SUBMODULE_DIR : CONTENT_DIR;
|
|
23
|
+
const REFERENCES_DIR = existsSync(join(SKILL_DIR, "references"))
|
|
24
|
+
? join(SKILL_DIR, "references")
|
|
25
|
+
: SKILL_DIR;
|
|
26
|
+
const EXTENDED_DIR = join(ROOT, "extended");
|
|
27
|
+
|
|
28
|
+
// Directories under extended/ that must never be served. `reference-generated/`
|
|
29
|
+
// is work-in-progress output from scripts/generate-reference.cjs: it duplicates
|
|
30
|
+
// every page in reference/ under a second key, which makes search return each
|
|
31
|
+
// class twice and advertises an unfinished section via get_doc. It is untracked,
|
|
32
|
+
// but untracked does NOT mean unpublished — `files: ["extended/"]` packs from the
|
|
33
|
+
// filesystem, so without this it also ships in the npm tarball.
|
|
34
|
+
const EXTENDED_SKIP_DIRS = new Set(["reference-generated"]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read all skill + extended markdown into the raw { skillFiles, extendedFiles }
|
|
38
|
+
* shape that buildContent() expects.
|
|
39
|
+
*/
|
|
40
|
+
export function readRawFiles() {
|
|
41
|
+
const skillFiles = {};
|
|
42
|
+
const extendedFiles = {};
|
|
43
|
+
|
|
44
|
+
// Core SKILL.md
|
|
45
|
+
const skillPath = join(SKILL_DIR, "SKILL.md");
|
|
46
|
+
if (existsSync(skillPath)) {
|
|
47
|
+
skillFiles["SKILL"] = readFileSync(skillPath, "utf-8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reference files (skip SKILL.md if it lives in the same dir as references)
|
|
51
|
+
if (existsSync(REFERENCES_DIR)) {
|
|
52
|
+
for (const file of readdirSync(REFERENCES_DIR)) {
|
|
53
|
+
if (!file.endsWith(".md") || file === "SKILL.md") continue;
|
|
54
|
+
skillFiles[file.replace(".md", "")] = readFileSync(join(REFERENCES_DIR, file), "utf-8");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extended docs + examples (recursive)
|
|
59
|
+
walkExtended(EXTENDED_DIR, "", extendedFiles);
|
|
60
|
+
|
|
61
|
+
return { skillFiles, extendedFiles };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function walkExtended(dir, prefix, out) {
|
|
65
|
+
if (!existsSync(dir)) return;
|
|
66
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
67
|
+
const fullPath = join(dir, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
// Only skip at the top level — a nested dir of the same name is fine.
|
|
70
|
+
if (!prefix && EXTENDED_SKIP_DIRS.has(entry.name)) continue;
|
|
71
|
+
walkExtended(fullPath, prefix ? `${prefix}/${entry.name}` : entry.name, out);
|
|
72
|
+
} else if (entry.name.endsWith(".md")) {
|
|
73
|
+
const key = prefix
|
|
74
|
+
? `${prefix}/${entry.name.replace(".md", "")}`
|
|
75
|
+
: entry.name.replace(".md", "");
|
|
76
|
+
out[key] = readFileSync(fullPath, "utf-8");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Read from disk and build the in-memory content structures. */
|
|
82
|
+
export function loadContentFromDisk() {
|
|
83
|
+
return buildContent(readRawFiles());
|
|
84
|
+
}
|
package/src/content.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Shared, transport-agnostic content layer.
|
|
3
|
+
//
|
|
4
|
+
// Pure logic only — NO filesystem, NO Node built-ins — so this module can be
|
|
5
|
+
// bundled into the Cloudflare Worker as-is. The stdio server reads files from
|
|
6
|
+
// disk (see content-fs.js) and the Worker imports a pre-generated bundle (see
|
|
7
|
+
// scripts/build-worker-content.js); both feed the same buildContent() here so
|
|
8
|
+
// the two deployments behave identically.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** Chart type aliases → reference file mapping */
|
|
12
|
+
export const CHART_TYPE_MAP = {
|
|
13
|
+
// XY family
|
|
14
|
+
"xy": "xy", "line": "xy", "area": "xy", "bar": "xy", "column": "xy",
|
|
15
|
+
"candlestick": "xy", "ohlc": "xy", "scatter": "xy", "bubble": "xy",
|
|
16
|
+
"stacked": "xy", "stacked-bar": "xy", "stacked-column": "xy",
|
|
17
|
+
"stacked-area": "xy", "range": "xy", "waterfall": "xy",
|
|
18
|
+
// Pie family
|
|
19
|
+
"pie": "pie", "donut": "pie", "funnel": "pie", "pyramid": "pie",
|
|
20
|
+
"semi-circle": "pie", "pictorial": "pie", "sliced": "pie",
|
|
21
|
+
// Map
|
|
22
|
+
"map": "map", "choropleth": "map", "geo": "map", "geographic": "map",
|
|
23
|
+
"world": "map", "country": "map", "bubble-map": "map", "point-map": "map",
|
|
24
|
+
// Hierarchy
|
|
25
|
+
"hierarchy": "hierarchy", "treemap": "hierarchy", "sunburst": "hierarchy",
|
|
26
|
+
"force-directed": "hierarchy", "pack": "hierarchy", "partition": "hierarchy",
|
|
27
|
+
"tree": "hierarchy", "org-chart": "hierarchy",
|
|
28
|
+
// Flow
|
|
29
|
+
"flow": "flow", "sankey": "flow", "chord": "flow", "arc": "flow",
|
|
30
|
+
"alluvial": "flow",
|
|
31
|
+
// Radar
|
|
32
|
+
"radar": "radar", "spider": "radar", "polar": "radar", "gauge": "radar",
|
|
33
|
+
"speedometer": "radar", "meter": "radar",
|
|
34
|
+
// Stock
|
|
35
|
+
"stock": "stock", "financial": "stock", "trading": "stock",
|
|
36
|
+
// Timeline
|
|
37
|
+
"timeline": "timeline", "serpentine": "timeline", "spiral": "timeline",
|
|
38
|
+
"curve": "timeline",
|
|
39
|
+
// Gantt
|
|
40
|
+
"gantt": "gantt", "project-timeline": "gantt",
|
|
41
|
+
// Word cloud
|
|
42
|
+
"wordcloud": "wordcloud", "word-cloud": "wordcloud", "tag-cloud": "wordcloud",
|
|
43
|
+
// Venn
|
|
44
|
+
"venn": "venn",
|
|
45
|
+
// UI
|
|
46
|
+
"ui": "ui-elements", "ui-elements": "ui-elements", "button": "ui-elements",
|
|
47
|
+
"slider": "ui-elements", "controls": "ui-elements",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function parseSections(content) {
|
|
51
|
+
const sections = [];
|
|
52
|
+
const lines = content.split("\n");
|
|
53
|
+
let currentHeading = "";
|
|
54
|
+
let currentBody = [];
|
|
55
|
+
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
58
|
+
if (headingMatch) {
|
|
59
|
+
const level = headingMatch[1].length;
|
|
60
|
+
const title = headingMatch[2].trim();
|
|
61
|
+
if (level <= 2) {
|
|
62
|
+
// Major section boundary (# or ##)
|
|
63
|
+
if (currentHeading || currentBody.length > 0) {
|
|
64
|
+
sections.push({ heading: currentHeading, body: currentBody.join("\n") });
|
|
65
|
+
}
|
|
66
|
+
currentHeading = title;
|
|
67
|
+
currentBody = [];
|
|
68
|
+
} else {
|
|
69
|
+
// Sub-heading (###) — include in current section body
|
|
70
|
+
currentBody.push(line);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
currentBody.push(line);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (currentHeading || currentBody.length > 0) {
|
|
77
|
+
sections.push({ heading: currentHeading, body: currentBody.join("\n") });
|
|
78
|
+
}
|
|
79
|
+
return sections;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Import sections carry two ### sub-blocks: one for ES modules, one for CDN /
|
|
84
|
+
* script tags. Return only the requested one (esm | html), or "" if not found.
|
|
85
|
+
*/
|
|
86
|
+
export function extractImportBlock(body, format) {
|
|
87
|
+
const lines = body.split("\n");
|
|
88
|
+
const wantEsm = format === "esm";
|
|
89
|
+
let capturing = false;
|
|
90
|
+
let captured = [];
|
|
91
|
+
let sawSub = false;
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
const sub = line.match(/^###\s+(.+)/);
|
|
94
|
+
if (sub) {
|
|
95
|
+
sawSub = true;
|
|
96
|
+
const h = sub[1].toLowerCase();
|
|
97
|
+
const isEsm = /es module|esm|typescript|import/.test(h);
|
|
98
|
+
// Note: "typescript" contains "script", so match "script tag"/"cdn", not bare "script".
|
|
99
|
+
const isCdn = /cdn|script tag|<script/.test(h);
|
|
100
|
+
capturing = wantEsm ? isEsm : isCdn;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (capturing) captured.push(line);
|
|
104
|
+
}
|
|
105
|
+
if (!sawSub) return ""; // no sub-blocks → caller falls back to full body
|
|
106
|
+
return captured.join("\n").trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build the in-memory content structures from raw markdown files.
|
|
111
|
+
*
|
|
112
|
+
* @param {{ skillFiles: Record<string,string>, extendedFiles: Record<string,string> }} raw
|
|
113
|
+
* skillFiles — name (no .md) → raw markdown for SKILL.md + references/*.md
|
|
114
|
+
* extendedFiles — path key (no .md) → raw markdown for everything under extended/
|
|
115
|
+
* @returns {{ docs: Map, extendedDocs: Map, examples: Map }}
|
|
116
|
+
*/
|
|
117
|
+
export function buildContent({ skillFiles = {}, extendedFiles = {} } = {}) {
|
|
118
|
+
/** @type {Map<string, {title: string, content: string, sections: {heading: string, body: string}[]}>} */
|
|
119
|
+
const docs = new Map();
|
|
120
|
+
/** @type {Map<string, {title: string, content: string, sections: {heading: string, body: string}[], path: string}>} */
|
|
121
|
+
const extendedDocs = new Map();
|
|
122
|
+
/** @type {Map<string, {title: string, category: string, content: string, source: string}>} */
|
|
123
|
+
const examples = new Map();
|
|
124
|
+
|
|
125
|
+
// Skill docs (SKILL.md + references/*.md)
|
|
126
|
+
for (const [name, content] of Object.entries(skillFiles)) {
|
|
127
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
128
|
+
const title = titleMatch ? titleMatch[1] : name;
|
|
129
|
+
docs.set(name, { title, content, sections: parseSections(content) });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Extended docs + examples (frontmatter-tagged markdown)
|
|
133
|
+
for (const [key, content] of Object.entries(extendedFiles)) {
|
|
134
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
135
|
+
const body = fmMatch ? fmMatch[2].trim() : content;
|
|
136
|
+
const fm = fmMatch ? fmMatch[1] : "";
|
|
137
|
+
const titleMatch = fm.match(/title:\s*"(.+?)"/);
|
|
138
|
+
const categoryMatch = fm.match(/category:\s*"(.+?)"/);
|
|
139
|
+
const sourceMatch = fm.match(/source:\s*"(.+?)"/);
|
|
140
|
+
const fallbackTitle = key.split("/").pop();
|
|
141
|
+
const title = titleMatch ? titleMatch[1] : fallbackTitle;
|
|
142
|
+
|
|
143
|
+
if (key.startsWith("examples/")) {
|
|
144
|
+
examples.set(key, {
|
|
145
|
+
title,
|
|
146
|
+
category: categoryMatch ? categoryMatch[1] : "",
|
|
147
|
+
content: body,
|
|
148
|
+
source: sourceMatch ? sourceMatch[1] : "",
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
extendedDocs.set(key, {
|
|
152
|
+
title,
|
|
153
|
+
content: body,
|
|
154
|
+
sections: parseSections(body),
|
|
155
|
+
path: key,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { docs, extendedDocs, examples };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Keyword search over the skill docs and (optionally) the extended docs.
|
|
165
|
+
* @param {{docs: Map, extendedDocs: Map}} content
|
|
166
|
+
*/
|
|
167
|
+
export function searchDocs(content, query, maxResults = 10, scope = "skill") {
|
|
168
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
169
|
+
const results = [];
|
|
170
|
+
|
|
171
|
+
// Skill docs (SKILL.md + references/*.md)
|
|
172
|
+
for (const [name, doc] of content.docs) {
|
|
173
|
+
for (const section of doc.sections) {
|
|
174
|
+
const text = (section.heading + " " + section.body).toLowerCase();
|
|
175
|
+
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
176
|
+
if (score > 0) {
|
|
177
|
+
results.push({
|
|
178
|
+
file: name,
|
|
179
|
+
source: "skill",
|
|
180
|
+
docTitle: doc.title,
|
|
181
|
+
heading: section.heading,
|
|
182
|
+
body: section.body.trim(),
|
|
183
|
+
// Nudge skill results slightly ahead of extended on ties — the skill
|
|
184
|
+
// is the curated layer; extended is the exhaustive API reference.
|
|
185
|
+
score: score + 0.1,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Extended docs (charts/, concepts/, getting-started/, reference/<class>) —
|
|
192
|
+
// this is where the per-class API + defaults live. Only scanned when scope=all.
|
|
193
|
+
if (scope === "all") {
|
|
194
|
+
for (const [key, doc] of content.extendedDocs) {
|
|
195
|
+
for (const section of doc.sections) {
|
|
196
|
+
const text = (section.heading + " " + section.body).toLowerCase();
|
|
197
|
+
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
198
|
+
if (score > 0) {
|
|
199
|
+
results.push({
|
|
200
|
+
file: key,
|
|
201
|
+
source: "extended",
|
|
202
|
+
docTitle: doc.title,
|
|
203
|
+
heading: section.heading,
|
|
204
|
+
body: section.body.trim(),
|
|
205
|
+
score,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
results.sort((a, b) => b.score - a.score);
|
|
213
|
+
return results.slice(0, maxResults);
|
|
214
|
+
}
|