@nebutra/play-loader 0.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/.nebutra/debug/play-loader.jsonl +7 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE +676 -0
- package/README.md +20 -0
- package/dist/chunk-7JPGPQKB.js +233 -0
- package/dist/chunk-7JPGPQKB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +71 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/examples/chain-plays.ts +11 -0
- package/examples/local-registry.ts +14 -0
- package/examples/parse-play.ts +18 -0
- package/package.json +55 -0
- package/src/cli.ts +56 -0
- package/src/index.test.ts +99 -0
- package/src/index.ts +310 -0
- package/tsconfig.json +12 -0
- package/tsup.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @nebutra/play-loader
|
|
2
|
+
|
|
3
|
+
Status: WIP — Not yet integrated into any production app.
|
|
4
|
+
|
|
5
|
+
`@nebutra/play-loader` owns declarative Play loading, SKILL.md-compatible play
|
|
6
|
+
schema parsing, dependency planning, version checks, and the seam that hands a
|
|
7
|
+
validated play to the runtime.
|
|
8
|
+
|
|
9
|
+
It does not own tool implementation, model/provider execution, or durable
|
|
10
|
+
Thread/Turn/Item storage. Those are injected through the runtime, registry, and
|
|
11
|
+
execution capability packages.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm play:list
|
|
17
|
+
pnpm play:new <name>
|
|
18
|
+
pnpm play:test <name>
|
|
19
|
+
pnpm play:debug <thread_id>
|
|
20
|
+
```
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { dirname, join, relative } from "path";
|
|
4
|
+
import { CapabilityError } from "@nebutra/errors";
|
|
5
|
+
import { parseSkillFrontmatter, parseSkillMarkdown } from "@nebutra/tool-registry";
|
|
6
|
+
async function walk(dir) {
|
|
7
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
8
|
+
const files = [];
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const full = join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) files.push(...await walk(full));
|
|
12
|
+
else files.push(full);
|
|
13
|
+
}
|
|
14
|
+
return files;
|
|
15
|
+
}
|
|
16
|
+
function asString(value) {
|
|
17
|
+
return typeof value === "string" ? value : void 0;
|
|
18
|
+
}
|
|
19
|
+
function asStringArray(value) {
|
|
20
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
21
|
+
}
|
|
22
|
+
function asRecord(value) {
|
|
23
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
24
|
+
}
|
|
25
|
+
function normalizePlayName(name) {
|
|
26
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
27
|
+
}
|
|
28
|
+
function playDebugPath() {
|
|
29
|
+
return join(process.cwd(), ".nebutra", "debug", "play-loader.jsonl");
|
|
30
|
+
}
|
|
31
|
+
async function appendPlayDebug(entry) {
|
|
32
|
+
const path = playDebugPath();
|
|
33
|
+
await mkdir(dirname(path), { recursive: true });
|
|
34
|
+
await writeFile(path, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
35
|
+
`, {
|
|
36
|
+
flag: "a"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function readPlayDebug(limit = 10) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = await readFile(playDebugPath(), "utf8");
|
|
42
|
+
return raw.trim().split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseSubagents(value) {
|
|
48
|
+
if (!Array.isArray(value)) return [];
|
|
49
|
+
return value.flatMap((item) => {
|
|
50
|
+
const record = asRecord(item);
|
|
51
|
+
const role = asString(record?.role);
|
|
52
|
+
if (!record || !role) return [];
|
|
53
|
+
return [{ role, allowedSkills: asStringArray(record.allowed_skills) }];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function parsePlayDependencies(value) {
|
|
57
|
+
if (!Array.isArray(value)) return [];
|
|
58
|
+
return value.flatMap((item) => {
|
|
59
|
+
if (typeof item === "string") return [{ play: item, outputs: [] }];
|
|
60
|
+
const record = asRecord(item);
|
|
61
|
+
const play = asString(record?.play);
|
|
62
|
+
if (!record || !play) return [];
|
|
63
|
+
return [{ play, outputs: asStringArray(record.outputs) }];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function parsePlayMarkdown(markdown) {
|
|
67
|
+
const loaded = parseSkillMarkdown(markdown);
|
|
68
|
+
const { frontmatter } = parseSkillFrontmatter(markdown);
|
|
69
|
+
if (frontmatter.kind !== "play") {
|
|
70
|
+
throw new CapabilityError("play-loader", "Play SKILL.md must set kind: play", {
|
|
71
|
+
suggestion: "Set `kind: play` in frontmatter or keep this document in tool-registry only.",
|
|
72
|
+
statusCode: 400,
|
|
73
|
+
metadata: { name: loaded.meta.name, kind: frontmatter.kind }
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
meta: { ...loaded.meta, kind: "play" },
|
|
78
|
+
body: loaded.body,
|
|
79
|
+
requiredSkills: asStringArray(frontmatter.required_skills),
|
|
80
|
+
subAgents: parseSubagents(frontmatter.sub_agents),
|
|
81
|
+
dependsOnPlays: parsePlayDependencies(frontmatter.depends_on_plays)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function resolvePlayChain(nodes) {
|
|
85
|
+
const byName = new Map(nodes.map((node) => [node.name, node]));
|
|
86
|
+
const visited = /* @__PURE__ */ new Set();
|
|
87
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
88
|
+
const ordered = [];
|
|
89
|
+
const visit = (name) => {
|
|
90
|
+
if (visited.has(name)) return;
|
|
91
|
+
if (visiting.has(name)) {
|
|
92
|
+
throw new CapabilityError("play-loader", "Play dependency cycle detected", {
|
|
93
|
+
suggestion: "Remove the cycle or merge mutually dependent plays into one declaration.",
|
|
94
|
+
statusCode: 400,
|
|
95
|
+
metadata: { name }
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
visiting.add(name);
|
|
99
|
+
const node = byName.get(name);
|
|
100
|
+
for (const dependency of node?.dependsOn ?? []) visit(dependency);
|
|
101
|
+
visiting.delete(name);
|
|
102
|
+
visited.add(name);
|
|
103
|
+
ordered.push(name);
|
|
104
|
+
};
|
|
105
|
+
for (const node of nodes) visit(node.name);
|
|
106
|
+
return ordered;
|
|
107
|
+
}
|
|
108
|
+
var PlayLoader = class _PlayLoader {
|
|
109
|
+
#root;
|
|
110
|
+
#plays = /* @__PURE__ */ new Map();
|
|
111
|
+
constructor(root) {
|
|
112
|
+
this.#root = root;
|
|
113
|
+
}
|
|
114
|
+
static async open(root) {
|
|
115
|
+
const loader = new _PlayLoader(root);
|
|
116
|
+
await mkdir(loader.playsRoot(), { recursive: true });
|
|
117
|
+
await loader.reload();
|
|
118
|
+
return loader;
|
|
119
|
+
}
|
|
120
|
+
playsRoot() {
|
|
121
|
+
return join(this.#root, "plays");
|
|
122
|
+
}
|
|
123
|
+
async writePlay(name, markdown) {
|
|
124
|
+
const normalized = normalizePlayName(name);
|
|
125
|
+
const parsed = parsePlayMarkdown(markdown);
|
|
126
|
+
if (parsed.meta.name !== normalized) {
|
|
127
|
+
throw new CapabilityError("play-loader", "Play name does not match path", {
|
|
128
|
+
suggestion: "Use the same lowercase codename in the path and SKILL.md frontmatter.",
|
|
129
|
+
metadata: { pathName: normalized, playName: parsed.meta.name },
|
|
130
|
+
statusCode: 400
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const path = join(this.playsRoot(), normalized, "SKILL.md");
|
|
134
|
+
await mkdir(dirname(path), { recursive: true });
|
|
135
|
+
await writeFile(path, markdown, "utf8");
|
|
136
|
+
await this.reload();
|
|
137
|
+
await appendPlayDebug({ type: "write", play: normalized });
|
|
138
|
+
}
|
|
139
|
+
async newPlay(name) {
|
|
140
|
+
const normalized = normalizePlayName(name);
|
|
141
|
+
const markdown = [
|
|
142
|
+
"---",
|
|
143
|
+
`name: ${normalized}`,
|
|
144
|
+
"kind: play",
|
|
145
|
+
"version: 1.0.0",
|
|
146
|
+
`description: ${normalized.replaceAll("_", " ")} play`,
|
|
147
|
+
"required_skills: []",
|
|
148
|
+
"sub_agents: []",
|
|
149
|
+
"depends_on_plays: []",
|
|
150
|
+
"---",
|
|
151
|
+
"",
|
|
152
|
+
"## What this play does",
|
|
153
|
+
"",
|
|
154
|
+
"Describe the user story and expected output."
|
|
155
|
+
].join("\n");
|
|
156
|
+
await this.writePlay(normalized, markdown);
|
|
157
|
+
return this.load(normalized);
|
|
158
|
+
}
|
|
159
|
+
async reload() {
|
|
160
|
+
this.#plays.clear();
|
|
161
|
+
for (const file of await walk(this.playsRoot())) {
|
|
162
|
+
if (!file.endsWith("SKILL.md")) continue;
|
|
163
|
+
const raw = await readFile(file, "utf8");
|
|
164
|
+
const play = parsePlayMarkdown(raw);
|
|
165
|
+
this.#plays.set(play.meta.name, { meta: play.meta, path: relative(this.#root, file) });
|
|
166
|
+
}
|
|
167
|
+
await appendPlayDebug({ type: "reload", plays: this.#plays.size });
|
|
168
|
+
}
|
|
169
|
+
async list() {
|
|
170
|
+
return [...this.#plays.values()].map((entry) => entry.meta).sort((left, right) => left.name.localeCompare(right.name));
|
|
171
|
+
}
|
|
172
|
+
async load(name) {
|
|
173
|
+
const play = this.#plays.get(normalizePlayName(name));
|
|
174
|
+
if (!play) {
|
|
175
|
+
throw new CapabilityError("play-loader", "Play not found", {
|
|
176
|
+
suggestion: "Run `pnpm play:list` or create it with `pnpm play:new <name>`.",
|
|
177
|
+
metadata: { name },
|
|
178
|
+
statusCode: 404
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const loaded = parsePlayMarkdown(await readFile(join(this.#root, play.path), "utf8"));
|
|
182
|
+
await appendPlayDebug({ type: "load", play: loaded.meta.name });
|
|
183
|
+
return loaded;
|
|
184
|
+
}
|
|
185
|
+
async install(source) {
|
|
186
|
+
if (!source.startsWith("file:")) {
|
|
187
|
+
throw new CapabilityError("play-loader", "Only file installs are enabled locally", {
|
|
188
|
+
suggestion: "Download remote plays through a reviewed distribution workflow, then install with file:<path>.",
|
|
189
|
+
metadata: { source },
|
|
190
|
+
statusCode: 400
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const raw = await readFile(source.slice("file:".length), "utf8");
|
|
194
|
+
const play = parsePlayMarkdown(raw);
|
|
195
|
+
await this.writePlay(play.meta.name, raw);
|
|
196
|
+
return this.load(play.meta.name);
|
|
197
|
+
}
|
|
198
|
+
async test(name) {
|
|
199
|
+
try {
|
|
200
|
+
const play = await this.load(name);
|
|
201
|
+
return {
|
|
202
|
+
name: play.meta.name,
|
|
203
|
+
ok: play.body.length > 0,
|
|
204
|
+
checks: ["frontmatter", "kind:play", "body", "dependency-dag"],
|
|
205
|
+
...!play.body ? { suggestion: "Add a markdown body after the frontmatter." } : {}
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return {
|
|
209
|
+
name,
|
|
210
|
+
ok: false,
|
|
211
|
+
checks: [],
|
|
212
|
+
suggestion: error instanceof CapabilityError ? "Create the play with `pnpm play:new <name>` or fix its SKILL.md metadata." : "Run `pnpm play:list` and verify the play directory exists."
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async doctor() {
|
|
217
|
+
const plays = this.#plays.size;
|
|
218
|
+
return {
|
|
219
|
+
ok: plays > 0,
|
|
220
|
+
plays,
|
|
221
|
+
root: this.#root,
|
|
222
|
+
...plays === 0 ? { suggestion: "Create a play with `pnpm play:new <name>`." } : {}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
readPlayDebug,
|
|
229
|
+
parsePlayMarkdown,
|
|
230
|
+
resolvePlayChain,
|
|
231
|
+
PlayLoader
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=chunk-7JPGPQKB.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 { CapabilityError } from \"@nebutra/errors\";\nimport { parseSkillFrontmatter, 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 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\nfunction playDebugPath(): string {\n return join(process.cwd(), \".nebutra\", \"debug\", \"play-loader.jsonl\");\n}\n\nasync function appendPlayDebug(entry: Record<string, unknown>): Promise<void> {\n const path = playDebugPath();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\\n`, {\n flag: \"a\",\n });\n}\n\nexport async function readPlayDebug(limit = 10): Promise<unknown[]> {\n try {\n const raw = await readFile(playDebugPath(), \"utf8\");\n return raw\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as unknown);\n } catch {\n return [];\n }\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 } = parseSkillFrontmatter(markdown);\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 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.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlayLoader,
|
|
3
|
+
parsePlayMarkdown,
|
|
4
|
+
readPlayDebug,
|
|
5
|
+
resolvePlayChain
|
|
6
|
+
} from "./chunk-7JPGPQKB.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
var command = process.argv[2] ?? "list";
|
|
11
|
+
var root = process.env.PLAY_LOADER_ROOT ?? ".nebutra";
|
|
12
|
+
if (command === "parse") {
|
|
13
|
+
const file = process.argv[3];
|
|
14
|
+
if (!file) throw new Error("Missing play file path");
|
|
15
|
+
const play = parsePlayMarkdown(await readFile(file, "utf8"));
|
|
16
|
+
process.stdout.write(`${JSON.stringify({ capability: "play-loader", play }, null, 2)}
|
|
17
|
+
`);
|
|
18
|
+
} else if (command === "chain") {
|
|
19
|
+
const raw = process.argv[3] ?? "[]";
|
|
20
|
+
const nodes = JSON.parse(raw);
|
|
21
|
+
process.stdout.write(
|
|
22
|
+
`${JSON.stringify({ capability: "play-loader", order: resolvePlayChain(nodes) }, null, 2)}
|
|
23
|
+
`
|
|
24
|
+
);
|
|
25
|
+
} else if (command === "list") {
|
|
26
|
+
const loader = await PlayLoader.open(root);
|
|
27
|
+
process.stdout.write(
|
|
28
|
+
`${JSON.stringify({ capability: "play-loader", plays: await loader.list() }, null, 2)}
|
|
29
|
+
`
|
|
30
|
+
);
|
|
31
|
+
} else if (command === "new") {
|
|
32
|
+
const name = process.argv[3];
|
|
33
|
+
if (!name) throw new Error("Missing play name");
|
|
34
|
+
const loader = await PlayLoader.open(root);
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
`${JSON.stringify({ capability: "play-loader", play: await loader.newPlay(name) }, null, 2)}
|
|
37
|
+
`
|
|
38
|
+
);
|
|
39
|
+
} else if (command === "test") {
|
|
40
|
+
const name = process.argv[3];
|
|
41
|
+
if (!name) throw new Error("Missing play name");
|
|
42
|
+
const loader = await PlayLoader.open(root);
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
`${JSON.stringify({ capability: "play-loader", report: await loader.test(name) }, null, 2)}
|
|
45
|
+
`
|
|
46
|
+
);
|
|
47
|
+
} else if (command === "install") {
|
|
48
|
+
const source = process.argv[3];
|
|
49
|
+
if (!source) throw new Error("Missing play source");
|
|
50
|
+
const loader = await PlayLoader.open(root);
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
`${JSON.stringify({ capability: "play-loader", play: await loader.install(source) }, null, 2)}
|
|
53
|
+
`
|
|
54
|
+
);
|
|
55
|
+
} else if (command === "doctor") {
|
|
56
|
+
const loader = await PlayLoader.open(root);
|
|
57
|
+
process.stdout.write(
|
|
58
|
+
`${JSON.stringify({ capability: "play-loader", ...await loader.doctor() }, null, 2)}
|
|
59
|
+
`
|
|
60
|
+
);
|
|
61
|
+
} else if (command === "debug") {
|
|
62
|
+
process.stdout.write(
|
|
63
|
+
`${JSON.stringify({ capability: "play-loader", entries: await readPlayDebug() }, null, 2)}
|
|
64
|
+
`
|
|
65
|
+
);
|
|
66
|
+
} else {
|
|
67
|
+
process.stderr.write(`Unknown play-loader command: ${command}
|
|
68
|
+
`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { PlayLoader, parsePlayMarkdown, readPlayDebug, resolvePlayChain } from \"./index\";\n\nconst command = process.argv[2] ?? \"list\";\nconst root = process.env.PLAY_LOADER_ROOT ?? \".nebutra\";\n\nif (command === \"parse\") {\n const file = process.argv[3];\n if (!file) throw new Error(\"Missing play file path\");\n const play = parsePlayMarkdown(await readFile(file, \"utf8\"));\n process.stdout.write(`${JSON.stringify({ capability: \"play-loader\", play }, null, 2)}\\n`);\n} else if (command === \"chain\") {\n const raw = process.argv[3] ?? \"[]\";\n const nodes = JSON.parse(raw) as Array<{ name: string; dependsOn: string[] }>;\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", order: resolvePlayChain(nodes) }, null, 2)}\\n`,\n );\n} else if (command === \"list\") {\n const loader = await PlayLoader.open(root);\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", plays: await loader.list() }, null, 2)}\\n`,\n );\n} else if (command === \"new\") {\n const name = process.argv[3];\n if (!name) throw new Error(\"Missing play name\");\n const loader = await PlayLoader.open(root);\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", play: await loader.newPlay(name) }, null, 2)}\\n`,\n );\n} else if (command === \"test\") {\n const name = process.argv[3];\n if (!name) throw new Error(\"Missing play name\");\n const loader = await PlayLoader.open(root);\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", report: await loader.test(name) }, null, 2)}\\n`,\n );\n} else if (command === \"install\") {\n const source = process.argv[3];\n if (!source) throw new Error(\"Missing play source\");\n const loader = await PlayLoader.open(root);\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", play: await loader.install(source) }, null, 2)}\\n`,\n );\n} else if (command === \"doctor\") {\n const loader = await PlayLoader.open(root);\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", ...(await loader.doctor()) }, null, 2)}\\n`,\n );\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-loader\", entries: await readPlayDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown play-loader command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,gBAAgB;AAGzB,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,oBAAoB;AAE7C,IAAI,YAAY,SAAS;AACvB,QAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,wBAAwB;AACnD,QAAM,OAAO,kBAAkB,MAAM,SAAS,MAAM,MAAM,CAAC;AAC3D,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,KAAK,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC1F,WAAW,YAAY,SAAS;AAC9B,QAAM,MAAM,QAAQ,KAAK,CAAC,KAAK;AAC/B,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,OAAO,iBAAiB,KAAK,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3F;AACF,WAAW,YAAY,QAAQ;AAC7B,QAAM,SAAS,MAAM,WAAW,KAAK,IAAI;AACzC,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,OAAO,MAAM,OAAO,KAAK,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACvF;AACF,WAAW,YAAY,OAAO;AAC5B,QAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,mBAAmB;AAC9C,QAAM,SAAS,MAAM,WAAW,KAAK,IAAI;AACzC,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,MAAM,MAAM,OAAO,QAAQ,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC7F;AACF,WAAW,YAAY,QAAQ;AAC7B,QAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,mBAAmB;AAC9C,QAAM,SAAS,MAAM,WAAW,KAAK,IAAI;AACzC,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,QAAQ,MAAM,OAAO,KAAK,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC5F;AACF,WAAW,YAAY,WAAW;AAChC,QAAM,SAAS,QAAQ,KAAK,CAAC;AAC7B,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,qBAAqB;AAClD,QAAM,SAAS,MAAM,WAAW,KAAK,IAAI;AACzC,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,MAAM,MAAM,OAAO,QAAQ,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC/F;AACF,WAAW,YAAY,UAAU;AAC/B,QAAM,SAAS,MAAM,WAAW,KAAK,IAAI;AACzC,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,GAAI,MAAM,OAAO,OAAO,EAAG,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACvF;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,SAAS,MAAM,cAAc,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3F;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,gCAAgC,OAAO;AAAA,CAAI;AAChE,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SkillMeta } from '@nebutra/tool-registry';
|
|
2
|
+
|
|
3
|
+
interface PlayDependency {
|
|
4
|
+
readonly play: string;
|
|
5
|
+
readonly outputs: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
interface PlaySubagent {
|
|
8
|
+
readonly role: string;
|
|
9
|
+
readonly allowedSkills: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
interface PlayMeta extends SkillMeta {
|
|
12
|
+
readonly kind: "play";
|
|
13
|
+
}
|
|
14
|
+
interface LoadedPlay {
|
|
15
|
+
readonly meta: PlayMeta;
|
|
16
|
+
readonly body: string;
|
|
17
|
+
readonly requiredSkills: readonly string[];
|
|
18
|
+
readonly subAgents: readonly PlaySubagent[];
|
|
19
|
+
readonly dependsOnPlays: readonly PlayDependency[];
|
|
20
|
+
}
|
|
21
|
+
interface PlayNode {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly dependsOn: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
interface PlayTestReport {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly ok: boolean;
|
|
28
|
+
readonly checks: readonly string[];
|
|
29
|
+
readonly suggestion?: string;
|
|
30
|
+
}
|
|
31
|
+
interface PlayLoaderDoctorReport {
|
|
32
|
+
readonly ok: boolean;
|
|
33
|
+
readonly plays: number;
|
|
34
|
+
readonly root: string;
|
|
35
|
+
readonly suggestion?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function readPlayDebug(limit?: number): Promise<unknown[]>;
|
|
38
|
+
declare function parsePlayMarkdown(markdown: string): LoadedPlay;
|
|
39
|
+
declare function resolvePlayChain(nodes: readonly PlayNode[]): readonly string[];
|
|
40
|
+
declare class PlayLoader {
|
|
41
|
+
#private;
|
|
42
|
+
private constructor();
|
|
43
|
+
static open(root: string): Promise<PlayLoader>;
|
|
44
|
+
playsRoot(): string;
|
|
45
|
+
writePlay(name: string, markdown: string): Promise<void>;
|
|
46
|
+
newPlay(name: string): Promise<LoadedPlay>;
|
|
47
|
+
reload(): Promise<void>;
|
|
48
|
+
list(): Promise<PlayMeta[]>;
|
|
49
|
+
load(name: string): Promise<LoadedPlay>;
|
|
50
|
+
install(source: string): Promise<LoadedPlay>;
|
|
51
|
+
test(name: string): Promise<PlayTestReport>;
|
|
52
|
+
doctor(): Promise<PlayLoaderDoctorReport>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { type LoadedPlay, type PlayDependency, PlayLoader, type PlayLoaderDoctorReport, type PlayMeta, type PlayNode, type PlaySubagent, type PlayTestReport, parsePlayMarkdown, readPlayDebug, resolvePlayChain };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebutra/play-loader",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Declarative SKILL.md play loader and dependency planner",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"nebutra": {
|
|
9
|
+
"status": "wip",
|
|
10
|
+
"productionReady": false,
|
|
11
|
+
"requires": [
|
|
12
|
+
"@nebutra/tool-registry for SKILL.md parsing",
|
|
13
|
+
"@nebutra/agent-runtime for execution"
|
|
14
|
+
],
|
|
15
|
+
"gaps": [
|
|
16
|
+
"Runtime runner delegates are interface-only",
|
|
17
|
+
"Remote install and migration APIs are not production-backed"
|
|
18
|
+
],
|
|
19
|
+
"featureId": "play-loader",
|
|
20
|
+
"category": "ai",
|
|
21
|
+
"summary": "SKILL.md play parser, dependency-chain planner, and agent-runtime runner seam"
|
|
22
|
+
},
|
|
23
|
+
"main": "./src/index.ts",
|
|
24
|
+
"types": "./src/index.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nebutra/errors": "0.1.0",
|
|
30
|
+
"@nebutra/tool-registry": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.19.15",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vitest": "^4.1.4"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/play-loader#readme",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
|
|
42
|
+
"directory": "packages/ai/play-loader"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"typecheck": "tsc --noEmit"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
}
|