@kolisachint/hoocode-agent 0.5.23 → 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 +83 -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/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +25 -11
- package/dist/core/extensions/loader.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport { hasAnyManifest } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport {\n\tcandidatePluginDirs,\n\tinstallHomeForScope,\n\tmarketplaceCacheDir,\n\tmarketplaceCacheMetaPath,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n\ttype PluginInstallScope,\n\tpluginHomeRoots,\n\tsanitizeForDir,\n} from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/**\n * Marketplace registries older versions wrote into the working tree, newest\n * convention first. Still read so a user who added a marketplace before the\n * registry moved to the agent dir does not silently lose it; nothing writes here.\n */\nfunction legacyStorePaths(cwd: string): string[] {\n\treturn [path.join(cwd, \".agents\", \"marketplaces.json\"), path.join(cwd, \".hoocode\", \"marketplaces.json\")];\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nfunction defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and refreshed on a TTL thereafter. Refreshing the *index* only changes\n * what is discoverable; installed plugins are still never auto-updated, and\n * plugin sources inside the official index are sha-pinned.\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n\t// GitHub's community index, `.github/plugin/marketplace.json`. Same owner as\n\t// copilot-plugins, which is what makes it a source-trust decision of the same\n\t// kind. Note that most of its in-repo entries carry their content in Copilot\n\t// UI `extensions/`, a surface hoocode does not model — those install and load\n\t// nothing, and say so.\n\t{ name: \"awesome-copilot\", url: \"https://github.com/github/awesome-copilot\" },\n];\n\n/**\n * How long a cached marketplace index is considered fresh.\n *\n * Refreshing an *index* is safe to automate: it changes what is discoverable,\n * not what is installed. The rule that an installed plugin is never auto-updated\n * from a remote is untouched — that is the supply-chain boundary, and this is a\n * catalog listing.\n */\nconst MARKETPLACE_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction marketplaceTtlMs(): number {\n\tconst raw = Number(process.env.HOOCODE_MARKETPLACE_TTL_MS);\n\treturn Number.isFinite(raw) && raw >= 0 ? raw : MARKETPLACE_TTL_MS;\n}\n\ntype CacheMeta = Record<string, string>;\n\nfunction readCacheMeta(agentDir: string): CacheMeta {\n\tconst parsed = readJsonFile<CacheMeta>(marketplaceCacheMetaPath(agentDir));\n\treturn parsed && typeof parsed === \"object\" ? parsed : {};\n}\n\nfunction recordFetch(agentDir: string, url: string): void {\n\tconst meta = readCacheMeta(agentDir);\n\tmeta[url] = new Date().toISOString();\n\tconst file = marketplaceCacheMetaPath(agentDir);\n\tmkdirSync(path.dirname(file), { recursive: true });\n\twriteFileSync(file, `${JSON.stringify(meta, null, 2)}\\n`, \"utf8\");\n}\n\nfunction isStale(agentDir: string, url: string): boolean {\n\tconst ttl = marketplaceTtlMs();\n\tif (ttl === 0) return true;\n\tconst at = readCacheMeta(agentDir)[url];\n\tif (!at) return true;\n\tconst age = Date.now() - Date.parse(at);\n\treturn !Number.isFinite(age) || age > ttl;\n}\n\nfunction readJsonFile<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Bring an existing clone up to date, falling back to a fresh clone when the pull\n * cannot fast-forward (a force-pushed remote, or a corrupt cache).\n *\n * The re-clone goes to a temp directory and is swapped in only on success. An\n * earlier version deleted the cache first, which meant a refresh attempted while\n * offline destroyed a perfectly good index — the opposite of the \"offline is\n * non-fatal\" property the caller depends on.\n */\nasync function refreshClone(url: string, dir: string): Promise<{ code: number; stdout: string; stderr: string }> {\n\tconst pulled = await execGit([\"-C\", dir, \"pull\", \"--ff-only\"]);\n\tif (pulled.code === 0) return pulled;\n\n\tconst staging = mkdtempSync(path.join(os.tmpdir(), \"hoo-market-refresh-\"));\n\tconst target = path.join(staging, \"clone\");\n\tconst cloned = await cloneGitRepo(url, target);\n\tif (cloned.code !== 0) {\n\t\trmSync(staging, { recursive: true, force: true });\n\t\treturn cloned;\n\t}\n\trmSync(dir, { recursive: true, force: true });\n\tmkdirSync(path.dirname(dir), { recursive: true });\n\ttry {\n\t\trenameSync(target, dir);\n\t} catch {\n\t\tcpSync(target, dir, { recursive: true });\n\t}\n\trmSync(staging, { recursive: true, force: true });\n\treturn cloned;\n}\n\nexport interface EnsureMarketplacesOptions {\n\t/** Refresh regardless of the TTL. */\n\tforce?: boolean;\n}\n\n/**\n * Make sure every well-known marketplace index is cached and reasonably fresh.\n *\n * Clones what is missing and pulls what has gone stale. Without the TTL the\n * indices were fetched exactly once and never again, so a plugin added upstream\n * stayed permanently invisible — the inherit half of the system quietly stopped\n * inheriting. Offline is non-fatal: each failure is returned and search degrades\n * to whatever is already on disk.\n */\nexport async function ensureWellKnownMarketplaces(\n\tagentDir: string = getAgentDir(),\n\toptions: EnsureMarketplacesOptions = {},\n): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(wk.url, agentDir);\n\t\tconst present = existsSync(dir);\n\t\tif (present && !options.force && !isStale(agentDir, wk.url)) continue;\n\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tconst res = present ? await refreshClone(wk.url, dir) : await cloneGitRepo(wk.url, dir);\n\t\tif (res.code !== 0) {\n\t\t\t// A failed *refresh* keeps the stale copy: an out-of-date index is far\n\t\t\t// more useful than none, and the network may simply be down.\n\t\t\tif (!present) rmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t\tcontinue;\n\t\t}\n\t\trecordFetch(agentDir, wk.url);\n\t}\n\treturn errors;\n}\n\n/**\n * Refresh every cached index — well-known and user-added — ignoring the TTL.\n * The explicit half of the freshness story, for when a user knows the upstream\n * changed and does not want to wait out the interval.\n */\nexport async function refreshMarketplaces(\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): Promise<{ refreshed: string[]; errors: string[] }> {\n\tconst errors = await ensureWellKnownMarketplaces(agentDir, { force: true });\n\tconst refreshed = WELL_KNOWN_MARKETPLACES.filter((wk) => existsSync(marketplaceCacheDir(wk.url, agentDir))).map(\n\t\t(wk) => wk.name,\n\t);\n\n\t// User-added marketplaces are cached the same way when they came from git; a\n\t// local-path marketplace is read in place and has nothing to refresh.\n\tfor (const record of readMarketplaceRecords(cwd, agentDir)) {\n\t\tconst isCachedClone = record.dir.startsWith(marketplaceCacheRoot(agentDir));\n\t\tif (!isCachedClone || WELL_KNOWN_MARKETPLACES.some((wk) => wk.url === record.location)) continue;\n\t\tconst res = await refreshClone(record.location, record.dir);\n\t\tif (res.code !== 0) errors.push(`${record.location}: ${(res.stderr || res.stdout).trim()}`);\n\t\telse {\n\t\t\trecordFetch(agentDir, record.location);\n\t\t\trefreshed.push(record.location);\n\t\t}\n\t}\n\treturn { refreshed, errors };\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string, agentDir: string = getAgentDir()): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(wk.url, agentDir);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\t// The registry lives in the agent dir. Project-local ones are a migration\n\t// read-path: merged in, never written back, so an older setup keeps working.\n\tconst user = [\n\t\t...readMarketplaceStore(marketplaceStorePath(agentDir)),\n\t\t...legacyStorePaths(cwd).flatMap((p) => readMarketplaceStore(p)),\n\t];\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. `npm`/`archive` are listed but not installable. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\" | \"archive\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string, agentDir: string = getAgentDir()): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd, agentDir)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(\n\tcwd: string,\n\tname: string,\n\tagentDir: string = getAgentDir(),\n): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd, agentDir).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/**\n * Whether `name` is already installed, by either name it can go by.\n *\n * A marketplace entry name and the plugin's own manifest `name` are allowed to\n * differ — both vendors say so outright — and installs land in a directory named\n * for the *entry*, while discovery reports the *manifest* id. Matching ids alone\n * meant a plugin installed as `42crunch-api-security-testing` (manifest id\n * `api-security-testing`) never counted as installed, so every InstallPlugin\n * call for the catalog name re-cloned it.\n */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\tif (listInstalledPlugins(cwd, agentDir).some((p) => p.id === name)) return true;\n\treturn candidatePluginDirs(cwd, name, agentDir).some((p) => existsSync(p));\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Materialize exactly `sha` at `dest`, without cloning the repository's history.\n *\n * `git fetch --depth 1 <sha>` is what makes honoring a pin affordable: a pinned\n * commit is usually not the tip of any branch, so the alternative is a full\n * clone — 55s on a large plugin repo against ~1s here, on sources where two\n * thirds of the official Claude marketplace is pinned. Servers that refuse a\n * by-sha fetch (AWS CodeCommit and other non-GitHub hosts) fall back to a clone\n * deep enough to reach the commit, then check it out.\n */\nasync function fetchGitCommit(\n\turl: string,\n\tdest: string,\n\tsha: string,\n\tref?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tmkdirSync(dest, { recursive: true });\n\tconst init = await execGit([\"init\", \"--quiet\", dest]);\n\tif (init.code === 0) {\n\t\tconst remote = await execGit([\"-C\", dest, \"remote\", \"add\", \"origin\", url]);\n\t\tif (remote.code === 0) {\n\t\t\tconst fetched = await execGit([\"-C\", dest, \"fetch\", \"--quiet\", \"--depth\", \"1\", \"origin\", sha]);\n\t\t\tif (fetched.code === 0) {\n\t\t\t\tconst checkout = await execGit([\"-C\", dest, \"checkout\", \"--quiet\", \"FETCH_HEAD\"]);\n\t\t\t\tif (checkout.code === 0) return checkout;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fallback: a full clone (shallow cannot reach an arbitrary commit), then the\n\t// pin. `ref` narrows it when the entry named one.\n\trmSync(dest, { recursive: true, force: true });\n\tconst cloned = ref\n\t\t? await execGit([\"clone\", \"--branch\", ref, \"--\", url, dest])\n\t\t: await execGit([\"clone\", \"--\", url, dest]);\n\tif (cloned.code !== 0) return cloned;\n\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n}\n\n/**\n * Clone a git repository into `dest` at the requested pin.\n *\n * **`sha` outranks `ref`.** Both vendors define it that way (\"when both `ref`\n * and `sha` are set, the `sha` is the effective pin\"), and a marketplace that\n * ships both is stating the exact commit it vouches for — a tag can be moved or\n * re-pointed after the catalog pinned it, so preferring `ref` installed a\n * different commit than the index promised. It is not hypothetical: the official\n * catalog's `42crunch-api-security-testing` pins `30287f5` while its `v1.5.5`\n * tag now resolves to `faf5305`.\n *\n * Returns the git result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (sha) return fetchGitCommit(url, dest, sha, ref);\n\tif (ref) return execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOptions {\n\t/**\n\t * Where the plugin lands. Defaults to `user`. The human path (`/plugin\n\t * install`) asks; the autonomous path passes the `pluginInstallScope` setting.\n\t */\n\tscope?: PluginInstallScope;\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** The scope the plugin was installed at. */\n\tscope?: PluginInstallScope;\n\t/**\n\t * The plugin's own manifest id, when it differs from the marketplace entry\n\t * name it was installed by. This is the id `ListPlugins` reports, so the\n\t * caller can name both rather than leaving the model to reconcile them.\n\t */\n\tid?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/** Capability surfaces hoocode can actually load out of an installed plugin. */\nfunction loadableCapabilities(plugin: NormalizedPlugin): string[] {\n\treturn [\n\t\tplugin.skillsDir && \"skills\",\n\t\tplugin.commandsDir && \"commands\",\n\t\tplugin.agentsDir && \"subagents\",\n\t\tplugin.themesDir && \"themes\",\n\t\tplugin.hooks && \"hooks\",\n\t\tplugin.mcpServers && Object.keys(plugin.mcpServers).length > 0 && \"MCP servers\",\n\t\tplugin.providers?.length && \"providers\",\n\t].filter((c): c is string => typeof c === \"string\");\n}\n\n/**\n * Install an available plugin by name into the consumption home for\n * `options.scope` — `~/.agents/plugins/` for `user` (the default), or\n * `<cwd>/.agents/plugins/` for `project`.\n *\n * `user` is the default for a reason worth keeping in view: a plugin is portable\n * and reusable across projects, so installing into the repo hides the capability\n * from every other checkout and puts content into `git status` that has nothing\n * to do with the change being made. `project` inverts both of those on purpose —\n * it is how a team pins a plugin to a repository — so it is chosen, never\n * defaulted into.\n *\n * Copies local sources; clones git sources. Transparent + reversible by\n * construction — the plugin lands in a named directory and\n * {@link uninstallPlugin} removes it from either scope. Callers activate the\n * result (live activation via AgentSession.activatePlugin, or a reload).\n */\nexport async function installAvailablePlugin(\n\tcwd: string,\n\tname: string,\n\tagentDir: string = getAgentDir(),\n\toptions: InstallOptions = {},\n): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name, agentDir);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst scope = options.scope ?? \"user\";\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst home = installHomeForScope(scope, cwd, agentDir);\n\tconst dest = path.join(home, sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(home, { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else if (resolved.kind === \"npm\") {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tmessage:\n\t\t\t\t`\"${name}\" is published as an npm package (${resolved.spec}), a source type hoocode cannot install yet. ` +\n\t\t\t\t\"The entry is listed so you know it exists; install it through the vendor CLI, or ask the marketplace \" +\n\t\t\t\t\"for a git source.\",\n\t\t};\n\t} else {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tmessage:\n\t\t\t\t`\"${name}\" is published as a zip archive (${resolved.url}), a source type hoocode cannot install yet. ` +\n\t\t\t\t\"The entry is listed so you know it exists; install it through the vendor CLI, or ask the marketplace \" +\n\t\t\t\t\"for a git source.\",\n\t\t};\n\t}\n\n\t// Drop the clone's `.git`. Nothing reads it — an installed plugin is never\n\t// updated from its remote, by design (that is the supply-chain boundary) — and\n\t// keeping it actively breaks project scope: a repository nested inside the\n\t// user's repository is an embedded repo, so `git add` writes a gitlink instead\n\t// of the plugin's files and the install cannot actually be committed. Which is\n\t// the one thing project scope is for. Cheaper installs at user scope too.\n\trmSync(path.join(dest, \".git\"), { recursive: true, force: true });\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!hasAnyManifest(dest)) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory). Such a directory now\n\t\t// *parses* on its own — the vendors make the manifest optional — but the\n\t\t// entry's name and description live only in the marketplace index, and the\n\t\t// derived id would be the sanitized directory name. Synthesize a native\n\t\t// manifest so both survive the install.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\tconst capabilities = loadableCapabilities(parsed);\n\tlet message =\n\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t`(${parsed.supportPlatform.join(\", \")}) to ${dest} [${scope} scope]. ` +\n\t\t`Remove it with UninstallPlugin.`;\n\tif (scope === \"project\") {\n\t\t// Project scope puts the plugin in the working tree, which is the point —\n\t\t// but it is also a `git status` entry and, once committed, code that runs\n\t\t// for whoever clones next. Executable capabilities are the half that\n\t\t// matters: hoocode has no workspace-trust gate for `<cwd>/.agents/plugins`\n\t\t// (see docs/plugin-system-architecture.md §5.9), so a committed hook or MCP\n\t\t// server loads for collaborators without a prompt.\n\t\tconst executables = [parsed.hooks && \"hooks\", parsed.mcpServers && \"MCP servers\"].filter(Boolean);\n\t\tmessage += ` It is now part of the working tree — commit it to share it with collaborators.`;\n\t\tif (executables.length > 0) {\n\t\t\tmessage +=\n\t\t\t\t` It carries ${executables.join(\" and \")}, which run for anyone who clones the repository;` +\n\t\t\t\t` keep it out of version control if that is not what you want.`;\n\t\t}\n\t}\n\tif (parsed.id !== name) {\n\t\t// The entry name and the manifest name are allowed to differ, and the\n\t\t// difference is otherwise invisible until ListPlugins reports an id the\n\t\t// caller never asked for.\n\t\tmessage += ` Listed by ListPlugins as \"${parsed.id}\"; either name uninstalls it.`;\n\t}\n\tif (capabilities.length === 0) {\n\t\t// An install that adds nothing should not read as an install that worked.\n\t\t// Common in github/awesome-copilot, where most entries carry their content\n\t\t// in Copilot UI `extensions/` that hoocode has no analog for.\n\t\tconst surfaces = parsed.unsupportedSurfaces?.length\n\t\t\t? ` Unsupported surfaces present: ${parsed.unsupportedSurfaces.join(\", \")}.`\n\t\t\t: \"\";\n\t\tmessage +=\n\t\t\t\" Note: it contributes no capabilities hoocode can load\" +\n\t\t\t` (no skills, commands, subagents, hooks or MCP servers).${surfaces}`;\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tscope,\n\t\t...(parsed.id !== name ? { id: parsed.id } : {}),\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** True when `target` is `root` or sits inside it. */\nfunction isUnderDir(target: string, root: string): boolean {\n\tconst normalized = path.resolve(root);\n\tif (path.resolve(target) === normalized) return true;\n\treturn path.resolve(target).startsWith(normalized.endsWith(path.sep) ? normalized : `${normalized}${path.sep}`);\n}\n\n/**\n * Remove an installed or authored plugin from every location it could occupy:\n * the consumption home, both production homes, and the legacy project-local\n * directories that older versions wrote into.\n *\n * `name` is matched two ways, because a plugin has two names: the marketplace\n * entry name it was installed under (which is what the directory is called) and\n * the manifest id `ListPlugins` reports. Resolving only the first left a plugin\n * whose names differ impossible to remove through the tool surface — the id the\n * model was shown was the one uninstall did not accept.\n *\n * Removal by id stays inside {@link pluginHomeRoots}, so a plugin discovered in\n * `<cwd>/.claude/skills` — repository content the team committed, which hoocode\n * did not install — is never deleted out of the working tree.\n */\nexport function uninstallPlugin(cwd: string, name: string, agentDir: string = getAgentDir()): UninstallOutcome {\n\tconst homes = pluginHomeRoots(cwd, agentDir);\n\tconst byManifestId = listInstalledPlugins(cwd, agentDir)\n\t\t.filter((p) => p.id === name)\n\t\t.map((p) => p.root)\n\t\t.filter((root) => homes.some((home) => isUnderDir(root, home)));\n\tconst candidates = [...new Set([...candidatePluginDirs(cwd, name, agentDir), ...byManifestId])];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACN,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EAEpB,eAAe,EACf,cAAc,GACd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAGN,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACnB,MAAM,kBAAkB,CAAC;AAE1B;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,GAAW,EAAY;IAChD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,mBAAmB,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAAA,CACzG;AAED,6EAA6E;AAC7E,SAAS,qBAAqB,GAAW;IACxC,OAAO,aAAa,CAAC,IAAI,GAAG,CAAC,wBAAwB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAAA,CACzE;AAED,4GAA0G;AAC1G,MAAM,UAAU,wBAAwB,GAAsB;IAC7D,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,EAAE,qBAAqB,EAAE,EAAE,CAAC;AAAA,CACrE;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA+D;IAClG,EAAE,IAAI,EAAE,yBAAyB,EAAE,GAAG,EAAE,uDAAuD,EAAE;IACjG,8EAA8E;IAC9E,6DAA6D;IAC7D,wEAAsE;IACtE,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,2CAA2C,EAAE;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,QAAQ;IACR,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,0EAA0E;IAC1E,gFAAgF;IAChF,oEAAoE;IACpE,yEAAyE;IACzE,6EAA6E;IAC7E,8EAA4E;IAC5E,yEAAyE;IACzE,mCAAmC;IACnC,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,2CAA2C,EAAE,GAAG,EAAE,aAAa,EAAE;CACjG,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE/C,SAAS,gBAAgB,GAAW;IACnC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC;AAAA,CACnE;AAID,SAAS,aAAa,CAAC,QAAgB,EAAa;IACnD,MAAM,MAAM,GAAG,YAAY,CAAY,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3E,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AAAA,CAC1D;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,GAAW,EAAQ;IACzD,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAChD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAAA,CAClE;AAED,SAAS,OAAO,CAAC,QAAgB,EAAE,GAAW,EAAW;IACxD,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,MAAM,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,GAAG,CAAC;AAAA,CAC1C;AAED,SAAS,YAAY,CAAI,IAAY,EAAY;IAChD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AAAA,CACD;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,YAAY,CAC1B,GAAW,EACX,GAAW,EACX,GAAY,EACgD;IAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAErC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,0EAA0E;IAC1E,4EAA4E;IAC5E,4BAA4B;IAC5B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC;IACf,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC;QACJ,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC;AAAA,CACd;AAED;;;GAGG;AACH,KAAK,UAAU,aAAa,CAAC,GAAW,EAA+B;IACtE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5E,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC/B,OAAO,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAClD;AAED,mFAAmF;AACnF,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,GAAW,EAAoB;IACvE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACpE,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAAA,CACtD;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAQ,GAAW,WAAW,EAAE,EAAW;IACxF,OAAO,uBAAuB,CAAC,IAAI,CAClC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,CACvF,CAAC;AAAA,CACF;AAkBD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAChD,QAAQ,GAAW,WAAW,EAAE,EAChC,OAAO,GAA8B,EAAE,EACnB;IACpB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,YAAY,IAAI,uBAAuB,EAAE,CAAC;QAClE,MAAM,GAAG,GAAG,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9B,uEAAuE;QACvE,0EAA0E;QAC1E,yEAAyE;QACzE,2EAA2E;QAC3E,mCAAmC;QACnC,EAAE;QACF,8EAA4E;QAC5E,wEAAwE;QACxE,2EAA2E;QAC3E,yEAAyE;QACzE,oEAAoE;QACpE,IAAI,OAAO,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACxG,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,OAAO,GAAG,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC;YAAE,SAAS;QAEtE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACtG,uEAAuE;QACvE,wEAAsE;QACtE,8EAA4E;QAC5E,oEAAoE;QACpE,0EAA0E;QAC1E,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,QAA4B,CAAC;QACjC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACjD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACzB,QAAQ;oBACP,GAAG,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC,GAAG,4DAA4D;wBACzF,uEAAuE,CAAC;gBACzE,GAAG,GAAG,QAAQ,CAAC;YAChB,CAAC;QACF,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpB,uEAAuE;YACvE,6DAA6D;YAC7D,IAAI,CAAC,OAAO;gBAAE,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChE,SAAS;QACV,CAAC;QACD,IAAI,QAAQ;YAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,GAAW,EACX,QAAQ,GAAW,WAAW,EAAE,EACqB;IACrD,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAC9G,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CACf,CAAC;IAEF,6EAA6E;IAC7E,sEAAsE;IACtE,KAAK,MAAM,MAAM,IAAI,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5D,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,aAAa,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,QAAQ,CAAC;YAAE,SAAS;QACjG,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5D,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;aACvF,CAAC;YACL,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YACvC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAAA,CAC7B;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,QAAQ,GAAW,WAAW,EAAE,EAAuB;IAC1G,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,wBAAwB,EAAE,CAAC;IACvC,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE3C,yEAAyE;IACzE,mEAAmE;IACnE,KAAK,MAAM,EAAE,IAAI,uBAAuB,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;YAC5D,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACzC,CAAC;IACF,CAAC;IAED,0EAA0E;IAC1E,6EAA6E;IAC7E,MAAM,IAAI,GAAG;QACZ,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACvD,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;KAChE,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,OAAO,CAAC;AAAA,CACf;AAgBD,sGAAsG;AACtG,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,QAAQ,GAAW,WAAW,EAAE,EAAqB;IACtG,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACrB,GAAG,CAAC,IAAI,CAAC;gBACR,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,UAAU,EAAE,mBAAmB,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI;gBAC/D,eAAe,EAAE,MAAM,CAAC,IAAI;gBAC5B,eAAe,EAAE,MAAM,CAAC,IAAI;gBAC5B,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe;aAChE,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACX;AAED,oDAAoD;AACpD,MAAM,UAAU,mBAAmB,CAClC,GAAW,EACX,IAAY,EACZ,QAAQ,GAAW,WAAW,EAAE,EACF;IAC9B,OAAO,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAAA,CACxE;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,QAAQ,GAAW,WAAW,EAAE,EAAsB;IACvG,OAAO,eAAe,CAAC,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAAA,CACzD;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAY,EAAE,QAAQ,GAAW,WAAW,EAAE,EAAW;IACvG,IAAI,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChF,OAAO,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC3E;AAED,SAAS,OAAO,CAAC,IAAc,EAA6D;IAC3F,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAAA,CACvB,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAAA,CACvB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAAA,CAC1E,CAAC,CAAC;AAAA,CACH;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,cAAc,CAC5B,GAAW,EACX,IAAY,EACZ,GAAW,EACX,GAAY,EACgD;IAC5D,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3E,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;YAC/F,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACxB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;gBAClF,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;oBAAE,OAAO,QAAQ,CAAC;YAC1C,CAAC;QACF,CAAC;IACF,CAAC;IAED,8EAA8E;IAC9E,kDAAkD;IAClD,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,GAAG;QACjB,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC5D,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,OAAO,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;AAAA,CACzD;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,YAAY,CAC1B,GAAW,EACX,IAAY,EACZ,GAAY,EACZ,GAAY,EACgD;IAC5D,IAAI,GAAG;QAAE,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,GAAG;QAAE,OAAO,OAAO,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACrF,OAAO,OAAO,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAAA,CAC3D;AAED;;;GAGG;AACH,KAAK,UAAU,gBAAgB,CAC9B,GAAW,EACX,MAAc,EACd,IAAY,EACZ,GAAY,EACZ,GAAY,EAC6C;IACzD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACxE,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3D,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACjG,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iDAAiD,MAAM,EAAE,EAAE,CAAC;QAC1F,CAAC;QACD,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACrB,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AAAA,CACD;AA4BD,gFAAgF;AAChF,SAAS,oBAAoB,CAAC,MAAwB,EAAY;IACjE,OAAO;QACN,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,WAAW,IAAI,UAAU;QAChC,MAAM,CAAC,SAAS,IAAI,WAAW;QAC/B,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,KAAK,IAAI,OAAO;QACvB,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa;QAC/E,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,WAAW;QACvC,iFAA6E;QAC7E,2EAA2E;QAC3E,wEAAwE;QACxE,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,UAAU;KAC7C,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;AAAA,CACpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,GAAW,EACX,IAAY,EACZ,QAAQ,GAAW,WAAW,EAAE,EAChC,OAAO,GAAmB,EAAE,EACF;IAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,IAAI,4CAA4C,EAAE,CAAC;IAE9G,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC;IACtC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAErC,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,iCAAiC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACxF,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC/E,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9F,CAAC;IACF,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QACnD,CAAC;IACF,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACpC,OAAO;YACN,SAAS,EAAE,KAAK;YAChB,OAAO,EACN,IAAI,IAAI,qCAAqC,QAAQ,CAAC,IAAI,+CAA+C;gBACzG,uGAAuG;gBACvG,mBAAmB;SACpB,CAAC;IACH,CAAC;SAAM,CAAC;QACP,OAAO;YACN,SAAS,EAAE,KAAK;YAChB,OAAO,EACN,IAAI,IAAI,oCAAoC,QAAQ,CAAC,GAAG,+CAA+C;gBACvG,uGAAuG;gBACvG,mBAAmB;SACpB,CAAC;IACH,CAAC;IAED,6EAA2E;IAC3E,iFAA+E;IAC/E,2EAA2E;IAC3E,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAElE,IAAI,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,2EAA2E;QAC3E,0EAA0E;QAC1E,6EAAyE;QACzE,2EAA2E;QAC3E,wEAAwE;QACxE,wCAAwC;QACxC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QACtD,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,aAAa,CACZ,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,EACrC,GAAG,IAAI,CAAC,SAAS,CAChB,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EACtF,IAAI,EACJ,CAAC,CACD,IAAI,EACL,MAAM,CACN,CAAC;QACF,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,yBAAyB,IAAI,wCAAwC,EAAE,CAAC;IAC7G,CAAC;IACD,MAAM,YAAY,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,OAAO,GACV,cAAc,IAAI,uBAAuB,KAAK,CAAC,eAAe,IAAI;QAClE,IAAI,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,KAAK,WAAW;QACtE,iCAAiC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,4EAA0E;QAC1E,0EAA0E;QAC1E,qEAAqE;QACrE,2EAA2E;QAC3E,6EAA4E;QAC5E,mDAAmD;QACnD,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,EAAE,MAAM,CAAC,UAAU,IAAI,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClG,OAAO,IAAI,mFAAiF,CAAC;QAC7F,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO;gBACN,eAAe,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,mDAAmD;oBAC3F,+DAA+D,CAAC;QAClE,CAAC;IACF,CAAC;IACD,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACxB,sEAAsE;QACtE,wEAAwE;QACxE,0BAA0B;QAC1B,OAAO,IAAI,8BAA8B,MAAM,CAAC,EAAE,+BAA+B,CAAC;IACnF,CAAC;IACD,IAAI,MAAM,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;QACrC,6EAA6E;QAC7E,0EAA0E;QAC1E,yEAAyE;QACzE,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,OAAO,IAAI,cAAc,GAAG,uCAAqC,CAAC;IACnE,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,MAAM,CAAC,mBAAmB,EAAE,MAAM;YAClD,CAAC,CAAC,kCAAkC,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC5E,CAAC,CAAC,EAAE,CAAC;QACN,OAAO;YACN,wDAAwD;gBACxD,qEAAqE,QAAQ,EAAE,CAAC;IAClF,CAAC;IACD,OAAO;QACN,SAAS,EAAE,IAAI;QACf,IAAI;QACJ,KAAK;QACL,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,OAAO;KACP,CAAC;AAAA,CACF;AAOD,sDAAsD;AACtD,SAAS,UAAU,CAAC,MAAc,EAAE,IAAY,EAAW;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACrD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AAAA,CAChH;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,IAAY,EAAE,QAAQ,GAAW,WAAW,EAAE,EAAoB;IAC9G,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC;SACtD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;SAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,IAAI,qBAAqB,EAAE,CAAC;IACnG,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,IAAI,IAAI,EAAE,CAAC;AAAA,CACxD","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport { hasAnyManifest } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport {\n\tcandidatePluginDirs,\n\tinstallHomeForScope,\n\tmarketplaceCacheDir,\n\tmarketplaceCacheMetaPath,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n\ttype PluginInstallScope,\n\tpluginHomeRoots,\n\tsanitizeForDir,\n} from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/**\n * Marketplace registries older versions wrote into the working tree, newest\n * convention first. Still read so a user who added a marketplace before the\n * registry moved to the agent dir does not silently lose it; nothing writes here.\n */\nfunction legacyStorePaths(cwd: string): string[] {\n\treturn [path.join(cwd, \".agents\", \"marketplaces.json\"), path.join(cwd, \".hoocode\", \"marketplaces.json\")];\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nfunction defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and refreshed on a TTL thereafter. Refreshing the *index* only changes\n * what is discoverable; installed plugins are still never auto-updated, and\n * plugin sources inside the official index are sha-pinned.\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string; ref?: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n\t// GitHub's community index, `.github/plugin/marketplace.json`. Same owner as\n\t// copilot-plugins, which is what makes it a source-trust decision of the same\n\t// kind.\n\t//\n\t// Pinned to the `marketplace` branch, and that is load-bearing. On `main` a\n\t// `plugins/<name>/` directory holds only `plugin.json` and `README.md`; the\n\t// real content lives in the repository's top-level `agents/`, `skills/` and\n\t// `extensions/` trees and is named from the manifest under the repo's own\n\t// `extensions[\"com.github.awesome-copilot\"]` namespace. CI *materializes* those\n\t// references into each plugin directory and publishes the result on\n\t// `marketplace` (`eng/materialize-plugins.mjs`), which is the branch the\n\t// vendor's own `copilot plugin install` consumes. Cloning the default branch\n\t// installed the stub — every entry landed with no capabilities and said so,\n\t// which read as \"hoocode does not model this surface\" when it was really\n\t// \"hoocode fetched the wrong ref\".\n\t{ name: \"awesome-copilot\", url: \"https://github.com/github/awesome-copilot\", ref: \"marketplace\" },\n];\n\n/**\n * How long a cached marketplace index is considered fresh.\n *\n * Refreshing an *index* is safe to automate: it changes what is discoverable,\n * not what is installed. The rule that an installed plugin is never auto-updated\n * from a remote is untouched — that is the supply-chain boundary, and this is a\n * catalog listing.\n */\nconst MARKETPLACE_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction marketplaceTtlMs(): number {\n\tconst raw = Number(process.env.HOOCODE_MARKETPLACE_TTL_MS);\n\treturn Number.isFinite(raw) && raw >= 0 ? raw : MARKETPLACE_TTL_MS;\n}\n\ntype CacheMeta = Record<string, string>;\n\nfunction readCacheMeta(agentDir: string): CacheMeta {\n\tconst parsed = readJsonFile<CacheMeta>(marketplaceCacheMetaPath(agentDir));\n\treturn parsed && typeof parsed === \"object\" ? parsed : {};\n}\n\nfunction recordFetch(agentDir: string, url: string): void {\n\tconst meta = readCacheMeta(agentDir);\n\tmeta[url] = new Date().toISOString();\n\tconst file = marketplaceCacheMetaPath(agentDir);\n\tmkdirSync(path.dirname(file), { recursive: true });\n\twriteFileSync(file, `${JSON.stringify(meta, null, 2)}\\n`, \"utf8\");\n}\n\nfunction isStale(agentDir: string, url: string): boolean {\n\tconst ttl = marketplaceTtlMs();\n\tif (ttl === 0) return true;\n\tconst at = readCacheMeta(agentDir)[url];\n\tif (!at) return true;\n\tconst age = Date.now() - Date.parse(at);\n\treturn !Number.isFinite(age) || age > ttl;\n}\n\nfunction readJsonFile<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Bring an existing clone up to date, falling back to a fresh clone when the pull\n * cannot fast-forward (a force-pushed remote, or a corrupt cache).\n *\n * The re-clone goes to a temp directory and is swapped in only on success. An\n * earlier version deleted the cache first, which meant a refresh attempted while\n * offline destroyed a perfectly good index — the opposite of the \"offline is\n * non-fatal\" property the caller depends on.\n */\nasync function refreshClone(\n\turl: string,\n\tdir: string,\n\tref?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tconst pulled = await execGit([\"-C\", dir, \"pull\", \"--ff-only\"]);\n\tif (pulled.code === 0) return pulled;\n\n\tconst staging = mkdtempSync(path.join(os.tmpdir(), \"hoo-market-refresh-\"));\n\tconst target = path.join(staging, \"clone\");\n\t// The re-clone must land on the same ref the cache was created from, or a\n\t// pinned marketplace silently falls back to the default branch on its first\n\t// non-fast-forward refresh.\n\tconst cloned = await cloneGitRepo(url, target, ref);\n\tif (cloned.code !== 0) {\n\t\trmSync(staging, { recursive: true, force: true });\n\t\treturn cloned;\n\t}\n\trmSync(dir, { recursive: true, force: true });\n\tmkdirSync(path.dirname(dir), { recursive: true });\n\ttry {\n\t\trenameSync(target, dir);\n\t} catch {\n\t\tcpSync(target, dir, { recursive: true });\n\t}\n\trmSync(staging, { recursive: true, force: true });\n\treturn cloned;\n}\n\n/**\n * Branch name a cached clone is sitting on, or undefined when it is detached or\n * unreadable (a corrupt cache answers \"not the ref I want\", which is correct).\n */\nasync function checkedOutRef(dir: string): Promise<string | undefined> {\n\tconst res = await execGit([\"-C\", dir, \"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n\tif (res.code !== 0) return undefined;\n\tconst name = res.stdout.trim();\n\treturn name && name !== \"HEAD\" ? name : undefined;\n}\n\n/** Whether `ref` is still a branch on the remote. False on any network failure. */\nasync function remoteHasRef(url: string, ref: string): Promise<boolean> {\n\tconst res = await execGit([\"ls-remote\", \"--heads\", \"--\", url, ref]);\n\treturn res.code === 0 && res.stdout.trim().length > 0;\n}\n\n/**\n * Whether {@link ensureWellKnownMarketplaces} would actually reach the network.\n *\n * Exists so an interactive caller can say \"Refreshing marketplace indices…\"\n * before a multi-second clone and stay silent when the answer is already on\n * disk. Deliberately does not probe the remote — the ref-mismatch case needs\n * network to detect, so this under-reports rather than paying for a round trip\n * to decide whether to print a line.\n */\nexport function wellKnownMarketplacesAreStale(agentDir: string = getAgentDir()): boolean {\n\treturn WELL_KNOWN_MARKETPLACES.some(\n\t\t(wk) => !existsSync(marketplaceCacheDir(wk.url, agentDir)) || isStale(agentDir, wk.url),\n\t);\n}\n\nexport interface EnsureMarketplacesOptions {\n\t/** Refresh regardless of the TTL. */\n\tforce?: boolean;\n\t/**\n\t * Marketplaces to ensure; defaults to {@link WELL_KNOWN_MARKETPLACES}.\n\t *\n\t * A seam, and a deliberate one. The ref-pinning rules below — discard a cache\n\t * on the wrong branch, fall back when a pin is retired, and do not loop\n\t * between those two — are only exercised against a remote whose branches can\n\t * be moved and deleted, which no real marketplace will do on demand. Without\n\t * this the whole path could only be checked by hand against GitHub, and a\n\t * check that needs the network is a check that rots.\n\t */\n\tmarketplaces?: ReadonlyArray<{ name: string; url: string; ref?: string }>;\n}\n\n/**\n * Make sure every well-known marketplace index is cached and reasonably fresh.\n *\n * Clones what is missing and pulls what has gone stale. Without the TTL the\n * indices were fetched exactly once and never again, so a plugin added upstream\n * stayed permanently invisible — the inherit half of the system quietly stopped\n * inheriting. Offline is non-fatal: each failure is returned and search degrades\n * to whatever is already on disk.\n *\n * The returned strings are notes for the caller to surface, not fatal errors —\n * a retired ref that fell back to the default branch reports here too, because\n * the consequence (entries that install with no capabilities) is otherwise\n * invisible and inexplicable.\n */\nexport async function ensureWellKnownMarketplaces(\n\tagentDir: string = getAgentDir(),\n\toptions: EnsureMarketplacesOptions = {},\n): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of options.marketplaces ?? WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(wk.url, agentDir);\n\t\tlet present = existsSync(dir);\n\t\t// A cache is keyed by URL, not by ref, so a marketplace that gains (or\n\t\t// changes) a pin finds a clone of the wrong branch sitting in its slot. A\n\t\t// `pull --ff-only` there would faithfully keep updating the wrong branch\n\t\t// forever, so the mismatch is resolved the only way it can be: discard and\n\t\t// clone the ref that is asked for.\n\t\t//\n\t\t// Guarded on the ref still existing upstream, which is not paranoia — it is\n\t\t// what stops a loop. When a pin is retired, the fallback below leaves a\n\t\t// default-branch clone in the slot; that clone mismatches the pin forever,\n\t\t// so an unguarded check would discard and re-download it on every single\n\t\t// run. Probing costs one `ls-remote` and only on the mismatch path.\n\t\tif (present && wk.ref && (await checkedOutRef(dir)) !== wk.ref && (await remoteHasRef(wk.url, wk.ref))) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\tpresent = false;\n\t\t}\n\t\tif (present && !options.force && !isStale(agentDir, wk.url)) continue;\n\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tlet res = present ? await refreshClone(wk.url, dir, wk.ref) : await cloneGitRepo(wk.url, dir, wk.ref);\n\t\t// A pinned ref that cannot be cloned is the one failure worth retrying\n\t\t// differently. The pin names someone else's distribution branch — for\n\t\t// awesome-copilot, a name held in a workflow env var — so it can be renamed\n\t\t// or retired without warning, and a cold cache would then lose that\n\t\t// marketplace outright rather than degrade. A warm cache already degrades\n\t\t// correctly (the stale copy is kept below, and it is *built* content, which\n\t\t// beats an unbuilt default branch), so this applies to the cold case only.\n\t\tlet degraded: string | undefined;\n\t\tif (res.code !== 0 && !present && wk.ref) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\tconst fallback = await cloneGitRepo(wk.url, dir);\n\t\t\tif (fallback.code === 0) {\n\t\t\t\tdegraded =\n\t\t\t\t\t`${wk.name}: branch \"${wk.ref}\" is unavailable, so the default branch was used instead. ` +\n\t\t\t\t\t\"Plugin entries there may be unbuilt and install with no capabilities.\";\n\t\t\t\tres = fallback;\n\t\t\t}\n\t\t}\n\t\tif (res.code !== 0) {\n\t\t\t// A failed *refresh* keeps the stale copy: an out-of-date index is far\n\t\t\t// more useful than none, and the network may simply be down.\n\t\t\tif (!present) rmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (degraded) errors.push(degraded);\n\t\trecordFetch(agentDir, wk.url);\n\t}\n\treturn errors;\n}\n\n/**\n * Refresh every cached index — well-known and user-added — ignoring the TTL.\n * The explicit half of the freshness story, for when a user knows the upstream\n * changed and does not want to wait out the interval.\n */\nexport async function refreshMarketplaces(\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): Promise<{ refreshed: string[]; errors: string[] }> {\n\tconst errors = await ensureWellKnownMarketplaces(agentDir, { force: true });\n\tconst refreshed = WELL_KNOWN_MARKETPLACES.filter((wk) => existsSync(marketplaceCacheDir(wk.url, agentDir))).map(\n\t\t(wk) => wk.name,\n\t);\n\n\t// User-added marketplaces are cached the same way when they came from git; a\n\t// local-path marketplace is read in place and has nothing to refresh.\n\tfor (const record of readMarketplaceRecords(cwd, agentDir)) {\n\t\tconst isCachedClone = record.dir.startsWith(marketplaceCacheRoot(agentDir));\n\t\tif (!isCachedClone || WELL_KNOWN_MARKETPLACES.some((wk) => wk.url === record.location)) continue;\n\t\tconst res = await refreshClone(record.location, record.dir);\n\t\tif (res.code !== 0) errors.push(`${record.location}: ${(res.stderr || res.stdout).trim()}`);\n\t\telse {\n\t\t\trecordFetch(agentDir, record.location);\n\t\t\trefreshed.push(record.location);\n\t\t}\n\t}\n\treturn { refreshed, errors };\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string, agentDir: string = getAgentDir()): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(wk.url, agentDir);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\t// The registry lives in the agent dir. Project-local ones are a migration\n\t// read-path: merged in, never written back, so an older setup keeps working.\n\tconst user = [\n\t\t...readMarketplaceStore(marketplaceStorePath(agentDir)),\n\t\t...legacyStorePaths(cwd).flatMap((p) => readMarketplaceStore(p)),\n\t];\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. `npm`/`archive` are listed but not installable. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\" | \"archive\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string, agentDir: string = getAgentDir()): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd, agentDir)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(\n\tcwd: string,\n\tname: string,\n\tagentDir: string = getAgentDir(),\n): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd, agentDir).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/**\n * Whether `name` is already installed, by either name it can go by.\n *\n * A marketplace entry name and the plugin's own manifest `name` are allowed to\n * differ — both vendors say so outright — and installs land in a directory named\n * for the *entry*, while discovery reports the *manifest* id. Matching ids alone\n * meant a plugin installed as `42crunch-api-security-testing` (manifest id\n * `api-security-testing`) never counted as installed, so every InstallPlugin\n * call for the catalog name re-cloned it.\n */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\tif (listInstalledPlugins(cwd, agentDir).some((p) => p.id === name)) return true;\n\treturn candidatePluginDirs(cwd, name, agentDir).some((p) => existsSync(p));\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Materialize exactly `sha` at `dest`, without cloning the repository's history.\n *\n * `git fetch --depth 1 <sha>` is what makes honoring a pin affordable: a pinned\n * commit is usually not the tip of any branch, so the alternative is a full\n * clone — 55s on a large plugin repo against ~1s here, on sources where two\n * thirds of the official Claude marketplace is pinned. Servers that refuse a\n * by-sha fetch (AWS CodeCommit and other non-GitHub hosts) fall back to a clone\n * deep enough to reach the commit, then check it out.\n */\nasync function fetchGitCommit(\n\turl: string,\n\tdest: string,\n\tsha: string,\n\tref?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tmkdirSync(dest, { recursive: true });\n\tconst init = await execGit([\"init\", \"--quiet\", dest]);\n\tif (init.code === 0) {\n\t\tconst remote = await execGit([\"-C\", dest, \"remote\", \"add\", \"origin\", url]);\n\t\tif (remote.code === 0) {\n\t\t\tconst fetched = await execGit([\"-C\", dest, \"fetch\", \"--quiet\", \"--depth\", \"1\", \"origin\", sha]);\n\t\t\tif (fetched.code === 0) {\n\t\t\t\tconst checkout = await execGit([\"-C\", dest, \"checkout\", \"--quiet\", \"FETCH_HEAD\"]);\n\t\t\t\tif (checkout.code === 0) return checkout;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fallback: a full clone (shallow cannot reach an arbitrary commit), then the\n\t// pin. `ref` narrows it when the entry named one.\n\trmSync(dest, { recursive: true, force: true });\n\tconst cloned = ref\n\t\t? await execGit([\"clone\", \"--branch\", ref, \"--\", url, dest])\n\t\t: await execGit([\"clone\", \"--\", url, dest]);\n\tif (cloned.code !== 0) return cloned;\n\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n}\n\n/**\n * Clone a git repository into `dest` at the requested pin.\n *\n * **`sha` outranks `ref`.** Both vendors define it that way (\"when both `ref`\n * and `sha` are set, the `sha` is the effective pin\"), and a marketplace that\n * ships both is stating the exact commit it vouches for — a tag can be moved or\n * re-pointed after the catalog pinned it, so preferring `ref` installed a\n * different commit than the index promised. It is not hypothetical: the official\n * catalog's `42crunch-api-security-testing` pins `30287f5` while its `v1.5.5`\n * tag now resolves to `faf5305`.\n *\n * Returns the git result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (sha) return fetchGitCommit(url, dest, sha, ref);\n\tif (ref) return execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOptions {\n\t/**\n\t * Where the plugin lands. Defaults to `user`. The human path (`/plugin\n\t * install`) asks; the autonomous path passes the `pluginInstallScope` setting.\n\t */\n\tscope?: PluginInstallScope;\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** The scope the plugin was installed at. */\n\tscope?: PluginInstallScope;\n\t/**\n\t * The plugin's own manifest id, when it differs from the marketplace entry\n\t * name it was installed by. This is the id `ListPlugins` reports, so the\n\t * caller can name both rather than leaving the model to reconcile them.\n\t */\n\tid?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/** Capability surfaces hoocode can actually load out of an installed plugin. */\nfunction loadableCapabilities(plugin: NormalizedPlugin): string[] {\n\treturn [\n\t\tplugin.skillsDir && \"skills\",\n\t\tplugin.commandsDir && \"commands\",\n\t\tplugin.agentsDir && \"subagents\",\n\t\tplugin.themesDir && \"themes\",\n\t\tplugin.hooks && \"hooks\",\n\t\tplugin.mcpServers && Object.keys(plugin.mcpServers).length > 0 && \"MCP servers\",\n\t\tplugin.providers?.length && \"providers\",\n\t\t// Not loaded at session start like the rest — a canvas has no passive half —\n\t\t// but it is unquestionably something the install brought, and reporting it\n\t\t// is what stops a canvas plugin reading as an install that did nothing.\n\t\tplugin.canvasExtensions?.length && \"canvases\",\n\t].filter((c): c is string => typeof c === \"string\");\n}\n\n/**\n * Install an available plugin by name into the consumption home for\n * `options.scope` — `~/.agents/plugins/` for `user` (the default), or\n * `<cwd>/.agents/plugins/` for `project`.\n *\n * `user` is the default for a reason worth keeping in view: a plugin is portable\n * and reusable across projects, so installing into the repo hides the capability\n * from every other checkout and puts content into `git status` that has nothing\n * to do with the change being made. `project` inverts both of those on purpose —\n * it is how a team pins a plugin to a repository — so it is chosen, never\n * defaulted into.\n *\n * Copies local sources; clones git sources. Transparent + reversible by\n * construction — the plugin lands in a named directory and\n * {@link uninstallPlugin} removes it from either scope. Callers activate the\n * result (live activation via AgentSession.activatePlugin, or a reload).\n */\nexport async function installAvailablePlugin(\n\tcwd: string,\n\tname: string,\n\tagentDir: string = getAgentDir(),\n\toptions: InstallOptions = {},\n): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name, agentDir);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst scope = options.scope ?? \"user\";\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst home = installHomeForScope(scope, cwd, agentDir);\n\tconst dest = path.join(home, sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(home, { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else if (resolved.kind === \"npm\") {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tmessage:\n\t\t\t\t`\"${name}\" is published as an npm package (${resolved.spec}), a source type hoocode cannot install yet. ` +\n\t\t\t\t\"The entry is listed so you know it exists; install it through the vendor CLI, or ask the marketplace \" +\n\t\t\t\t\"for a git source.\",\n\t\t};\n\t} else {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tmessage:\n\t\t\t\t`\"${name}\" is published as a zip archive (${resolved.url}), a source type hoocode cannot install yet. ` +\n\t\t\t\t\"The entry is listed so you know it exists; install it through the vendor CLI, or ask the marketplace \" +\n\t\t\t\t\"for a git source.\",\n\t\t};\n\t}\n\n\t// Drop the clone's `.git`. Nothing reads it — an installed plugin is never\n\t// updated from its remote, by design (that is the supply-chain boundary) — and\n\t// keeping it actively breaks project scope: a repository nested inside the\n\t// user's repository is an embedded repo, so `git add` writes a gitlink instead\n\t// of the plugin's files and the install cannot actually be committed. Which is\n\t// the one thing project scope is for. Cheaper installs at user scope too.\n\trmSync(path.join(dest, \".git\"), { recursive: true, force: true });\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!hasAnyManifest(dest)) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory). Such a directory now\n\t\t// *parses* on its own — the vendors make the manifest optional — but the\n\t\t// entry's name and description live only in the marketplace index, and the\n\t\t// derived id would be the sanitized directory name. Synthesize a native\n\t\t// manifest so both survive the install.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\tconst capabilities = loadableCapabilities(parsed);\n\tlet message =\n\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t`(${parsed.supportPlatform.join(\", \")}) to ${dest} [${scope} scope]. ` +\n\t\t`Remove it with UninstallPlugin.`;\n\tif (scope === \"project\") {\n\t\t// Project scope puts the plugin in the working tree, which is the point —\n\t\t// but it is also a `git status` entry and, once committed, code that runs\n\t\t// for whoever clones next. Executable capabilities are the half that\n\t\t// matters: hoocode has no workspace-trust gate for `<cwd>/.agents/plugins`\n\t\t// (see docs/plugin-system-architecture.md §5.9), so a committed hook or MCP\n\t\t// server loads for collaborators without a prompt.\n\t\tconst executables = [parsed.hooks && \"hooks\", parsed.mcpServers && \"MCP servers\"].filter(Boolean);\n\t\tmessage += ` It is now part of the working tree — commit it to share it with collaborators.`;\n\t\tif (executables.length > 0) {\n\t\t\tmessage +=\n\t\t\t\t` It carries ${executables.join(\" and \")}, which run for anyone who clones the repository;` +\n\t\t\t\t` keep it out of version control if that is not what you want.`;\n\t\t}\n\t}\n\tif (parsed.id !== name) {\n\t\t// The entry name and the manifest name are allowed to differ, and the\n\t\t// difference is otherwise invisible until ListPlugins reports an id the\n\t\t// caller never asked for.\n\t\tmessage += ` Listed by ListPlugins as \"${parsed.id}\"; either name uninstalls it.`;\n\t}\n\tif (parsed.canvasExtensions?.length) {\n\t\t// A canvas is the one capability that is not loaded on activation: it has no\n\t\t// passive half, so it waits for someone to open it. Saying so here is the\n\t\t// difference between \"installed and inert\" and \"installed, run /canvas\".\n\t\tconst ids = parsed.canvasExtensions.map((extension) => extension.id).join(\", \");\n\t\tmessage += ` Canvases: ${ids} — open one with /canvas open <id>.`;\n\t}\n\tif (capabilities.length === 0) {\n\t\t// An install that adds nothing should not read as an install that worked.\n\t\tconst surfaces = parsed.unsupportedSurfaces?.length\n\t\t\t? ` Unsupported surfaces present: ${parsed.unsupportedSurfaces.join(\", \")}.`\n\t\t\t: \"\";\n\t\tmessage +=\n\t\t\t\" Note: it contributes no capabilities hoocode can load\" +\n\t\t\t` (no skills, commands, subagents, canvases, hooks or MCP servers).${surfaces}`;\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tscope,\n\t\t...(parsed.id !== name ? { id: parsed.id } : {}),\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** True when `target` is `root` or sits inside it. */\nfunction isUnderDir(target: string, root: string): boolean {\n\tconst normalized = path.resolve(root);\n\tif (path.resolve(target) === normalized) return true;\n\treturn path.resolve(target).startsWith(normalized.endsWith(path.sep) ? normalized : `${normalized}${path.sep}`);\n}\n\n/**\n * Remove an installed or authored plugin from every location it could occupy:\n * the consumption home, both production homes, and the legacy project-local\n * directories that older versions wrote into.\n *\n * `name` is matched two ways, because a plugin has two names: the marketplace\n * entry name it was installed under (which is what the directory is called) and\n * the manifest id `ListPlugins` reports. Resolving only the first left a plugin\n * whose names differ impossible to remove through the tool surface — the id the\n * model was shown was the one uninstall did not accept.\n *\n * Removal by id stays inside {@link pluginHomeRoots}, so a plugin discovered in\n * `<cwd>/.claude/skills` — repository content the team committed, which hoocode\n * did not install — is never deleted out of the working tree.\n */\nexport function uninstallPlugin(cwd: string, name: string, agentDir: string = getAgentDir()): UninstallOutcome {\n\tconst homes = pluginHomeRoots(cwd, agentDir);\n\tconst byManifestId = listInstalledPlugins(cwd, agentDir)\n\t\t.filter((p) => p.id === name)\n\t\t.map((p) => p.root)\n\t\t.filter((root) => homes.some((home) => isUnderDir(root, home)));\n\tconst candidates = [...new Set([...candidatePluginDirs(cwd, name, agentDir), ...byManifestId])];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listing.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,MAAM,CAO5E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"listing.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,MAAM,CAO5E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAWrE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,SAAS,EAAE,CAoBb;AAED,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,OAAO,GAAE,mBAAwB,GAC/B,OAAO,EAAE,CAuBX","sourcesContent":["/**\n * Row shapes for the plugin listings, shared by every surface that prints one.\n *\n * `/plugin list`, `SearchPlugins` and `ListPlugins` describe the same objects,\n * and each used to build its own `name [a, b] — description` string, so the same\n * plugin printed three ways and the model's view never quite matched the user's.\n * They all go through here now; only the styling and the width differ.\n */\n\nimport type { ListGroup, ListRow } from \"../../format-list.js\";\nimport type { AvailablePlugin } from \"./install.js\";\nimport { pluginScopeOf } from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\n\n/** Human-readable source for an available plugin (a URL, a path, an npm spec). */\nexport function formatPluginSource(source: AvailablePlugin[\"source\"]): string {\n\tif (typeof source === \"string\") return source;\n\tif (source.source === \"npm\") return source.package;\n\t// `archive` carries a url like `url` does; without this it printed\n\t// `https://…/p.zip/undefined` through the git-subdir branch below.\n\tif (source.source === \"url\" || source.source === \"archive\") return source.url;\n\treturn `${source.url}/${source.path}`;\n}\n\n/**\n * Capability classes a plugin actually ships.\n *\n * Covers every capability `NormalizedPlugin` can carry — themes and providers\n * included, which the old `ListPlugins` inline list omitted, so a plugin that\n * shipped only a theme or only a provider reported no capabilities at all.\n */\nexport function pluginCapabilities(plugin: NormalizedPlugin): string[] {\n\treturn [\n\t\tplugin.skillsDir && \"skills\",\n\t\tplugin.commandsDir && \"commands\",\n\t\tplugin.agentsDir && \"agents\",\n\t\tplugin.themesDir && \"themes\",\n\t\tplugin.hooks && \"hooks\",\n\t\tplugin.mcpServers && \"mcp\",\n\t\tplugin.providers?.length && \"providers\",\n\t\tplugin.canvasExtensions?.length && \"canvases\",\n\t].filter((value): value is string => typeof value === \"string\");\n}\n\n/**\n * Available plugins, grouped by the marketplace offering them.\n *\n * Grouping is what makes provenance legible: a flat list cannot answer \"where\n * would this come from if I installed it\", which is the one question that\n * matters at a trust boundary. Marketplace order is preserved from the caller.\n */\nexport function availablePluginGroups(\n\tplugins: readonly AvailablePlugin[],\n\toptions: { installed?: ReadonlySet<string> } = {},\n): ListGroup[] {\n\tconst groups = new Map<string, ListRow[]>();\n\tfor (const plugin of plugins) {\n\t\tconst rows = groups.get(plugin.marketplaceName) ?? [];\n\t\t// npm and archive entries are catalogued but not fetchable here, and saying\n\t\t// so in the row is cheaper than an InstallPlugin round trip that only ends\n\t\t// in the same message.\n\t\tconst installable = plugin.sourceKind !== \"npm\" && plugin.sourceKind !== \"archive\";\n\t\trows.push({\n\t\t\tname: plugin.name,\n\t\t\tmarker: options.installed?.has(plugin.name) ? \"✓\" : undefined,\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\tinstallable ? plugin.sourceKind : `${plugin.sourceKind}, not installable`,\n\t\t\t],\n\t\t\tdetail: plugin.description ?? formatPluginSource(plugin.source),\n\t\t});\n\t\tgroups.set(plugin.marketplaceName, rows);\n\t}\n\treturn Array.from(groups, ([title, rows]) => ({ title, rows }));\n}\n\nexport interface InstalledRowOptions {\n\t/** Working directory, used to place each plugin in a scope. */\n\tcwd?: string;\n\t/** Whether `cwd` is a trusted workspace; decides if executables actually load. */\n\ttrusted?: boolean;\n}\n\n/** Installed plugins as rows: id@version, platforms, format, scope, capabilities. */\nexport function installedPluginRows(\n\tplugins: readonly NormalizedPlugin[],\n\toptions: InstalledRowOptions = {},\n): ListRow[] {\n\treturn plugins.map((plugin) => {\n\t\tconst capabilities = pluginCapabilities(plugin);\n\t\t// Scope answers \"why is this loaded, and who else gets it\" — the plugin's\n\t\t// own path always knew, and nothing surfaced it.\n\t\tconst scope = options.cwd ? pluginScopeOf(plugin.root, options.cwd) : undefined;\n\t\t// A working-tree plugin's executables load only in a trusted workspace, so\n\t\t// listing \"hooks\" unqualified would overstate what is actually running.\n\t\tconst withheld = scope && scope !== \"user\" && !options.trusted && (plugin.hooks || plugin.mcpServers);\n\t\treturn {\n\t\t\tname: `${plugin.id}${plugin.version ? `@${plugin.version}` : \"\"}`,\n\t\t\t// The manifest format is parenthesised because it is usually the same\n\t\t\t// word as the platform (\"claude/github · claude\" reads as a stutter).\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\t`(${plugin.format})`,\n\t\t\t\t...(scope ? [scope] : []),\n\t\t\t\tcapabilities.join(\", \") || \"no capabilities\",\n\t\t\t\t...(withheld ? [\"hooks/mcp withheld, workspace not trusted\"] : []),\n\t\t\t],\n\t\t\tdetail: plugin.description,\n\t\t};\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listing.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,kFAAkF;AAClF,MAAM,UAAU,kBAAkB,CAAC,MAAiC,EAAU;IAC7E,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IACnD,mEAAmE;IACnE,qEAAmE;IACnE,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IAC9E,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;AAAA,CACtC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAwB,EAAY;IACtE,OAAO;QACN,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,WAAW,IAAI,UAAU;QAChC,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,KAAK,IAAI,OAAO;QACvB,MAAM,CAAC,UAAU,IAAI,KAAK;QAC1B,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,WAAW;
|
|
1
|
+
{"version":3,"file":"listing.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Row shapes for the plugin listings, shared by every surface that prints one.\n *\n * `/plugin list`, `SearchPlugins` and `ListPlugins` describe the same objects,\n * and each used to build its own `name [a, b] — description` string, so the same\n * plugin printed three ways and the model's view never quite matched the user's.\n * They all go through here now; only the styling and the width differ.\n */\n\nimport type { ListGroup, ListRow } from \"../../format-list.js\";\nimport type { AvailablePlugin } from \"./install.js\";\nimport { pluginScopeOf } from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\n\n/** Human-readable source for an available plugin (a URL, a path, an npm spec). */\nexport function formatPluginSource(source: AvailablePlugin[\"source\"]): string {\n\tif (typeof source === \"string\") return source;\n\tif (source.source === \"npm\") return source.package;\n\t// `archive` carries a url like `url` does; without this it printed\n\t// `https://…/p.zip/undefined` through the git-subdir branch below.\n\tif (source.source === \"url\" || source.source === \"archive\") return source.url;\n\treturn `${source.url}/${source.path}`;\n}\n\n/**\n * Capability classes a plugin actually ships.\n *\n * Covers every capability `NormalizedPlugin` can carry — themes and providers\n * included, which the old `ListPlugins` inline list omitted, so a plugin that\n * shipped only a theme or only a provider reported no capabilities at all.\n */\nexport function pluginCapabilities(plugin: NormalizedPlugin): string[] {\n\treturn [\n\t\tplugin.skillsDir && \"skills\",\n\t\tplugin.commandsDir && \"commands\",\n\t\tplugin.agentsDir && \"agents\",\n\t\tplugin.themesDir && \"themes\",\n\t\tplugin.hooks && \"hooks\",\n\t\tplugin.mcpServers && \"mcp\",\n\t\tplugin.providers?.length && \"providers\",\n\t\tplugin.canvasExtensions?.length && \"canvases\",\n\t].filter((value): value is string => typeof value === \"string\");\n}\n\n/**\n * Available plugins, grouped by the marketplace offering them.\n *\n * Grouping is what makes provenance legible: a flat list cannot answer \"where\n * would this come from if I installed it\", which is the one question that\n * matters at a trust boundary. Marketplace order is preserved from the caller.\n */\nexport function availablePluginGroups(\n\tplugins: readonly AvailablePlugin[],\n\toptions: { installed?: ReadonlySet<string> } = {},\n): ListGroup[] {\n\tconst groups = new Map<string, ListRow[]>();\n\tfor (const plugin of plugins) {\n\t\tconst rows = groups.get(plugin.marketplaceName) ?? [];\n\t\t// npm and archive entries are catalogued but not fetchable here, and saying\n\t\t// so in the row is cheaper than an InstallPlugin round trip that only ends\n\t\t// in the same message.\n\t\tconst installable = plugin.sourceKind !== \"npm\" && plugin.sourceKind !== \"archive\";\n\t\trows.push({\n\t\t\tname: plugin.name,\n\t\t\tmarker: options.installed?.has(plugin.name) ? \"✓\" : undefined,\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\tinstallable ? plugin.sourceKind : `${plugin.sourceKind}, not installable`,\n\t\t\t],\n\t\t\tdetail: plugin.description ?? formatPluginSource(plugin.source),\n\t\t});\n\t\tgroups.set(plugin.marketplaceName, rows);\n\t}\n\treturn Array.from(groups, ([title, rows]) => ({ title, rows }));\n}\n\nexport interface InstalledRowOptions {\n\t/** Working directory, used to place each plugin in a scope. */\n\tcwd?: string;\n\t/** Whether `cwd` is a trusted workspace; decides if executables actually load. */\n\ttrusted?: boolean;\n}\n\n/** Installed plugins as rows: id@version, platforms, format, scope, capabilities. */\nexport function installedPluginRows(\n\tplugins: readonly NormalizedPlugin[],\n\toptions: InstalledRowOptions = {},\n): ListRow[] {\n\treturn plugins.map((plugin) => {\n\t\tconst capabilities = pluginCapabilities(plugin);\n\t\t// Scope answers \"why is this loaded, and who else gets it\" — the plugin's\n\t\t// own path always knew, and nothing surfaced it.\n\t\tconst scope = options.cwd ? pluginScopeOf(plugin.root, options.cwd) : undefined;\n\t\t// A working-tree plugin's executables load only in a trusted workspace, so\n\t\t// listing \"hooks\" unqualified would overstate what is actually running.\n\t\tconst withheld = scope && scope !== \"user\" && !options.trusted && (plugin.hooks || plugin.mcpServers);\n\t\treturn {\n\t\t\tname: `${plugin.id}${plugin.version ? `@${plugin.version}` : \"\"}`,\n\t\t\t// The manifest format is parenthesised because it is usually the same\n\t\t\t// word as the platform (\"claude/github · claude\" reads as a stutter).\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\t`(${plugin.format})`,\n\t\t\t\t...(scope ? [scope] : []),\n\t\t\t\tcapabilities.join(\", \") || \"no capabilities\",\n\t\t\t\t...(withheld ? [\"hooks/mcp withheld, workspace not trusted\"] : []),\n\t\t\t],\n\t\t\tdetail: plugin.description,\n\t\t};\n\t});\n}\n"]}
|
|
@@ -40,6 +40,13 @@ export interface PluginProvider {
|
|
|
40
40
|
name: string;
|
|
41
41
|
config: ProviderConfig;
|
|
42
42
|
}
|
|
43
|
+
/** One canvas extension shipped inside a plugin: its id and the directory holding `extension.mjs`. */
|
|
44
|
+
export interface PluginCanvasExtension {
|
|
45
|
+
/** Directory name, or the plugin id when the plugin root is itself the extension. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Absolute path to the extension directory. */
|
|
48
|
+
dir: string;
|
|
49
|
+
}
|
|
43
50
|
/** Normalized, format-agnostic representation of a plugin. */
|
|
44
51
|
export interface NormalizedPlugin {
|
|
45
52
|
/** Stable identifier (manifest `name`). */
|
|
@@ -72,6 +79,17 @@ export interface NormalizedPlugin {
|
|
|
72
79
|
mcpServers?: Record<string, unknown>;
|
|
73
80
|
/** Native-only: providers to register. */
|
|
74
81
|
providers?: PluginProvider[];
|
|
82
|
+
/**
|
|
83
|
+
* Canvas extensions this plugin ships, resolved to their directories.
|
|
84
|
+
*
|
|
85
|
+
* Unlike every other capability here this one is not loaded into the session:
|
|
86
|
+
* a canvas has no passive half (see `docs/canvas-extensions-design.md` §5.1),
|
|
87
|
+
* so the plugin contributes *search roots* that `/canvas` lists and opens on
|
|
88
|
+
* demand rather than anything that runs at load. Resolving them at parse time
|
|
89
|
+
* is what lets `ListPlugins`, the install summary and `/canvas` all agree on
|
|
90
|
+
* what a plugin actually brought.
|
|
91
|
+
*/
|
|
92
|
+
canvasExtensions?: PluginCanvasExtension[];
|
|
75
93
|
/**
|
|
76
94
|
* Manifest keys this adapter does not model, preserved verbatim.
|
|
77
95
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACtC,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;AAEzE,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAChC,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,gBAAgB,GAAG,IAAI,CAE7G","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n\t/**\n\t * Manifest keys this adapter does not model, preserved verbatim.\n\t *\n\t * Load-bearing, not diagnostic: an edit re-emits the manifest, so anything not\n\t * carried here is silently dropped from a plugin we did not write in full.\n\t * Vendor schemas grow independently of ours (Copilot has `extensions` and\n\t * `lspServers`; Claude keeps adding component keys), and losing one on a\n\t * plugin destined for someone else's ecosystem is worse than failing to read\n\t * it.\n\t */\n\tunknownFields?: Record<string, unknown>;\n\t/**\n\t * On-disk surfaces present but unhandled, e.g. `[\".lsp.json\", \"monitors/\"]`.\n\t * Reported rather than implemented: the files stay on disk untouched, and\n\t * `ListPlugins` says so, which turns the next round of vendor drift into a\n\t * visible signal instead of a silent gap.\n\t */\n\tunsupportedSurfaces?: string[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string, options?: { requireManifest?: boolean }): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root, options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACtC,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;AAEzE,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,sGAAsG;AACtG,MAAM,WAAW,qBAAqB;IACrC,qFAAqF;IACrF,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAChC,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAC3C;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,gBAAgB,GAAG,IAAI,CAE7G","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** One canvas extension shipped inside a plugin: its id and the directory holding `extension.mjs`. */\nexport interface PluginCanvasExtension {\n\t/** Directory name, or the plugin id when the plugin root is itself the extension. */\n\tid: string;\n\t/** Absolute path to the extension directory. */\n\tdir: string;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n\t/**\n\t * Canvas extensions this plugin ships, resolved to their directories.\n\t *\n\t * Unlike every other capability here this one is not loaded into the session:\n\t * a canvas has no passive half (see `docs/canvas-extensions-design.md` §5.1),\n\t * so the plugin contributes *search roots* that `/canvas` lists and opens on\n\t * demand rather than anything that runs at load. Resolving them at parse time\n\t * is what lets `ListPlugins`, the install summary and `/canvas` all agree on\n\t * what a plugin actually brought.\n\t */\n\tcanvasExtensions?: PluginCanvasExtension[];\n\t/**\n\t * Manifest keys this adapter does not model, preserved verbatim.\n\t *\n\t * Load-bearing, not diagnostic: an edit re-emits the manifest, so anything not\n\t * carried here is silently dropped from a plugin we did not write in full.\n\t * Vendor schemas grow independently of ours (Copilot has `extensions` and\n\t * `lspServers`; Claude keeps adding component keys), and losing one on a\n\t * plugin destined for someone else's ecosystem is worse than failing to read\n\t * it.\n\t */\n\tunknownFields?: Record<string, unknown>;\n\t/**\n\t * On-disk surfaces present but unhandled, e.g. `[\".lsp.json\", \"monitors/\"]`.\n\t * Reported rather than implemented: the files stay on disk untouched, and\n\t * `ListPlugins` says so, which turns the next round of vendor drift into a\n\t * visible signal instead of a silent gap.\n\t */\n\tunsupportedSurfaces?: string[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string, options?: { requireManifest?: boolean }): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root, options);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAqG5D;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAuC,EAA2B;IAC9G,OAAO,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAAA,CAC7C","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** One canvas extension shipped inside a plugin: its id and the directory holding `extension.mjs`. */\nexport interface PluginCanvasExtension {\n\t/** Directory name, or the plugin id when the plugin root is itself the extension. */\n\tid: string;\n\t/** Absolute path to the extension directory. */\n\tdir: string;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n\t/**\n\t * Canvas extensions this plugin ships, resolved to their directories.\n\t *\n\t * Unlike every other capability here this one is not loaded into the session:\n\t * a canvas has no passive half (see `docs/canvas-extensions-design.md` §5.1),\n\t * so the plugin contributes *search roots* that `/canvas` lists and opens on\n\t * demand rather than anything that runs at load. Resolving them at parse time\n\t * is what lets `ListPlugins`, the install summary and `/canvas` all agree on\n\t * what a plugin actually brought.\n\t */\n\tcanvasExtensions?: PluginCanvasExtension[];\n\t/**\n\t * Manifest keys this adapter does not model, preserved verbatim.\n\t *\n\t * Load-bearing, not diagnostic: an edit re-emits the manifest, so anything not\n\t * carried here is silently dropped from a plugin we did not write in full.\n\t * Vendor schemas grow independently of ours (Copilot has `extensions` and\n\t * `lspServers`; Claude keeps adding component keys), and losing one on a\n\t * plugin destined for someone else's ecosystem is worse than failing to read\n\t * it.\n\t */\n\tunknownFields?: Record<string, unknown>;\n\t/**\n\t * On-disk surfaces present but unhandled, e.g. `[\".lsp.json\", \"monitors/\"]`.\n\t * Reported rather than implemented: the files stay on disk untouched, and\n\t * `ListPlugins` says so, which turns the next round of vendor drift into a\n\t * visible signal instead of a silent gap.\n\t */\n\tunsupportedSurfaces?: string[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string, options?: { requireManifest?: boolean }): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root, options);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packaging.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/packaging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAoB,KAAK,cAAc,EAA0B,MAAM,+BAA+B,CAAC;AAC9G,OAAO,EAAwC,KAAK,UAAU,EAAgC,MAAM,YAAY,CAAC;AAEjH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAKN,KAAK,YAAY,EAEjB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAK3D,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,cAAc,CAAC;IACzB,yEAAyE;IACzE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAQ/E;AA4GD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAEjE;AAwBD;;;GAGG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,CAAC,EAAE,cAAc,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;CAAO,GAC5E,OAAO,CAAC,aAAa,CAAC,CAwFxB;AAiBD,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,GACpB,WAAW,CAyDb;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAOxE","sourcesContent":["/**\n * Packaging — everything that has to happen before a plugin can be published,\n * and nothing that publishes it.\n *\n * The split is the point. Preparing a release is mechanical: run the gates at\n * their strictest, write a README, produce the marketplace index entry. All of\n * that is automated. *Publishing* — pushing an artifact into a marketplace that\n * other agents install from autonomously — stays a human act, because an agent\n * that can do it unattended is a supply-chain compromise primitive. See\n * docs/plugin-system-architecture.md §3.2–§3.3.\n *\n * Packaging works **in place**, in the plugin's production home. There is no\n * separate output location: by the time a plugin reaches here it is already in\n * its target platform's layout, and copying it somewhere else would just create\n * a fourth directory role to confuse with the other three (§8.3).\n *\n * The two platforms are not symmetric here, and the lane is built for the harder\n * one. A `claude` plugin is already live locally from `~/.claude/skills/<id>/`,\n * so publishing is optional and additive. For `github`, local installs are\n * deprecated — publishing is the *only* route into the ecosystem — so the\n * GitHub flow (a PR against a marketplace repo's index) is the primary case and\n * the Claude submission is the variant.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { CapabilityDoc } from \"../../capabilities/registry.js\";\nimport { isPluginPlatform, type PluginPlatform, resolvePluginPlatforms } from \"./formats/platform-targets.js\";\nimport { formatGateFindings, type GateFinding, type GateResult, runStaticGates, withFindings } from \"./gates.js\";\nimport { productionRoot } from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { runSmokeGate } from \"./smoke.js\";\nimport {\n\tdistractorCandidates,\n\tloadTriggerCases,\n\townCandidates,\n\trunTriggerEval,\n\ttype TriggerJudge,\n\ttriggerFindings,\n} from \"./trigger-eval.js\";\n\n/**\n * Where packaging records what it produced, at the plugin root.\n *\n * A dotfile beside the existing `.authored.json` provenance marker rather than a\n * visible `marketplace-entry.json`: the plugin directory is the thing that gets\n * copied into a marketplace repo, and a build record is not part of the artifact.\n */\nexport const PUBLISH_RECORD_FILE = \".hoocode-publish.json\";\n\n/** Placeholder `source` for a plugin that is not in a git checkout — deliberately unusable as-is. */\nconst SOURCE_PLACEHOLDER = \"<REPLACE: git URL, or path within the marketplace repo>\";\n\n/** A `marketplace.json` `plugins[]` entry, ready to paste into an index. */\nexport interface MarketplaceEntry {\n\tname: string;\n\tdescription?: string;\n\tversion?: string;\n\tsource: string | { source: \"git\"; url: string } | { source: \"github\"; repo: string };\n}\n\nexport interface PublishRecord {\n\tpackagedAt: string;\n\tplatform: PluginPlatform;\n\tentry: MarketplaceEntry;\n\t/** Whether the gates were green when this record was written. */\n\tok: boolean;\n}\n\nexport interface PackageResult {\n\tok: boolean;\n\tdir: string;\n\tplatform: PluginPlatform;\n\t/** Files packaging created or refreshed, relative to the plugin root. */\n\twritten: string[];\n\tentry: MarketplaceEntry;\n\tevaluation: GateResult;\n\t/** Human-facing next steps; deliberately instructions, not actions. */\n\tinstructions: string;\n}\n\n/**\n * Which ecosystem this plugin is being packaged for.\n *\n * Where it lives is the strongest evidence — the production homes are per\n * platform, so a plugin under `~/.agents/publish/github/` is a github artifact\n * whatever its manifests say. A plugin outside a production home (installed, or\n * authored by an older version) falls back to what it declares, then to the\n * session default.\n */\nexport function resolvePackagePlatform(plugin: NormalizedPlugin): PluginPlatform {\n\tconst root = path.resolve(plugin.root);\n\tfor (const platform of [\"claude\", \"github\"] as const) {\n\t\tconst home = path.resolve(productionRoot(platform));\n\t\tif (root === home || root.startsWith(`${home}${path.sep}`)) return platform;\n\t}\n\tconst declared = plugin.supportPlatform.filter(isPluginPlatform);\n\treturn declared[0] ?? resolvePluginPlatforms()[0];\n}\n\n/** Best-effort origin URL of the repo containing `dir`, if it is in one. */\nfunction gitOriginUrl(dir: string): string | undefined {\n\ttry {\n\t\tconst url = execFileSync(\"git\", [\"-C\", dir, \"remote\", \"get-url\", \"origin\"], {\n\t\t\tencoding: \"utf8\",\n\t\t\ttimeout: 5_000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t}).trim();\n\t\treturn url || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction capabilityLines(plugin: NormalizedPlugin): string[] {\n\tconst lines: string[] = [];\n\tconst add = (label: string, present: unknown) => {\n\t\tif (present) lines.push(`- ${label}`);\n\t};\n\tadd(\"Skills\", plugin.skillsDir);\n\tadd(\"Slash commands\", plugin.commandsDir);\n\tadd(\"Subagents\", plugin.agentsDir);\n\tadd(\"Hooks\", plugin.hooks);\n\tadd(\"MCP servers\", plugin.mcpServers);\n\treturn lines;\n}\n\nfunction installSection(plugin: NormalizedPlugin, platform: PluginPlatform): string[] {\n\tif (platform === \"claude\") {\n\t\treturn [\n\t\t\t\"## Install\",\n\t\t\t\"\",\n\t\t\t\"```bash\",\n\t\t\t\"claude plugin marketplace add <marketplace-repo>\",\n\t\t\t`claude plugin install ${plugin.id}@<marketplace>`,\n\t\t\t\"```\",\n\t\t\t\"\",\n\t\t\t`Or copy this directory to \\`~/.claude/skills/${plugin.id}/\\` — a folder there`,\n\t\t\t\"carrying a `.claude-plugin/plugin.json` loads with no install step.\",\n\t\t];\n\t}\n\treturn [\n\t\t\"## Install\",\n\t\t\"\",\n\t\t\"```bash\",\n\t\t\"copilot plugin marketplace add <marketplace-repo>\",\n\t\t`copilot plugin install ${plugin.id}@<marketplace>`,\n\t\t\"```\",\n\t];\n}\n\n/**\n * Generate a README from what the plugin actually contains.\n *\n * Never overwrites: a hand-written README is the author's description of their\n * own plugin, and a generated one derived from the directory listing is strictly\n * worse. This only fills the gap where there is nothing at all.\n */\nfunction writeReadme(plugin: NormalizedPlugin, platform: PluginPlatform): string | undefined {\n\tconst file = path.join(plugin.root, \"README.md\");\n\tif (existsSync(file)) return undefined;\n\n\tconst capabilities = capabilityLines(plugin);\n\tconst body = [\n\t\t`# ${plugin.id}`,\n\t\t\"\",\n\t\tplugin.description ?? \"_No description provided._\",\n\t\t\"\",\n\t\t...(capabilities.length > 0 ? [\"## Provides\", \"\", ...capabilities, \"\"] : []),\n\t\t...installSection(plugin, platform),\n\t\t\"\",\n\t\t...(plugin.unsupportedSurfaces?.length\n\t\t\t? [\n\t\t\t\t\t\"## Notes\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t`This plugin ships surfaces hoocode does not load: ${plugin.unsupportedSurfaces.join(\", \")}.`,\n\t\t\t\t\t\"They are preserved as-is and are the target platform's concern.\",\n\t\t\t\t\t\"\",\n\t\t\t\t]\n\t\t\t: []),\n\t].join(\"\\n\");\n\n\twriteFileSync(file, `${body.trimEnd()}\\n`, \"utf8\");\n\treturn \"README.md\";\n}\n\n/**\n * Build the marketplace index entry.\n *\n * `source` is where the *published* plugin will live, which packaging cannot\n * know: the artifact is on this machine and has not been pushed anywhere. When\n * the plugin sits in a git checkout its origin is a plausible starting point;\n * otherwise the field is a clearly-marked placeholder for the human to fill.\n * Guessing here would produce an index entry that resolves to nothing, which is\n * worse than an obvious blank.\n */\nfunction buildEntry(plugin: NormalizedPlugin): MarketplaceEntry {\n\tconst origin = gitOriginUrl(plugin.root);\n\treturn {\n\t\tname: plugin.id,\n\t\t...(plugin.description ? { description: plugin.description } : {}),\n\t\t...(plugin.version ? { version: plugin.version } : {}),\n\t\tsource: origin ? { source: \"git\", url: origin } : SOURCE_PLACEHOLDER,\n\t};\n}\n\n/** True when the entry still needs a human to say where the plugin will be hosted. */\nexport function entryNeedsSource(entry: MarketplaceEntry): boolean {\n\treturn entry.source === SOURCE_PLACEHOLDER;\n}\n\nfunction publishSteps(plugin: NormalizedPlugin, platform: PluginPlatform): string[] {\n\tif (platform === \"github\") {\n\t\treturn [\n\t\t\t\"Publish (GitHub Copilot) — this is the only route into the ecosystem,\",\n\t\t\t\"local installs being deprecated:\",\n\t\t\t\"\",\n\t\t\t\" 1. Copy this directory into your marketplace repo.\",\n\t\t\t\" 2. Add the entry above to its `.github/marketplace.json` `plugins` array.\",\n\t\t\t\" 3. Open a pull request; the plugin is installable once it merges.\",\n\t\t];\n\t}\n\treturn [\n\t\t`Publish (Claude Code) — optional: the plugin is already live locally from`,\n\t\t`\\`~/.claude/skills/${plugin.id}/\\`. To share it:`,\n\t\t\"\",\n\t\t\" 1. Copy this directory into your marketplace repo.\",\n\t\t\" 2. Add the entry above to its `.claude-plugin/marketplace.json` `plugins` array.\",\n\t\t\" 3. Open a pull request, or submit to the community directory for review\",\n\t\t\" (approved plugins are pinned to a commit SHA).\",\n\t];\n}\n\n/**\n * Package a plugin for publication: gate it at full strictness, write a README,\n * produce its marketplace entry, and record what it produced. Touches no remote.\n */\nexport async function packagePlugin(\n\tplugin: NormalizedPlugin,\n\tplatformOverride?: PluginPlatform,\n\toptions: { judge?: TriggerJudge; distractors?: readonly CapabilityDoc[] } = {},\n): Promise<PackageResult> {\n\tconst platform = platformOverride ?? resolvePackagePlatform(plugin);\n\n\t// A plugin can only be published into an ecosystem whose manifest it carries.\n\t// `claude plugin validate` says so too, but as a wall of validator output about\n\t// a missing file; a plugin in the native layout has simply been authored for\n\t// the wrong target, and that is worth saying in one sentence. Nothing is\n\t// written — there is no artifact to package.\n\tif (!plugin.supportPlatform.includes(platform)) {\n\t\tconst finding: GateFinding = {\n\t\t\tgate: \"G1\",\n\t\t\tseverity: \"error\",\n\t\t\tmessage:\n\t\t\t\t`Plugin \"${plugin.id}\" carries no ${platform} manifest (it offers: ${plugin.supportPlatform.join(\", \")}), ` +\n\t\t\t\t`so it cannot be published to that ecosystem. Re-author it with --platform ${platform}.`,\n\t\t};\n\t\tconst evaluation: GateResult = { ok: false, findings: [finding], conformance: \"round-trip\", plugin };\n\t\treturn {\n\t\t\tok: false,\n\t\t\tdir: plugin.root,\n\t\t\tplatform,\n\t\t\twritten: [],\n\t\t\tentry: buildEntry(plugin),\n\t\t\tevaluation,\n\t\t\tinstructions: `Plugin \"${plugin.id}\" cannot be packaged for ${platform}.\\n\\n${formatGateFindings(evaluation)}`,\n\t\t};\n\t}\n\n\t// Strict: warnings that are tolerable in a local plugin — an unresolvable\n\t// binary, a vendor-validator nit — are not tolerable in something other people\n\t// will install on the strength of the marketplace vouching for it.\n\tlet evaluation = runStaticGates(plugin.root, { platform, strict: true });\n\tif (evaluation.plugin) {\n\t\tevaluation = withFindings(evaluation, await runSmokeGate(evaluation.plugin), true);\n\t}\n\n\t// G4 is the publish path's gate (§4.3) and nothing else's: it costs a model\n\t// call, and the question it asks — is this description actually worth the\n\t// context it occupies — only becomes other people's problem at publication.\n\t// With no judge or no gold set it reports `not-run`, which is `info`, so a\n\t// machine without a model is never blocked from publishing by a check it\n\t// could not perform.\n\tconst own = ownCandidates(plugin);\n\tif (own.length > 0) {\n\t\tconst outcome = await runTriggerEval(\n\t\t\tplugin.id,\n\t\t\t[...own, ...distractorCandidates(plugin.id, options.distractors ?? [])],\n\t\t\tloadTriggerCases(plugin.root),\n\t\t\toptions.judge,\n\t\t);\n\t\tevaluation = withFindings(evaluation, triggerFindings(outcome), true);\n\t}\n\n\tconst written: string[] = [];\n\tconst readme = writeReadme(plugin, platform);\n\tif (readme) written.push(readme);\n\n\tconst entry = buildEntry(plugin);\n\tconst record: PublishRecord = {\n\t\tpackagedAt: new Date().toISOString(),\n\t\tplatform,\n\t\tentry,\n\t\tok: evaluation.ok,\n\t};\n\twriteFileSync(path.join(plugin.root, PUBLISH_RECORD_FILE), `${JSON.stringify(record, null, 2)}\\n`, \"utf8\");\n\twritten.push(PUBLISH_RECORD_FILE);\n\n\tconst instructions = [\n\t\tevaluation.ok\n\t\t\t? `Plugin \"${plugin.id}\" is ready to publish (${platform}). The remaining step is yours.`\n\t\t\t: `Plugin \"${plugin.id}\" is NOT ready to publish — these must pass first:`,\n\t\t\"\",\n\t\tformatGateFindings(evaluation),\n\t\t\"\",\n\t\t\"Marketplace entry:\",\n\t\t\"```json\",\n\t\tJSON.stringify(entry, null, 2),\n\t\t\"```\",\n\t\t...(entryNeedsSource(entry) ? [\"\", \"Replace `source` with where the plugin will be hosted.\"] : []),\n\t\t\"\",\n\t\t...publishSteps(plugin, platform),\n\t\t\"\",\n\t\t\"Publishing is deliberately not automated: an agent that can push executable\",\n\t\t\"code into a marketplace other agents install from unattended is a supply-chain\",\n\t\t\"risk, so the last step stays with you.\",\n\t].join(\"\\n\");\n\n\treturn { ok: evaluation.ok, dir: plugin.root, platform, written, entry, evaluation, instructions };\n}\n\n// ── Staging into a local marketplace checkout ────────────────────────────────\n\n/**\n * Files that exist for hoocode's benefit and have no business in a published\n * artifact: the provenance marker and packaging's own build record.\n */\nconst INTERNAL_FILES = new Set([PUBLISH_RECORD_FILE, \".authored.json\"]);\n\n/** Where each platform's marketplace index lives, and the subdirectory plugins are vendored into. */\nconst INDEX_FILE: Record<PluginPlatform, string> = {\n\tclaude: path.join(\".claude-plugin\", \"marketplace.json\"),\n\tgithub: path.join(\".github\", \"marketplace.json\"),\n};\nconst VENDOR_DIR = \"plugins\";\n\nexport interface StageResult {\n\tok: boolean;\n\tmessage: string;\n\t/** Path the plugin was copied to, relative to the marketplace root. */\n\tpluginPath?: string;\n\t/** The index that was written, relative to the marketplace root. */\n\tindexPath?: string;\n\t/** True when the marketplace had no index for this platform and one was created. */\n\tcreatedIndex?: boolean;\n}\n\ninterface RawIndex {\n\tname?: string;\n\tplugins?: Array<Record<string, unknown>>;\n\t[key: string]: unknown;\n}\n\n/**\n * Copy a packaged plugin into a **local** marketplace checkout and add its index\n * entry, leaving the result uncommitted.\n *\n * This is the far side of the automation line drawn in §3.3, and it is where the\n * line actually falls. Everything here is local, reversible, and shows up in\n * `git status` — the human reviews a diff and decides. What stays manual is the\n * step that leaves the machine: no commit, no push, no PR. Those are what make a\n * plugin installable by other people's agents, and that is the act install is\n * trusting a human to have performed.\n *\n * Only reachable from `/plugin publish`, never from a tool: a human typed the\n * command, which is the whole point.\n */\nexport function stageIntoMarketplace(\n\tplugin: NormalizedPlugin,\n\tplatform: PluginPlatform,\n\tmarketplaceDir: string,\n): StageResult {\n\tconst root = path.resolve(marketplaceDir);\n\tif (!existsSync(root)) {\n\t\treturn { ok: false, message: `Marketplace directory not found: ${root}` };\n\t}\n\tconst pluginRoot = path.resolve(plugin.root);\n\tif (pluginRoot === root || pluginRoot.startsWith(`${root}${path.sep}`)) {\n\t\treturn { ok: false, message: `Plugin \"${plugin.id}\" already lives inside ${root}; nothing to stage.` };\n\t}\n\n\tconst indexRel = INDEX_FILE[platform];\n\tconst indexAbs = path.join(root, indexRel);\n\tlet index: RawIndex;\n\tlet createdIndex = false;\n\tif (existsSync(indexAbs)) {\n\t\ttry {\n\t\t\tindex = JSON.parse(readFileSync(indexAbs, \"utf8\")) as RawIndex;\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: `${indexRel} is not valid JSON (${(error as Error).message}); fix it first.` };\n\t\t}\n\t} else {\n\t\t// A marketplace repo that does not have an index yet is a normal starting\n\t\t// point, and refusing here would mean the first plugin can never be staged.\n\t\tindex = { name: path.basename(root), plugins: [] };\n\t\tcreatedIndex = true;\n\t}\n\tif (!Array.isArray(index.plugins)) index.plugins = [];\n\n\tconst dest = path.join(root, VENDOR_DIR, plugin.id);\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(path.dirname(dest), { recursive: true });\n\tcpSync(pluginRoot, dest, {\n\t\trecursive: true,\n\t\tfilter: (src) => !INTERNAL_FILES.has(path.basename(src)),\n\t});\n\n\t// Vendored in the repo, so the source is a path relative to it — not the\n\t// git origin `packagePlugin` guessed, which points at wherever the plugin was\n\t// authored rather than at the marketplace.\n\tconst entry: MarketplaceEntry = { ...buildEntry(plugin), source: `./${VENDOR_DIR}/${plugin.id}` };\n\tconst existingAt = index.plugins.findIndex((p) => p?.name === plugin.id);\n\tif (existingAt >= 0) index.plugins[existingAt] = { ...index.plugins[existingAt], ...entry };\n\telse index.plugins.push(entry as unknown as Record<string, unknown>);\n\n\tmkdirSync(path.dirname(indexAbs), { recursive: true });\n\twriteFileSync(indexAbs, `${JSON.stringify(index, null, 2)}\\n`, \"utf8\");\n\n\tconst verb = existingAt >= 0 ? \"Updated\" : \"Added\";\n\treturn {\n\t\tok: true,\n\t\tmessage:\n\t\t\t`${verb} \"${plugin.id}\" in ${path.basename(root)}: copied to ${VENDOR_DIR}/${plugin.id}/ and ` +\n\t\t\t`${createdIndex ? \"created\" : \"updated\"} ${indexRel}. Nothing was committed or pushed.`,\n\t\tpluginPath: `${VENDOR_DIR}/${plugin.id}`,\n\t\tindexPath: indexRel,\n\t\tcreatedIndex,\n\t};\n}\n\n/** Read back the record packaging left, for callers that want to show it. */\nexport function readPublishRecord(dir: string): PublishRecord | undefined {\n\ttry {\n\t\tconst file = path.join(dir, PUBLISH_RECORD_FILE);\n\t\treturn existsSync(file) ? (JSON.parse(readFileSync(file, \"utf8\")) as PublishRecord) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"packaging.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/packaging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAoB,KAAK,cAAc,EAA0B,MAAM,+BAA+B,CAAC;AAC9G,OAAO,EAAwC,KAAK,UAAU,EAAgC,MAAM,YAAY,CAAC;AAEjH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAKN,KAAK,YAAY,EAEjB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAK3D,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,cAAc,CAAC;IACzB,yEAAyE;IACzE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAQ/E;AA6GD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAEjE;AAwBD;;;GAGG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,CAAC,EAAE,cAAc,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;CAAO,GAC5E,OAAO,CAAC,aAAa,CAAC,CAwFxB;AAiBD,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,GACpB,WAAW,CAyDb;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAOxE","sourcesContent":["/**\n * Packaging — everything that has to happen before a plugin can be published,\n * and nothing that publishes it.\n *\n * The split is the point. Preparing a release is mechanical: run the gates at\n * their strictest, write a README, produce the marketplace index entry. All of\n * that is automated. *Publishing* — pushing an artifact into a marketplace that\n * other agents install from autonomously — stays a human act, because an agent\n * that can do it unattended is a supply-chain compromise primitive. See\n * docs/plugin-system-architecture.md §3.2–§3.3.\n *\n * Packaging works **in place**, in the plugin's production home. There is no\n * separate output location: by the time a plugin reaches here it is already in\n * its target platform's layout, and copying it somewhere else would just create\n * a fourth directory role to confuse with the other three (§8.3).\n *\n * The two platforms are not symmetric here, and the lane is built for the harder\n * one. A `claude` plugin is already live locally from `~/.claude/skills/<id>/`,\n * so publishing is optional and additive. For `github`, local installs are\n * deprecated — publishing is the *only* route into the ecosystem — so the\n * GitHub flow (a PR against a marketplace repo's index) is the primary case and\n * the Claude submission is the variant.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { CapabilityDoc } from \"../../capabilities/registry.js\";\nimport { isPluginPlatform, type PluginPlatform, resolvePluginPlatforms } from \"./formats/platform-targets.js\";\nimport { formatGateFindings, type GateFinding, type GateResult, runStaticGates, withFindings } from \"./gates.js\";\nimport { productionRoot } from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { runSmokeGate } from \"./smoke.js\";\nimport {\n\tdistractorCandidates,\n\tloadTriggerCases,\n\townCandidates,\n\trunTriggerEval,\n\ttype TriggerJudge,\n\ttriggerFindings,\n} from \"./trigger-eval.js\";\n\n/**\n * Where packaging records what it produced, at the plugin root.\n *\n * A dotfile beside the existing `.authored.json` provenance marker rather than a\n * visible `marketplace-entry.json`: the plugin directory is the thing that gets\n * copied into a marketplace repo, and a build record is not part of the artifact.\n */\nexport const PUBLISH_RECORD_FILE = \".hoocode-publish.json\";\n\n/** Placeholder `source` for a plugin that is not in a git checkout — deliberately unusable as-is. */\nconst SOURCE_PLACEHOLDER = \"<REPLACE: git URL, or path within the marketplace repo>\";\n\n/** A `marketplace.json` `plugins[]` entry, ready to paste into an index. */\nexport interface MarketplaceEntry {\n\tname: string;\n\tdescription?: string;\n\tversion?: string;\n\tsource: string | { source: \"git\"; url: string } | { source: \"github\"; repo: string };\n}\n\nexport interface PublishRecord {\n\tpackagedAt: string;\n\tplatform: PluginPlatform;\n\tentry: MarketplaceEntry;\n\t/** Whether the gates were green when this record was written. */\n\tok: boolean;\n}\n\nexport interface PackageResult {\n\tok: boolean;\n\tdir: string;\n\tplatform: PluginPlatform;\n\t/** Files packaging created or refreshed, relative to the plugin root. */\n\twritten: string[];\n\tentry: MarketplaceEntry;\n\tevaluation: GateResult;\n\t/** Human-facing next steps; deliberately instructions, not actions. */\n\tinstructions: string;\n}\n\n/**\n * Which ecosystem this plugin is being packaged for.\n *\n * Where it lives is the strongest evidence — the production homes are per\n * platform, so a plugin under `~/.agents/publish/github/` is a github artifact\n * whatever its manifests say. A plugin outside a production home (installed, or\n * authored by an older version) falls back to what it declares, then to the\n * session default.\n */\nexport function resolvePackagePlatform(plugin: NormalizedPlugin): PluginPlatform {\n\tconst root = path.resolve(plugin.root);\n\tfor (const platform of [\"claude\", \"github\"] as const) {\n\t\tconst home = path.resolve(productionRoot(platform));\n\t\tif (root === home || root.startsWith(`${home}${path.sep}`)) return platform;\n\t}\n\tconst declared = plugin.supportPlatform.filter(isPluginPlatform);\n\treturn declared[0] ?? resolvePluginPlatforms()[0];\n}\n\n/** Best-effort origin URL of the repo containing `dir`, if it is in one. */\nfunction gitOriginUrl(dir: string): string | undefined {\n\ttry {\n\t\tconst url = execFileSync(\"git\", [\"-C\", dir, \"remote\", \"get-url\", \"origin\"], {\n\t\t\tencoding: \"utf8\",\n\t\t\ttimeout: 5_000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t}).trim();\n\t\treturn url || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction capabilityLines(plugin: NormalizedPlugin): string[] {\n\tconst lines: string[] = [];\n\tconst add = (label: string, present: unknown) => {\n\t\tif (present) lines.push(`- ${label}`);\n\t};\n\tadd(\"Skills\", plugin.skillsDir);\n\tadd(\"Slash commands\", plugin.commandsDir);\n\tadd(\"Subagents\", plugin.agentsDir);\n\tadd(\"Hooks\", plugin.hooks);\n\tadd(\"MCP servers\", plugin.mcpServers);\n\tadd(\"Canvases\", plugin.canvasExtensions?.length);\n\treturn lines;\n}\n\nfunction installSection(plugin: NormalizedPlugin, platform: PluginPlatform): string[] {\n\tif (platform === \"claude\") {\n\t\treturn [\n\t\t\t\"## Install\",\n\t\t\t\"\",\n\t\t\t\"```bash\",\n\t\t\t\"claude plugin marketplace add <marketplace-repo>\",\n\t\t\t`claude plugin install ${plugin.id}@<marketplace>`,\n\t\t\t\"```\",\n\t\t\t\"\",\n\t\t\t`Or copy this directory to \\`~/.claude/skills/${plugin.id}/\\` — a folder there`,\n\t\t\t\"carrying a `.claude-plugin/plugin.json` loads with no install step.\",\n\t\t];\n\t}\n\treturn [\n\t\t\"## Install\",\n\t\t\"\",\n\t\t\"```bash\",\n\t\t\"copilot plugin marketplace add <marketplace-repo>\",\n\t\t`copilot plugin install ${plugin.id}@<marketplace>`,\n\t\t\"```\",\n\t];\n}\n\n/**\n * Generate a README from what the plugin actually contains.\n *\n * Never overwrites: a hand-written README is the author's description of their\n * own plugin, and a generated one derived from the directory listing is strictly\n * worse. This only fills the gap where there is nothing at all.\n */\nfunction writeReadme(plugin: NormalizedPlugin, platform: PluginPlatform): string | undefined {\n\tconst file = path.join(plugin.root, \"README.md\");\n\tif (existsSync(file)) return undefined;\n\n\tconst capabilities = capabilityLines(plugin);\n\tconst body = [\n\t\t`# ${plugin.id}`,\n\t\t\"\",\n\t\tplugin.description ?? \"_No description provided._\",\n\t\t\"\",\n\t\t...(capabilities.length > 0 ? [\"## Provides\", \"\", ...capabilities, \"\"] : []),\n\t\t...installSection(plugin, platform),\n\t\t\"\",\n\t\t...(plugin.unsupportedSurfaces?.length\n\t\t\t? [\n\t\t\t\t\t\"## Notes\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t`This plugin ships surfaces hoocode does not load: ${plugin.unsupportedSurfaces.join(\", \")}.`,\n\t\t\t\t\t\"They are preserved as-is and are the target platform's concern.\",\n\t\t\t\t\t\"\",\n\t\t\t\t]\n\t\t\t: []),\n\t].join(\"\\n\");\n\n\twriteFileSync(file, `${body.trimEnd()}\\n`, \"utf8\");\n\treturn \"README.md\";\n}\n\n/**\n * Build the marketplace index entry.\n *\n * `source` is where the *published* plugin will live, which packaging cannot\n * know: the artifact is on this machine and has not been pushed anywhere. When\n * the plugin sits in a git checkout its origin is a plausible starting point;\n * otherwise the field is a clearly-marked placeholder for the human to fill.\n * Guessing here would produce an index entry that resolves to nothing, which is\n * worse than an obvious blank.\n */\nfunction buildEntry(plugin: NormalizedPlugin): MarketplaceEntry {\n\tconst origin = gitOriginUrl(plugin.root);\n\treturn {\n\t\tname: plugin.id,\n\t\t...(plugin.description ? { description: plugin.description } : {}),\n\t\t...(plugin.version ? { version: plugin.version } : {}),\n\t\tsource: origin ? { source: \"git\", url: origin } : SOURCE_PLACEHOLDER,\n\t};\n}\n\n/** True when the entry still needs a human to say where the plugin will be hosted. */\nexport function entryNeedsSource(entry: MarketplaceEntry): boolean {\n\treturn entry.source === SOURCE_PLACEHOLDER;\n}\n\nfunction publishSteps(plugin: NormalizedPlugin, platform: PluginPlatform): string[] {\n\tif (platform === \"github\") {\n\t\treturn [\n\t\t\t\"Publish (GitHub Copilot) — this is the only route into the ecosystem,\",\n\t\t\t\"local installs being deprecated:\",\n\t\t\t\"\",\n\t\t\t\" 1. Copy this directory into your marketplace repo.\",\n\t\t\t\" 2. Add the entry above to its `.github/marketplace.json` `plugins` array.\",\n\t\t\t\" 3. Open a pull request; the plugin is installable once it merges.\",\n\t\t];\n\t}\n\treturn [\n\t\t`Publish (Claude Code) — optional: the plugin is already live locally from`,\n\t\t`\\`~/.claude/skills/${plugin.id}/\\`. To share it:`,\n\t\t\"\",\n\t\t\" 1. Copy this directory into your marketplace repo.\",\n\t\t\" 2. Add the entry above to its `.claude-plugin/marketplace.json` `plugins` array.\",\n\t\t\" 3. Open a pull request, or submit to the community directory for review\",\n\t\t\" (approved plugins are pinned to a commit SHA).\",\n\t];\n}\n\n/**\n * Package a plugin for publication: gate it at full strictness, write a README,\n * produce its marketplace entry, and record what it produced. Touches no remote.\n */\nexport async function packagePlugin(\n\tplugin: NormalizedPlugin,\n\tplatformOverride?: PluginPlatform,\n\toptions: { judge?: TriggerJudge; distractors?: readonly CapabilityDoc[] } = {},\n): Promise<PackageResult> {\n\tconst platform = platformOverride ?? resolvePackagePlatform(plugin);\n\n\t// A plugin can only be published into an ecosystem whose manifest it carries.\n\t// `claude plugin validate` says so too, but as a wall of validator output about\n\t// a missing file; a plugin in the native layout has simply been authored for\n\t// the wrong target, and that is worth saying in one sentence. Nothing is\n\t// written — there is no artifact to package.\n\tif (!plugin.supportPlatform.includes(platform)) {\n\t\tconst finding: GateFinding = {\n\t\t\tgate: \"G1\",\n\t\t\tseverity: \"error\",\n\t\t\tmessage:\n\t\t\t\t`Plugin \"${plugin.id}\" carries no ${platform} manifest (it offers: ${plugin.supportPlatform.join(\", \")}), ` +\n\t\t\t\t`so it cannot be published to that ecosystem. Re-author it with --platform ${platform}.`,\n\t\t};\n\t\tconst evaluation: GateResult = { ok: false, findings: [finding], conformance: \"round-trip\", plugin };\n\t\treturn {\n\t\t\tok: false,\n\t\t\tdir: plugin.root,\n\t\t\tplatform,\n\t\t\twritten: [],\n\t\t\tentry: buildEntry(plugin),\n\t\t\tevaluation,\n\t\t\tinstructions: `Plugin \"${plugin.id}\" cannot be packaged for ${platform}.\\n\\n${formatGateFindings(evaluation)}`,\n\t\t};\n\t}\n\n\t// Strict: warnings that are tolerable in a local plugin — an unresolvable\n\t// binary, a vendor-validator nit — are not tolerable in something other people\n\t// will install on the strength of the marketplace vouching for it.\n\tlet evaluation = runStaticGates(plugin.root, { platform, strict: true });\n\tif (evaluation.plugin) {\n\t\tevaluation = withFindings(evaluation, await runSmokeGate(evaluation.plugin), true);\n\t}\n\n\t// G4 is the publish path's gate (§4.3) and nothing else's: it costs a model\n\t// call, and the question it asks — is this description actually worth the\n\t// context it occupies — only becomes other people's problem at publication.\n\t// With no judge or no gold set it reports `not-run`, which is `info`, so a\n\t// machine without a model is never blocked from publishing by a check it\n\t// could not perform.\n\tconst own = ownCandidates(plugin);\n\tif (own.length > 0) {\n\t\tconst outcome = await runTriggerEval(\n\t\t\tplugin.id,\n\t\t\t[...own, ...distractorCandidates(plugin.id, options.distractors ?? [])],\n\t\t\tloadTriggerCases(plugin.root),\n\t\t\toptions.judge,\n\t\t);\n\t\tevaluation = withFindings(evaluation, triggerFindings(outcome), true);\n\t}\n\n\tconst written: string[] = [];\n\tconst readme = writeReadme(plugin, platform);\n\tif (readme) written.push(readme);\n\n\tconst entry = buildEntry(plugin);\n\tconst record: PublishRecord = {\n\t\tpackagedAt: new Date().toISOString(),\n\t\tplatform,\n\t\tentry,\n\t\tok: evaluation.ok,\n\t};\n\twriteFileSync(path.join(plugin.root, PUBLISH_RECORD_FILE), `${JSON.stringify(record, null, 2)}\\n`, \"utf8\");\n\twritten.push(PUBLISH_RECORD_FILE);\n\n\tconst instructions = [\n\t\tevaluation.ok\n\t\t\t? `Plugin \"${plugin.id}\" is ready to publish (${platform}). The remaining step is yours.`\n\t\t\t: `Plugin \"${plugin.id}\" is NOT ready to publish — these must pass first:`,\n\t\t\"\",\n\t\tformatGateFindings(evaluation),\n\t\t\"\",\n\t\t\"Marketplace entry:\",\n\t\t\"```json\",\n\t\tJSON.stringify(entry, null, 2),\n\t\t\"```\",\n\t\t...(entryNeedsSource(entry) ? [\"\", \"Replace `source` with where the plugin will be hosted.\"] : []),\n\t\t\"\",\n\t\t...publishSteps(plugin, platform),\n\t\t\"\",\n\t\t\"Publishing is deliberately not automated: an agent that can push executable\",\n\t\t\"code into a marketplace other agents install from unattended is a supply-chain\",\n\t\t\"risk, so the last step stays with you.\",\n\t].join(\"\\n\");\n\n\treturn { ok: evaluation.ok, dir: plugin.root, platform, written, entry, evaluation, instructions };\n}\n\n// ── Staging into a local marketplace checkout ────────────────────────────────\n\n/**\n * Files that exist for hoocode's benefit and have no business in a published\n * artifact: the provenance marker and packaging's own build record.\n */\nconst INTERNAL_FILES = new Set([PUBLISH_RECORD_FILE, \".authored.json\"]);\n\n/** Where each platform's marketplace index lives, and the subdirectory plugins are vendored into. */\nconst INDEX_FILE: Record<PluginPlatform, string> = {\n\tclaude: path.join(\".claude-plugin\", \"marketplace.json\"),\n\tgithub: path.join(\".github\", \"marketplace.json\"),\n};\nconst VENDOR_DIR = \"plugins\";\n\nexport interface StageResult {\n\tok: boolean;\n\tmessage: string;\n\t/** Path the plugin was copied to, relative to the marketplace root. */\n\tpluginPath?: string;\n\t/** The index that was written, relative to the marketplace root. */\n\tindexPath?: string;\n\t/** True when the marketplace had no index for this platform and one was created. */\n\tcreatedIndex?: boolean;\n}\n\ninterface RawIndex {\n\tname?: string;\n\tplugins?: Array<Record<string, unknown>>;\n\t[key: string]: unknown;\n}\n\n/**\n * Copy a packaged plugin into a **local** marketplace checkout and add its index\n * entry, leaving the result uncommitted.\n *\n * This is the far side of the automation line drawn in §3.3, and it is where the\n * line actually falls. Everything here is local, reversible, and shows up in\n * `git status` — the human reviews a diff and decides. What stays manual is the\n * step that leaves the machine: no commit, no push, no PR. Those are what make a\n * plugin installable by other people's agents, and that is the act install is\n * trusting a human to have performed.\n *\n * Only reachable from `/plugin publish`, never from a tool: a human typed the\n * command, which is the whole point.\n */\nexport function stageIntoMarketplace(\n\tplugin: NormalizedPlugin,\n\tplatform: PluginPlatform,\n\tmarketplaceDir: string,\n): StageResult {\n\tconst root = path.resolve(marketplaceDir);\n\tif (!existsSync(root)) {\n\t\treturn { ok: false, message: `Marketplace directory not found: ${root}` };\n\t}\n\tconst pluginRoot = path.resolve(plugin.root);\n\tif (pluginRoot === root || pluginRoot.startsWith(`${root}${path.sep}`)) {\n\t\treturn { ok: false, message: `Plugin \"${plugin.id}\" already lives inside ${root}; nothing to stage.` };\n\t}\n\n\tconst indexRel = INDEX_FILE[platform];\n\tconst indexAbs = path.join(root, indexRel);\n\tlet index: RawIndex;\n\tlet createdIndex = false;\n\tif (existsSync(indexAbs)) {\n\t\ttry {\n\t\t\tindex = JSON.parse(readFileSync(indexAbs, \"utf8\")) as RawIndex;\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: `${indexRel} is not valid JSON (${(error as Error).message}); fix it first.` };\n\t\t}\n\t} else {\n\t\t// A marketplace repo that does not have an index yet is a normal starting\n\t\t// point, and refusing here would mean the first plugin can never be staged.\n\t\tindex = { name: path.basename(root), plugins: [] };\n\t\tcreatedIndex = true;\n\t}\n\tif (!Array.isArray(index.plugins)) index.plugins = [];\n\n\tconst dest = path.join(root, VENDOR_DIR, plugin.id);\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(path.dirname(dest), { recursive: true });\n\tcpSync(pluginRoot, dest, {\n\t\trecursive: true,\n\t\tfilter: (src) => !INTERNAL_FILES.has(path.basename(src)),\n\t});\n\n\t// Vendored in the repo, so the source is a path relative to it — not the\n\t// git origin `packagePlugin` guessed, which points at wherever the plugin was\n\t// authored rather than at the marketplace.\n\tconst entry: MarketplaceEntry = { ...buildEntry(plugin), source: `./${VENDOR_DIR}/${plugin.id}` };\n\tconst existingAt = index.plugins.findIndex((p) => p?.name === plugin.id);\n\tif (existingAt >= 0) index.plugins[existingAt] = { ...index.plugins[existingAt], ...entry };\n\telse index.plugins.push(entry as unknown as Record<string, unknown>);\n\n\tmkdirSync(path.dirname(indexAbs), { recursive: true });\n\twriteFileSync(indexAbs, `${JSON.stringify(index, null, 2)}\\n`, \"utf8\");\n\n\tconst verb = existingAt >= 0 ? \"Updated\" : \"Added\";\n\treturn {\n\t\tok: true,\n\t\tmessage:\n\t\t\t`${verb} \"${plugin.id}\" in ${path.basename(root)}: copied to ${VENDOR_DIR}/${plugin.id}/ and ` +\n\t\t\t`${createdIndex ? \"created\" : \"updated\"} ${indexRel}. Nothing was committed or pushed.`,\n\t\tpluginPath: `${VENDOR_DIR}/${plugin.id}`,\n\t\tindexPath: indexRel,\n\t\tcreatedIndex,\n\t};\n}\n\n/** Read back the record packaging left, for callers that want to show it. */\nexport function readPublishRecord(dir: string): PublishRecord | undefined {\n\ttry {\n\t\tconst file = path.join(dir, PUBLISH_RECORD_FILE);\n\t\treturn existsSync(file) ? (JSON.parse(readFileSync(file, \"utf8\")) as PublishRecord) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -83,6 +83,7 @@ function capabilityLines(plugin) {
|
|
|
83
83
|
add("Subagents", plugin.agentsDir);
|
|
84
84
|
add("Hooks", plugin.hooks);
|
|
85
85
|
add("MCP servers", plugin.mcpServers);
|
|
86
|
+
add("Canvases", plugin.canvasExtensions?.length);
|
|
86
87
|
return lines;
|
|
87
88
|
}
|
|
88
89
|
function installSection(plugin, platform) {
|