@kolisachint/hoocode-agent 0.5.24 → 0.5.25
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/CHANGELOG.md +70 -0
- package/dist/core/brand.d.ts +1 -0
- package/dist/core/brand.d.ts.map +1 -1
- package/dist/core/brand.js +1 -0
- package/dist/core/brand.js.map +1 -1
- package/dist/core/canvas/discovery.d.ts +11 -3
- package/dist/core/canvas/discovery.d.ts.map +1 -1
- package/dist/core/canvas/discovery.js +18 -3
- package/dist/core/canvas/discovery.js.map +1 -1
- package/dist/core/canvas/plugin-canvases.d.ts +30 -0
- package/dist/core/canvas/plugin-canvases.d.ts.map +1 -0
- package/dist/core/canvas/plugin-canvases.js +51 -0
- package/dist/core/canvas/plugin-canvases.js.map +1 -0
- package/dist/core/canvas/session.d.ts +7 -0
- package/dist/core/canvas/session.d.ts.map +1 -1
- package/dist/core/canvas/session.js +8 -1
- package/dist/core/canvas/session.js.map +1 -1
- package/dist/core/canvas/trust.d.ts +4 -5
- package/dist/core/canvas/trust.d.ts.map +1 -1
- package/dist/core/canvas/trust.js +23 -8
- package/dist/core/canvas/trust.js.map +1 -1
- package/dist/core/extensions/plugins/formats/copilot.d.ts +10 -0
- package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/copilot.js +36 -4
- package/dist/core/extensions/plugins/formats/copilot.js.map +1 -1
- package/dist/core/extensions/plugins/formats/jsonManifest.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/jsonManifest.js +15 -2
- package/dist/core/extensions/plugins/formats/jsonManifest.js.map +1 -1
- package/dist/core/extensions/plugins/formats/shared.d.ts +30 -2
- package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/shared.js +72 -0
- package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
- package/dist/core/extensions/plugins/install.d.ts +31 -0
- package/dist/core/extensions/plugins/install.d.ts.map +1 -1
- package/dist/core/extensions/plugins/install.js +102 -12
- package/dist/core/extensions/plugins/install.js.map +1 -1
- package/dist/core/extensions/plugins/listing.d.ts.map +1 -1
- package/dist/core/extensions/plugins/listing.js +1 -0
- package/dist/core/extensions/plugins/listing.js.map +1 -1
- package/dist/core/extensions/plugins/manifest.d.ts +18 -0
- package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
- package/dist/core/extensions/plugins/manifest.js.map +1 -1
- package/dist/core/extensions/plugins/packaging.d.ts.map +1 -1
- package/dist/core/extensions/plugins/packaging.js +1 -0
- package/dist/core/extensions/plugins/packaging.js.map +1 -1
- package/dist/extensions/core/canvas.d.ts.map +1 -1
- package/dist/extensions/core/canvas.js +1 -1
- package/dist/extensions/core/canvas.js.map +1 -1
- package/dist/extensions/core/marketplace.d.ts.map +1 -1
- package/dist/extensions/core/marketplace.js +28 -1
- package/dist/extensions/core/marketplace.js.map +1 -1
- package/dist/extensions/core/scaffold.d.ts +1 -1
- package/dist/extensions/core/scaffold.d.ts.map +1 -1
- package/dist/extensions/core/scaffold.js +185 -1
- package/dist/extensions/core/scaffold.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +11 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +40 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +11 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +20 -1
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* see what is on offer and decide to trust the workspace. Listing is not running.
|
|
27
27
|
*/
|
|
28
28
|
import * as path from "node:path";
|
|
29
|
-
import { getAgentDir } from "../../config.js";
|
|
29
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "../../config.js";
|
|
30
30
|
import { isRepositorySupplied, shouldWithholdRepositorySupplied } from "../extensions/plugins/trust.js";
|
|
31
31
|
/** Thrown when something tries to run a canvas the workspace has not earned. */
|
|
32
32
|
export class CanvasTrustError extends Error {
|
|
@@ -43,11 +43,10 @@ export class CanvasTrustError extends Error {
|
|
|
43
43
|
* Whether an extension sits in the working tree, and therefore arrived with the
|
|
44
44
|
* repository as far as anyone but its author can tell.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* question being asked; it only decides *whether* to ask about trust.
|
|
46
|
+
* Every project-scope home counts — see {@link canvasProjectScopeRoots}. None of
|
|
47
|
+
* them can distinguish "I put this here" from "this arrived in the clone", which
|
|
48
|
+
* is exactly why location is not the question being asked; it only decides
|
|
49
|
+
* *whether* to ask about trust.
|
|
51
50
|
*
|
|
52
51
|
* User scope (`~/.copilot/extensions/`) is not project-supplied: it is outside any
|
|
53
52
|
* repository and got there by a deliberate local act.
|
|
@@ -55,9 +54,25 @@ export class CanvasTrustError extends Error {
|
|
|
55
54
|
export function isProjectSuppliedCanvas(extension, cwd) {
|
|
56
55
|
return isRepositorySupplied(extension.dir, canvasProjectScopeRoots(cwd));
|
|
57
56
|
}
|
|
58
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* A canvas extension's project-scope homes — the locations that travel with a
|
|
59
|
+
* clone.
|
|
60
|
+
*
|
|
61
|
+
* The plugin homes belong here for the same reason `loader.ts` counts them for
|
|
62
|
+
* plugins: a plugin installed at project scope, or committed into the working
|
|
63
|
+
* tree, arrives in every collaborator's clone, and a canvas it ships is a
|
|
64
|
+
* process with a listening socket exactly like one dropped in `.github/`. The
|
|
65
|
+
* person who ran the install knows they chose it; nobody who clones the result
|
|
66
|
+
* does, and location cannot tell those two apart.
|
|
67
|
+
*/
|
|
59
68
|
function canvasProjectScopeRoots(cwd) {
|
|
60
|
-
return [
|
|
69
|
+
return [
|
|
70
|
+
path.join(cwd, ".agents", "extensions"),
|
|
71
|
+
path.join(cwd, ".github", "extensions"),
|
|
72
|
+
path.join(cwd, ".agents", "plugins"),
|
|
73
|
+
path.join(cwd, CONFIG_DIR_NAME, "plugins"),
|
|
74
|
+
path.join(cwd, ".claude", "skills"),
|
|
75
|
+
];
|
|
61
76
|
}
|
|
62
77
|
/**
|
|
63
78
|
* Whether an extension must not be forked: it came with the repository and this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trust.js","sourceRoot":"","sources":["../../../src/core/canvas/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"trust.js","sourceRoot":"","sources":["../../../src/core/canvas/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAGxG,gFAAgF;AAChF,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACjC,WAAW,CAAS;IAE7B,YAAY,WAAmB,EAAE,GAAW,EAAE;QAC7C,KAAK,CACJ,qBAAqB,WAAW,oCAAoC,GAAG,gCAAgC;YACtG,gFAAgF;YAChF,qDAAqD,CACtD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAAA,CAC/B;CACD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAoC,EAAE,GAAW,EAAW;IACnG,OAAO,oBAAoB,CAAC,SAAS,CAAC,GAAG,EAAE,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;AAAA,CACzE;AAED;;;;;;;;;;GAUG;AACH,SAAS,uBAAuB,CAAC,GAAW,EAAY;IACvD,OAAO;QACN,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,SAAS,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC;KACnC,CAAC;AAAA,CACF;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,SAAoC,EACpC,GAAW,EACX,QAAQ,GAAW,WAAW,EAAE,EACtB;IACV,OAAO,gCAAgC,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,uBAAuB,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,CACpG;AAcD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CACnC,UAAuC,EACvC,GAAW,EACX,QAAQ,GAAW,WAAW,EAAE,EACR;IACxB,MAAM,KAAK,GAA0B,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACpE,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACpC,IAAI,oBAAoB,CAAC,SAAS,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;YACpD,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["/**\n * Trust gating for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §5. The argument is already written\n * down in `core/extensions/plugins/trust.ts`, for the same reason:\n *\n * > Its skills and commands are text the model reads, which is no worse than\n * > reading the repository itself, but its **hooks and MCP servers are processes**\n * > that start on session load.\n *\n * A canvas extension is a process **that also opens a listening socket**, so it\n * belongs in that record on identical grounds and needs no new mechanism. The gate\n * itself is shared: `isRepositorySupplied` and `shouldWithholdRepositorySupplied`\n * live in `plugins/trust.ts` and are used by the plugin gate too, so this module\n * supplies only the roots that are specific to canvas extensions. The record lives\n * outside the repository, so repository content cannot forge it.\n *\n * One difference from plugins, and it matters. `shouldWithholdExecutables` can\n * withhold a plugin's hooks and MCP servers while still loading its skills,\n * because a plugin has passive capabilities worth having. **A canvas has none.**\n * Its declaration, its actions, and its UI all come from running its code. There\n * is nothing to partially allow, so an untrusted canvas is withheld whole.\n *\n * What stays available is discovery: `discovery.ts` only reads directory entries,\n * so an untrusted canvas can still be listed, named, and offered — the person can\n * see what is on offer and decide to trust the workspace. Listing is not running.\n */\n\nimport * as path from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../../config.js\";\nimport { isRepositorySupplied, shouldWithholdRepositorySupplied } from \"../extensions/plugins/trust.js\";\nimport type { DiscoveredCanvasExtension } from \"./discovery.js\";\n\n/** Thrown when something tries to run a canvas the workspace has not earned. */\nexport class CanvasTrustError extends Error {\n\treadonly extensionId: string;\n\n\tconstructor(extensionId: string, cwd: string) {\n\t\tsuper(\n\t\t\t`Canvas extension \"${extensionId}\" came with this repository and \"${cwd}\" is not a trusted workspace. ` +\n\t\t\t\t`Running it would start a process and open a listening socket on your machine. ` +\n\t\t\t\t`Trust the workspace first if that is what you want.`,\n\t\t);\n\t\tthis.name = \"CanvasTrustError\";\n\t\tthis.extensionId = extensionId;\n\t}\n}\n\n/**\n * Whether an extension sits in the working tree, and therefore arrived with the\n * repository as far as anyone but its author can tell.\n *\n * Every project-scope home counts — see {@link canvasProjectScopeRoots}. None of\n * them can distinguish \"I put this here\" from \"this arrived in the clone\", which\n * is exactly why location is not the question being asked; it only decides\n * *whether* to ask about trust.\n *\n * User scope (`~/.copilot/extensions/`) is not project-supplied: it is outside any\n * repository and got there by a deliberate local act.\n */\nexport function isProjectSuppliedCanvas(extension: DiscoveredCanvasExtension, cwd: string): boolean {\n\treturn isRepositorySupplied(extension.dir, canvasProjectScopeRoots(cwd));\n}\n\n/**\n * A canvas extension's project-scope homes — the locations that travel with a\n * clone.\n *\n * The plugin homes belong here for the same reason `loader.ts` counts them for\n * plugins: a plugin installed at project scope, or committed into the working\n * tree, arrives in every collaborator's clone, and a canvas it ships is a\n * process with a listening socket exactly like one dropped in `.github/`. The\n * person who ran the install knows they chose it; nobody who clones the result\n * does, and location cannot tell those two apart.\n */\nfunction canvasProjectScopeRoots(cwd: string): string[] {\n\treturn [\n\t\tpath.join(cwd, \".agents\", \"extensions\"),\n\t\tpath.join(cwd, \".github\", \"extensions\"),\n\t\tpath.join(cwd, \".agents\", \"plugins\"),\n\t\tpath.join(cwd, CONFIG_DIR_NAME, \"plugins\"),\n\t\tpath.join(cwd, \".claude\", \"skills\"),\n\t];\n}\n\n/**\n * Whether an extension must not be forked: it came with the repository and this\n * machine has not trusted the workspace.\n */\nexport function shouldWithholdCanvas(\n\textension: DiscoveredCanvasExtension,\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): boolean {\n\treturn shouldWithholdRepositorySupplied(extension.dir, cwd, canvasProjectScopeRoots(cwd), agentDir);\n}\n\n/** A withheld extension, with the reason, so a caller can explain rather than hide. */\nexport interface WithheldCanvasExtension {\n\textension: DiscoveredCanvasExtension;\n\treason: \"untrusted-workspace\";\n}\n\n/** Discovered extensions split into what may be forked and what may not. */\nexport interface GatedCanvasExtensions {\n\trunnable: DiscoveredCanvasExtension[];\n\twithheld: WithheldCanvasExtension[];\n}\n\n/**\n * Partition discovered extensions by trust.\n *\n * Callers should present `withheld` rather than dropping it: the point of the gate\n * is that the person can see a repository offers a canvas and choose, not that the\n * offer disappears.\n */\nexport function gateCanvasExtensions(\n\textensions: DiscoveredCanvasExtension[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): GatedCanvasExtensions {\n\tconst gated: GatedCanvasExtensions = { runnable: [], withheld: [] };\n\tfor (const extension of extensions) {\n\t\tif (shouldWithholdCanvas(extension, cwd, agentDir)) {\n\t\t\tgated.withheld.push({ extension, reason: \"untrusted-workspace\" });\n\t\t} else {\n\t\t\tgated.runnable.push(extension);\n\t\t}\n\t}\n\treturn gated;\n}\n"]}
|
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
* reader still accepts bare `.md` names)
|
|
25
25
|
* hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })
|
|
26
26
|
* MCP servers .mcp.json ({ mcpServers } | { servers })
|
|
27
|
+
* canvases extensions/<name>/extension.mjs (or the manifest's `extensions` path override;
|
|
28
|
+
* opened on demand by /canvas, never loaded)
|
|
29
|
+
*
|
|
30
|
+
* Plus the Agent Plugins vendor content namespace, which is how
|
|
31
|
+
* `github/awesome-copilot` publishes its whole catalog — portable skills stay at
|
|
32
|
+
* `skills/`, and Copilot-specific capabilities move under a reverse-DNS
|
|
33
|
+
* directory:
|
|
34
|
+
*
|
|
35
|
+
* subagents com.github.copilot/agents/<name>.md
|
|
36
|
+
* canvases com.github.copilot/extensions/<name>/extension.mjs
|
|
27
37
|
*
|
|
28
38
|
* Workspace-level (non-plugin) conventions, per the current GitHub Copilot
|
|
29
39
|
* customization docs (docs.github.com — agent skills, custom agents, prompt
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAqBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA2DhF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAgB3C;AAED,eAAO,MAAM,aAAa,EAAE,mBA8J3B,CAAC","sourcesContent":["/**\n * GitHub Copilot format.\n *\n * hoocode's preferred Copilot home is the `.github/plugin/` marker directory —\n * the convention used by the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) and the documented location\n * for GitHub-hosted marketplaces. The official Copilot CLI plugin reference\n * (docs.github.com copilot/reference/copilot-cli-reference/cli-plugin-reference,\n * \"File locations\"; verified 2026-07) accepts several locations for both files\n * (`.plugin/`, plugin root, `.github/plugin/`, `.claude-plugin/`), so this\n * adapter reads them all — `.github/plugin/` first, then root, then `.plugin/`,\n * then the legacy hoocode layout — and writes `.github/plugin/`.\n * (`.claude-plugin/` belongs to the Claude adapter, which wins precedence, so a\n * `.claude-plugin`-only directory correctly parses as a Claude plugin; Copilot\n * reads those natively anyway.) The capability tree matches the Claude layout:\n *\n * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)\n * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the\n * analog of a slash command; a manifest\n * `commands` path or `commands/` dir is read too)\n * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the\n * `.agent.md` suffix and a YAML-list `tools`\n * frontmatter are what Copilot recognizes; the\n * reader still accepts bare `.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\n *\n * Workspace-level (non-plugin) conventions, per the current GitHub Copilot\n * customization docs (docs.github.com — agent skills, custom agents, prompt\n * files; verified 2026-07):\n *\n * skills .github/skills/<name>/SKILL.md (agentskills.io open standard)\n * custom agents .github/agents/<name>.agent.md (frontmatter: name, description,\n * tools as a YAML list, model, ...)\n * prompt files .github/prompts/<name>.prompt.md (VS Code / Copilot prompt files)\n *\n * Earlier hoocode releases authored a different Copilot mapping\n * (`.github/copilot-plugin.json` + `.github/prompts/*.prompt.md` +\n * `.github/chatmodes/*.chatmode.md`); the reader still accepts that layout as a\n * legacy fallback so previously authored plugins keep loading. Copilot\n * conventions move quickly; this file is the single place that encodes them,\n * so tracking an upstream change never reaches beyond this adapter.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tdetectUnsupportedSurfaces,\n\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order, matching the Copilot CLI reference exactly:\n * `.plugin/` → root → `.github/plugin/` → (`.claude-plugin/`, which belongs to\n * the Claude adapter), with hoocode's legacy `.github/copilot-plugin.json` last.\n *\n * The order is the vendor's, not ours: a plugin carrying more than one manifest\n * must resolve to the same one under hoocode as under Copilot CLI, or the two\n * read different metadata from the same directory.\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(\".plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/**\n * Authored manifest location: root `plugin.json`, which is what the Copilot CLI\n * plugin-creating guide teaches and probe position #2. An earlier version emitted\n * `.github/plugin/plugin.json` while the comment here claimed root was canonical;\n * the comment was right. (`.github/plugin/` is still *read*, third in the probe\n * order above, since real marketplace-indexed plugins use it.)\n */\nconst emitManifestRelPath = \"plugin.json\";\n\n/**\n * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current\n * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands\n * to it (both the writer and this reader), so it is the authored location for\n * commands — not merely a legacy fallback.\n */\nconst PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\n// Legacy authored layout (read-only fallback).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\n// Documented alternates, not legacy: the reference lists both locations for each.\nconst ALT_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\nconst ALT_HOOKS_FILE = path.join(\"hooks\", \"hooks.json\");\n// hoocode's own older authored layout, still read so those plugins keep loading.\nconst LEGACY_HOOKS_FILE = path.join(MARKER_DIR, \"hooks\", \"hooks.json\");\n\n/** Read a hooks JSON file (either `{ hooks: {...} }` or a bare event map). */\nfunction readHooksFile(root: string, rel: string): NormalizedPlugin[\"hooks\"] {\n\tconst file = path.join(root, rel);\n\tif (!fs.existsSync(file)) return undefined;\n\tconst raw = readJson<{ hooks?: Record<string, unknown> } | Record<string, unknown>>(file);\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst config = (\"hooks\" in raw && raw.hooks ? raw.hooks : raw) as NormalizedPlugin[\"hooks\"];\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction manifestPathFor(root: string): string | undefined {\n\tfor (const rel of MANIFEST_REL_PATHS) {\n\t\tif (readJson(path.join(root, rel)) != null) return path.join(root, rel);\n\t}\n\treturn undefined;\n}\n\n/**\n * Every plugin-relative path this adapter reads, for the Tier 2 drift check\n * (§2.2). Assembled from the constants above rather than restated, so a path\n * added to the reader cannot be missing from the declaration.\n */\nexport function copilotReadPaths(): string[] {\n\treturn [\n\t\t...MANIFEST_REL_PATHS,\n\t\tPROMPTS_DIR,\n\t\tLEGACY_CHATMODES_DIR,\n\t\tALT_MCP_FILE,\n\t\tALT_HOOKS_FILE,\n\t\tLEGACY_HOOKS_FILE,\n\t\t\"skills\",\n\t\t\"agents\",\n\t\t// The `<name>.agent.md` subagent convention, which the reference writes as\n\t\t// a suffix rather than a path.\n\t\t\".agent.md\",\n\t\t\"hooks.json\",\n\t\t\".mcp.json\",\n\t].map((p) => p.replace(/\\\\/g, \"/\"));\n}\n\nexport const copilotFormat: PluginFormatAdapter = {\n\tid: \"copilot\",\n\tplatform: \"github\",\n\tprecedence: 2,\n\tlabel: \"GitHub Copilot (.github)\",\n\t// .github/plugin/ first (hoocode's preferred Copilot home and the documented\n\t// location for GitHub-hosted marketplaces), then the legacy .github/ spot,\n\t// then the Copilot CLI's other probe locations (root, .plugin/).\n\tmarketplaceFiles: [\n\t\tpath.join(MARKER_DIR, \"plugin\", \"marketplace.json\"),\n\t\tpath.join(MARKER_DIR, \"marketplace.json\"),\n\t\t\"marketplace.json\",\n\t\tpath.join(\".plugin\", \"marketplace.json\"),\n\t],\n\n\tworkspace: {\n\t\troot: MARKER_DIR,\n\t\t// Copilot reads repo skills from .github/skills (also .claude/skills and\n\t\t// .agents/skills, which the sibling adapters cover).\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\t// Custom agents take `tools` as a YAML list (not the Claude comma string).\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"agents\", `${slug(a.name)}.agent.md`),\n\t\t\tcontent: emitMarkdown(\n\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\ta.body,\n\t\t\t),\n\t\t}),\n\t\t// The closest Copilot equivalent of a slash command is a prompt file.\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"prompts\", `${slug(c.name)}.prompt.md`),\n\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t}),\n\t},\n\n\thasManifest(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tdetectPlugin(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\tconst manifestPath = manifestPathFor(root);\n\t\tif (!manifestPath) return null;\n\t\tconst raw = readJson<RawManifest>(manifestPath);\n\t\tif (!raw) return null;\n\n\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\tif (!id) return null;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tversion: raw.version,\n\t\t\tdescription: raw.description,\n\t\t\tauthor: parseAuthor(raw.author),\n\t\t\troot,\n\t\t\tmanifestPath,\n\t\t\tformat: \"copilot\",\n\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\tsupportPlatform: [\"github\"],\n\t\t\t// Claude-mirror layout (manifest overrides honored), with the legacy\n\t\t\t// prompts/chatmodes locations as read-only fallbacks.\n\t\t\tskillsDir: resolveCapabilityDir(root, raw.skills, \"skills\"),\n\t\t\t// Commands map to Copilot prompt files at `.github/prompts/`; a manifest\n\t\t\t// `commands` override or a conventional `commands/` dir still wins if present.\n\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, PROMPTS_DIR),\n\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Manifest first, then the reference's two locations (root `hooks.json`\n\t\t\t// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.\n\t\t\thooks:\n\t\t\t\tnormalizeHooks(raw.hooks, root) ??\n\t\t\t\treadHooksFile(root, \"hooks.json\") ??\n\t\t\t\treadHooksFile(root, ALT_HOOKS_FILE) ??\n\t\t\t\treadHooksFile(root, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\n\t\t\t// `extensions` and `lspServers` are real Copilot manifest keys we do not\n\t\t\t// model; carried here so an edit cannot drop them.\n\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t};\n\t},\n\n\temit(draft: PluginDraft): EmittedFile[] {\n\t\t// Only the manifest is Copilot-specific — the canonical location is root\n\t\t// `plugin.json` (Copilot CLI spec) — and the capability tree mirrors the\n\t\t// Claude layout, so a plugin authored for both platforms is one tree with a\n\t\t// root manifest for Copilot and a .claude-plugin/ manifest for Claude.\n\t\tconst files: EmittedFile[] = [];\n\n\t\tfiles.push({\n\t\t\tpath: emitManifestRelPath,\n\t\t\tcontent: emitJson({\n\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\tname: draft.id,\n\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t// Spec: `author` is an object with a required `name`.\n\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t}),\n\t\t});\n\n\t\tfor (const s of draft.skills ?? []) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t});\n\t\t}\n\t\tfor (const c of draft.commands ?? []) {\n\t\t\t// Copilot's analog of a slash command is a prompt file:\n\t\t\t// `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;\n\t\t\t// verified 2026-07) — same target as this adapter's workspace.emitCommand.\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),\n\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t});\n\t\t}\n\t\tfor (const a of draft.agents ?? []) {\n\t\t\t// Copilot recognizes custom agents by the `.agent.md` suffix, and takes\n\t\t\t// `tools` as a YAML list (not the Claude comma string) — matching this\n\t\t\t// adapter's workspace.emitAgent and the current GitHub Copilot docs\n\t\t\t// (docs.github.com custom agents / plugins-creating; verified 2026-07).\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.agent.md`),\n\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\t\ta.body,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\tif (draft.mcpServers?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: \".mcp.json\",\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\tmcpServers: Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (draft.hooks?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t});\n\t\t}\n\t\treturn files;\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAuBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAmEhF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAsB3C;AAED,eAAO,MAAM,aAAa,EAAE,mBAuK3B,CAAC","sourcesContent":["/**\n * GitHub Copilot format.\n *\n * hoocode's preferred Copilot home is the `.github/plugin/` marker directory —\n * the convention used by the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) and the documented location\n * for GitHub-hosted marketplaces. The official Copilot CLI plugin reference\n * (docs.github.com copilot/reference/copilot-cli-reference/cli-plugin-reference,\n * \"File locations\"; verified 2026-07) accepts several locations for both files\n * (`.plugin/`, plugin root, `.github/plugin/`, `.claude-plugin/`), so this\n * adapter reads them all — `.github/plugin/` first, then root, then `.plugin/`,\n * then the legacy hoocode layout — and writes `.github/plugin/`.\n * (`.claude-plugin/` belongs to the Claude adapter, which wins precedence, so a\n * `.claude-plugin`-only directory correctly parses as a Claude plugin; Copilot\n * reads those natively anyway.) The capability tree matches the Claude layout:\n *\n * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)\n * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the\n * analog of a slash command; a manifest\n * `commands` path or `commands/` dir is read too)\n * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the\n * `.agent.md` suffix and a YAML-list `tools`\n * frontmatter are what Copilot recognizes; the\n * reader still accepts bare `.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\n * canvases extensions/<name>/extension.mjs (or the manifest's `extensions` path override;\n * opened on demand by /canvas, never loaded)\n *\n * Plus the Agent Plugins vendor content namespace, which is how\n * `github/awesome-copilot` publishes its whole catalog — portable skills stay at\n * `skills/`, and Copilot-specific capabilities move under a reverse-DNS\n * directory:\n *\n * subagents com.github.copilot/agents/<name>.md\n * canvases com.github.copilot/extensions/<name>/extension.mjs\n *\n * Workspace-level (non-plugin) conventions, per the current GitHub Copilot\n * customization docs (docs.github.com — agent skills, custom agents, prompt\n * files; verified 2026-07):\n *\n * skills .github/skills/<name>/SKILL.md (agentskills.io open standard)\n * custom agents .github/agents/<name>.agent.md (frontmatter: name, description,\n * tools as a YAML list, model, ...)\n * prompt files .github/prompts/<name>.prompt.md (VS Code / Copilot prompt files)\n *\n * Earlier hoocode releases authored a different Copilot mapping\n * (`.github/copilot-plugin.json` + `.github/prompts/*.prompt.md` +\n * `.github/chatmodes/*.chatmode.md`); the reader still accepts that layout as a\n * legacy fallback so previously authored plugins keep loading. Copilot\n * conventions move quickly; this file is the single place that encodes them,\n * so tracking an upstream change never reaches beyond this adapter.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tCOPILOT_CONTENT_DIR,\n\tdetectCanvasExtensions,\n\tdetectUnsupportedSurfaces,\n\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order, matching the Copilot CLI reference exactly:\n * `.plugin/` → root → `.github/plugin/` → (`.claude-plugin/`, which belongs to\n * the Claude adapter), with hoocode's legacy `.github/copilot-plugin.json` last.\n *\n * The order is the vendor's, not ours: a plugin carrying more than one manifest\n * must resolve to the same one under hoocode as under Copilot CLI, or the two\n * read different metadata from the same directory.\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(\".plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/**\n * Authored manifest location: root `plugin.json`, which is what the Copilot CLI\n * plugin-creating guide teaches and probe position #2. An earlier version emitted\n * `.github/plugin/plugin.json` while the comment here claimed root was canonical;\n * the comment was right. (`.github/plugin/` is still *read*, third in the probe\n * order above, since real marketplace-indexed plugins use it.)\n */\nconst emitManifestRelPath = \"plugin.json\";\n\n/**\n * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current\n * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands\n * to it (both the writer and this reader), so it is the authored location for\n * commands — not merely a legacy fallback.\n */\nconst PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\n// Legacy authored layout (read-only fallback).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\n/**\n * Copilot's subagent home inside the vendor content namespace. This is the\n * layout `github/awesome-copilot` publishes: portable skills stay at `skills/`\n * and everything Copilot-specific is emitted under `com.github.copilot/`\n * (`eng/materialize-plugins.mjs`). Without it every agent in that catalog parses\n * as nothing.\n */\nconst CONTENT_AGENTS_DIR = path.join(COPILOT_CONTENT_DIR, \"agents\");\n// Documented alternates, not legacy: the reference lists both locations for each.\nconst ALT_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\nconst ALT_HOOKS_FILE = path.join(\"hooks\", \"hooks.json\");\n// hoocode's own older authored layout, still read so those plugins keep loading.\nconst LEGACY_HOOKS_FILE = path.join(MARKER_DIR, \"hooks\", \"hooks.json\");\n\n/** Read a hooks JSON file (either `{ hooks: {...} }` or a bare event map). */\nfunction readHooksFile(root: string, rel: string): NormalizedPlugin[\"hooks\"] {\n\tconst file = path.join(root, rel);\n\tif (!fs.existsSync(file)) return undefined;\n\tconst raw = readJson<{ hooks?: Record<string, unknown> } | Record<string, unknown>>(file);\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst config = (\"hooks\" in raw && raw.hooks ? raw.hooks : raw) as NormalizedPlugin[\"hooks\"];\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction manifestPathFor(root: string): string | undefined {\n\tfor (const rel of MANIFEST_REL_PATHS) {\n\t\tif (readJson(path.join(root, rel)) != null) return path.join(root, rel);\n\t}\n\treturn undefined;\n}\n\n/**\n * Every plugin-relative path this adapter reads, for the Tier 2 drift check\n * (§2.2). Assembled from the constants above rather than restated, so a path\n * added to the reader cannot be missing from the declaration.\n */\nexport function copilotReadPaths(): string[] {\n\treturn [\n\t\t...MANIFEST_REL_PATHS,\n\t\tPROMPTS_DIR,\n\t\tLEGACY_CHATMODES_DIR,\n\t\tALT_MCP_FILE,\n\t\tALT_HOOKS_FILE,\n\t\tLEGACY_HOOKS_FILE,\n\t\t\"skills\",\n\t\t\"agents\",\n\t\tCONTENT_AGENTS_DIR,\n\t\t// Canvas extensions: the container, the namespaced container, and the entry\n\t\t// file that is the whole detection contract.\n\t\t\"extensions\",\n\t\tpath.join(COPILOT_CONTENT_DIR, \"extensions\"),\n\t\t\"extension.mjs\",\n\t\t// The `<name>.agent.md` subagent convention, which the reference writes as\n\t\t// a suffix rather than a path.\n\t\t\".agent.md\",\n\t\t\"hooks.json\",\n\t\t\".mcp.json\",\n\t].map((p) => p.replace(/\\\\/g, \"/\"));\n}\n\nexport const copilotFormat: PluginFormatAdapter = {\n\tid: \"copilot\",\n\tplatform: \"github\",\n\tprecedence: 2,\n\tlabel: \"GitHub Copilot (.github)\",\n\t// .github/plugin/ first (hoocode's preferred Copilot home and the documented\n\t// location for GitHub-hosted marketplaces), then the legacy .github/ spot,\n\t// then the Copilot CLI's other probe locations (root, .plugin/).\n\tmarketplaceFiles: [\n\t\tpath.join(MARKER_DIR, \"plugin\", \"marketplace.json\"),\n\t\tpath.join(MARKER_DIR, \"marketplace.json\"),\n\t\t\"marketplace.json\",\n\t\tpath.join(\".plugin\", \"marketplace.json\"),\n\t],\n\n\tworkspace: {\n\t\troot: MARKER_DIR,\n\t\t// Copilot reads repo skills from .github/skills (also .claude/skills and\n\t\t// .agents/skills, which the sibling adapters cover).\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\t// Custom agents take `tools` as a YAML list (not the Claude comma string).\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"agents\", `${slug(a.name)}.agent.md`),\n\t\t\tcontent: emitMarkdown(\n\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\ta.body,\n\t\t\t),\n\t\t}),\n\t\t// The closest Copilot equivalent of a slash command is a prompt file.\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"prompts\", `${slug(c.name)}.prompt.md`),\n\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t}),\n\t},\n\n\thasManifest(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tdetectPlugin(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\tconst manifestPath = manifestPathFor(root);\n\t\tif (!manifestPath) return null;\n\t\tconst raw = readJson<RawManifest>(manifestPath);\n\t\tif (!raw) return null;\n\n\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\tif (!id) return null;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tversion: raw.version,\n\t\t\tdescription: raw.description,\n\t\t\tauthor: parseAuthor(raw.author),\n\t\t\troot,\n\t\t\tmanifestPath,\n\t\t\tformat: \"copilot\",\n\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\tsupportPlatform: [\"github\"],\n\t\t\t// Claude-mirror layout (manifest overrides honored), with the legacy\n\t\t\t// prompts/chatmodes locations as read-only fallbacks.\n\t\t\tskillsDir: resolveCapabilityDir(root, raw.skills, \"skills\"),\n\t\t\t// Commands map to Copilot prompt files at `.github/prompts/`; a manifest\n\t\t\t// `commands` override or a conventional `commands/` dir still wins if present.\n\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, PROMPTS_DIR),\n\t\t\tagentsDir:\n\t\t\t\tresolveCapabilityDir(root, raw.agents, \"agents\") ??\n\t\t\t\tdirIfExists(root, CONTENT_AGENTS_DIR) ??\n\t\t\t\tdirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Manifest first, then the reference's two locations (root `hooks.json`\n\t\t\t// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.\n\t\t\thooks:\n\t\t\t\tnormalizeHooks(raw.hooks, root) ??\n\t\t\t\treadHooksFile(root, \"hooks.json\") ??\n\t\t\t\treadHooksFile(root, ALT_HOOKS_FILE) ??\n\t\t\t\treadHooksFile(root, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\n\t\t\t// Canvas extensions: `extensions/` (or the manifest's own path) and the\n\t\t\t// namespaced `com.github.copilot/extensions/`. Not loaded into the\n\t\t\t// session — `/canvas` opens them on demand — but resolved here so the\n\t\t\t// plugin can report what it brought.\n\t\t\tcanvasExtensions: detectCanvasExtensions(root, id, raw.extensions),\n\t\t\t// `lspServers` is a real Copilot manifest key we do not model, and\n\t\t\t// `extensions` carries a vendor-metadata map alongside the canvas path\n\t\t\t// read above; both are carried here so an edit cannot drop them.\n\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t};\n\t},\n\n\temit(draft: PluginDraft): EmittedFile[] {\n\t\t// Only the manifest is Copilot-specific — the canonical location is root\n\t\t// `plugin.json` (Copilot CLI spec) — and the capability tree mirrors the\n\t\t// Claude layout, so a plugin authored for both platforms is one tree with a\n\t\t// root manifest for Copilot and a .claude-plugin/ manifest for Claude.\n\t\tconst files: EmittedFile[] = [];\n\n\t\tfiles.push({\n\t\t\tpath: emitManifestRelPath,\n\t\t\tcontent: emitJson({\n\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\tname: draft.id,\n\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t// Spec: `author` is an object with a required `name`.\n\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t}),\n\t\t});\n\n\t\tfor (const s of draft.skills ?? []) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t});\n\t\t}\n\t\tfor (const c of draft.commands ?? []) {\n\t\t\t// Copilot's analog of a slash command is a prompt file:\n\t\t\t// `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;\n\t\t\t// verified 2026-07) — same target as this adapter's workspace.emitCommand.\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),\n\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t});\n\t\t}\n\t\tfor (const a of draft.agents ?? []) {\n\t\t\t// Copilot recognizes custom agents by the `.agent.md` suffix, and takes\n\t\t\t// `tools` as a YAML list (not the Claude comma string) — matching this\n\t\t\t// adapter's workspace.emitAgent and the current GitHub Copilot docs\n\t\t\t// (docs.github.com custom agents / plugins-creating; verified 2026-07).\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.agent.md`),\n\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\t\ta.body,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\tif (draft.mcpServers?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: \".mcp.json\",\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\tmcpServers: Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (draft.hooks?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t});\n\t\t}\n\t\treturn files;\n\t},\n};\n"]}
|
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
* reader still accepts bare `.md` names)
|
|
25
25
|
* hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })
|
|
26
26
|
* MCP servers .mcp.json ({ mcpServers } | { servers })
|
|
27
|
+
* canvases extensions/<name>/extension.mjs (or the manifest's `extensions` path override;
|
|
28
|
+
* opened on demand by /canvas, never loaded)
|
|
29
|
+
*
|
|
30
|
+
* Plus the Agent Plugins vendor content namespace, which is how
|
|
31
|
+
* `github/awesome-copilot` publishes its whole catalog — portable skills stay at
|
|
32
|
+
* `skills/`, and Copilot-specific capabilities move under a reverse-DNS
|
|
33
|
+
* directory:
|
|
34
|
+
*
|
|
35
|
+
* subagents com.github.copilot/agents/<name>.md
|
|
36
|
+
* canvases com.github.copilot/extensions/<name>/extension.mjs
|
|
27
37
|
*
|
|
28
38
|
* Workspace-level (non-plugin) conventions, per the current GitHub Copilot
|
|
29
39
|
* customization docs (docs.github.com — agent skills, custom agents, prompt
|
|
@@ -43,7 +53,7 @@
|
|
|
43
53
|
*/
|
|
44
54
|
import * as fs from "node:fs";
|
|
45
55
|
import * as path from "node:path";
|
|
46
|
-
import { authoredHooksToConfig, detectUnsupportedSurfaces, dirIfExists, emitJson, emitMarkdown, normalizeHooks, normalizeMcp, parseAuthor, readJson, resolveCapabilityDir, slug, toolsYamlList, unknownManifestFields, } from "./shared.js";
|
|
56
|
+
import { authoredHooksToConfig, COPILOT_CONTENT_DIR, detectCanvasExtensions, detectUnsupportedSurfaces, dirIfExists, emitJson, emitMarkdown, normalizeHooks, normalizeMcp, parseAuthor, readJson, resolveCapabilityDir, slug, toolsYamlList, unknownManifestFields, } from "./shared.js";
|
|
47
57
|
const MARKER_DIR = ".github";
|
|
48
58
|
/**
|
|
49
59
|
* Manifest probe order, matching the Copilot CLI reference exactly:
|
|
@@ -77,6 +87,14 @@ const emitManifestRelPath = "plugin.json";
|
|
|
77
87
|
const PROMPTS_DIR = path.join(MARKER_DIR, "prompts");
|
|
78
88
|
// Legacy authored layout (read-only fallback).
|
|
79
89
|
const LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, "chatmodes");
|
|
90
|
+
/**
|
|
91
|
+
* Copilot's subagent home inside the vendor content namespace. This is the
|
|
92
|
+
* layout `github/awesome-copilot` publishes: portable skills stay at `skills/`
|
|
93
|
+
* and everything Copilot-specific is emitted under `com.github.copilot/`
|
|
94
|
+
* (`eng/materialize-plugins.mjs`). Without it every agent in that catalog parses
|
|
95
|
+
* as nothing.
|
|
96
|
+
*/
|
|
97
|
+
const CONTENT_AGENTS_DIR = path.join(COPILOT_CONTENT_DIR, "agents");
|
|
80
98
|
// Documented alternates, not legacy: the reference lists both locations for each.
|
|
81
99
|
const ALT_MCP_FILE = path.join(MARKER_DIR, "mcp.json");
|
|
82
100
|
const ALT_HOOKS_FILE = path.join("hooks", "hooks.json");
|
|
@@ -115,6 +133,12 @@ export function copilotReadPaths() {
|
|
|
115
133
|
LEGACY_HOOKS_FILE,
|
|
116
134
|
"skills",
|
|
117
135
|
"agents",
|
|
136
|
+
CONTENT_AGENTS_DIR,
|
|
137
|
+
// Canvas extensions: the container, the namespaced container, and the entry
|
|
138
|
+
// file that is the whole detection contract.
|
|
139
|
+
"extensions",
|
|
140
|
+
path.join(COPILOT_CONTENT_DIR, "extensions"),
|
|
141
|
+
"extension.mjs",
|
|
118
142
|
// The `<name>.agent.md` subagent convention, which the reference writes as
|
|
119
143
|
// a suffix rather than a path.
|
|
120
144
|
".agent.md",
|
|
@@ -187,7 +211,9 @@ export const copilotFormat = {
|
|
|
187
211
|
// Commands map to Copilot prompt files at `.github/prompts/`; a manifest
|
|
188
212
|
// `commands` override or a conventional `commands/` dir still wins if present.
|
|
189
213
|
commandsDir: resolveCapabilityDir(root, raw.commands, "commands") ?? dirIfExists(root, PROMPTS_DIR),
|
|
190
|
-
agentsDir: resolveCapabilityDir(root, raw.agents, "agents") ??
|
|
214
|
+
agentsDir: resolveCapabilityDir(root, raw.agents, "agents") ??
|
|
215
|
+
dirIfExists(root, CONTENT_AGENTS_DIR) ??
|
|
216
|
+
dirIfExists(root, LEGACY_CHATMODES_DIR),
|
|
191
217
|
themesDir: resolveCapabilityDir(root, raw.themes, "themes"),
|
|
192
218
|
// Manifest first, then the reference's two locations (root `hooks.json`
|
|
193
219
|
// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.
|
|
@@ -198,8 +224,14 @@ export const copilotFormat = {
|
|
|
198
224
|
mcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),
|
|
199
225
|
// Providers are a native-only concept.
|
|
200
226
|
providers: undefined,
|
|
201
|
-
//
|
|
202
|
-
//
|
|
227
|
+
// Canvas extensions: `extensions/` (or the manifest's own path) and the
|
|
228
|
+
// namespaced `com.github.copilot/extensions/`. Not loaded into the
|
|
229
|
+
// session — `/canvas` opens them on demand — but resolved here so the
|
|
230
|
+
// plugin can report what it brought.
|
|
231
|
+
canvasExtensions: detectCanvasExtensions(root, id, raw.extensions),
|
|
232
|
+
// `lspServers` is a real Copilot manifest key we do not model, and
|
|
233
|
+
// `extensions` carries a vendor-metadata map alongside the canvas path
|
|
234
|
+
// read above; both are carried here so an edit cannot drop them.
|
|
203
235
|
unknownFields: unknownManifestFields(raw),
|
|
204
236
|
unsupportedSurfaces: detectUnsupportedSurfaces(root),
|
|
205
237
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot format.\n *\n * hoocode's preferred Copilot home is the `.github/plugin/` marker directory —\n * the convention used by the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) and the documented location\n * for GitHub-hosted marketplaces. The official Copilot CLI plugin reference\n * (docs.github.com copilot/reference/copilot-cli-reference/cli-plugin-reference,\n * \"File locations\"; verified 2026-07) accepts several locations for both files\n * (`.plugin/`, plugin root, `.github/plugin/`, `.claude-plugin/`), so this\n * adapter reads them all — `.github/plugin/` first, then root, then `.plugin/`,\n * then the legacy hoocode layout — and writes `.github/plugin/`.\n * (`.claude-plugin/` belongs to the Claude adapter, which wins precedence, so a\n * `.claude-plugin`-only directory correctly parses as a Claude plugin; Copilot\n * reads those natively anyway.) The capability tree matches the Claude layout:\n *\n * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)\n * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the\n * analog of a slash command; a manifest\n * `commands` path or `commands/` dir is read too)\n * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the\n * `.agent.md` suffix and a YAML-list `tools`\n * frontmatter are what Copilot recognizes; the\n * reader still accepts bare `.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\n *\n * Workspace-level (non-plugin) conventions, per the current GitHub Copilot\n * customization docs (docs.github.com — agent skills, custom agents, prompt\n * files; verified 2026-07):\n *\n * skills .github/skills/<name>/SKILL.md (agentskills.io open standard)\n * custom agents .github/agents/<name>.agent.md (frontmatter: name, description,\n * tools as a YAML list, model, ...)\n * prompt files .github/prompts/<name>.prompt.md (VS Code / Copilot prompt files)\n *\n * Earlier hoocode releases authored a different Copilot mapping\n * (`.github/copilot-plugin.json` + `.github/prompts/*.prompt.md` +\n * `.github/chatmodes/*.chatmode.md`); the reader still accepts that layout as a\n * legacy fallback so previously authored plugins keep loading. Copilot\n * conventions move quickly; this file is the single place that encodes them,\n * so tracking an upstream change never reaches beyond this adapter.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tdetectUnsupportedSurfaces,\n\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order, matching the Copilot CLI reference exactly:\n * `.plugin/` → root → `.github/plugin/` → (`.claude-plugin/`, which belongs to\n * the Claude adapter), with hoocode's legacy `.github/copilot-plugin.json` last.\n *\n * The order is the vendor's, not ours: a plugin carrying more than one manifest\n * must resolve to the same one under hoocode as under Copilot CLI, or the two\n * read different metadata from the same directory.\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(\".plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/**\n * Authored manifest location: root `plugin.json`, which is what the Copilot CLI\n * plugin-creating guide teaches and probe position #2. An earlier version emitted\n * `.github/plugin/plugin.json` while the comment here claimed root was canonical;\n * the comment was right. (`.github/plugin/` is still *read*, third in the probe\n * order above, since real marketplace-indexed plugins use it.)\n */\nconst emitManifestRelPath = \"plugin.json\";\n\n/**\n * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current\n * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands\n * to it (both the writer and this reader), so it is the authored location for\n * commands — not merely a legacy fallback.\n */\nconst PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\n// Legacy authored layout (read-only fallback).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\n// Documented alternates, not legacy: the reference lists both locations for each.\nconst ALT_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\nconst ALT_HOOKS_FILE = path.join(\"hooks\", \"hooks.json\");\n// hoocode's own older authored layout, still read so those plugins keep loading.\nconst LEGACY_HOOKS_FILE = path.join(MARKER_DIR, \"hooks\", \"hooks.json\");\n\n/** Read a hooks JSON file (either `{ hooks: {...} }` or a bare event map). */\nfunction readHooksFile(root: string, rel: string): NormalizedPlugin[\"hooks\"] {\n\tconst file = path.join(root, rel);\n\tif (!fs.existsSync(file)) return undefined;\n\tconst raw = readJson<{ hooks?: Record<string, unknown> } | Record<string, unknown>>(file);\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst config = (\"hooks\" in raw && raw.hooks ? raw.hooks : raw) as NormalizedPlugin[\"hooks\"];\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction manifestPathFor(root: string): string | undefined {\n\tfor (const rel of MANIFEST_REL_PATHS) {\n\t\tif (readJson(path.join(root, rel)) != null) return path.join(root, rel);\n\t}\n\treturn undefined;\n}\n\n/**\n * Every plugin-relative path this adapter reads, for the Tier 2 drift check\n * (§2.2). Assembled from the constants above rather than restated, so a path\n * added to the reader cannot be missing from the declaration.\n */\nexport function copilotReadPaths(): string[] {\n\treturn [\n\t\t...MANIFEST_REL_PATHS,\n\t\tPROMPTS_DIR,\n\t\tLEGACY_CHATMODES_DIR,\n\t\tALT_MCP_FILE,\n\t\tALT_HOOKS_FILE,\n\t\tLEGACY_HOOKS_FILE,\n\t\t\"skills\",\n\t\t\"agents\",\n\t\t// The `<name>.agent.md` subagent convention, which the reference writes as\n\t\t// a suffix rather than a path.\n\t\t\".agent.md\",\n\t\t\"hooks.json\",\n\t\t\".mcp.json\",\n\t].map((p) => p.replace(/\\\\/g, \"/\"));\n}\n\nexport const copilotFormat: PluginFormatAdapter = {\n\tid: \"copilot\",\n\tplatform: \"github\",\n\tprecedence: 2,\n\tlabel: \"GitHub Copilot (.github)\",\n\t// .github/plugin/ first (hoocode's preferred Copilot home and the documented\n\t// location for GitHub-hosted marketplaces), then the legacy .github/ spot,\n\t// then the Copilot CLI's other probe locations (root, .plugin/).\n\tmarketplaceFiles: [\n\t\tpath.join(MARKER_DIR, \"plugin\", \"marketplace.json\"),\n\t\tpath.join(MARKER_DIR, \"marketplace.json\"),\n\t\t\"marketplace.json\",\n\t\tpath.join(\".plugin\", \"marketplace.json\"),\n\t],\n\n\tworkspace: {\n\t\troot: MARKER_DIR,\n\t\t// Copilot reads repo skills from .github/skills (also .claude/skills and\n\t\t// .agents/skills, which the sibling adapters cover).\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\t// Custom agents take `tools` as a YAML list (not the Claude comma string).\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"agents\", `${slug(a.name)}.agent.md`),\n\t\t\tcontent: emitMarkdown(\n\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\ta.body,\n\t\t\t),\n\t\t}),\n\t\t// The closest Copilot equivalent of a slash command is a prompt file.\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"prompts\", `${slug(c.name)}.prompt.md`),\n\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t}),\n\t},\n\n\thasManifest(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tdetectPlugin(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\tconst manifestPath = manifestPathFor(root);\n\t\tif (!manifestPath) return null;\n\t\tconst raw = readJson<RawManifest>(manifestPath);\n\t\tif (!raw) return null;\n\n\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\tif (!id) return null;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tversion: raw.version,\n\t\t\tdescription: raw.description,\n\t\t\tauthor: parseAuthor(raw.author),\n\t\t\troot,\n\t\t\tmanifestPath,\n\t\t\tformat: \"copilot\",\n\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\tsupportPlatform: [\"github\"],\n\t\t\t// Claude-mirror layout (manifest overrides honored), with the legacy\n\t\t\t// prompts/chatmodes locations as read-only fallbacks.\n\t\t\tskillsDir: resolveCapabilityDir(root, raw.skills, \"skills\"),\n\t\t\t// Commands map to Copilot prompt files at `.github/prompts/`; a manifest\n\t\t\t// `commands` override or a conventional `commands/` dir still wins if present.\n\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, PROMPTS_DIR),\n\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Manifest first, then the reference's two locations (root `hooks.json`\n\t\t\t// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.\n\t\t\thooks:\n\t\t\t\tnormalizeHooks(raw.hooks, root) ??\n\t\t\t\treadHooksFile(root, \"hooks.json\") ??\n\t\t\t\treadHooksFile(root, ALT_HOOKS_FILE) ??\n\t\t\t\treadHooksFile(root, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\n\t\t\t// `extensions` and `lspServers` are real Copilot manifest keys we do not\n\t\t\t// model; carried here so an edit cannot drop them.\n\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t};\n\t},\n\n\temit(draft: PluginDraft): EmittedFile[] {\n\t\t// Only the manifest is Copilot-specific — the canonical location is root\n\t\t// `plugin.json` (Copilot CLI spec) — and the capability tree mirrors the\n\t\t// Claude layout, so a plugin authored for both platforms is one tree with a\n\t\t// root manifest for Copilot and a .claude-plugin/ manifest for Claude.\n\t\tconst files: EmittedFile[] = [];\n\n\t\tfiles.push({\n\t\t\tpath: emitManifestRelPath,\n\t\t\tcontent: emitJson({\n\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\tname: draft.id,\n\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t// Spec: `author` is an object with a required `name`.\n\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t}),\n\t\t});\n\n\t\tfor (const s of draft.skills ?? []) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t});\n\t\t}\n\t\tfor (const c of draft.commands ?? []) {\n\t\t\t// Copilot's analog of a slash command is a prompt file:\n\t\t\t// `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;\n\t\t\t// verified 2026-07) — same target as this adapter's workspace.emitCommand.\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),\n\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t});\n\t\t}\n\t\tfor (const a of draft.agents ?? []) {\n\t\t\t// Copilot recognizes custom agents by the `.agent.md` suffix, and takes\n\t\t\t// `tools` as a YAML list (not the Claude comma string) — matching this\n\t\t\t// adapter's workspace.emitAgent and the current GitHub Copilot docs\n\t\t\t// (docs.github.com custom agents / plugins-creating; verified 2026-07).\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.agent.md`),\n\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\t\ta.body,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\tif (draft.mcpServers?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: \".mcp.json\",\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\tmcpServers: Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (draft.hooks?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t});\n\t\t}\n\t\treturn files;\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot format.\n *\n * hoocode's preferred Copilot home is the `.github/plugin/` marker directory —\n * the convention used by the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) and the documented location\n * for GitHub-hosted marketplaces. The official Copilot CLI plugin reference\n * (docs.github.com copilot/reference/copilot-cli-reference/cli-plugin-reference,\n * \"File locations\"; verified 2026-07) accepts several locations for both files\n * (`.plugin/`, plugin root, `.github/plugin/`, `.claude-plugin/`), so this\n * adapter reads them all — `.github/plugin/` first, then root, then `.plugin/`,\n * then the legacy hoocode layout — and writes `.github/plugin/`.\n * (`.claude-plugin/` belongs to the Claude adapter, which wins precedence, so a\n * `.claude-plugin`-only directory correctly parses as a Claude plugin; Copilot\n * reads those natively anyway.) The capability tree matches the Claude layout:\n *\n * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)\n * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the\n * analog of a slash command; a manifest\n * `commands` path or `commands/` dir is read too)\n * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the\n * `.agent.md` suffix and a YAML-list `tools`\n * frontmatter are what Copilot recognizes; the\n * reader still accepts bare `.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\n * canvases extensions/<name>/extension.mjs (or the manifest's `extensions` path override;\n * opened on demand by /canvas, never loaded)\n *\n * Plus the Agent Plugins vendor content namespace, which is how\n * `github/awesome-copilot` publishes its whole catalog — portable skills stay at\n * `skills/`, and Copilot-specific capabilities move under a reverse-DNS\n * directory:\n *\n * subagents com.github.copilot/agents/<name>.md\n * canvases com.github.copilot/extensions/<name>/extension.mjs\n *\n * Workspace-level (non-plugin) conventions, per the current GitHub Copilot\n * customization docs (docs.github.com — agent skills, custom agents, prompt\n * files; verified 2026-07):\n *\n * skills .github/skills/<name>/SKILL.md (agentskills.io open standard)\n * custom agents .github/agents/<name>.agent.md (frontmatter: name, description,\n * tools as a YAML list, model, ...)\n * prompt files .github/prompts/<name>.prompt.md (VS Code / Copilot prompt files)\n *\n * Earlier hoocode releases authored a different Copilot mapping\n * (`.github/copilot-plugin.json` + `.github/prompts/*.prompt.md` +\n * `.github/chatmodes/*.chatmode.md`); the reader still accepts that layout as a\n * legacy fallback so previously authored plugins keep loading. Copilot\n * conventions move quickly; this file is the single place that encodes them,\n * so tracking an upstream change never reaches beyond this adapter.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tCOPILOT_CONTENT_DIR,\n\tdetectCanvasExtensions,\n\tdetectUnsupportedSurfaces,\n\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order, matching the Copilot CLI reference exactly:\n * `.plugin/` → root → `.github/plugin/` → (`.claude-plugin/`, which belongs to\n * the Claude adapter), with hoocode's legacy `.github/copilot-plugin.json` last.\n *\n * The order is the vendor's, not ours: a plugin carrying more than one manifest\n * must resolve to the same one under hoocode as under Copilot CLI, or the two\n * read different metadata from the same directory.\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(\".plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/**\n * Authored manifest location: root `plugin.json`, which is what the Copilot CLI\n * plugin-creating guide teaches and probe position #2. An earlier version emitted\n * `.github/plugin/plugin.json` while the comment here claimed root was canonical;\n * the comment was right. (`.github/plugin/` is still *read*, third in the probe\n * order above, since real marketplace-indexed plugins use it.)\n */\nconst emitManifestRelPath = \"plugin.json\";\n\n/**\n * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current\n * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands\n * to it (both the writer and this reader), so it is the authored location for\n * commands — not merely a legacy fallback.\n */\nconst PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\n// Legacy authored layout (read-only fallback).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\n/**\n * Copilot's subagent home inside the vendor content namespace. This is the\n * layout `github/awesome-copilot` publishes: portable skills stay at `skills/`\n * and everything Copilot-specific is emitted under `com.github.copilot/`\n * (`eng/materialize-plugins.mjs`). Without it every agent in that catalog parses\n * as nothing.\n */\nconst CONTENT_AGENTS_DIR = path.join(COPILOT_CONTENT_DIR, \"agents\");\n// Documented alternates, not legacy: the reference lists both locations for each.\nconst ALT_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\nconst ALT_HOOKS_FILE = path.join(\"hooks\", \"hooks.json\");\n// hoocode's own older authored layout, still read so those plugins keep loading.\nconst LEGACY_HOOKS_FILE = path.join(MARKER_DIR, \"hooks\", \"hooks.json\");\n\n/** Read a hooks JSON file (either `{ hooks: {...} }` or a bare event map). */\nfunction readHooksFile(root: string, rel: string): NormalizedPlugin[\"hooks\"] {\n\tconst file = path.join(root, rel);\n\tif (!fs.existsSync(file)) return undefined;\n\tconst raw = readJson<{ hooks?: Record<string, unknown> } | Record<string, unknown>>(file);\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst config = (\"hooks\" in raw && raw.hooks ? raw.hooks : raw) as NormalizedPlugin[\"hooks\"];\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction manifestPathFor(root: string): string | undefined {\n\tfor (const rel of MANIFEST_REL_PATHS) {\n\t\tif (readJson(path.join(root, rel)) != null) return path.join(root, rel);\n\t}\n\treturn undefined;\n}\n\n/**\n * Every plugin-relative path this adapter reads, for the Tier 2 drift check\n * (§2.2). Assembled from the constants above rather than restated, so a path\n * added to the reader cannot be missing from the declaration.\n */\nexport function copilotReadPaths(): string[] {\n\treturn [\n\t\t...MANIFEST_REL_PATHS,\n\t\tPROMPTS_DIR,\n\t\tLEGACY_CHATMODES_DIR,\n\t\tALT_MCP_FILE,\n\t\tALT_HOOKS_FILE,\n\t\tLEGACY_HOOKS_FILE,\n\t\t\"skills\",\n\t\t\"agents\",\n\t\tCONTENT_AGENTS_DIR,\n\t\t// Canvas extensions: the container, the namespaced container, and the entry\n\t\t// file that is the whole detection contract.\n\t\t\"extensions\",\n\t\tpath.join(COPILOT_CONTENT_DIR, \"extensions\"),\n\t\t\"extension.mjs\",\n\t\t// The `<name>.agent.md` subagent convention, which the reference writes as\n\t\t// a suffix rather than a path.\n\t\t\".agent.md\",\n\t\t\"hooks.json\",\n\t\t\".mcp.json\",\n\t].map((p) => p.replace(/\\\\/g, \"/\"));\n}\n\nexport const copilotFormat: PluginFormatAdapter = {\n\tid: \"copilot\",\n\tplatform: \"github\",\n\tprecedence: 2,\n\tlabel: \"GitHub Copilot (.github)\",\n\t// .github/plugin/ first (hoocode's preferred Copilot home and the documented\n\t// location for GitHub-hosted marketplaces), then the legacy .github/ spot,\n\t// then the Copilot CLI's other probe locations (root, .plugin/).\n\tmarketplaceFiles: [\n\t\tpath.join(MARKER_DIR, \"plugin\", \"marketplace.json\"),\n\t\tpath.join(MARKER_DIR, \"marketplace.json\"),\n\t\t\"marketplace.json\",\n\t\tpath.join(\".plugin\", \"marketplace.json\"),\n\t],\n\n\tworkspace: {\n\t\troot: MARKER_DIR,\n\t\t// Copilot reads repo skills from .github/skills (also .claude/skills and\n\t\t// .agents/skills, which the sibling adapters cover).\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\t// Custom agents take `tools` as a YAML list (not the Claude comma string).\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"agents\", `${slug(a.name)}.agent.md`),\n\t\t\tcontent: emitMarkdown(\n\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\ta.body,\n\t\t\t),\n\t\t}),\n\t\t// The closest Copilot equivalent of a slash command is a prompt file.\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"prompts\", `${slug(c.name)}.prompt.md`),\n\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t}),\n\t},\n\n\thasManifest(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tdetectPlugin(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\tconst manifestPath = manifestPathFor(root);\n\t\tif (!manifestPath) return null;\n\t\tconst raw = readJson<RawManifest>(manifestPath);\n\t\tif (!raw) return null;\n\n\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\tif (!id) return null;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tversion: raw.version,\n\t\t\tdescription: raw.description,\n\t\t\tauthor: parseAuthor(raw.author),\n\t\t\troot,\n\t\t\tmanifestPath,\n\t\t\tformat: \"copilot\",\n\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\tsupportPlatform: [\"github\"],\n\t\t\t// Claude-mirror layout (manifest overrides honored), with the legacy\n\t\t\t// prompts/chatmodes locations as read-only fallbacks.\n\t\t\tskillsDir: resolveCapabilityDir(root, raw.skills, \"skills\"),\n\t\t\t// Commands map to Copilot prompt files at `.github/prompts/`; a manifest\n\t\t\t// `commands` override or a conventional `commands/` dir still wins if present.\n\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, PROMPTS_DIR),\n\t\t\tagentsDir:\n\t\t\t\tresolveCapabilityDir(root, raw.agents, \"agents\") ??\n\t\t\t\tdirIfExists(root, CONTENT_AGENTS_DIR) ??\n\t\t\t\tdirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Manifest first, then the reference's two locations (root `hooks.json`\n\t\t\t// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.\n\t\t\thooks:\n\t\t\t\tnormalizeHooks(raw.hooks, root) ??\n\t\t\t\treadHooksFile(root, \"hooks.json\") ??\n\t\t\t\treadHooksFile(root, ALT_HOOKS_FILE) ??\n\t\t\t\treadHooksFile(root, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\n\t\t\t// Canvas extensions: `extensions/` (or the manifest's own path) and the\n\t\t\t// namespaced `com.github.copilot/extensions/`. Not loaded into the\n\t\t\t// session — `/canvas` opens them on demand — but resolved here so the\n\t\t\t// plugin can report what it brought.\n\t\t\tcanvasExtensions: detectCanvasExtensions(root, id, raw.extensions),\n\t\t\t// `lspServers` is a real Copilot manifest key we do not model, and\n\t\t\t// `extensions` carries a vendor-metadata map alongside the canvas path\n\t\t\t// read above; both are carried here so an edit cannot drop them.\n\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t};\n\t},\n\n\temit(draft: PluginDraft): EmittedFile[] {\n\t\t// Only the manifest is Copilot-specific — the canonical location is root\n\t\t// `plugin.json` (Copilot CLI spec) — and the capability tree mirrors the\n\t\t// Claude layout, so a plugin authored for both platforms is one tree with a\n\t\t// root manifest for Copilot and a .claude-plugin/ manifest for Claude.\n\t\tconst files: EmittedFile[] = [];\n\n\t\tfiles.push({\n\t\t\tpath: emitManifestRelPath,\n\t\t\tcontent: emitJson({\n\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\tname: draft.id,\n\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t// Spec: `author` is an object with a required `name`.\n\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t}),\n\t\t});\n\n\t\tfor (const s of draft.skills ?? []) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t});\n\t\t}\n\t\tfor (const c of draft.commands ?? []) {\n\t\t\t// Copilot's analog of a slash command is a prompt file:\n\t\t\t// `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;\n\t\t\t// verified 2026-07) — same target as this adapter's workspace.emitCommand.\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),\n\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t});\n\t\t}\n\t\tfor (const a of draft.agents ?? []) {\n\t\t\t// Copilot recognizes custom agents by the `.agent.md` suffix, and takes\n\t\t\t// `tools` as a YAML list (not the Claude comma string) — matching this\n\t\t\t// adapter's workspace.emitAgent and the current GitHub Copilot docs\n\t\t\t// (docs.github.com custom agents / plugins-creating; verified 2026-07).\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.agent.md`),\n\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\t\ta.body,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\tif (draft.mcpServers?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: \".mcp.json\",\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\tmcpServers: Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (draft.hooks?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t});\n\t\t}\n\t\treturn files;\n\t},\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonManifest.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/jsonManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"jsonManifest.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/jsonManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIhG,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,UAAqE,CAAC;AAE1G;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UASpC,CAAC;AAOF,UAAU,mBAAmB;IAC5B,EAAE,EAAE,OAAO,CAAC,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC;IACjD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,6GAA6G;IAC7G,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CA2HxF","sourcesContent":["/**\n * Factory for the `plugin.json`-style formats (native `.agents-plugin` and\n * Claude Code `.claude-plugin`). They share one on-disk layout — a JSON manifest\n * under a marker directory plus `skills/`, `commands/`, `agents/`, `themes/`,\n * `hooks/hooks.json` — and differ only in the marker directory, the format id,\n * and whether `providers` are honored (native-only). Both adapters are produced\n * from this factory so the shared reader/writer lives in one place.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin, PluginProvider } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tclaudeStyleWorkspace,\n\tdetectCanvasExtensions,\n\tdetectUnsupportedSurfaces,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter, PluginFormatId } from \"./types.js\";\n\nconst MANIFEST_FILE = \"plugin.json\";\n\n/** Component locations that make a directory a plugin even with no manifest. */\nexport const CONVENTIONAL_COMPONENTS = [\"skills\", \"commands\", \"agents\", \"hooks\", \".mcp.json\", \"SKILL.md\"];\n\n/**\n * Everything this adapter resolves, including surfaces that are read from the\n * manifest rather than auto-discovered. Distinct from\n * {@link CONVENTIONAL_COMPONENTS}, which drives *detection* — `themes` is parsed\n * but has never made a directory a plugin on its own.\n */\nexport const JSON_MANIFEST_READ_PATHS = [\n\t...CONVENTIONAL_COMPONENTS,\n\t\"themes\",\n\t\"hooks/hooks.json\",\n\t// Canvas extensions: the container, the namespaced container, and the entry\n\t// file that is the whole detection contract.\n\t\"extensions\",\n\t\"com.github.copilot/extensions\",\n\t\"extension.mjs\",\n];\n\n/** True when `root` carries any component in its default location. */\nfunction hasConventionalComponents(root: string): boolean {\n\treturn CONVENTIONAL_COMPONENTS.some((rel) => fs.existsSync(path.join(root, rel)));\n}\n\ninterface JsonManifestOptions {\n\tid: Extract<PluginFormatId, \"agents\" | \"claude\">;\n\t/**\n\t * Honor the vendor's manifest-optional rule. True for Claude, whose reference\n\t * states the manifest is optional for marketplace and `--plugin-dir` plugins.\n\t */\n\tallowManifestless?: boolean;\n\t/** Marker subdirectory, e.g. \".agents-plugin\". */\n\tmanifestDir: string;\n\t/** Workspace-level artifact root, e.g. \".agents\" / \".claude\" (skills/, agents/, commands/ live under it). */\n\tworkspaceRoot: string;\n\tprecedence: number;\n\tlabel: string;\n\t/** Native honors `providers`; the Claude-compat path ignores them. */\n\tsupportsProviders: boolean;\n}\n\nexport function createJsonManifestAdapter(opts: JsonManifestOptions): PluginFormatAdapter {\n\tconst manifestRelPath = path.join(opts.manifestDir, MANIFEST_FILE);\n\n\treturn {\n\t\tid: opts.id,\n\t\tplatform: opts.id, // \"agents\" | \"claude\" map 1:1 to their platform token\n\t\tprecedence: opts.precedence,\n\t\tlabel: opts.label,\n\t\tmarketplaceFiles: [path.join(opts.manifestDir, \"marketplace.json\")],\n\t\tworkspace: claudeStyleWorkspace(opts.workspaceRoot),\n\n\t\thasManifest(root: string): boolean {\n\t\t\treturn readJson(path.join(root, manifestRelPath)) != null;\n\t\t},\n\n\t\tdetectPlugin(root: string): boolean {\n\t\t\tif (this.hasManifest(root)) return true;\n\t\t\t// Manifest-optional: a directory whose components sit in the default\n\t\t\t// locations is a plugin, with the name derived from the directory.\n\t\t\t//\n\t\t\t// Deliberately NOT applied inside a skills directory: there the manifest\n\t\t\t// is precisely what promotes a folder from plain skill to plugin, so\n\t\t\t// `discoverPlugins` passes `requireManifest` for those roots. Two vendor\n\t\t\t// rules that look contradictory until you notice they describe different\n\t\t\t// containers. See docs/plugin-system-architecture.md §1.6 and §2.1.\n\t\t\treturn opts.allowManifestless === true && hasConventionalComponents(root);\n\t\t},\n\n\t\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\t\tconst manifestPath = path.join(root, manifestRelPath);\n\t\t\tconst raw = readJson<RawManifest>(manifestPath) ?? (this.detectPlugin(root) ? {} : null);\n\t\t\tif (!raw) return null;\n\n\t\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\t\tif (!id) return null;\n\n\t\t\tconst providers: PluginProvider[] | undefined =\n\t\t\t\topts.supportsProviders && Array.isArray(raw.providers) ? (raw.providers as PluginProvider[]) : undefined;\n\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tversion: raw.version,\n\t\t\t\tdescription: raw.description,\n\t\t\t\tauthor: parseAuthor(raw.author),\n\t\t\t\troot,\n\t\t\t\tmanifestPath,\n\t\t\t\tformat: opts.id,\n\t\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\t\tsupportPlatform: [opts.id],\n\t\t\t\t// A plugin shipping exactly one skill may put SKILL.md at its root\n\t\t\t\t// instead of creating skills/ (Claude reference, \"Skills\").\n\t\t\t\tskillsDir:\n\t\t\t\t\tresolveCapabilityDir(root, raw.skills, \"skills\") ??\n\t\t\t\t\t(fs.existsSync(path.join(root, \"SKILL.md\")) ? root : undefined),\n\t\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\"),\n\t\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\"),\n\t\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t\thooks: normalizeHooks(raw.hooks, root),\n\t\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root),\n\t\t\t\tproviders,\n\t\t\t\t// A canvas plugin is format-agnostic on disk — `mobile-canvas-ghcp`\n\t\t\t\t// ships a `.claude-plugin/` manifest and an `extensions/` tree — so\n\t\t\t\t// this is read here as well as in the Copilot adapter, not only there.\n\t\t\t\tcanvasExtensions: detectCanvasExtensions(root, id, raw.extensions),\n\t\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t\t};\n\t\t},\n\n\t\temit(draft: PluginDraft): EmittedFile[] {\n\t\t\tconst files: EmittedFile[] = [];\n\t\t\tconst mcpServers = draft.mcpServers?.length\n\t\t\t\t? Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\n\t\t\tfiles.push({\n\t\t\t\tpath: manifestRelPath,\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\t\tname: draft.id,\n\t\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t\t// Claude Code's schema documents `author` as an object with `name`.\n\t\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t\t\t...(mcpServers ? { mcpServers } : {}),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tfor (const s of draft.skills ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const c of draft.commands ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"commands\", `${slug(c.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const a of draft.agents ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t\t{ name: a.name, description: a.description, tools: a.tools, model: a.model },\n\t\t\t\t\t\ta.body,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (draft.hooks?.length) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn files;\n\t\t},\n\t};\n}\n"]}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
-
import { authoredHooksToConfig, claudeStyleWorkspace, detectUnsupportedSurfaces, emitJson, emitMarkdown, normalizeHooks, normalizeMcp, parseAuthor, readJson, resolveCapabilityDir, slug, unknownManifestFields, } from "./shared.js";
|
|
11
|
+
import { authoredHooksToConfig, claudeStyleWorkspace, detectCanvasExtensions, detectUnsupportedSurfaces, emitJson, emitMarkdown, normalizeHooks, normalizeMcp, parseAuthor, readJson, resolveCapabilityDir, slug, unknownManifestFields, } from "./shared.js";
|
|
12
12
|
const MANIFEST_FILE = "plugin.json";
|
|
13
13
|
/** Component locations that make a directory a plugin even with no manifest. */
|
|
14
14
|
export const CONVENTIONAL_COMPONENTS = ["skills", "commands", "agents", "hooks", ".mcp.json", "SKILL.md"];
|
|
@@ -18,7 +18,16 @@ export const CONVENTIONAL_COMPONENTS = ["skills", "commands", "agents", "hooks",
|
|
|
18
18
|
* {@link CONVENTIONAL_COMPONENTS}, which drives *detection* — `themes` is parsed
|
|
19
19
|
* but has never made a directory a plugin on its own.
|
|
20
20
|
*/
|
|
21
|
-
export const JSON_MANIFEST_READ_PATHS = [
|
|
21
|
+
export const JSON_MANIFEST_READ_PATHS = [
|
|
22
|
+
...CONVENTIONAL_COMPONENTS,
|
|
23
|
+
"themes",
|
|
24
|
+
"hooks/hooks.json",
|
|
25
|
+
// Canvas extensions: the container, the namespaced container, and the entry
|
|
26
|
+
// file that is the whole detection contract.
|
|
27
|
+
"extensions",
|
|
28
|
+
"com.github.copilot/extensions",
|
|
29
|
+
"extension.mjs",
|
|
30
|
+
];
|
|
22
31
|
/** True when `root` carries any component in its default location. */
|
|
23
32
|
function hasConventionalComponents(root) {
|
|
24
33
|
return CONVENTIONAL_COMPONENTS.some((rel) => fs.existsSync(path.join(root, rel)));
|
|
@@ -77,6 +86,10 @@ export function createJsonManifestAdapter(opts) {
|
|
|
77
86
|
hooks: normalizeHooks(raw.hooks, root),
|
|
78
87
|
mcpServers: normalizeMcp(raw.mcpServers, root),
|
|
79
88
|
providers,
|
|
89
|
+
// A canvas plugin is format-agnostic on disk — `mobile-canvas-ghcp`
|
|
90
|
+
// ships a `.claude-plugin/` manifest and an `extensions/` tree — so
|
|
91
|
+
// this is read here as well as in the Copilot adapter, not only there.
|
|
92
|
+
canvasExtensions: detectCanvasExtensions(root, id, raw.extensions),
|
|
80
93
|
unknownFields: unknownManifestFields(raw),
|
|
81
94
|
unsupportedSurfaces: detectUnsupportedSurfaces(root),
|
|
82
95
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonManifest.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/jsonManifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Factory for the `plugin.json`-style formats (native `.agents-plugin` and\n * Claude Code `.claude-plugin`). They share one on-disk layout — a JSON manifest\n * under a marker directory plus `skills/`, `commands/`, `agents/`, `themes/`,\n * `hooks/hooks.json` — and differ only in the marker directory, the format id,\n * and whether `providers` are honored (native-only). Both adapters are produced\n * from this factory so the shared reader/writer lives in one place.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin, PluginProvider } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tclaudeStyleWorkspace,\n\tdetectUnsupportedSurfaces,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter, PluginFormatId } from \"./types.js\";\n\nconst MANIFEST_FILE = \"plugin.json\";\n\n/** Component locations that make a directory a plugin even with no manifest. */\nexport const CONVENTIONAL_COMPONENTS = [\"skills\", \"commands\", \"agents\", \"hooks\", \".mcp.json\", \"SKILL.md\"];\n\n/**\n * Everything this adapter resolves, including surfaces that are read from the\n * manifest rather than auto-discovered. Distinct from\n * {@link CONVENTIONAL_COMPONENTS}, which drives *detection* — `themes` is parsed\n * but has never made a directory a plugin on its own.\n */\nexport const JSON_MANIFEST_READ_PATHS = [...CONVENTIONAL_COMPONENTS, \"themes\", \"hooks/hooks.json\"];\n\n/** True when `root` carries any component in its default location. */\nfunction hasConventionalComponents(root: string): boolean {\n\treturn CONVENTIONAL_COMPONENTS.some((rel) => fs.existsSync(path.join(root, rel)));\n}\n\ninterface JsonManifestOptions {\n\tid: Extract<PluginFormatId, \"agents\" | \"claude\">;\n\t/**\n\t * Honor the vendor's manifest-optional rule. True for Claude, whose reference\n\t * states the manifest is optional for marketplace and `--plugin-dir` plugins.\n\t */\n\tallowManifestless?: boolean;\n\t/** Marker subdirectory, e.g. \".agents-plugin\". */\n\tmanifestDir: string;\n\t/** Workspace-level artifact root, e.g. \".agents\" / \".claude\" (skills/, agents/, commands/ live under it). */\n\tworkspaceRoot: string;\n\tprecedence: number;\n\tlabel: string;\n\t/** Native honors `providers`; the Claude-compat path ignores them. */\n\tsupportsProviders: boolean;\n}\n\nexport function createJsonManifestAdapter(opts: JsonManifestOptions): PluginFormatAdapter {\n\tconst manifestRelPath = path.join(opts.manifestDir, MANIFEST_FILE);\n\n\treturn {\n\t\tid: opts.id,\n\t\tplatform: opts.id, // \"agents\" | \"claude\" map 1:1 to their platform token\n\t\tprecedence: opts.precedence,\n\t\tlabel: opts.label,\n\t\tmarketplaceFiles: [path.join(opts.manifestDir, \"marketplace.json\")],\n\t\tworkspace: claudeStyleWorkspace(opts.workspaceRoot),\n\n\t\thasManifest(root: string): boolean {\n\t\t\treturn readJson(path.join(root, manifestRelPath)) != null;\n\t\t},\n\n\t\tdetectPlugin(root: string): boolean {\n\t\t\tif (this.hasManifest(root)) return true;\n\t\t\t// Manifest-optional: a directory whose components sit in the default\n\t\t\t// locations is a plugin, with the name derived from the directory.\n\t\t\t//\n\t\t\t// Deliberately NOT applied inside a skills directory: there the manifest\n\t\t\t// is precisely what promotes a folder from plain skill to plugin, so\n\t\t\t// `discoverPlugins` passes `requireManifest` for those roots. Two vendor\n\t\t\t// rules that look contradictory until you notice they describe different\n\t\t\t// containers. See docs/plugin-system-architecture.md §1.6 and §2.1.\n\t\t\treturn opts.allowManifestless === true && hasConventionalComponents(root);\n\t\t},\n\n\t\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\t\tconst manifestPath = path.join(root, manifestRelPath);\n\t\t\tconst raw = readJson<RawManifest>(manifestPath) ?? (this.detectPlugin(root) ? {} : null);\n\t\t\tif (!raw) return null;\n\n\t\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\t\tif (!id) return null;\n\n\t\t\tconst providers: PluginProvider[] | undefined =\n\t\t\t\topts.supportsProviders && Array.isArray(raw.providers) ? (raw.providers as PluginProvider[]) : undefined;\n\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tversion: raw.version,\n\t\t\t\tdescription: raw.description,\n\t\t\t\tauthor: parseAuthor(raw.author),\n\t\t\t\troot,\n\t\t\t\tmanifestPath,\n\t\t\t\tformat: opts.id,\n\t\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\t\tsupportPlatform: [opts.id],\n\t\t\t\t// A plugin shipping exactly one skill may put SKILL.md at its root\n\t\t\t\t// instead of creating skills/ (Claude reference, \"Skills\").\n\t\t\t\tskillsDir:\n\t\t\t\t\tresolveCapabilityDir(root, raw.skills, \"skills\") ??\n\t\t\t\t\t(fs.existsSync(path.join(root, \"SKILL.md\")) ? root : undefined),\n\t\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\"),\n\t\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\"),\n\t\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t\thooks: normalizeHooks(raw.hooks, root),\n\t\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root),\n\t\t\t\tproviders,\n\t\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t\t};\n\t\t},\n\n\t\temit(draft: PluginDraft): EmittedFile[] {\n\t\t\tconst files: EmittedFile[] = [];\n\t\t\tconst mcpServers = draft.mcpServers?.length\n\t\t\t\t? Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\n\t\t\tfiles.push({\n\t\t\t\tpath: manifestRelPath,\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\t\tname: draft.id,\n\t\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t\t// Claude Code's schema documents `author` as an object with `name`.\n\t\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t\t\t...(mcpServers ? { mcpServers } : {}),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tfor (const s of draft.skills ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const c of draft.commands ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"commands\", `${slug(c.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const a of draft.agents ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t\t{ name: a.name, description: a.description, tools: a.tools, model: a.model },\n\t\t\t\t\t\ta.body,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (draft.hooks?.length) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn files;\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"jsonManifest.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/jsonManifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Factory for the `plugin.json`-style formats (native `.agents-plugin` and\n * Claude Code `.claude-plugin`). They share one on-disk layout — a JSON manifest\n * under a marker directory plus `skills/`, `commands/`, `agents/`, `themes/`,\n * `hooks/hooks.json` — and differ only in the marker directory, the format id,\n * and whether `providers` are honored (native-only). Both adapters are produced\n * from this factory so the shared reader/writer lives in one place.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin, PluginProvider } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tclaudeStyleWorkspace,\n\tdetectCanvasExtensions,\n\tdetectUnsupportedSurfaces,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter, PluginFormatId } from \"./types.js\";\n\nconst MANIFEST_FILE = \"plugin.json\";\n\n/** Component locations that make a directory a plugin even with no manifest. */\nexport const CONVENTIONAL_COMPONENTS = [\"skills\", \"commands\", \"agents\", \"hooks\", \".mcp.json\", \"SKILL.md\"];\n\n/**\n * Everything this adapter resolves, including surfaces that are read from the\n * manifest rather than auto-discovered. Distinct from\n * {@link CONVENTIONAL_COMPONENTS}, which drives *detection* — `themes` is parsed\n * but has never made a directory a plugin on its own.\n */\nexport const JSON_MANIFEST_READ_PATHS = [\n\t...CONVENTIONAL_COMPONENTS,\n\t\"themes\",\n\t\"hooks/hooks.json\",\n\t// Canvas extensions: the container, the namespaced container, and the entry\n\t// file that is the whole detection contract.\n\t\"extensions\",\n\t\"com.github.copilot/extensions\",\n\t\"extension.mjs\",\n];\n\n/** True when `root` carries any component in its default location. */\nfunction hasConventionalComponents(root: string): boolean {\n\treturn CONVENTIONAL_COMPONENTS.some((rel) => fs.existsSync(path.join(root, rel)));\n}\n\ninterface JsonManifestOptions {\n\tid: Extract<PluginFormatId, \"agents\" | \"claude\">;\n\t/**\n\t * Honor the vendor's manifest-optional rule. True for Claude, whose reference\n\t * states the manifest is optional for marketplace and `--plugin-dir` plugins.\n\t */\n\tallowManifestless?: boolean;\n\t/** Marker subdirectory, e.g. \".agents-plugin\". */\n\tmanifestDir: string;\n\t/** Workspace-level artifact root, e.g. \".agents\" / \".claude\" (skills/, agents/, commands/ live under it). */\n\tworkspaceRoot: string;\n\tprecedence: number;\n\tlabel: string;\n\t/** Native honors `providers`; the Claude-compat path ignores them. */\n\tsupportsProviders: boolean;\n}\n\nexport function createJsonManifestAdapter(opts: JsonManifestOptions): PluginFormatAdapter {\n\tconst manifestRelPath = path.join(opts.manifestDir, MANIFEST_FILE);\n\n\treturn {\n\t\tid: opts.id,\n\t\tplatform: opts.id, // \"agents\" | \"claude\" map 1:1 to their platform token\n\t\tprecedence: opts.precedence,\n\t\tlabel: opts.label,\n\t\tmarketplaceFiles: [path.join(opts.manifestDir, \"marketplace.json\")],\n\t\tworkspace: claudeStyleWorkspace(opts.workspaceRoot),\n\n\t\thasManifest(root: string): boolean {\n\t\t\treturn readJson(path.join(root, manifestRelPath)) != null;\n\t\t},\n\n\t\tdetectPlugin(root: string): boolean {\n\t\t\tif (this.hasManifest(root)) return true;\n\t\t\t// Manifest-optional: a directory whose components sit in the default\n\t\t\t// locations is a plugin, with the name derived from the directory.\n\t\t\t//\n\t\t\t// Deliberately NOT applied inside a skills directory: there the manifest\n\t\t\t// is precisely what promotes a folder from plain skill to plugin, so\n\t\t\t// `discoverPlugins` passes `requireManifest` for those roots. Two vendor\n\t\t\t// rules that look contradictory until you notice they describe different\n\t\t\t// containers. See docs/plugin-system-architecture.md §1.6 and §2.1.\n\t\t\treturn opts.allowManifestless === true && hasConventionalComponents(root);\n\t\t},\n\n\t\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\t\tconst manifestPath = path.join(root, manifestRelPath);\n\t\t\tconst raw = readJson<RawManifest>(manifestPath) ?? (this.detectPlugin(root) ? {} : null);\n\t\t\tif (!raw) return null;\n\n\t\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\t\tif (!id) return null;\n\n\t\t\tconst providers: PluginProvider[] | undefined =\n\t\t\t\topts.supportsProviders && Array.isArray(raw.providers) ? (raw.providers as PluginProvider[]) : undefined;\n\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tversion: raw.version,\n\t\t\t\tdescription: raw.description,\n\t\t\t\tauthor: parseAuthor(raw.author),\n\t\t\t\troot,\n\t\t\t\tmanifestPath,\n\t\t\t\tformat: opts.id,\n\t\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\t\tsupportPlatform: [opts.id],\n\t\t\t\t// A plugin shipping exactly one skill may put SKILL.md at its root\n\t\t\t\t// instead of creating skills/ (Claude reference, \"Skills\").\n\t\t\t\tskillsDir:\n\t\t\t\t\tresolveCapabilityDir(root, raw.skills, \"skills\") ??\n\t\t\t\t\t(fs.existsSync(path.join(root, \"SKILL.md\")) ? root : undefined),\n\t\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\"),\n\t\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\"),\n\t\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t\thooks: normalizeHooks(raw.hooks, root),\n\t\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root),\n\t\t\t\tproviders,\n\t\t\t\t// A canvas plugin is format-agnostic on disk — `mobile-canvas-ghcp`\n\t\t\t\t// ships a `.claude-plugin/` manifest and an `extensions/` tree — so\n\t\t\t\t// this is read here as well as in the Copilot adapter, not only there.\n\t\t\t\tcanvasExtensions: detectCanvasExtensions(root, id, raw.extensions),\n\t\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t\t};\n\t\t},\n\n\t\temit(draft: PluginDraft): EmittedFile[] {\n\t\t\tconst files: EmittedFile[] = [];\n\t\t\tconst mcpServers = draft.mcpServers?.length\n\t\t\t\t? Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\n\t\t\tfiles.push({\n\t\t\t\tpath: manifestRelPath,\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\t\tname: draft.id,\n\t\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t\t// Claude Code's schema documents `author` as an object with `name`.\n\t\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t\t\t...(mcpServers ? { mcpServers } : {}),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tfor (const s of draft.skills ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const c of draft.commands ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"commands\", `${slug(c.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const a of draft.agents ?? []) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.md`),\n\t\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t\t{ name: a.name, description: a.description, tools: a.tools, model: a.model },\n\t\t\t\t\t\ta.body,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (draft.hooks?.length) {\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn files;\n\t\t},\n\t};\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* module cycle: `manifest.ts → formats/index.ts → formats/<adapter>.ts →
|
|
7
7
|
* shared.ts`, with the back-reference to manifest types erased at compile time.
|
|
8
8
|
*/
|
|
9
|
-
import type { PluginHooksConfig } from "../manifest.js";
|
|
9
|
+
import type { PluginCanvasExtension, PluginHooksConfig } from "../manifest.js";
|
|
10
10
|
import type { AuthoredHook, WorkspaceLayout } from "./types.js";
|
|
11
11
|
/** Raw manifest shape accepted by the `plugin.json`-style formats (agents/claude/copilot). */
|
|
12
12
|
export interface RawManifest {
|
|
@@ -28,7 +28,14 @@ export interface RawManifest {
|
|
|
28
28
|
mcpServers?: Record<string, unknown> | string;
|
|
29
29
|
/** Native-only. */
|
|
30
30
|
providers?: unknown;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Copilot: canvas-extension directory (a path, or list of paths, relative to
|
|
33
|
+
* the plugin root — e.g. `"extensions": "extensions"`). The Agent Plugins spec
|
|
34
|
+
* gives the same key a second, unrelated meaning: a map of vendor metadata
|
|
35
|
+
* namespaces (`{ "com.github.copilot": { logo } }`). Both readings are handled
|
|
36
|
+
* — see {@link detectCanvasExtensions} — and the map form is preserved
|
|
37
|
+
* verbatim through `unknownFields`.
|
|
38
|
+
*/
|
|
32
39
|
extensions?: unknown;
|
|
33
40
|
/** Copilot: LSP config path or inline definitions. Parsed for preservation only. */
|
|
34
41
|
lspServers?: unknown;
|
|
@@ -37,6 +44,27 @@ export interface RawManifest {
|
|
|
37
44
|
}
|
|
38
45
|
/** Manifest keys not modelled here, preserved verbatim so a re-emit cannot drop them. */
|
|
39
46
|
export declare function unknownManifestFields(raw: RawManifest): Record<string, unknown> | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Vendor content namespace directory. The Agent Plugins layout both vendors are
|
|
49
|
+
* converging on keeps portable capabilities at the plugin root (`skills/`) and
|
|
50
|
+
* puts vendor-specific ones under a reverse-DNS directory, so Copilot's agents
|
|
51
|
+
* and canvas extensions ship as `com.github.copilot/<capability>/`.
|
|
52
|
+
* `github/awesome-copilot` publishes its whole catalog this way.
|
|
53
|
+
*/
|
|
54
|
+
export declare const COPILOT_CONTENT_DIR = "com.github.copilot";
|
|
55
|
+
/**
|
|
56
|
+
* Canvas extensions a plugin ships (see `docs/canvas-extensions-design.md` §4.3).
|
|
57
|
+
*
|
|
58
|
+
* Discovery keys off `<dir>/extension.mjs` and nothing else, exactly as
|
|
59
|
+
* `core/canvas/discovery.ts` does for the workspace and user search roots — the
|
|
60
|
+
* entry file is the whole contract, so a plugin's canvases are found by the same
|
|
61
|
+
* rule as everyone else's.
|
|
62
|
+
*
|
|
63
|
+
* A plugin whose *root* carries `extension.mjs` is itself one canvas extension,
|
|
64
|
+
* which is how a single-canvas repository is laid out; its id is the plugin id,
|
|
65
|
+
* since there is no directory name below the root to take one from.
|
|
66
|
+
*/
|
|
67
|
+
export declare function detectCanvasExtensions(root: string, pluginId: string, override: unknown): PluginCanvasExtension[] | undefined;
|
|
40
68
|
/** Which unsupported surfaces are present under `root`. */
|
|
41
69
|
export declare function detectUnsupportedSurfaces(root: string): string[] | undefined;
|
|
42
70
|
export declare function readJson<T>(file: string): T | null;
|