@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/core/brand.d.ts +1 -0
  3. package/dist/core/brand.d.ts.map +1 -1
  4. package/dist/core/brand.js +1 -0
  5. package/dist/core/brand.js.map +1 -1
  6. package/dist/core/canvas/discovery.d.ts +11 -3
  7. package/dist/core/canvas/discovery.d.ts.map +1 -1
  8. package/dist/core/canvas/discovery.js +18 -3
  9. package/dist/core/canvas/discovery.js.map +1 -1
  10. package/dist/core/canvas/plugin-canvases.d.ts +30 -0
  11. package/dist/core/canvas/plugin-canvases.d.ts.map +1 -0
  12. package/dist/core/canvas/plugin-canvases.js +51 -0
  13. package/dist/core/canvas/plugin-canvases.js.map +1 -0
  14. package/dist/core/canvas/session.d.ts +7 -0
  15. package/dist/core/canvas/session.d.ts.map +1 -1
  16. package/dist/core/canvas/session.js +8 -1
  17. package/dist/core/canvas/session.js.map +1 -1
  18. package/dist/core/canvas/trust.d.ts +4 -5
  19. package/dist/core/canvas/trust.d.ts.map +1 -1
  20. package/dist/core/canvas/trust.js +23 -8
  21. package/dist/core/canvas/trust.js.map +1 -1
  22. package/dist/core/extensions/loader.d.ts.map +1 -1
  23. package/dist/core/extensions/loader.js +25 -11
  24. package/dist/core/extensions/loader.js.map +1 -1
  25. package/dist/core/extensions/plugins/formats/copilot.d.ts +10 -0
  26. package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -1
  27. package/dist/core/extensions/plugins/formats/copilot.js +36 -4
  28. package/dist/core/extensions/plugins/formats/copilot.js.map +1 -1
  29. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts.map +1 -1
  30. package/dist/core/extensions/plugins/formats/jsonManifest.js +15 -2
  31. package/dist/core/extensions/plugins/formats/jsonManifest.js.map +1 -1
  32. package/dist/core/extensions/plugins/formats/shared.d.ts +30 -2
  33. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  34. package/dist/core/extensions/plugins/formats/shared.js +72 -0
  35. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  36. package/dist/core/extensions/plugins/install.d.ts +31 -0
  37. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  38. package/dist/core/extensions/plugins/install.js +102 -12
  39. package/dist/core/extensions/plugins/install.js.map +1 -1
  40. package/dist/core/extensions/plugins/listing.d.ts.map +1 -1
  41. package/dist/core/extensions/plugins/listing.js +1 -0
  42. package/dist/core/extensions/plugins/listing.js.map +1 -1
  43. package/dist/core/extensions/plugins/manifest.d.ts +18 -0
  44. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  45. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  46. package/dist/core/extensions/plugins/packaging.d.ts.map +1 -1
  47. package/dist/core/extensions/plugins/packaging.js +1 -0
  48. package/dist/core/extensions/plugins/packaging.js.map +1 -1
  49. package/dist/extensions/core/canvas.d.ts.map +1 -1
  50. package/dist/extensions/core/canvas.js +1 -1
  51. package/dist/extensions/core/canvas.js.map +1 -1
  52. package/dist/extensions/core/marketplace.d.ts.map +1 -1
  53. package/dist/extensions/core/marketplace.js +28 -1
  54. package/dist/extensions/core/marketplace.js.map +1 -1
  55. package/dist/extensions/core/scaffold.d.ts +1 -1
  56. package/dist/extensions/core/scaffold.d.ts.map +1 -1
  57. package/dist/extensions/core/scaffold.js +185 -1
  58. package/dist/extensions/core/scaffold.js.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.d.ts +11 -0
  60. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  61. package/dist/modes/interactive/interactive-mode.js +40 -0
  62. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  63. package/dist/modes/interactive/resource-display.d.ts +11 -0
  64. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  65. package/dist/modes/interactive/resource-display.js +20 -1
  66. package/dist/modes/interactive/resource-display.js.map +1 -1
  67. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  68. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  69. package/examples/extensions/sandbox/package.json +1 -1
  70. package/examples/extensions/with-deps/package.json +1 -1
  71. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"packaging.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/packaging.ts"],"names":[],"mappings":"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","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.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/packaging.ts"],"names":[],"mappings":"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","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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AA6C5F,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwJlD","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * The two agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { type CanvasOverview, CanvasSession, parseCanvasRef } from \"../../core/canvas/session.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\"No canvas extensions found in .agents/extensions, .github/extensions, or ~/.copilot/extensions.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) lines.push(` open ${describeInstance(instance)}`);\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function setupCanvas(pi: ExtensionAPI): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | /canvas open <extension>[:<canvas>] | /canvas close <instanceId>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opening forks a process and binds a port, so it can be slow and must be\n\t\t\t// interruptible. The loader's signal is what makes Esc mean something: it reaches\n\t\t\t// the registry's abandon path, which tells the extension to release a port it may\n\t\t\t// already have bound. Outside a terminal (--print, RPC) there is nothing to draw\n\t\t\t// and nothing to press, so the open simply runs.\n\t\t\tconst outcome = ctx.hasUI\n\t\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn loader;\n\t\t\t\t\t})\n\t\t\t\t: await canvas\n\t\t\t\t\t\t.open(ref)\n\t\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t\t.catch(\n\t\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAgD5F,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwJlD","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * The two agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { type CanvasOverview, CanvasSession, parseCanvasRef } from \"../../core/canvas/session.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\n\t\t\t\"No canvas extensions found in .agents/extensions, .github/extensions, ~/.copilot/extensions,\",\n\t\t\t\"or any installed plugin. Scaffold one with /new-canvas <name>, or install one with /plugin.\",\n\t\t);\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) lines.push(` open ${describeInstance(instance)}`);\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function setupCanvas(pi: ExtensionAPI): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | /canvas open <extension>[:<canvas>] | /canvas close <instanceId>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opening forks a process and binds a port, so it can be slow and must be\n\t\t\t// interruptible. The loader's signal is what makes Esc mean something: it reaches\n\t\t\t// the registry's abandon path, which tells the extension to release a port it may\n\t\t\t// already have bound. Outside a terminal (--print, RPC) there is nothing to draw\n\t\t\t// and nothing to press, so the open simply runs.\n\t\t\tconst outcome = ctx.hasUI\n\t\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn loader;\n\t\t\t\t\t})\n\t\t\t\t: await canvas\n\t\t\t\t\t\t.open(ref)\n\t\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t\t.catch(\n\t\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
@@ -34,7 +34,7 @@ function renderOverview(overview) {
34
34
  lines.push(`Canvases are unavailable: ${overview.availability.reason}`, "");
35
35
  }
36
36
  if (overview.listings.length === 0) {
37
- lines.push("No canvas extensions found in .agents/extensions, .github/extensions, or ~/.copilot/extensions.");
37
+ lines.push("No canvas extensions found in .agents/extensions, .github/extensions, ~/.copilot/extensions,", "or any installed plugin. Scaffold one with /new-canvas <name>, or install one with /plugin.");
38
38
  return lines.join("\n");
39
39
  }
40
40
  for (const listing of overview.listings) {
@@ -1 +1 @@
1
- {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * The two agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { type CanvasOverview, CanvasSession, parseCanvasRef } from \"../../core/canvas/session.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\"No canvas extensions found in .agents/extensions, .github/extensions, or ~/.copilot/extensions.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) lines.push(` open ${describeInstance(instance)}`);\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function setupCanvas(pi: ExtensionAPI): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | /canvas open <extension>[:<canvas>] | /canvas close <instanceId>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opening forks a process and binds a port, so it can be slow and must be\n\t\t\t// interruptible. The loader's signal is what makes Esc mean something: it reaches\n\t\t\t// the registry's abandon path, which tells the extension to release a port it may\n\t\t\t// already have bound. Outside a terminal (--print, RPC) there is nothing to draw\n\t\t\t// and nothing to press, so the open simply runs.\n\t\t\tconst outcome = ctx.hasUI\n\t\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn loader;\n\t\t\t\t\t})\n\t\t\t\t: await canvas\n\t\t\t\t\t\t.open(ref)\n\t\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t\t.catch(\n\t\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
1
+ {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * The two agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { type CanvasOverview, CanvasSession, parseCanvasRef } from \"../../core/canvas/session.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\n\t\t\t\"No canvas extensions found in .agents/extensions, .github/extensions, ~/.copilot/extensions,\",\n\t\t\t\"or any installed plugin. Scaffold one with /new-canvas <name>, or install one with /plugin.\",\n\t\t);\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) lines.push(` open ${describeInstance(instance)}`);\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function setupCanvas(pi: ExtensionAPI): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | /canvas open <extension>[:<canvas>] | /canvas close <instanceId>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opening forks a process and binds a port, so it can be slow and must be\n\t\t\t// interruptible. The loader's signal is what makes Esc mean something: it reaches\n\t\t\t// the registry's abandon path, which tells the extension to release a port it may\n\t\t\t// already have bound. Outside a terminal (--print, RPC) there is nothing to draw\n\t\t\t// and nothing to press, so the open simply runs.\n\t\t\tconst outcome = ctx.hasUI\n\t\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn loader;\n\t\t\t\t\t})\n\t\t\t\t: await canvas\n\t\t\t\t\t\t.open(ref)\n\t\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t\t.catch(\n\t\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAoCH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAiE5F,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2SvD","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * at the chosen scope — `~/.agents/plugins/<name>` for `user`, or\n * `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader\n * after a reload. `install` asks which, since a human is right here to answer;\n * the autonomous tool reads the `pluginInstallScope` setting instead.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name> [--scope user|project]\n * /plugin remove <name>\n * /plugin trust [list] allow repo-committed plugins to run code here\n * /plugin untrust\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH, SEGMENT_SEP } from \"../../core/brand.js\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\tlistInstalledPlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n} from \"../../core/extensions/plugins/install.js\";\nimport { availablePluginGroups } from \"../../core/extensions/plugins/listing.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n\ttype PluginInstallScope,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport {\n\tisWorkspaceTrusted,\n\tlistTrustedWorkspaces,\n\ttrustWorkspace,\n\tuntrustWorkspace,\n} from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\nfunction isInstallScope(value: string): value is PluginInstallScope {\n\treturn value === \"user\" || value === \"project\";\n}\n\n/** Labels carry the consequence, since that is the whole content of the choice. */\nconst SCOPE_CHOICES: ReadonlyArray<{ scope: PluginInstallScope; label: string }> = [\n\t{ scope: \"user\", label: \"User — ~/.agents/plugins, available in every project (recommended)\" },\n\t{ scope: \"project\", label: \"Project — <repo>/.agents/plugins, committed and shared with collaborators\" },\n];\n\n/**\n * Ask where the plugin should land. Returns undefined when the user dismisses\n * the selector, which cancels the install rather than guessing.\n *\n * Headless (no UI to ask through) resolves to `fallback` — the standing setting\n * — so a scripted or `--print` run still installs somewhere predictable.\n */\nasync function promptForScope(\n\tctx: ExtensionCommandContext,\n\tfallback: PluginInstallScope,\n): Promise<PluginInstallScope | undefined> {\n\tif (!ctx.hasUI) return fallback;\n\tconst labels = SCOPE_CHOICES.map((c) => c.label);\n\tconst picked = await ctx.ui.select(\"Install scope\", labels);\n\tif (picked === undefined) return undefined;\n\treturn SCOPE_CHOICES.find((c) => c.label === picked)?.scope ?? fallback;\n}\n\n/**\n * Chat styling for a listing.\n *\n * The name stays in the terminal's default foreground and everything else steps\n * down from it, so the column you scan is the brightest thing on the row. Note\n * this only reaches the screen because `showStatus` passes pre-styled messages\n * through instead of wrapping them in a blanket dim.\n */\nfunction listStyle(theme: ExtensionCommandContext[\"ui\"][\"theme\"]): ListStyle {\n\treturn {\n\t\tmarker: (text) => theme.fg(\"success\", text),\n\t\tfacts: (text) => theme.fg(\"muted\", text),\n\t\tdetail: (text) => theme.fg(\"dim\", text),\n\t\ttrailer: (text) => theme.fg(\"dim\", text),\n\t\tgroupTitle: (text) => theme.fg(\"mdLink\", text),\n\t};\n}\n\n/** `⬡ 4 plugins …` — the counted header every listing opens with. */\nfunction listHeader(\n\ttheme: ExtensionCommandContext[\"ui\"][\"theme\"],\n\tglyph: string,\n\tsummary: string,\n\thint?: string,\n): string {\n\tconst head = `${theme.fg(\"accent\", glyph)} ${theme.fg(\"muted\", summary)}`;\n\treturn hint ? `${head}\\n${theme.fg(\"dim\", ` ${hint}`)}` : head;\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> [--scope user|project] | /plugin remove <name> | /plugin trust [list] | /plugin untrust | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\", \"trust\", \"untrust\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t\tconst rows = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: market?.name ?? r.location,\n\t\t\t\t\t\t\tfacts: [plural(market?.plugins.length ?? 0, \"plugin\"), (market?.supportPlatform ?? []).join(\", \")],\n\t\t\t\t\t\t\t// The location is the longest and least-scanned token on the row;\n\t\t\t\t\t\t\t// on its own line it stops forcing the wrap.\n\t\t\t\t\t\t\ttrailer: r.location,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tconst body = renderList([{ rows }], {\n\t\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\t\tindent: 2,\n\t\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t\t});\n\t\t\t\t\tconst header = listHeader(theme, CATEGORY_GLYPH.marketplaces, plural(records.length, \"marketplace\"));\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin list to see what they offer\")}`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tif (available.length === 0) {\n\t\t\t\t\tctx.ui.notify(\"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t// Installed state is the fact the old listing could not answer: without\n\t\t\t\t// it, /plugin install on something already present silently re-clones\n\t\t\t\t// over it, discarding any local edits to the plugin directory.\n\t\t\t\tconst installed = new Set(listInstalledPlugins(cwd).map((p) => p.id));\n\t\t\t\tconst groups = availablePluginGroups(available, { installed });\n\t\t\t\tconst body = renderList(groups, {\n\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\tindent: 2,\n\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t});\n\t\t\t\tconst installedCount = available.filter((p) => installed.has(p.name)).length;\n\t\t\t\tconst summary =\n\t\t\t\t\t`${plural(available.length, \"plugin\")} across ${plural(groups.length, \"marketplace\")}` +\n\t\t\t\t\t(installedCount > 0 ? ` ${SEGMENT_SEP} ${installedCount} installed` : \"\");\n\t\t\t\tconst header = listHeader(\n\t\t\t\t\ttheme,\n\t\t\t\t\tCATEGORY_GLYPH.plugins,\n\t\t\t\t\tsummary,\n\t\t\t\t\tinstalledCount > 0 ? \"✓ already installed\" : undefined,\n\t\t\t\t);\n\t\t\t\tctx.ui.notify(`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin install <name> to add one\")}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> [--scope user|project] ───────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst rest = trimmed.slice(\"install\".length).trim();\n\t\t\t\tconst scopeMatch = /(?:^|\\s)--scope[= ]\\s*(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--scope[= ]\\s*\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name> [--scope user|project]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (scopeMatch && !isInstallScope(scopeMatch[1])) {\n\t\t\t\t\tctx.ui.notify(`Unknown scope \"${scopeMatch[1]}\". Use --scope user or --scope project.`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// An explicit --scope wins; otherwise ask, because this is the human\n\t\t\t\t// path and the destination is a real choice (portable vs. committed to\n\t\t\t\t// the repo). With no UI to ask through there is nobody to ask, so it\n\t\t\t\t// falls back to the same setting the autonomous path uses.\n\t\t\t\tconst scope = scopeMatch\n\t\t\t\t\t? (scopeMatch[1] as PluginInstallScope)\n\t\t\t\t\t: await promptForScope(ctx, SettingsManager.create(cwd, getAgentDir()).getPluginInstallScope());\n\t\t\t\tif (!scope) {\n\t\t\t\t\tctx.ui.notify(\"Install cancelled.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name, getAgentDir(), { scope });\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A person typing this command in this directory is the human act\n\t\t\t\t// workspace trust asks for, so an explicit project-scope install grants\n\t\t\t\t// it — otherwise the plugin you just chose to install here would load\n\t\t\t\t// with its hooks withheld, which is nobody's idea of what happened.\n\t\t\t\t// The autonomous path deliberately does not do this.\n\t\t\t\tlet trustNote = \"\";\n\t\t\t\tif (scope === \"project\" && !isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\t\ttrustNote = ` Trusted this workspace, so its plugins may run hooks and MCP servers here (\\`/plugin untrust\\` reverses it).`;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message}${trustNote} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── trust / untrust ─────────────────────────────────────────────────\n\t\t\tif (trimmed === \"trust\" || trimmed.startsWith(\"trust \")) {\n\t\t\t\tconst sub = trimmed.slice(\"trust\".length).trim();\n\t\t\t\tif (sub === \"list\") {\n\t\t\t\t\tconst workspaces = listTrustedWorkspaces(getAgentDir());\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\tworkspaces.length\n\t\t\t\t\t\t\t? `Trusted workspaces:\\n${workspaces.map((w) => `${w.path} (since ${w.at.slice(0, 10)})`).join(\"\\n\")}`\n\t\t\t\t\t\t\t: \"No trusted workspaces. Plugins in a working tree load skills and commands, but not hooks or MCP servers.\",\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sub) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin trust | /plugin trust list | /plugin untrust\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Already trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Trusted ${cwd}. Plugins committed to this repository may now run hooks and MCP servers here, ` +\n\t\t\t\t\t\t\"including any added by a later pull. Reverse it with /plugin untrust. Reloading…\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed === \"untrust\") {\n\t\t\t\tif (!untrustWorkspace(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Not trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`Revoked trust for ${cwd}. Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> [--scope user|project] | \" +\n\t\t\t\t\t\"/plugin trust [list] | /plugin untrust | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAsCH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAyF5F,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA6SvD","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * at the chosen scope — `~/.agents/plugins/<name>` for `user`, or\n * `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader\n * after a reload. `install` asks which, since a human is right here to answer;\n * the autonomous tool reads the `pluginInstallScope` setting instead.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name> [--scope user|project]\n * /plugin remove <name>\n * /plugin trust [list] allow repo-committed plugins to run code here\n * /plugin untrust\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH, SEGMENT_SEP } from \"../../core/brand.js\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tensureWellKnownMarketplaces,\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\tlistInstalledPlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n\twellKnownMarketplacesAreStale,\n} from \"../../core/extensions/plugins/install.js\";\nimport { availablePluginGroups } from \"../../core/extensions/plugins/listing.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n\ttype PluginInstallScope,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport {\n\tisWorkspaceTrusted,\n\tlistTrustedWorkspaces,\n\ttrustWorkspace,\n\tuntrustWorkspace,\n} from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\nfunction isInstallScope(value: string): value is PluginInstallScope {\n\treturn value === \"user\" || value === \"project\";\n}\n\n/** Labels carry the consequence, since that is the whole content of the choice. */\nconst SCOPE_CHOICES: ReadonlyArray<{ scope: PluginInstallScope; label: string }> = [\n\t{ scope: \"user\", label: \"User — ~/.agents/plugins, available in every project (recommended)\" },\n\t{ scope: \"project\", label: \"Project — <repo>/.agents/plugins, committed and shared with collaborators\" },\n];\n\n/**\n * Ask where the plugin should land. Returns undefined when the user dismisses\n * the selector, which cancels the install rather than guessing.\n *\n * Headless (no UI to ask through) resolves to `fallback` — the standing setting\n * — so a scripted or `--print` run still installs somewhere predictable.\n */\nasync function promptForScope(\n\tctx: ExtensionCommandContext,\n\tfallback: PluginInstallScope,\n): Promise<PluginInstallScope | undefined> {\n\tif (!ctx.hasUI) return fallback;\n\tconst labels = SCOPE_CHOICES.map((c) => c.label);\n\tconst picked = await ctx.ui.select(\"Install scope\", labels);\n\tif (picked === undefined) return undefined;\n\treturn SCOPE_CHOICES.find((c) => c.label === picked)?.scope ?? fallback;\n}\n\n/**\n * Chat styling for a listing.\n *\n * The name stays in the terminal's default foreground and everything else steps\n * down from it, so the column you scan is the brightest thing on the row. Note\n * this only reaches the screen because `showStatus` passes pre-styled messages\n * through instead of wrapping them in a blanket dim.\n */\nfunction listStyle(theme: ExtensionCommandContext[\"ui\"][\"theme\"]): ListStyle {\n\treturn {\n\t\tmarker: (text) => theme.fg(\"success\", text),\n\t\tfacts: (text) => theme.fg(\"muted\", text),\n\t\tdetail: (text) => theme.fg(\"dim\", text),\n\t\ttrailer: (text) => theme.fg(\"dim\", text),\n\t\tgroupTitle: (text) => theme.fg(\"mdLink\", text),\n\t};\n}\n\n/** `⬡ 4 plugins …` — the counted header every listing opens with. */\nfunction listHeader(\n\ttheme: ExtensionCommandContext[\"ui\"][\"theme\"],\n\tglyph: string,\n\tsummary: string,\n\thint?: string,\n): string {\n\tconst head = `${theme.fg(\"accent\", glyph)} ${theme.fg(\"muted\", summary)}`;\n\treturn hint ? `${head}\\n${theme.fg(\"dim\", ` ${hint}`)}` : head;\n}\n\n/**\n * Bring the curated marketplace indices up to date before reading them.\n *\n * `SearchPlugins` (the model's path) has always done this; the human `/plugin`\n * path never did, so a cache could sit stale indefinitely — and a *wrong* cache\n * with it. That is not hypothetical: `awesome-copilot` is pinned to its built\n * distribution branch, and the correction only reaches a cache that something\n * asks to refresh. Someone who only ever types `/plugin install` would have kept\n * installing unbuilt stubs and had no way to know why.\n *\n * TTL-respecting, so it is free when the cache is fresh. The notice is printed\n * only when a fetch will actually happen, because a silent multi-second clone\n * looks like a hang and a line printed every time looks like noise.\n */\nasync function refreshIndices(ctx: ExtensionCommandContext): Promise<void> {\n\tconst agentDir = getAgentDir();\n\tif (wellKnownMarketplacesAreStale(agentDir)) ctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\tconst errors = await ensureWellKnownMarketplaces(agentDir);\n\t// Non-fatal by construction: whatever is already on disk still lists. Worth\n\t// saying though — a retired ref reports here, and its consequence (entries\n\t// that install with no capabilities) is otherwise inexplicable.\n\tif (errors.length > 0) ctx.ui.notify(`Marketplace indices: ${errors.join(\"; \")}`, \"warning\");\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> [--scope user|project] | /plugin remove <name> | /plugin trust [list] | /plugin untrust | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\", \"trust\", \"untrust\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t\tconst rows = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: market?.name ?? r.location,\n\t\t\t\t\t\t\tfacts: [plural(market?.plugins.length ?? 0, \"plugin\"), (market?.supportPlatform ?? []).join(\", \")],\n\t\t\t\t\t\t\t// The location is the longest and least-scanned token on the row;\n\t\t\t\t\t\t\t// on its own line it stops forcing the wrap.\n\t\t\t\t\t\t\ttrailer: r.location,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tconst body = renderList([{ rows }], {\n\t\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\t\tindent: 2,\n\t\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t\t});\n\t\t\t\t\tconst header = listHeader(theme, CATEGORY_GLYPH.marketplaces, plural(records.length, \"marketplace\"));\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin list to see what they offer\")}`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tawait refreshIndices(ctx);\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tif (available.length === 0) {\n\t\t\t\t\tctx.ui.notify(\"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t// Installed state is the fact the old listing could not answer: without\n\t\t\t\t// it, /plugin install on something already present silently re-clones\n\t\t\t\t// over it, discarding any local edits to the plugin directory.\n\t\t\t\tconst installed = new Set(listInstalledPlugins(cwd).map((p) => p.id));\n\t\t\t\tconst groups = availablePluginGroups(available, { installed });\n\t\t\t\tconst body = renderList(groups, {\n\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\tindent: 2,\n\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t});\n\t\t\t\tconst installedCount = available.filter((p) => installed.has(p.name)).length;\n\t\t\t\tconst summary =\n\t\t\t\t\t`${plural(available.length, \"plugin\")} across ${plural(groups.length, \"marketplace\")}` +\n\t\t\t\t\t(installedCount > 0 ? ` ${SEGMENT_SEP} ${installedCount} installed` : \"\");\n\t\t\t\tconst header = listHeader(\n\t\t\t\t\ttheme,\n\t\t\t\t\tCATEGORY_GLYPH.plugins,\n\t\t\t\t\tsummary,\n\t\t\t\t\tinstalledCount > 0 ? \"✓ already installed\" : undefined,\n\t\t\t\t);\n\t\t\t\tctx.ui.notify(`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin install <name> to add one\")}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> [--scope user|project] ───────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst rest = trimmed.slice(\"install\".length).trim();\n\t\t\t\tconst scopeMatch = /(?:^|\\s)--scope[= ]\\s*(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--scope[= ]\\s*\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name> [--scope user|project]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (scopeMatch && !isInstallScope(scopeMatch[1])) {\n\t\t\t\t\tctx.ui.notify(`Unknown scope \"${scopeMatch[1]}\". Use --scope user or --scope project.`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait refreshIndices(ctx);\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// An explicit --scope wins; otherwise ask, because this is the human\n\t\t\t\t// path and the destination is a real choice (portable vs. committed to\n\t\t\t\t// the repo). With no UI to ask through there is nobody to ask, so it\n\t\t\t\t// falls back to the same setting the autonomous path uses.\n\t\t\t\tconst scope = scopeMatch\n\t\t\t\t\t? (scopeMatch[1] as PluginInstallScope)\n\t\t\t\t\t: await promptForScope(ctx, SettingsManager.create(cwd, getAgentDir()).getPluginInstallScope());\n\t\t\t\tif (!scope) {\n\t\t\t\t\tctx.ui.notify(\"Install cancelled.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name, getAgentDir(), { scope });\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A person typing this command in this directory is the human act\n\t\t\t\t// workspace trust asks for, so an explicit project-scope install grants\n\t\t\t\t// it — otherwise the plugin you just chose to install here would load\n\t\t\t\t// with its hooks withheld, which is nobody's idea of what happened.\n\t\t\t\t// The autonomous path deliberately does not do this.\n\t\t\t\tlet trustNote = \"\";\n\t\t\t\tif (scope === \"project\" && !isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\t\ttrustNote = ` Trusted this workspace, so its plugins may run hooks and MCP servers here (\\`/plugin untrust\\` reverses it).`;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message}${trustNote} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── trust / untrust ─────────────────────────────────────────────────\n\t\t\tif (trimmed === \"trust\" || trimmed.startsWith(\"trust \")) {\n\t\t\t\tconst sub = trimmed.slice(\"trust\".length).trim();\n\t\t\t\tif (sub === \"list\") {\n\t\t\t\t\tconst workspaces = listTrustedWorkspaces(getAgentDir());\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\tworkspaces.length\n\t\t\t\t\t\t\t? `Trusted workspaces:\\n${workspaces.map((w) => `${w.path} (since ${w.at.slice(0, 10)})`).join(\"\\n\")}`\n\t\t\t\t\t\t\t: \"No trusted workspaces. Plugins in a working tree load skills and commands, but not hooks or MCP servers.\",\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sub) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin trust | /plugin trust list | /plugin untrust\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Already trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Trusted ${cwd}. Plugins committed to this repository may now run hooks and MCP servers here, ` +\n\t\t\t\t\t\t\"including any added by a later pull. Reverse it with /plugin untrust. Reloading…\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed === \"untrust\") {\n\t\t\t\tif (!untrustWorkspace(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Not trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`Revoked trust for ${cwd}. Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> [--scope user|project] | \" +\n\t\t\t\t\t\"/plugin trust [list] | /plugin untrust | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
@@ -38,7 +38,7 @@ import { join } from "node:path";
38
38
  import { getAgentDir } from "../../config.js";
39
39
  import { CATEGORY_GLYPH, SEGMENT_SEP } from "../../core/brand.js";
40
40
  import { getPlugin } from "../../core/extensions/plugins/authoring.js";
41
- import { findAvailablePlugin, installAvailablePlugin, listAvailablePlugins, listInstalledPlugins, readMarketplaceRecords, refreshMarketplaces, uninstallPlugin, } from "../../core/extensions/plugins/install.js";
41
+ import { ensureWellKnownMarketplaces, findAvailablePlugin, installAvailablePlugin, listAvailablePlugins, listInstalledPlugins, readMarketplaceRecords, refreshMarketplaces, uninstallPlugin, wellKnownMarketplacesAreStale, } from "../../core/extensions/plugins/install.js";
42
42
  import { availablePluginGroups } from "../../core/extensions/plugins/listing.js";
43
43
  import { marketplaceCacheDir, marketplaceCacheRoot, marketplaceStorePath, } from "../../core/extensions/plugins/locations.js";
44
44
  import { parseMarketplaceDir, readMarketplaceStore, resolvePluginSource, writeMarketplaceStore, } from "../../core/extensions/plugins/marketplace.js";
@@ -95,6 +95,31 @@ function listHeader(theme, glyph, summary, hint) {
95
95
  const head = `${theme.fg("accent", glyph)} ${theme.fg("muted", summary)}`;
96
96
  return hint ? `${head}\n${theme.fg("dim", ` ${hint}`)}` : head;
97
97
  }
98
+ /**
99
+ * Bring the curated marketplace indices up to date before reading them.
100
+ *
101
+ * `SearchPlugins` (the model's path) has always done this; the human `/plugin`
102
+ * path never did, so a cache could sit stale indefinitely — and a *wrong* cache
103
+ * with it. That is not hypothetical: `awesome-copilot` is pinned to its built
104
+ * distribution branch, and the correction only reaches a cache that something
105
+ * asks to refresh. Someone who only ever types `/plugin install` would have kept
106
+ * installing unbuilt stubs and had no way to know why.
107
+ *
108
+ * TTL-respecting, so it is free when the cache is fresh. The notice is printed
109
+ * only when a fetch will actually happen, because a silent multi-second clone
110
+ * looks like a hang and a line printed every time looks like noise.
111
+ */
112
+ async function refreshIndices(ctx) {
113
+ const agentDir = getAgentDir();
114
+ if (wellKnownMarketplacesAreStale(agentDir))
115
+ ctx.ui.notify("Refreshing marketplace indices…", "info");
116
+ const errors = await ensureWellKnownMarketplaces(agentDir);
117
+ // Non-fatal by construction: whatever is already on disk still lists. Worth
118
+ // saying though — a retired ref reports here, and its consequence (entries
119
+ // that install with no capabilities) is otherwise inexplicable.
120
+ if (errors.length > 0)
121
+ ctx.ui.notify(`Marketplace indices: ${errors.join("; ")}`, "warning");
122
+ }
98
123
  export function setupMarketplace(pi) {
99
124
  pi.registerCommand("plugin", {
100
125
  description: "Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> [--scope user|project] | /plugin remove <name> | /plugin trust [list] | /plugin untrust | /plugin publish <name> [--to <dir>]",
@@ -184,6 +209,7 @@ export function setupMarketplace(pi) {
184
209
  }
185
210
  // ── list available plugins ──────────────────────────────────────────
186
211
  if (trimmed === "list" || trimmed === "") {
212
+ await refreshIndices(ctx);
187
213
  const available = listAvailablePlugins(cwd);
188
214
  if (available.length === 0) {
189
215
  ctx.ui.notify("No plugins available. Add a marketplace first.", "info");
@@ -220,6 +246,7 @@ export function setupMarketplace(pi) {
220
246
  ctx.ui.notify(`Unknown scope "${scopeMatch[1]}". Use --scope user or --scope project.`, "warning");
221
247
  return;
222
248
  }
249
+ await refreshIndices(ctx);
223
250
  if (!findAvailablePlugin(cwd, name)) {
224
251
  ctx.ui.notify(`Plugin "${name}" not found in any marketplace.`, "error");
225
252
  return;