@kolisachint/hoocode-agent 0.5.12 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -0
- package/dist/config.js.map +1 -1
- package/dist/core/context-files.d.ts +16 -3
- package/dist/core/context-files.d.ts.map +1 -1
- package/dist/core/context-files.js +77 -6
- package/dist/core/context-files.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +18 -6
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +35 -19
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/plugins/install.d.ts +51 -10
- package/dist/core/extensions/plugins/install.d.ts.map +1 -1
- package/dist/core/extensions/plugins/install.js +178 -31
- package/dist/core/extensions/plugins/install.js.map +1 -1
- package/dist/core/extensions/plugins/listing.d.ts +8 -2
- package/dist/core/extensions/plugins/listing.d.ts.map +1 -1
- package/dist/core/extensions/plugins/listing.js +29 -5
- package/dist/core/extensions/plugins/listing.js.map +1 -1
- package/dist/core/extensions/plugins/locations.d.ts +62 -12
- package/dist/core/extensions/plugins/locations.d.ts.map +1 -1
- package/dist/core/extensions/plugins/locations.js +74 -18
- package/dist/core/extensions/plugins/locations.js.map +1 -1
- package/dist/core/extensions/plugins/marketplace.d.ts +29 -3
- package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
- package/dist/core/extensions/plugins/marketplace.js +40 -7
- package/dist/core/extensions/plugins/marketplace.js.map +1 -1
- package/dist/core/extensions/plugins/trust.d.ts +56 -0
- package/dist/core/extensions/plugins/trust.d.ts.map +1 -0
- package/dist/core/extensions/plugins/trust.js +88 -0
- package/dist/core/extensions/plugins/trust.js.map +1 -0
- package/dist/core/learn/digest.d.ts +17 -0
- package/dist/core/learn/digest.d.ts.map +1 -0
- package/dist/core/learn/digest.js +131 -0
- package/dist/core/learn/digest.js.map +1 -0
- package/dist/core/learn/extract.d.ts +178 -0
- package/dist/core/learn/extract.d.ts.map +1 -0
- package/dist/core/learn/extract.js +705 -0
- package/dist/core/learn/extract.js.map +1 -0
- package/dist/core/learn/normalize.d.ts +65 -0
- package/dist/core/learn/normalize.d.ts.map +1 -0
- package/dist/core/learn/normalize.js +245 -0
- package/dist/core/learn/normalize.js.map +1 -0
- package/dist/core/learn/state.d.ts +115 -0
- package/dist/core/learn/state.d.ts.map +1 -0
- package/dist/core/learn/state.js +151 -0
- package/dist/core/learn/state.js.map +1 -0
- package/dist/core/settings-defaults.d.ts +6 -0
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +6 -0
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +21 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +31 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +6 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/tools/plugins.d.ts.map +1 -1
- package/dist/core/tools/plugins.js +18 -3
- package/dist/core/tools/plugins.js.map +1 -1
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +2 -0
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/extensions/core/learn.d.ts +22 -0
- package/dist/extensions/core/learn.d.ts.map +1 -0
- package/dist/extensions/core/learn.js +167 -0
- package/dist/extensions/core/learn.js.map +1 -0
- package/dist/extensions/core/marketplace.d.ts +7 -3
- package/dist/extensions/core/marketplace.d.ts.map +1 -1
- package/dist/extensions/core/marketplace.js +103 -11
- package/dist/extensions/core/marketplace.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +15 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +6 -1
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/docs/settings.md +31 -0
- package/docs/usage.md +64 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learn.js","sourceRoot":"","sources":["../../../src/extensions/core/learn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/learn` — promote what recent sessions actually taught into durable rules\n * and skills.\n *\n * The command is a thin shell on purpose. It runs the deterministic extractor\n * over session transcripts on disk, renders the ranked result, and injects it\n * as a follow-up message; every judgement after that belongs to the model,\n * which can read the repo and phrase a rule far better than a heuristic can.\n *\n * Reading transcripts from disk rather than the live context is what makes this\n * work: the on-disk history survives compaction, and it spans past sessions, so\n * \"you have said this in five separate sessions\" is available as a number\n * instead of a guess. That number is the whole reason the command exists.\n *\n * Follows /grill in modes.ts: no session switch, no mode change, no config\n * write — just a follow-up message. Writes to AGENTS.md happen through ordinary\n * edit tools, so the existing permission prompt is the approval step and no\n * separate picker is needed.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { loadProjectContextFiles } from \"../../core/context-files.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { isEmptyDigest, renderLearnDigest } from \"../../core/learn/digest.js\";\nimport { buildCoverageIndex, extractLearnDigest, matchCoverage } from \"../../core/learn/extract.js\";\nimport {\n\tgetLearnStatePath,\n\treadLearnState,\n\trecordSurfaced,\n\tsummarizeLearnState,\n\twriteLearnState,\n} from \"../../core/learn/state.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\n\n/** Guards against double-registration when default extensions load more than once. */\nconst REGISTERED = Symbol.for(\"hoocode.learn.registered\");\n\n/** User-scope destination offered for personal rules that travel across repos. */\nconst USER_SCOPE_PATH = join(homedir(), \".agents\", \"AGENTS.md\");\n\n/** Render a home-relative path the way the user would type it. */\nfunction displayPath(path: string): string {\n\tconst home = homedir();\n\treturn path.startsWith(home) ? `~${path.slice(home.length)}` : path;\n}\n\nfunction shortDate(iso: string | undefined): string {\n\tif (!iso) return \"unknown\";\n\tconst date = new Date(iso);\n\treturn Number.isNaN(date.getTime()) ? \"unknown\" : date.toISOString().slice(0, 10);\n}\n\n/**\n * `/learn stats` — what became of past proposals.\n *\n * Reads the state file and recomputes coverage; it does not re-mine sessions,\n * so it is instant and answers a different question than a normal run: not\n * \"what should I write down\" but \"is this command earning its place\".\n */\nfunction reportStats(ctx: ExtensionCommandContext): void {\n\tconst agentDir = getHooCodeDir();\n\tconst statePath = getLearnStatePath(agentDir, ctx.sessionManager.getSessionDir());\n\tconst state = readLearnState(statePath);\n\n\tif (Object.keys(state.surfaced).length === 0) {\n\t\tctx.ui.notify(\"No /learn history for this directory yet.\", \"info\");\n\t\treturn;\n\t}\n\n\tconst coverage = buildCoverageIndex({ cwd: ctx.cwd, agentDir });\n\tconst stats = summarizeLearnState(state, (normalized) => {\n\t\tconst match = matchCoverage(normalized, coverage);\n\t\treturn !!(match.rule || match.skill);\n\t});\n\n\tconst contextTokens = loadProjectContextFiles({ cwd: ctx.cwd, agentDir }).agentsFiles.reduce(\n\t\t(sum, file) => sum + (file.tokens ?? 0),\n\t\t0,\n\t);\n\n\tconst lines: string[] = [];\n\tlines.push(`/learn history for this directory — ${shortDate(stats.earliest)} to ${shortDate(stats.latest)}`);\n\tlines.push(\n\t\t` Proposals shown ${stats.total} (${stats.directives} directive, ${stats.fixes} fix, ${stats.workflows} workflow)`,\n\t);\n\tif (stats.lastRun) lines.push(` Last run ${shortDate(stats.lastRun)}`);\n\tlines.push(\"\");\n\n\tif (stats.open === 0) {\n\t\tlines.push(\"No directive proposals yet, so there is nothing to measure adoption against.\");\n\t} else {\n\t\tconst rate = Math.round((stats.adopted / stats.open) * 100);\n\t\tlines.push(\"Directive adoption — the only category with a coverage signal\");\n\t\tlines.push(` Written down ${stats.adopted} of ${stats.open} (${rate}%)`);\n\t\tlines.push(` Passed over ${stats.declined}`);\n\t\tlines.push(\"\");\n\t\t// Without this the number invites the wrong conclusion. Adoption is a proxy\n\t\t// for usefulness, and a proposal correctly rejected as not durable counts\n\t\t// against it exactly like a junk one — so near-100% means the bar is too\n\t\t// low, not that the extractor is perfect.\n\t\tlines.push(\" A very high rate means the bar is too low, not that every proposal was good.\");\n\t\tlines.push(\" Near zero means the extractor is proposing the wrong things.\");\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Context files ~${contextTokens} tokens, re-sent every request`);\n\n\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n}\n\nexport function setupLearn(pi: ExtensionAPI): void {\n\tconst guarded = pi as unknown as Record<symbol, boolean>;\n\tif (guarded[REGISTERED]) return;\n\tguarded[REGISTERED] = true;\n\n\tpi.registerCommand(\"learn\", {\n\t\tdescription: \"Mine recent sessions for durable rules and skills. Usage: /learn [all|stats]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t(\n\t\t\t\t[\n\t\t\t\t\t{ value: \"all\", label: \"re-propose everything\" },\n\t\t\t\t\t{ value: \"stats\", label: \"what happened to past proposals\" },\n\t\t\t\t] as const\n\t\t\t)\n\t\t\t\t.filter((option) => option.value.startsWith(prefix))\n\t\t\t\t.map((option) => ({ value: option.value, label: option.label })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst argument = args.trim().toLowerCase();\n\t\t\tif (argument && argument !== \"all\" && argument !== \"stats\") {\n\t\t\t\tctx.ui.notify(\"Usage: /learn [all|stats]\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (argument === \"stats\") {\n\t\t\t\treportStats(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst ignoreState = argument === \"all\";\n\n\t\t\t// Read per-invocation so a settings edit takes effect without a reload,\n\t\t\t// and so a project settings.json can narrow the window for one repo.\n\t\t\tconst agentDir = getHooCodeDir();\n\t\t\tconst window = SettingsManager.create(ctx.cwd, agentDir).getLearnSettings();\n\t\t\tconst sessionDir = ctx.sessionManager.getSessionDir();\n\t\t\tconst statePath = getLearnStatePath(agentDir, sessionDir);\n\n\t\t\tlet digest: ReturnType<typeof extractLearnDigest>;\n\t\t\ttry {\n\t\t\t\tdigest = extractLearnDigest({\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\t// The live session manager already knows where this cwd's sessions\n\t\t\t\t\t// live, which avoids re-deriving (and re-creating) the directory.\n\t\t\t\t\tsessionDir,\n\t\t\t\t\tmaxSessions: window.maxSessions,\n\t\t\t\t\tmaxAgeDays: window.maxAgeDays,\n\t\t\t\t\tminRepeats: window.minRepeats,\n\t\t\t\t\tminWorkflowRepeats: window.minWorkflowRepeats,\n\t\t\t\t\tmaxProposals: window.maxProposals,\n\t\t\t\t\tstate: readLearnState(statePath),\n\t\t\t\t\tignoreState,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(`/learn could not read session history: ${error}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (digest.scannedSessions === 0) {\n\t\t\t\tctx.ui.notify(\"No recent sessions in this directory to learn from.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isEmptyDigest(digest)) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\tdigest.suppressed > 0\n\t\t\t\t\t\t? `Scanned ${digest.scannedSessions} session(s) — nothing new since last time (${digest.suppressed} already shown). Run /learn all to see them again.`\n\t\t\t\t\t\t: `Scanned ${digest.scannedSessions} session(s) — nothing repeated often enough to be worth a rule yet.`,\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\tconst counts = [\n\t\t\t\tdigest.directives.length > 0 ? `${digest.directives.length} directive(s)` : undefined,\n\t\t\t\tdigest.fixes.length > 0 ? `${digest.fixes.length} fix(es)` : undefined,\n\t\t\t\tdigest.workflows.length > 0 ? `${digest.workflows.length} workflow(s)` : undefined,\n\t\t\t].filter((part): part is string => !!part);\n\t\t\tconst held = digest.suppressed > 0 ? `, ${digest.suppressed} held back` : \"\";\n\t\t\tctx.ui.notify(`Mined ${digest.scannedSessions} session(s): ${counts.join(\", \")}${held}.`, \"info\");\n\n\t\t\t// Record before delivering: what matters is that these were put in front\n\t\t\t// of the user, which is true whether or not they act on the digest.\n\t\t\twriteLearnState(statePath, recordSurfaced(readLearnState(statePath), digest.surfaced));\n\n\t\t\tpi.sendUserMessage(renderLearnDigest(digest, { userScopePath: displayPath(USER_SCOPE_PATH) }), {\n\t\t\t\tdeliverAs: \"followUp\",\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* `/plugin` installs plugins from marketplaces (a git repo or local dir with a
|
|
5
5
|
* native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,
|
|
6
6
|
* or Copilot-style `.github/marketplace.json` index). Installed plugins are placed
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* at the chosen scope — `~/.agents/plugins/<name>` for `user`, or
|
|
8
|
+
* `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader
|
|
9
|
+
* after a reload. `install` asks which, since a human is right here to answer;
|
|
10
|
+
* the autonomous tool reads the `pluginInstallScope` setting instead.
|
|
9
11
|
*
|
|
10
12
|
* Adding a marketplace is the human trust boundary and stays here; the shared
|
|
11
13
|
* mechanics (discovery, install, remove, the bundled default marketplace) live in
|
|
@@ -16,8 +18,10 @@
|
|
|
16
18
|
* /plugin marketplace list
|
|
17
19
|
* /plugin marketplace refresh re-fetch every cached index now
|
|
18
20
|
* /plugin list list available plugins across marketplaces
|
|
19
|
-
* /plugin install <name>
|
|
21
|
+
* /plugin install <name> [--scope user|project]
|
|
20
22
|
* /plugin remove <name>
|
|
23
|
+
* /plugin trust [list] allow repo-committed plugins to run code here
|
|
24
|
+
* /plugin untrust
|
|
21
25
|
* /plugin publish <name> [--to <marketplace-dir>]
|
|
22
26
|
*
|
|
23
27
|
* `publish` is the human end of the publish lane (§3.2). The model can package a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA4BH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAoC5F,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmOvD","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 * in the global consumption home (`~/.agents/plugins/<name>`) and loaded by the\n * plugin loader after a reload.\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>\n * /plugin remove <name>\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 { 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} 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 type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\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> | /plugin remove <name> | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\"]\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> ──────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst name = trimmed.slice(\"install\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name>\", \"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\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name);\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\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\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> | \" +\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;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"]}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* `/plugin` installs plugins from marketplaces (a git repo or local dir with a
|
|
5
5
|
* native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,
|
|
6
6
|
* or Copilot-style `.github/marketplace.json` index). Installed plugins are placed
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* at the chosen scope — `~/.agents/plugins/<name>` for `user`, or
|
|
8
|
+
* `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader
|
|
9
|
+
* after a reload. `install` asks which, since a human is right here to answer;
|
|
10
|
+
* the autonomous tool reads the `pluginInstallScope` setting instead.
|
|
9
11
|
*
|
|
10
12
|
* Adding a marketplace is the human trust boundary and stays here; the shared
|
|
11
13
|
* mechanics (discovery, install, remove, the bundled default marketplace) live in
|
|
@@ -16,8 +18,10 @@
|
|
|
16
18
|
* /plugin marketplace list
|
|
17
19
|
* /plugin marketplace refresh re-fetch every cached index now
|
|
18
20
|
* /plugin list list available plugins across marketplaces
|
|
19
|
-
* /plugin install <name>
|
|
21
|
+
* /plugin install <name> [--scope user|project]
|
|
20
22
|
* /plugin remove <name>
|
|
23
|
+
* /plugin trust [list] allow repo-committed plugins to run code here
|
|
24
|
+
* /plugin untrust
|
|
21
25
|
* /plugin publish <name> [--to <marketplace-dir>]
|
|
22
26
|
*
|
|
23
27
|
* `publish` is the human end of the publish lane (§3.2). The model can package a
|
|
@@ -31,6 +35,7 @@
|
|
|
31
35
|
*/
|
|
32
36
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
33
37
|
import { join } from "node:path";
|
|
38
|
+
import { getAgentDir } from "../../config.js";
|
|
34
39
|
import { CATEGORY_GLYPH, SEGMENT_SEP } from "../../core/brand.js";
|
|
35
40
|
import { getPlugin } from "../../core/extensions/plugins/authoring.js";
|
|
36
41
|
import { findAvailablePlugin, installAvailablePlugin, listAvailablePlugins, listInstalledPlugins, readMarketplaceRecords, refreshMarketplaces, uninstallPlugin, } from "../../core/extensions/plugins/install.js";
|
|
@@ -38,10 +43,36 @@ import { availablePluginGroups } from "../../core/extensions/plugins/listing.js"
|
|
|
38
43
|
import { marketplaceCacheDir, marketplaceCacheRoot, marketplaceStorePath, } from "../../core/extensions/plugins/locations.js";
|
|
39
44
|
import { parseMarketplaceDir, readMarketplaceStore, resolvePluginSource, writeMarketplaceStore, } from "../../core/extensions/plugins/marketplace.js";
|
|
40
45
|
import { entryNeedsSource, packagePlugin, stageIntoMarketplace } from "../../core/extensions/plugins/packaging.js";
|
|
46
|
+
import { isWorkspaceTrusted, listTrustedWorkspaces, trustWorkspace, untrustWorkspace, } from "../../core/extensions/plugins/trust.js";
|
|
41
47
|
import { plural, renderList } from "../../core/format-list.js";
|
|
48
|
+
import { SettingsManager } from "../../core/settings-manager.js";
|
|
42
49
|
function isGitSource(loc) {
|
|
43
50
|
return /^https?:\/\//.test(loc) || loc.startsWith("git@") || loc.endsWith(".git");
|
|
44
51
|
}
|
|
52
|
+
function isInstallScope(value) {
|
|
53
|
+
return value === "user" || value === "project";
|
|
54
|
+
}
|
|
55
|
+
/** Labels carry the consequence, since that is the whole content of the choice. */
|
|
56
|
+
const SCOPE_CHOICES = [
|
|
57
|
+
{ scope: "user", label: "User — ~/.agents/plugins, available in every project (recommended)" },
|
|
58
|
+
{ scope: "project", label: "Project — <repo>/.agents/plugins, committed and shared with collaborators" },
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Ask where the plugin should land. Returns undefined when the user dismisses
|
|
62
|
+
* the selector, which cancels the install rather than guessing.
|
|
63
|
+
*
|
|
64
|
+
* Headless (no UI to ask through) resolves to `fallback` — the standing setting
|
|
65
|
+
* — so a scripted or `--print` run still installs somewhere predictable.
|
|
66
|
+
*/
|
|
67
|
+
async function promptForScope(ctx, fallback) {
|
|
68
|
+
if (!ctx.hasUI)
|
|
69
|
+
return fallback;
|
|
70
|
+
const labels = SCOPE_CHOICES.map((c) => c.label);
|
|
71
|
+
const picked = await ctx.ui.select("Install scope", labels);
|
|
72
|
+
if (picked === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return SCOPE_CHOICES.find((c) => c.label === picked)?.scope ?? fallback;
|
|
75
|
+
}
|
|
45
76
|
/**
|
|
46
77
|
* Chat styling for a listing.
|
|
47
78
|
*
|
|
@@ -66,8 +97,8 @@ function listHeader(theme, glyph, summary, hint) {
|
|
|
66
97
|
}
|
|
67
98
|
export function setupMarketplace(pi) {
|
|
68
99
|
pi.registerCommand("plugin", {
|
|
69
|
-
description: "Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> | /plugin remove <name> | /plugin publish <name> [--to <dir>]",
|
|
70
|
-
getArgumentCompletions: (prefix) => ["marketplace", "list", "install", "remove", "refresh", "publish"]
|
|
100
|
+
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>]",
|
|
101
|
+
getArgumentCompletions: (prefix) => ["marketplace", "list", "install", "remove", "refresh", "publish", "trust", "untrust"]
|
|
71
102
|
.filter((s) => s.startsWith(prefix))
|
|
72
103
|
.map((s) => ({ value: s, label: s })),
|
|
73
104
|
handler: async (args, ctx) => {
|
|
@@ -176,23 +207,83 @@ export function setupMarketplace(pi) {
|
|
|
176
207
|
ctx.ui.notify(`${header}\n${body}\n\n${theme.fg("dim", " /plugin install <name> to add one")}`, "info");
|
|
177
208
|
return;
|
|
178
209
|
}
|
|
179
|
-
// ── install <name>
|
|
210
|
+
// ── install <name> [--scope user|project] ───────────────────────────
|
|
180
211
|
if (trimmed.startsWith("install")) {
|
|
181
|
-
const
|
|
212
|
+
const rest = trimmed.slice("install".length).trim();
|
|
213
|
+
const scopeMatch = /(?:^|\s)--scope[= ]\s*(\S+)/.exec(rest);
|
|
214
|
+
const name = rest.replace(/(?:^|\s)--scope[= ]\s*\S+/, "").trim();
|
|
182
215
|
if (!name) {
|
|
183
|
-
ctx.ui.notify("Usage: /plugin install <name>", "warning");
|
|
216
|
+
ctx.ui.notify("Usage: /plugin install <name> [--scope user|project]", "warning");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (scopeMatch && !isInstallScope(scopeMatch[1])) {
|
|
220
|
+
ctx.ui.notify(`Unknown scope "${scopeMatch[1]}". Use --scope user or --scope project.`, "warning");
|
|
184
221
|
return;
|
|
185
222
|
}
|
|
186
223
|
if (!findAvailablePlugin(cwd, name)) {
|
|
187
224
|
ctx.ui.notify(`Plugin "${name}" not found in any marketplace.`, "error");
|
|
188
225
|
return;
|
|
189
226
|
}
|
|
190
|
-
|
|
227
|
+
// An explicit --scope wins; otherwise ask, because this is the human
|
|
228
|
+
// path and the destination is a real choice (portable vs. committed to
|
|
229
|
+
// the repo). With no UI to ask through there is nobody to ask, so it
|
|
230
|
+
// falls back to the same setting the autonomous path uses.
|
|
231
|
+
const scope = scopeMatch
|
|
232
|
+
? scopeMatch[1]
|
|
233
|
+
: await promptForScope(ctx, SettingsManager.create(cwd, getAgentDir()).getPluginInstallScope());
|
|
234
|
+
if (!scope) {
|
|
235
|
+
ctx.ui.notify("Install cancelled.", "info");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const outcome = await installAvailablePlugin(cwd, name, getAgentDir(), { scope });
|
|
191
239
|
if (!outcome.installed) {
|
|
192
240
|
ctx.ui.notify(outcome.message, "error");
|
|
193
241
|
return;
|
|
194
242
|
}
|
|
195
|
-
|
|
243
|
+
// A person typing this command in this directory is the human act
|
|
244
|
+
// workspace trust asks for, so an explicit project-scope install grants
|
|
245
|
+
// it — otherwise the plugin you just chose to install here would load
|
|
246
|
+
// with its hooks withheld, which is nobody's idea of what happened.
|
|
247
|
+
// The autonomous path deliberately does not do this.
|
|
248
|
+
let trustNote = "";
|
|
249
|
+
if (scope === "project" && !isWorkspaceTrusted(cwd, getAgentDir())) {
|
|
250
|
+
trustWorkspace(cwd, getAgentDir());
|
|
251
|
+
trustNote = ` Trusted this workspace, so its plugins may run hooks and MCP servers here (\`/plugin untrust\` reverses it).`;
|
|
252
|
+
}
|
|
253
|
+
ctx.ui.notify(`${outcome.message}${trustNote} Reloading…`, "info");
|
|
254
|
+
await ctx.reload();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// ── trust / untrust ─────────────────────────────────────────────────
|
|
258
|
+
if (trimmed === "trust" || trimmed.startsWith("trust ")) {
|
|
259
|
+
const sub = trimmed.slice("trust".length).trim();
|
|
260
|
+
if (sub === "list") {
|
|
261
|
+
const workspaces = listTrustedWorkspaces(getAgentDir());
|
|
262
|
+
ctx.ui.notify(workspaces.length
|
|
263
|
+
? `Trusted workspaces:\n${workspaces.map((w) => `${w.path} (since ${w.at.slice(0, 10)})`).join("\n")}`
|
|
264
|
+
: "No trusted workspaces. Plugins in a working tree load skills and commands, but not hooks or MCP servers.", "info");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (sub) {
|
|
268
|
+
ctx.ui.notify("Usage: /plugin trust | /plugin trust list | /plugin untrust", "warning");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (isWorkspaceTrusted(cwd, getAgentDir())) {
|
|
272
|
+
ctx.ui.notify(`Already trusted: ${cwd}`, "info");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
trustWorkspace(cwd, getAgentDir());
|
|
276
|
+
ctx.ui.notify(`Trusted ${cwd}. Plugins committed to this repository may now run hooks and MCP servers here, ` +
|
|
277
|
+
"including any added by a later pull. Reverse it with /plugin untrust. Reloading…", "info");
|
|
278
|
+
await ctx.reload();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (trimmed === "untrust") {
|
|
282
|
+
if (!untrustWorkspace(cwd, getAgentDir())) {
|
|
283
|
+
ctx.ui.notify(`Not trusted: ${cwd}`, "info");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
ctx.ui.notify(`Revoked trust for ${cwd}. Reloading…`, "info");
|
|
196
287
|
await ctx.reload();
|
|
197
288
|
return;
|
|
198
289
|
}
|
|
@@ -251,7 +342,8 @@ export function setupMarketplace(pi) {
|
|
|
251
342
|
await ctx.reload();
|
|
252
343
|
return;
|
|
253
344
|
}
|
|
254
|
-
ctx.ui.notify("Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> | " +
|
|
345
|
+
ctx.ui.notify("Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> [--scope user|project] | " +
|
|
346
|
+
"/plugin trust [list] | /plugin untrust | " +
|
|
255
347
|
"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]", "warning");
|
|
256
348
|
},
|
|
257
349
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"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","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 * in the global consumption home (`~/.agents/plugins/<name>`) and loaded by the\n * plugin loader after a reload.\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>\n * /plugin remove <name>\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 { 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} 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 type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\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> | /plugin remove <name> | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\"]\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> ──────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst name = trimmed.slice(\"install\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name>\", \"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\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name);\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\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\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> | \" +\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.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"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","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"]}
|
|
@@ -38,6 +38,7 @@ export interface SettingsConfig {
|
|
|
38
38
|
autoResizeImages: boolean;
|
|
39
39
|
blockImages: boolean;
|
|
40
40
|
enableSkillCommands: boolean;
|
|
41
|
+
pluginInstallScope: "user" | "project";
|
|
41
42
|
steeringMode: "all" | "one-at-a-time";
|
|
42
43
|
followUpMode: "all" | "one-at-a-time";
|
|
43
44
|
transport: Transport;
|
|
@@ -75,6 +76,7 @@ export interface SettingsCallbacks {
|
|
|
75
76
|
onAutoResizeImagesChange: (enabled: boolean) => void;
|
|
76
77
|
onBlockImagesChange: (blocked: boolean) => void;
|
|
77
78
|
onEnableSkillCommandsChange: (enabled: boolean) => void;
|
|
79
|
+
onPluginInstallScopeChange: (scope: "user" | "project") => void;
|
|
78
80
|
onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
|
|
79
81
|
onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
|
|
80
82
|
onTransportChange: (transport: Transport) => void;
|