mcpill 1.1.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/.claude/commands/add-card.md +9 -0
- package/.claude/commands/append-card.md +10 -0
- package/.claude/commands/play-card.md +14 -0
- package/.claude/commands/turn.md +28 -0
- package/.claude/settings.json +10 -0
- package/.flowdeck/.flowdeckignore +5 -0
- package/.flowdeck/AGENT.md +46 -0
- package/.flowdeck/TODO.md.template +14 -0
- package/.flowdeck/_discard/start/DISCARD.md +8 -0
- package/.flowdeck/_discard/start/TODO.md +35 -0
- package/.flowdeck/_discard/start/turn.log +28 -0
- package/.flowdeck/_energy/ADR.md.template +100 -0
- package/.flowdeck/_energy/CLAUDE.md.template +95 -0
- package/.flowdeck/_energy/GENERALINSIGHTS.md.template +57 -0
- package/.flowdeck/_energy/MISSION.md.template +89 -0
- package/.flowdeck/_energy/OPEN-QUESTIONS.md.template +109 -0
- package/.flowdeck/_energy/PROJECTINSIGHTS.md.template +64 -0
- package/.flowdeck/_energy/SPEC.md.template +101 -0
- package/.flowdeck/_frozen/FROZEN.md +4 -0
- package/.flowdeck/_meld/MELD.md +4 -0
- package/.flowdeck/_stock/STOCK.md +4 -0
- package/.flowdeck/reframe/TODO.md +71 -0
- package/CHANGELOG.md +8 -0
- package/FLOWDECK.md +17 -0
- package/README.md +85 -0
- package/dist/cli.js +954 -0
- package/package.json +34 -0
- package/src/__tests__/init.test.ts +74 -0
- package/src/__tests__/loaders/config.test.ts +54 -0
- package/src/__tests__/loaders/prompts.test.ts +116 -0
- package/src/__tests__/loaders/resources.test.ts +86 -0
- package/src/__tests__/loaders/tools.test.ts +128 -0
- package/src/__tests__/pack.test.ts +98 -0
- package/src/__tests__/validate.test.ts +152 -0
- package/src/cli.ts +76 -0
- package/src/commands/compile.ts +163 -0
- package/src/commands/init.ts +145 -0
- package/src/commands/pack.ts +52 -0
- package/src/commands/publish.ts +17 -0
- package/src/commands/run.ts +101 -0
- package/src/commands/validate.ts +70 -0
- package/src/compiler/merge-tools.ts +99 -0
- package/src/compiler/parse.ts +236 -0
- package/src/compiler/serialize.ts +100 -0
- package/src/compiler/types.ts +27 -0
- package/src/loaders/config.ts +25 -0
- package/src/loaders/prompts.ts +60 -0
- package/src/loaders/resources.ts +54 -0
- package/src/loaders/tools.ts +63 -0
- package/src/templates/prompts/greeting.md +11 -0
- package/src/templates/server.md +9 -0
- package/src/templates/tools/echo.md +15 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +13 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { z } from "mcpill-runtime";
|
|
4
|
+
|
|
5
|
+
type ZodRawShape = Record<string, z.ZodTypeAny>;
|
|
6
|
+
|
|
7
|
+
export type RawPromptDef = {
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
argsShape: ZodRawShape;
|
|
11
|
+
messages: Array<{ role: string; content: string }>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const typeMap: Record<string, z.ZodTypeAny> = {
|
|
15
|
+
string: z.string(),
|
|
16
|
+
number: z.number(),
|
|
17
|
+
boolean: z.boolean(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function loadPrompts(mcpillDir: string): Promise<RawPromptDef[]> {
|
|
21
|
+
const promptsPath = path.join(mcpillDir, "prompts.json");
|
|
22
|
+
let raw: unknown;
|
|
23
|
+
try {
|
|
24
|
+
const content = fs.readFileSync(promptsPath, "utf-8");
|
|
25
|
+
raw = JSON.parse(content);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28
|
+
throw new Error(`prompts.json is not valid JSON: ${msg}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!Array.isArray(raw)) {
|
|
32
|
+
throw new Error("prompts.json is not valid JSON: must be an array");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const prompts: RawPromptDef[] = [];
|
|
36
|
+
|
|
37
|
+
for (const entry of raw as Array<Record<string, unknown>>) {
|
|
38
|
+
const argsShape: ZodRawShape = {};
|
|
39
|
+
const args = (entry["args"] ?? {}) as Record<string, { type: string }>;
|
|
40
|
+
|
|
41
|
+
for (const [argName, argDef] of Object.entries(args)) {
|
|
42
|
+
const typeName = argDef.type;
|
|
43
|
+
if (!(typeName in typeMap)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Prompt '${entry["name"]}': arg '${argName}' uses unsupported type '${typeName}'. Supported: string, number, boolean`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
argsShape[argName] = typeMap[typeName]!;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
prompts.push({
|
|
52
|
+
name: entry["name"] as string,
|
|
53
|
+
description: entry["description"] as string | undefined,
|
|
54
|
+
argsShape,
|
|
55
|
+
messages: entry["messages"] as Array<{ role: string; content: string }>,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return prompts;
|
|
60
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export type RawResourceDef = {
|
|
5
|
+
uri: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
mimeType?: string;
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function parseFrontmatter(block: string): Record<string, string> {
|
|
12
|
+
const result: Record<string, string> = {};
|
|
13
|
+
for (const line of block.split("\n")) {
|
|
14
|
+
const colonIdx = line.indexOf(":");
|
|
15
|
+
if (colonIdx === -1) continue;
|
|
16
|
+
const key = line.slice(0, colonIdx).trim();
|
|
17
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
18
|
+
if (key) result[key] = value;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function loadResources(mcpillDir: string): Promise<RawResourceDef[]> {
|
|
24
|
+
const resourcesPath = path.join(mcpillDir, "resources.md");
|
|
25
|
+
const fileContent = fs.readFileSync(resourcesPath, "utf-8");
|
|
26
|
+
const blocks = fileContent.split("\n---\n");
|
|
27
|
+
|
|
28
|
+
const resources: RawResourceDef[] = [];
|
|
29
|
+
|
|
30
|
+
// blocks alternate: [frontmatter, body, frontmatter, body, ...]
|
|
31
|
+
// even indices (0-indexed) = frontmatter = block N (1-indexed odd)
|
|
32
|
+
for (let i = 0; i < blocks.length; i += 2) {
|
|
33
|
+
const frontmatterBlock = blocks[i] ?? "";
|
|
34
|
+
if (frontmatterBlock.trim() === "") continue;
|
|
35
|
+
|
|
36
|
+
const bodyBlock = blocks[i + 1] ?? "";
|
|
37
|
+
const frontmatter = parseFrontmatter(frontmatterBlock);
|
|
38
|
+
|
|
39
|
+
if (!frontmatter["uri"]) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`resources.md block ${i + 1} is missing required frontmatter field: uri`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resources.push({
|
|
46
|
+
uri: frontmatter["uri"],
|
|
47
|
+
name: frontmatter["name"],
|
|
48
|
+
mimeType: frontmatter["mimeType"],
|
|
49
|
+
content: bodyBlock.trim(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return resources;
|
|
54
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { pathToFileURL } from "url";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export type RawToolDef = {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
schema: Record<string, unknown>;
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
9
|
+
handler: Function;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function loadTools(mcpillDir: string): Promise<RawToolDef[]> {
|
|
13
|
+
const toolsPath = path.join(mcpillDir, "tools.js");
|
|
14
|
+
let mod: unknown;
|
|
15
|
+
try {
|
|
16
|
+
mod = await import(pathToFileURL(toolsPath).href);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19
|
+
throw new Error(`tools.js failed to load: ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tools = (mod as { default?: unknown }).default;
|
|
23
|
+
if (!Array.isArray(tools)) {
|
|
24
|
+
throw new Error("tools.js failed to load: default export must be an array");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < tools.length; i++) {
|
|
28
|
+
const tool = tools[i] as Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
for (const field of ["name", "description", "schema", "handler"] as const) {
|
|
31
|
+
if (tool[field] === undefined || tool[field] === null) {
|
|
32
|
+
throw new Error(`Tool at index ${i} is missing required field: ${field}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof tool["name"] !== "string") {
|
|
37
|
+
throw new Error(`Tool at index ${i} is missing required field: name`);
|
|
38
|
+
}
|
|
39
|
+
if (typeof tool["description"] !== "string") {
|
|
40
|
+
throw new Error(`Tool at index ${i} is missing required field: description`);
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
typeof tool["schema"] !== "object" ||
|
|
44
|
+
Array.isArray(tool["schema"]) ||
|
|
45
|
+
tool["schema"] === null
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(`Tool at index ${i} is missing required field: schema`);
|
|
48
|
+
}
|
|
49
|
+
const name = tool["name"] as string;
|
|
50
|
+
for (const [key, value] of Object.entries(tool["schema"] as Record<string, unknown>)) {
|
|
51
|
+
if (value == null || typeof value !== "object" || !("_def" in (value as object))) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Tool '${name}': schema field '${key}' must be a Zod type (e.g. z.string()). Got a plain value instead.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (typeof tool["handler"] !== "function") {
|
|
58
|
+
throw new Error(`Tool '${tool["name"]}': handler must be a function`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return tools as RawToolDef[];
|
|
63
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED