| 1 | // Packaging tests: the plugin bundle must satisfy the Codewhale Engine's |
| 2 | // Agent Plugins v1 contract (plugin.json + sibling mcp.json), because the |
| 3 | // Engine — not this repo — installs it. |
| 4 | import { test } from "node:test"; |
| 5 | import assert from "node:assert/strict"; |
| 6 | import fs from "node:fs"; |
| 7 | import path from "node:path"; |
| 8 | import url from "node:url"; |
| 9 | |
| 10 | const ROOT = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); |
| 11 | const read = (name) => JSON.parse(fs.readFileSync(path.join(ROOT, name), "utf8")); |
| 12 | |
| 13 | test("plugin.json is an Agent Plugins v1 manifest with the Codewhale extension", () => { |
| 14 | const manifest = read("plugin.json"); |
| 15 | assert.equal(manifest.$schema, "https://agent-plugins.org/schemas/plugin.json"); |
| 16 | assert.equal(manifest.name, "computer-use"); |
| 17 | assert.match(manifest.version, /^\d+\.\d+\.\d+$/); |
| 18 | |
| 19 | const ext = manifest.extensions["net.codewhale"]; |
| 20 | assert.deepEqual(ext.commands, { path: "commands" }); |
| 21 | assert.deepEqual(ext.skills, { path: "skills" }); |
| 22 | assert.deepEqual(ext.when.binaries, ["node"]); |
| 23 | // HarmonyOS is a target device, never a host that runs the server. |
| 24 | assert.deepEqual(ext.when.os, ["macos"]); |
| 25 | |
| 26 | const rootKeys = ["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]; |
| 27 | for (const key of Object.keys(manifest)) assert.ok(rootKeys.includes(key), `unknown root key ${key}`); |
| 28 | }); |
| 29 | |
| 30 | test("mcp.json declares one stdio server on a contained relative path", () => { |
| 31 | const servers = read("mcp.json").mcpServers; |
| 32 | assert.deepEqual(Object.keys(servers), ["computer"]); |
| 33 | const server = servers.computer; |
| 34 | assert.equal(server.type, "stdio"); |
| 35 | assert.equal(server.command, "node"); |
| 36 | assert.equal(server.cwd, "."); |
| 37 | for (const arg of server.args) { |
| 38 | assert.ok(!path.isAbsolute(arg), `${arg} must be relative`); |
| 39 | assert.ok(!arg.split("/").includes(".."), `${arg} must not escape the plugin root`); |
| 40 | } |
| 41 | assert.ok(fs.existsSync(path.join(ROOT, server.args[0])), "server entrypoint exists"); |
| 42 | }); |
| 43 | |
| 44 | test("declared components exist and every skill is named for its directory", () => { |
| 45 | assert.ok(fs.existsSync(path.join(ROOT, "commands/computer.md"))); |
| 46 | const skills = fs.readdirSync(path.join(ROOT, "skills")); |
| 47 | assert.deepEqual(skills.sort(), ["computer-use", "recording"]); |
| 48 | for (const skill of skills) { |
| 49 | const text = fs.readFileSync(path.join(ROOT, "skills", skill, "SKILL.md"), "utf8").replace(/\r\n/g, "\n"); |
| 50 | assert.match(text, new RegExp(`^---\\nname: ${skill}\\ndescription: .+`), `${skill} frontmatter`); |
| 51 | } |
| 52 | }); |
| 53 |