@nebutra/play-loader 0.1.1 → 0.1.3

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.
@@ -23,8 +23,19 @@ function asStringArray(value) {
23
23
  function asRecord(value) {
24
24
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
25
25
  }
26
+ function isPlayNameChar(char) {
27
+ return char >= "a" && char <= "z" || char >= "0" && char <= "9" || char === "_" || char === "-";
28
+ }
29
+ function trimPlayNameSeparators(value) {
30
+ let start = 0;
31
+ let end = value.length;
32
+ while (start < end && value[start] === "_") start++;
33
+ while (end > start && value[end - 1] === "_") end--;
34
+ return value.slice(start, end);
35
+ }
26
36
  function normalizePlayName(name) {
27
- return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
37
+ const normalized = name.trim().toLowerCase().split("").map((char) => isPlayNameChar(char) ? char : "_").join("");
38
+ return trimPlayNameSeparators(normalized);
28
39
  }
29
40
  async function appendPlayDebug(entry) {
30
41
  await appendCapabilityDebug("play-loader", entry);
@@ -219,4 +230,4 @@ export {
219
230
  resolvePlayChain,
220
231
  PlayLoader
221
232
  };
222
- //# sourceMappingURL=chunk-TVBZH43E.js.map
233
+ //# sourceMappingURL=chunk-SEGGGISV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { type ParsedFrontmatter, parseSkillMarkdown, type SkillMeta } from \"@nebutra/tool-registry\";\n\nexport interface PlayDependency {\n readonly play: string;\n readonly outputs: readonly string[];\n}\n\nexport interface PlaySubagent {\n readonly role: string;\n readonly allowedSkills: readonly string[];\n}\n\nexport interface PlayMeta extends SkillMeta {\n readonly kind: \"play\";\n}\n\nexport interface LoadedPlay {\n readonly meta: PlayMeta;\n readonly body: string;\n readonly frontmatter: ParsedFrontmatter;\n readonly requiredSkills: readonly string[];\n readonly subAgents: readonly PlaySubagent[];\n readonly dependsOnPlays: readonly PlayDependency[];\n}\n\nexport interface PlayNode {\n readonly name: string;\n readonly dependsOn: readonly string[];\n}\n\nexport interface PlayTestReport {\n readonly name: string;\n readonly ok: boolean;\n readonly checks: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface PlayLoaderDoctorReport {\n readonly ok: boolean;\n readonly plays: number;\n readonly root: string;\n readonly suggestion?: string;\n}\n\nasync function walk(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n const files: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) files.push(...(await walk(full)));\n else files.push(full);\n }\n return files;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : [];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction isPlayNameChar(char: string): boolean {\n return (\n (char >= \"a\" && char <= \"z\") || (char >= \"0\" && char <= \"9\") || char === \"_\" || char === \"-\"\n );\n}\n\nfunction trimPlayNameSeparators(value: string): string {\n let start = 0;\n let end = value.length;\n while (start < end && value[start] === \"_\") start++;\n while (end > start && value[end - 1] === \"_\") end--;\n return value.slice(start, end);\n}\n\nfunction normalizePlayName(name: string): string {\n const normalized = name\n .trim()\n .toLowerCase()\n .split(\"\")\n .map((char) => (isPlayNameChar(char) ? char : \"_\"))\n .join(\"\");\n return trimPlayNameSeparators(normalized);\n}\n\nasync function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"play-loader\", entry);\n}\n\nexport async function readPlayDebug(limit = 10): Promise<unknown[]> {\n return readCapabilityDebug(\"play-loader\", { limit });\n}\n\nfunction parseSubagents(value: unknown): PlaySubagent[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlaySubagent[] => {\n const record = asRecord(item);\n const role = asString(record?.role);\n if (!record || !role) return [];\n return [{ role, allowedSkills: asStringArray(record.allowed_skills) }];\n });\n}\n\nfunction parsePlayDependencies(value: unknown): PlayDependency[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlayDependency[] => {\n if (typeof item === \"string\") return [{ play: item, outputs: [] }];\n const record = asRecord(item);\n const play = asString(record?.play);\n if (!record || !play) return [];\n return [{ play, outputs: asStringArray(record.outputs) }];\n });\n}\n\nexport function parsePlayMarkdown(markdown: string): LoadedPlay {\n const loaded = parseSkillMarkdown(markdown);\n const { frontmatter } = loaded;\n if (frontmatter.kind !== \"play\") {\n throw new CapabilityError(\"play-loader\", \"Play SKILL.md must set kind: play\", {\n suggestion: \"Set `kind: play` in frontmatter or keep this document in tool-registry only.\",\n statusCode: 400,\n metadata: { name: loaded.meta.name, kind: frontmatter.kind },\n });\n }\n\n return {\n meta: { ...loaded.meta, kind: \"play\" },\n body: loaded.body,\n frontmatter,\n requiredSkills: asStringArray(frontmatter.required_skills),\n subAgents: parseSubagents(frontmatter.sub_agents),\n dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays),\n };\n}\n\nexport function resolvePlayChain(nodes: readonly PlayNode[]): readonly string[] {\n const byName = new Map(nodes.map((node) => [node.name, node]));\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const ordered: string[] = [];\n\n const visit = (name: string): void => {\n if (visited.has(name)) return;\n if (visiting.has(name)) {\n throw new CapabilityError(\"play-loader\", \"Play dependency cycle detected\", {\n suggestion: \"Remove the cycle or merge mutually dependent plays into one declaration.\",\n statusCode: 400,\n metadata: { name },\n });\n }\n visiting.add(name);\n const node = byName.get(name);\n for (const dependency of node?.dependsOn ?? []) visit(dependency);\n visiting.delete(name);\n visited.add(name);\n ordered.push(name);\n };\n\n for (const node of nodes) visit(node.name);\n return ordered;\n}\n\nexport class PlayLoader {\n readonly #root: string;\n readonly #plays = new Map<string, { readonly meta: PlayMeta; readonly path: string }>();\n\n private constructor(root: string) {\n this.#root = root;\n }\n\n static async open(root: string): Promise<PlayLoader> {\n const loader = new PlayLoader(root);\n await mkdir(loader.playsRoot(), { recursive: true });\n await loader.reload();\n return loader;\n }\n\n playsRoot(): string {\n return join(this.#root, \"plays\");\n }\n\n async writePlay(name: string, markdown: string): Promise<void> {\n const normalized = normalizePlayName(name);\n const parsed = parsePlayMarkdown(markdown);\n if (parsed.meta.name !== normalized) {\n throw new CapabilityError(\"play-loader\", \"Play name does not match path\", {\n suggestion: \"Use the same lowercase codename in the path and SKILL.md frontmatter.\",\n metadata: { pathName: normalized, playName: parsed.meta.name },\n statusCode: 400,\n });\n }\n const path = join(this.playsRoot(), normalized, \"SKILL.md\");\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, markdown, \"utf8\");\n await this.reload();\n await appendPlayDebug({ type: \"write\", play: normalized });\n }\n\n async newPlay(name: string): Promise<LoadedPlay> {\n const normalized = normalizePlayName(name);\n const markdown = [\n \"---\",\n `name: ${normalized}`,\n \"kind: play\",\n \"version: 1.0.0\",\n `description: ${normalized.replaceAll(\"_\", \" \")} play`,\n \"required_skills: []\",\n \"sub_agents: []\",\n \"depends_on_plays: []\",\n \"---\",\n \"\",\n \"## What this play does\",\n \"\",\n \"Describe the user story and expected output.\",\n ].join(\"\\n\");\n await this.writePlay(normalized, markdown);\n return this.load(normalized);\n }\n\n async reload(): Promise<void> {\n this.#plays.clear();\n for (const file of await walk(this.playsRoot())) {\n if (!file.endsWith(\"SKILL.md\")) continue;\n const raw = await readFile(file, \"utf8\");\n const play = parsePlayMarkdown(raw);\n this.#plays.set(play.meta.name, { meta: play.meta, path: relative(this.#root, file) });\n }\n await appendPlayDebug({ type: \"reload\", plays: this.#plays.size });\n }\n\n async list(): Promise<PlayMeta[]> {\n return [...this.#plays.values()]\n .map((entry) => entry.meta)\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n async load(name: string): Promise<LoadedPlay> {\n const play = this.#plays.get(normalizePlayName(name));\n if (!play) {\n throw new CapabilityError(\"play-loader\", \"Play not found\", {\n suggestion: \"Run `pnpm play:list` or create it with `pnpm play:new <name>`.\",\n metadata: { name },\n statusCode: 404,\n });\n }\n const loaded = parsePlayMarkdown(await readFile(join(this.#root, play.path), \"utf8\"));\n await appendPlayDebug({ type: \"load\", play: loaded.meta.name });\n return loaded;\n }\n\n async install(source: string): Promise<LoadedPlay> {\n if (!source.startsWith(\"file:\")) {\n throw new CapabilityError(\"play-loader\", \"Only file installs are enabled locally\", {\n suggestion:\n \"Download remote plays through a reviewed distribution workflow, then install with file:<path>.\",\n metadata: { source },\n statusCode: 400,\n });\n }\n const raw = await readFile(source.slice(\"file:\".length), \"utf8\");\n const play = parsePlayMarkdown(raw);\n await this.writePlay(play.meta.name, raw);\n return this.load(play.meta.name);\n }\n\n async test(name: string): Promise<PlayTestReport> {\n try {\n const play = await this.load(name);\n return {\n name: play.meta.name,\n ok: play.body.length > 0,\n checks: [\"frontmatter\", \"kind:play\", \"body\", \"dependency-dag\"],\n ...(!play.body ? { suggestion: \"Add a markdown body after the frontmatter.\" } : {}),\n };\n } catch (error) {\n return {\n name,\n ok: false,\n checks: [],\n suggestion:\n error instanceof CapabilityError\n ? \"Create the play with `pnpm play:new <name>` or fix its SKILL.md metadata.\"\n : \"Run `pnpm play:list` and verify the play directory exists.\",\n };\n }\n }\n\n async doctor(): Promise<PlayLoaderDoctorReport> {\n const plays = this.#plays.size;\n return {\n ok: plays > 0,\n plays,\n root: this.#root,\n ...(plays === 0 ? { suggestion: \"Create a play with `pnpm play:new <name>`.\" } : {}),\n };\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  parsePlayMarkdown,
4
4
  readPlayDebug,
5
5
  resolvePlayChain
6
- } from "./chunk-TVBZH43E.js";
6
+ } from "./chunk-SEGGGISV.js";
7
7
 
8
8
  // src/cli.ts
9
9
  import { readFile } from "fs/promises";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  parsePlayMarkdown,
4
4
  readPlayDebug,
5
5
  resolvePlayChain
6
- } from "./chunk-TVBZH43E.js";
6
+ } from "./chunk-SEGGGISV.js";
7
7
  export {
8
8
  PlayLoader,
9
9
  parsePlayMarkdown,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/play-loader",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Declarative SKILL.md play loader and dependency planner",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -21,18 +21,22 @@
21
21
  "surface": "product-orchestration",
22
22
  "summary": "SKILL.md play parser, dependency-chain planner, and agent-runtime runner seam"
23
23
  },
24
- "main": "./src/index.ts",
25
- "types": "./src/index.ts",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
26
  "exports": {
27
- ".": "./src/index.ts"
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
28
32
  },
29
33
  "dependencies": {
30
- "@nebutra/capability-kit": "0.2.1",
31
- "@nebutra/errors": "0.1.1",
32
- "@nebutra/tool-registry": "0.1.1"
34
+ "@nebutra/capability-kit": "0.2.4",
35
+ "@nebutra/tool-registry": "0.1.3",
36
+ "@nebutra/errors": "0.1.2"
33
37
  },
34
38
  "devDependencies": {
35
- "@types/node": "^22.19.15",
39
+ "@types/node": "^25.9.1",
36
40
  "tsup": "^8.5.1",
37
41
  "typescript": "^5.9.3",
38
42
  "vitest": "^4.1.4"
@@ -49,6 +53,12 @@
49
53
  "publishConfig": {
50
54
  "access": "public"
51
55
  },
56
+ "files": [
57
+ "dist",
58
+ "README.md",
59
+ "LICENSE",
60
+ "CHANGELOG.md"
61
+ ],
52
62
  "scripts": {
53
63
  "build": "tsup",
54
64
  "test": "vitest run",
@@ -1,7 +0,0 @@
1
- {"at":"2026-05-19T08:15:22.732Z","type":"reload","plays":0}
2
- {"at":"2026-05-19T08:15:22.765Z","type":"reload","plays":1}
3
- {"at":"2026-05-19T08:15:22.768Z","type":"write","play":"launch_page"}
4
- {"at":"2026-05-19T08:15:22.780Z","type":"load","play":"launch_page"}
5
- {"at":"2026-05-19T08:15:22.825Z","type":"reload","plays":2}
6
- {"at":"2026-05-19T08:15:22.825Z","type":"write","play":"launch_page_copy"}
7
- {"at":"2026-05-19T08:15:22.844Z","type":"load","play":"launch_page_copy"}
@@ -1,22 +0,0 @@
1
-
2
- > @nebutra/play-loader@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
3
- > tsup
4
-
5
- CLI Building entry: src/cli.ts, src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader/tsup.config.ts
9
- CLI Target: es2022
10
- CLI Cleaning output folder
11
- ESM Build start
12
- ESM dist/cli.js 2.28 KB
13
- ESM dist/index.js 226.00 B
14
- ESM dist/chunk-TVBZH43E.js 7.56 KB
15
- ESM dist/cli.js.map 4.29 KB
16
- ESM dist/index.js.map 71.00 B
17
- ESM dist/chunk-TVBZH43E.js.map 15.16 KB
18
- ESM ⚡️ Build success in 81ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 6327ms
21
- DTS dist/cli.d.ts 13.00 B
22
- DTS dist/index.d.ts 1.91 KB
@@ -1,14 +0,0 @@
1
-
2
- > @nebutra/play-loader@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
3
- > vitest run
4
-
5
-
6
-  RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
7
-
8
- ✓ src/index.test.ts (5 tests) 309ms
9
-
10
-  Test Files  1 passed (1)
11
-  Tests  5 passed (5)
12
-  Start at  08:15:20
13
-  Duration  2.26s (transform 925ms, setup 0ms, import 1.18s, tests 309ms, environment 0ms)
14
-
@@ -1,4 +0,0 @@
1
-
2
- > @nebutra/play-loader@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/play-loader
3
- > tsc --noEmit
4
-
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { type ParsedFrontmatter, parseSkillMarkdown, type SkillMeta } from \"@nebutra/tool-registry\";\n\nexport interface PlayDependency {\n readonly play: string;\n readonly outputs: readonly string[];\n}\n\nexport interface PlaySubagent {\n readonly role: string;\n readonly allowedSkills: readonly string[];\n}\n\nexport interface PlayMeta extends SkillMeta {\n readonly kind: \"play\";\n}\n\nexport interface LoadedPlay {\n readonly meta: PlayMeta;\n readonly body: string;\n readonly frontmatter: ParsedFrontmatter;\n readonly requiredSkills: readonly string[];\n readonly subAgents: readonly PlaySubagent[];\n readonly dependsOnPlays: readonly PlayDependency[];\n}\n\nexport interface PlayNode {\n readonly name: string;\n readonly dependsOn: readonly string[];\n}\n\nexport interface PlayTestReport {\n readonly name: string;\n readonly ok: boolean;\n readonly checks: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface PlayLoaderDoctorReport {\n readonly ok: boolean;\n readonly plays: number;\n readonly root: string;\n readonly suggestion?: string;\n}\n\nasync function walk(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n const files: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) files.push(...(await walk(full)));\n else files.push(full);\n }\n return files;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : [];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction normalizePlayName(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\nasync function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"play-loader\", entry);\n}\n\nexport async function readPlayDebug(limit = 10): Promise<unknown[]> {\n return readCapabilityDebug(\"play-loader\", { limit });\n}\n\nfunction parseSubagents(value: unknown): PlaySubagent[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlaySubagent[] => {\n const record = asRecord(item);\n const role = asString(record?.role);\n if (!record || !role) return [];\n return [{ role, allowedSkills: asStringArray(record.allowed_skills) }];\n });\n}\n\nfunction parsePlayDependencies(value: unknown): PlayDependency[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item): PlayDependency[] => {\n if (typeof item === \"string\") return [{ play: item, outputs: [] }];\n const record = asRecord(item);\n const play = asString(record?.play);\n if (!record || !play) return [];\n return [{ play, outputs: asStringArray(record.outputs) }];\n });\n}\n\nexport function parsePlayMarkdown(markdown: string): LoadedPlay {\n const loaded = parseSkillMarkdown(markdown);\n const { frontmatter } = loaded;\n if (frontmatter.kind !== \"play\") {\n throw new CapabilityError(\"play-loader\", \"Play SKILL.md must set kind: play\", {\n suggestion: \"Set `kind: play` in frontmatter or keep this document in tool-registry only.\",\n statusCode: 400,\n metadata: { name: loaded.meta.name, kind: frontmatter.kind },\n });\n }\n\n return {\n meta: { ...loaded.meta, kind: \"play\" },\n body: loaded.body,\n frontmatter,\n requiredSkills: asStringArray(frontmatter.required_skills),\n subAgents: parseSubagents(frontmatter.sub_agents),\n dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays),\n };\n}\n\nexport function resolvePlayChain(nodes: readonly PlayNode[]): readonly string[] {\n const byName = new Map(nodes.map((node) => [node.name, node]));\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const ordered: string[] = [];\n\n const visit = (name: string): void => {\n if (visited.has(name)) return;\n if (visiting.has(name)) {\n throw new CapabilityError(\"play-loader\", \"Play dependency cycle detected\", {\n suggestion: \"Remove the cycle or merge mutually dependent plays into one declaration.\",\n statusCode: 400,\n metadata: { name },\n });\n }\n visiting.add(name);\n const node = byName.get(name);\n for (const dependency of node?.dependsOn ?? []) visit(dependency);\n visiting.delete(name);\n visited.add(name);\n ordered.push(name);\n };\n\n for (const node of nodes) visit(node.name);\n return ordered;\n}\n\nexport class PlayLoader {\n readonly #root: string;\n readonly #plays = new Map<string, { readonly meta: PlayMeta; readonly path: string }>();\n\n private constructor(root: string) {\n this.#root = root;\n }\n\n static async open(root: string): Promise<PlayLoader> {\n const loader = new PlayLoader(root);\n await mkdir(loader.playsRoot(), { recursive: true });\n await loader.reload();\n return loader;\n }\n\n playsRoot(): string {\n return join(this.#root, \"plays\");\n }\n\n async writePlay(name: string, markdown: string): Promise<void> {\n const normalized = normalizePlayName(name);\n const parsed = parsePlayMarkdown(markdown);\n if (parsed.meta.name !== normalized) {\n throw new CapabilityError(\"play-loader\", \"Play name does not match path\", {\n suggestion: \"Use the same lowercase codename in the path and SKILL.md frontmatter.\",\n metadata: { pathName: normalized, playName: parsed.meta.name },\n statusCode: 400,\n });\n }\n const path = join(this.playsRoot(), normalized, \"SKILL.md\");\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, markdown, \"utf8\");\n await this.reload();\n await appendPlayDebug({ type: \"write\", play: normalized });\n }\n\n async newPlay(name: string): Promise<LoadedPlay> {\n const normalized = normalizePlayName(name);\n const markdown = [\n \"---\",\n `name: ${normalized}`,\n \"kind: play\",\n \"version: 1.0.0\",\n `description: ${normalized.replaceAll(\"_\", \" \")} play`,\n \"required_skills: []\",\n \"sub_agents: []\",\n \"depends_on_plays: []\",\n \"---\",\n \"\",\n \"## What this play does\",\n \"\",\n \"Describe the user story and expected output.\",\n ].join(\"\\n\");\n await this.writePlay(normalized, markdown);\n return this.load(normalized);\n }\n\n async reload(): Promise<void> {\n this.#plays.clear();\n for (const file of await walk(this.playsRoot())) {\n if (!file.endsWith(\"SKILL.md\")) continue;\n const raw = await readFile(file, \"utf8\");\n const play = parsePlayMarkdown(raw);\n this.#plays.set(play.meta.name, { meta: play.meta, path: relative(this.#root, file) });\n }\n await appendPlayDebug({ type: \"reload\", plays: this.#plays.size });\n }\n\n async list(): Promise<PlayMeta[]> {\n return [...this.#plays.values()]\n .map((entry) => entry.meta)\n .sort((left, right) => left.name.localeCompare(right.name));\n }\n\n async load(name: string): Promise<LoadedPlay> {\n const play = this.#plays.get(normalizePlayName(name));\n if (!play) {\n throw new CapabilityError(\"play-loader\", \"Play not found\", {\n suggestion: \"Run `pnpm play:list` or create it with `pnpm play:new <name>`.\",\n metadata: { name },\n statusCode: 404,\n });\n }\n const loaded = parsePlayMarkdown(await readFile(join(this.#root, play.path), \"utf8\"));\n await appendPlayDebug({ type: \"load\", play: loaded.meta.name });\n return loaded;\n }\n\n async install(source: string): Promise<LoadedPlay> {\n if (!source.startsWith(\"file:\")) {\n throw new CapabilityError(\"play-loader\", \"Only file installs are enabled locally\", {\n suggestion:\n \"Download remote plays through a reviewed distribution workflow, then install with file:<path>.\",\n metadata: { source },\n statusCode: 400,\n });\n }\n const raw = await readFile(source.slice(\"file:\".length), \"utf8\");\n const play = parsePlayMarkdown(raw);\n await this.writePlay(play.meta.name, raw);\n return this.load(play.meta.name);\n }\n\n async test(name: string): Promise<PlayTestReport> {\n try {\n const play = await this.load(name);\n return {\n name: play.meta.name,\n ok: play.body.length > 0,\n checks: [\"frontmatter\", \"kind:play\", \"body\", \"dependency-dag\"],\n ...(!play.body ? { suggestion: \"Add a markdown body after the frontmatter.\" } : {}),\n };\n } catch (error) {\n return {\n name,\n ok: false,\n checks: [],\n suggestion:\n error instanceof CapabilityError\n ? \"Create the play with `pnpm play:new <name>` or fix its SKILL.md metadata.\"\n : \"Run `pnpm play:list` and verify the play directory exists.\",\n };\n }\n }\n\n async doctor(): Promise<PlayLoaderDoctorReport> {\n const plays = this.#plays.size;\n return {\n ok: plays > 0,\n plays,\n root: this.#root,\n ...(plays === 0 ? { suggestion: \"Create a play with `pnpm play:new <name>`.\" } : {}),\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,11 +0,0 @@
1
- import { resolvePlayChain } from "../src";
2
-
3
- process.stdout.write(
4
- `${JSON.stringify(
5
- resolvePlayChain([
6
- { name: "pitch", dependsOn: ["brand", "demo"] },
7
- { name: "brand", dependsOn: [] },
8
- { name: "demo", dependsOn: ["brand"] },
9
- ]),
10
- )}\n`,
11
- );
@@ -1,14 +0,0 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { PlayLoader } from "../src";
5
-
6
- const root = await mkdtemp(join(tmpdir(), "play-loader-"));
7
- try {
8
- const loader = await PlayLoader.open(root);
9
- await loader.newPlay("demo_play");
10
- process.stdout.write(`${JSON.stringify(await loader.list())}\n`);
11
- process.stdout.write(`${JSON.stringify(await loader.test("demo_play"))}\n`);
12
- } finally {
13
- await rm(root, { recursive: true, force: true });
14
- }
@@ -1,18 +0,0 @@
1
- import { parsePlayMarkdown } from "../src";
2
-
3
- const play = parsePlayMarkdown(`---
4
- name: launch_page
5
- kind: play
6
- version: 1.0.0
7
- description: Build a launch page
8
- required_skills: [content_store.write]
9
- sub_agents: []
10
- depends_on_plays: []
11
- ---
12
-
13
- ## What this play does
14
-
15
- Create a page from a concise brief.
16
- `);
17
-
18
- process.stdout.write(`${JSON.stringify({ name: play.meta.name, skills: play.requiredSkills })}\n`);
package/src/cli.ts DELETED
@@ -1,56 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { PlayLoader, parsePlayMarkdown, readPlayDebug, resolvePlayChain } from "./index";
3
-
4
- const command = process.argv[2] ?? "list";
5
- const root = process.env.PLAY_LOADER_ROOT ?? ".nebutra";
6
-
7
- if (command === "parse") {
8
- const file = process.argv[3];
9
- if (!file) throw new Error("Missing play file path");
10
- const play = parsePlayMarkdown(await readFile(file, "utf8"));
11
- process.stdout.write(`${JSON.stringify({ capability: "play-loader", play }, null, 2)}\n`);
12
- } else if (command === "chain") {
13
- const raw = process.argv[3] ?? "[]";
14
- const nodes = JSON.parse(raw) as Array<{ name: string; dependsOn: string[] }>;
15
- process.stdout.write(
16
- `${JSON.stringify({ capability: "play-loader", order: resolvePlayChain(nodes) }, null, 2)}\n`,
17
- );
18
- } else if (command === "list") {
19
- const loader = await PlayLoader.open(root);
20
- process.stdout.write(
21
- `${JSON.stringify({ capability: "play-loader", plays: await loader.list() }, null, 2)}\n`,
22
- );
23
- } else if (command === "new") {
24
- const name = process.argv[3];
25
- if (!name) throw new Error("Missing play name");
26
- const loader = await PlayLoader.open(root);
27
- process.stdout.write(
28
- `${JSON.stringify({ capability: "play-loader", play: await loader.newPlay(name) }, null, 2)}\n`,
29
- );
30
- } else if (command === "test") {
31
- const name = process.argv[3];
32
- if (!name) throw new Error("Missing play name");
33
- const loader = await PlayLoader.open(root);
34
- process.stdout.write(
35
- `${JSON.stringify({ capability: "play-loader", report: await loader.test(name) }, null, 2)}\n`,
36
- );
37
- } else if (command === "install") {
38
- const source = process.argv[3];
39
- if (!source) throw new Error("Missing play source");
40
- const loader = await PlayLoader.open(root);
41
- process.stdout.write(
42
- `${JSON.stringify({ capability: "play-loader", play: await loader.install(source) }, null, 2)}\n`,
43
- );
44
- } else if (command === "doctor") {
45
- const loader = await PlayLoader.open(root);
46
- process.stdout.write(
47
- `${JSON.stringify({ capability: "play-loader", ...(await loader.doctor()) }, null, 2)}\n`,
48
- );
49
- } else if (command === "debug") {
50
- process.stdout.write(
51
- `${JSON.stringify({ capability: "play-loader", entries: await readPlayDebug() }, null, 2)}\n`,
52
- );
53
- } else {
54
- process.stderr.write(`Unknown play-loader command: ${command}\n`);
55
- process.exitCode = 1;
56
- }
package/src/index.test.ts DELETED
@@ -1,108 +0,0 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { PlayLoader, parsePlayMarkdown, resolvePlayChain } from "./index";
6
-
7
- let root: string | undefined;
8
-
9
- afterEach(async () => {
10
- if (root) await rm(root, { recursive: true, force: true });
11
- root = undefined;
12
- });
13
-
14
- const playMarkdown = `---
15
- name: launch_page
16
- kind: play
17
- version: 1.0.0
18
- description: Build a launch page
19
- inputs:
20
- idea: { type: string }
21
- outputs:
22
- url: { type: string }
23
- budget:
24
- duration_s: 60
25
- required_skills:
26
- - content_store.write
27
- - tool_protocol.call
28
- sub_agents:
29
- - role: researcher
30
- allowed_skills: [content_store.search]
31
- depends_on_plays:
32
- - play: brand_profile
33
- outputs: [BRAND.md]
34
- ---
35
-
36
- ## What this play does
37
-
38
- Create a launch page from a compact brief.
39
- `;
40
-
41
- describe("play-loader", () => {
42
- it("parses play metadata from SKILL.md without inventing a second file format", () => {
43
- const play = parsePlayMarkdown(playMarkdown);
44
-
45
- expect(play.meta).toMatchObject({
46
- name: "launch_page",
47
- kind: "play",
48
- version: "1.0.0",
49
- description: "Build a launch page",
50
- });
51
- expect(play.requiredSkills).toEqual(["content_store.write", "tool_protocol.call"]);
52
- expect(play.subAgents).toEqual([
53
- { role: "researcher", allowedSkills: ["content_store.search"] },
54
- ]);
55
- expect(play.dependsOnPlays).toEqual([{ play: "brand_profile", outputs: ["BRAND.md"] }]);
56
- expect(play.body).toContain("Create a launch page");
57
- });
58
-
59
- it("keeps raw play frontmatter on the shared SKILL parser contract", () => {
60
- const play = parsePlayMarkdown(playMarkdown);
61
-
62
- expect(play.frontmatter).toMatchObject({
63
- kind: "play",
64
- required_skills: ["content_store.write", "tool_protocol.call"],
65
- });
66
- });
67
-
68
- it("rejects non-play SKILL.md documents with a fix suggestion", () => {
69
- const markdown = playMarkdown.replace("kind: play", "kind: skill");
70
- expect(() => parsePlayMarkdown(markdown)).toThrow(/kind: play/i);
71
- });
72
-
73
- it("orders play dependencies and rejects cycles", () => {
74
- const chain = resolvePlayChain([
75
- { name: "pitch", dependsOn: ["brand", "demo"] },
76
- { name: "brand", dependsOn: [] },
77
- { name: "demo", dependsOn: ["brand"] },
78
- ]);
79
- expect(chain).toEqual(["brand", "demo", "pitch"]);
80
-
81
- expect(() =>
82
- resolvePlayChain([
83
- { name: "a", dependsOn: ["b"] },
84
- { name: "b", dependsOn: ["a"] },
85
- ]),
86
- ).toThrow(/cycle/i);
87
- });
88
-
89
- it("lists and tests local plays from a portable root", async () => {
90
- root = await mkdtemp(join(tmpdir(), "play-loader-"));
91
- const loader = await PlayLoader.open(root);
92
- await loader.writePlay("launch_page", playMarkdown);
93
-
94
- await expect(loader.list()).resolves.toEqual([
95
- expect.objectContaining({ name: "launch_page", kind: "play" }),
96
- ]);
97
- await expect(loader.test("launch_page")).resolves.toMatchObject({
98
- name: "launch_page",
99
- ok: true,
100
- });
101
-
102
- const source = join(root, "external.md");
103
- await writeFile(source, playMarkdown.replaceAll("launch_page", "launch_page_copy"), "utf8");
104
- await expect(loader.install(`file:${source}`)).resolves.toMatchObject({
105
- meta: { name: "launch_page_copy" },
106
- });
107
- });
108
- });