{"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"]}