@withpica/mcp-server 2.50.0 → 2.51.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/prompts/creator-question-atlas.d.ts.map +1 -1
  3. package/dist/prompts/creator-question-atlas.js +17 -0
  4. package/dist/prompts/creator-question-atlas.js.map +1 -1
  5. package/dist/resources/index.d.ts +10 -0
  6. package/dist/resources/index.d.ts.map +1 -1
  7. package/dist/resources/index.js +134 -1
  8. package/dist/resources/index.js.map +1 -1
  9. package/dist/server-instructions.d.ts +4 -3
  10. package/dist/server-instructions.d.ts.map +1 -1
  11. package/dist/server-instructions.js +4 -1
  12. package/dist/server-instructions.js.map +1 -1
  13. package/dist/skills/index.d.ts +42 -0
  14. package/dist/skills/index.d.ts.map +1 -0
  15. package/dist/skills/index.js +59 -0
  16. package/dist/skills/index.js.map +1 -0
  17. package/dist/skills/skills.generated.d.ts +25 -0
  18. package/dist/skills/skills.generated.d.ts.map +1 -0
  19. package/dist/skills/skills.generated.js +86 -0
  20. package/dist/skills/skills.generated.js.map +1 -0
  21. package/dist/tools/discovery.d.ts.map +1 -1
  22. package/dist/tools/discovery.js +8 -0
  23. package/dist/tools/discovery.js.map +1 -1
  24. package/dist/tools/index.d.ts +43 -2
  25. package/dist/tools/index.d.ts.map +1 -1
  26. package/dist/tools/index.js +87 -10
  27. package/dist/tools/index.js.map +1 -1
  28. package/dist/tools/metadata.d.ts.map +1 -1
  29. package/dist/tools/metadata.js +13 -0
  30. package/dist/tools/metadata.js.map +1 -1
  31. package/dist/tools/skills.d.ts +25 -0
  32. package/dist/tools/skills.d.ts.map +1 -0
  33. package/dist/tools/skills.js +144 -0
  34. package/dist/tools/skills.js.map +1 -0
  35. package/package.json +6 -5
  36. package/scripts/build-skills.ts +229 -0
  37. package/server.json +2 -2
@@ -0,0 +1,229 @@
1
+ // Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
2
+
3
+ /**
4
+ * ADR-140 Phase 2b — generator for `skills.generated.ts`.
5
+ *
6
+ * Reads `src/skills/<name>/SKILL.md` files, parses YAML frontmatter, and emits
7
+ * a TypeScript constant consumed by SkillsRegistry. Generation at build time
8
+ * (not runtime FS reads) so npm-published `dist/` ships bundled bytes — `.md`
9
+ * files are excluded by `.npmignore`.
10
+ *
11
+ * Frontmatter shape:
12
+ * ---
13
+ * name: kebab-case-name
14
+ * description: one-line
15
+ * triggers:
16
+ * - phrase one
17
+ * - phrase two
18
+ * audience: manager / artist / etc
19
+ * tools_required:
20
+ * - tool_a
21
+ * - tool_b
22
+ * output: one-line description
23
+ * ---
24
+ *
25
+ * Hand-rolled YAML parser (frontmatter only — no nested objects, no anchors).
26
+ * Adding a `js-yaml` dep would be overkill for the constrained shape.
27
+ *
28
+ * Re-runs are deterministic: same .md files produce a byte-identical output.
29
+ */
30
+
31
+ import { readFileSync, readdirSync, writeFileSync, existsSync } from "node:fs";
32
+ import { dirname, join } from "node:path";
33
+ import { fileURLToPath } from "node:url";
34
+
35
+ const __filename = fileURLToPath(import.meta.url);
36
+ const __dirname = dirname(__filename);
37
+ const MCP_SERVER_ROOT = join(__dirname, "..");
38
+ const SKILLS_ROOT = join(MCP_SERVER_ROOT, "src/skills");
39
+ const OUTPUT_PATH = join(SKILLS_ROOT, "skills.generated.ts");
40
+
41
+ interface SkillFrontmatter {
42
+ name: string;
43
+ description: string;
44
+ triggers: string[];
45
+ audience: string;
46
+ tools_required: string[];
47
+ output: string;
48
+ }
49
+
50
+ interface ParsedSkill extends SkillFrontmatter {
51
+ body: string;
52
+ }
53
+
54
+ function parseFrontmatter(src: string, sourceFile: string): ParsedSkill {
55
+ // Strip leading HTML comment (copyright header) if present, then expect ---
56
+ let i = 0;
57
+ if (src.startsWith("<!--")) {
58
+ const end = src.indexOf("-->");
59
+ if (end === -1) {
60
+ throw new Error(`${sourceFile}: unterminated HTML comment header`);
61
+ }
62
+ i = end + 3;
63
+ // Skip whitespace/newlines
64
+ while (i < src.length && /\s/.test(src[i])) i++;
65
+ }
66
+
67
+ if (src.slice(i, i + 3) !== "---") {
68
+ throw new Error(`${sourceFile}: missing --- frontmatter opener`);
69
+ }
70
+ i += 3;
71
+ // Skip newline after opener
72
+ while (i < src.length && src[i] !== "\n") i++;
73
+ i++;
74
+
75
+ const closeIdx = src.indexOf("\n---", i);
76
+ if (closeIdx === -1) {
77
+ throw new Error(`${sourceFile}: missing --- frontmatter closer`);
78
+ }
79
+
80
+ const frontmatterText = src.slice(i, closeIdx);
81
+ const bodyStart = closeIdx + 4; // skip \n---
82
+ const body = src.slice(bodyStart).replace(/^\n+/, "").trimEnd() + "\n";
83
+
84
+ // Parse: key: value OR key: (list follows)
85
+ const fm: Record<string, string | string[]> = {};
86
+ const lines = frontmatterText.split("\n");
87
+ let currentListKey: string | null = null;
88
+
89
+ for (const line of lines) {
90
+ if (line.trim() === "") {
91
+ currentListKey = null;
92
+ continue;
93
+ }
94
+ const listItemMatch = line.match(/^\s+-\s+(.+)$/);
95
+ if (listItemMatch && currentListKey) {
96
+ (fm[currentListKey] as string[]).push(listItemMatch[1].trim());
97
+ continue;
98
+ }
99
+ const kvMatch = line.match(/^([a-z_][a-z0-9_]*)\s*:\s*(.*)$/i);
100
+ if (!kvMatch) continue;
101
+ const key = kvMatch[1];
102
+ const value = kvMatch[2].trim();
103
+ if (value === "") {
104
+ // List follows
105
+ fm[key] = [];
106
+ currentListKey = key;
107
+ } else {
108
+ fm[key] = value;
109
+ currentListKey = null;
110
+ }
111
+ }
112
+
113
+ // Validate required fields
114
+ const required = [
115
+ "name",
116
+ "description",
117
+ "triggers",
118
+ "audience",
119
+ "tools_required",
120
+ "output",
121
+ ];
122
+ for (const field of required) {
123
+ if (!(field in fm)) {
124
+ throw new Error(`${sourceFile}: missing required frontmatter field "${field}"`);
125
+ }
126
+ }
127
+ if (!Array.isArray(fm.triggers) || fm.triggers.length === 0) {
128
+ throw new Error(`${sourceFile}: triggers must be a non-empty list`);
129
+ }
130
+ if (!Array.isArray(fm.tools_required) || fm.tools_required.length === 0) {
131
+ throw new Error(`${sourceFile}: tools_required must be a non-empty list`);
132
+ }
133
+
134
+ return {
135
+ name: fm.name as string,
136
+ description: fm.description as string,
137
+ triggers: fm.triggers as string[],
138
+ audience: fm.audience as string,
139
+ tools_required: fm.tools_required as string[],
140
+ output: fm.output as string,
141
+ body,
142
+ };
143
+ }
144
+
145
+ function discoverSkills(): ParsedSkill[] {
146
+ if (!existsSync(SKILLS_ROOT)) {
147
+ return [];
148
+ }
149
+ const entries = readdirSync(SKILLS_ROOT, { withFileTypes: true });
150
+ const skills: ParsedSkill[] = [];
151
+ for (const entry of entries) {
152
+ if (!entry.isDirectory()) continue;
153
+ const skillFile = join(SKILLS_ROOT, entry.name, "SKILL.md");
154
+ if (!existsSync(skillFile)) continue;
155
+ const src = readFileSync(skillFile, "utf-8");
156
+ const parsed = parseFrontmatter(src, `src/skills/${entry.name}/SKILL.md`);
157
+ if (parsed.name !== entry.name) {
158
+ throw new Error(
159
+ `src/skills/${entry.name}/SKILL.md: frontmatter name "${parsed.name}" must match directory name "${entry.name}"`,
160
+ );
161
+ }
162
+ skills.push(parsed);
163
+ }
164
+ skills.sort((a, b) => a.name.localeCompare(b.name));
165
+ return skills;
166
+ }
167
+
168
+ function emit(skills: ParsedSkill[]): string {
169
+ const header = `// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
170
+
171
+ /**
172
+ * GENERATED FILE — DO NOT EDIT BY HAND.
173
+ *
174
+ * Regenerated by \`scripts/build-skills.ts\` from \`src/skills/<name>/SKILL.md\`.
175
+ * Runs in \`prebuild\` and \`prepublishOnly\`. See ADR-140 Phase 2b.
176
+ */
177
+
178
+ export interface Skill {
179
+ /** Stable kebab-case identifier matching the directory name. */
180
+ name: string;
181
+ /** One-line description shown in pica_skill_list. */
182
+ description: string;
183
+ /** Trigger phrases an agent should recognise as invoking this skill. */
184
+ triggers: string[];
185
+ /** Audience tag (manager / artist / sync supervisor / etc). */
186
+ audience: string;
187
+ /** Tools the skill body chains. Lint-checkable. */
188
+ tools_required: string[];
189
+ /** One-line output shape description. */
190
+ output: string;
191
+ /** Full skill methodology body — markdown, exposed by pica_skill_get. */
192
+ body: string;
193
+ }
194
+
195
+ `;
196
+ const lines: string[] = [header, "export const SKILLS: Record<string, Skill> = {"];
197
+ for (const skill of skills) {
198
+ lines.push(` ${JSON.stringify(skill.name)}: {`);
199
+ lines.push(` name: ${JSON.stringify(skill.name)},`);
200
+ lines.push(` description: ${JSON.stringify(skill.description)},`);
201
+ lines.push(` triggers: ${JSON.stringify(skill.triggers)},`);
202
+ lines.push(` audience: ${JSON.stringify(skill.audience)},`);
203
+ lines.push(` tools_required: ${JSON.stringify(skill.tools_required)},`);
204
+ lines.push(` output: ${JSON.stringify(skill.output)},`);
205
+ lines.push(` body: ${JSON.stringify(skill.body)},`);
206
+ lines.push(` },`);
207
+ }
208
+ lines.push("};");
209
+ lines.push("");
210
+ lines.push(
211
+ `export const SKILL_NAMES: ReadonlyArray<string> = ${JSON.stringify(skills.map((s) => s.name))};`,
212
+ );
213
+ lines.push("");
214
+ return lines.join("\n");
215
+ }
216
+
217
+ function main(): void {
218
+ const skills = discoverSkills();
219
+ const output = emit(skills);
220
+ writeFileSync(OUTPUT_PATH, output, "utf-8");
221
+ console.log(
222
+ `[build-skills] Wrote ${skills.length} skill(s) to src/skills/skills.generated.ts`,
223
+ );
224
+ for (const skill of skills) {
225
+ console.log(` - ${skill.name} (${skill.body.length} bytes body)`);
226
+ }
227
+ }
228
+
229
+ main();
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/withpica/pica",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.50.0",
9
+ "version": "2.51.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@withpica/mcp-server",
14
- "version": "2.50.0",
14
+ "version": "2.51.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }