@yagni-app/code 1.0.5 → 1.0.7
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/README.md +89 -27
- package/dist/claudeCompat.d.ts +59 -0
- package/dist/claudeCompat.js +109 -2
- package/dist/claudePlugins.d.ts +45 -3
- package/dist/claudePlugins.js +129 -19
- package/dist/cli.js +28 -4
- package/dist/doctor.d.ts +42 -4
- package/dist/doctor.js +150 -15
- package/dist/extension/condensedTools.d.ts +12 -1
- package/dist/extension/condensedTools.js +17 -9
- package/dist/extension/index.d.ts +18 -5
- package/dist/extension/index.js +319 -72
- package/dist/extension/mcp/approval.d.ts +45 -0
- package/dist/extension/mcp/approval.js +164 -0
- package/dist/extension/mcp/auth.d.ts +124 -0
- package/dist/extension/mcp/auth.js +560 -0
- package/dist/extension/mcp/authStore.d.ts +61 -0
- package/dist/extension/mcp/authStore.js +105 -0
- package/dist/extension/mcp/callbackPage.d.ts +31 -0
- package/dist/extension/mcp/callbackPage.js +222 -0
- package/dist/extension/mcp/cliConfig.d.ts +12 -0
- package/dist/extension/mcp/cliConfig.js +12 -0
- package/dist/extension/mcp/config.d.ts +153 -0
- package/dist/extension/mcp/config.js +381 -0
- package/dist/extension/mcp/log.d.ts +28 -0
- package/dist/extension/mcp/log.js +82 -0
- package/dist/extension/mcp/manager.d.ts +100 -0
- package/dist/extension/mcp/manager.js +273 -0
- package/dist/extension/mcp/names.d.ts +25 -0
- package/dist/extension/mcp/names.js +40 -0
- package/dist/extension/mcp/panel.d.ts +33 -0
- package/dist/extension/mcp/panel.js +268 -0
- package/dist/extension/mcp/prompts.d.ts +23 -0
- package/dist/extension/mcp/prompts.js +93 -0
- package/dist/extension/mcp/startup.d.ts +55 -0
- package/dist/extension/mcp/startup.js +152 -0
- package/dist/extension/mcp/tools.d.ts +31 -0
- package/dist/extension/mcp/tools.js +117 -0
- package/dist/extension/mcp/transports.d.ts +17 -0
- package/dist/extension/mcp/transports.js +44 -0
- package/dist/extension/permission/gate.d.ts +104 -2
- package/dist/extension/permission/gate.js +387 -31
- package/dist/extension/permission/guardian.d.ts +24 -5
- package/dist/extension/permission/guardian.js +162 -24
- package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
- package/dist/extension/permissionRules/bashFileArgs.js +236 -0
- package/dist/extension/permissionRules/engine.d.ts +50 -0
- package/dist/extension/permissionRules/engine.js +238 -0
- package/dist/extension/permissionRules/loadConfig.d.ts +53 -0
- package/dist/extension/permissionRules/loadConfig.js +90 -0
- package/dist/extension/permissionRules/parser.d.ts +38 -0
- package/dist/extension/permissionRules/parser.js +136 -0
- package/dist/extension/permissionRules/pathRules.d.ts +58 -0
- package/dist/extension/permissionRules/pathRules.js +120 -0
- package/dist/extension/permissionRules/shellRules.d.ts +52 -0
- package/dist/extension/permissionRules/shellRules.js +221 -0
- package/dist/extension/pipeline/invocation.d.ts +3 -6
- package/dist/extension/pipeline/invocation.js +3 -6
- package/dist/extension/pipeline/personas.js +5 -0
- package/dist/extension/pipeline/runner.d.ts +0 -1
- package/dist/extension/pipeline/runner.js +6 -14
- package/dist/extension/plugins/inventory.d.ts +88 -0
- package/dist/extension/plugins/inventory.js +144 -0
- package/dist/extension/plugins/panel.d.ts +45 -0
- package/dist/extension/plugins/panel.js +293 -0
- package/dist/extension/sandbox/bash.d.ts +99 -0
- package/dist/extension/sandbox/bash.js +190 -0
- package/dist/extension/sandbox/config.d.ts +114 -0
- package/dist/extension/sandbox/config.js +366 -0
- package/dist/extension/sandbox/manager.d.ts +98 -0
- package/dist/extension/sandbox/manager.js +216 -0
- package/dist/extension/sandbox/panel.d.ts +111 -0
- package/dist/extension/sandbox/panel.js +342 -0
- package/dist/extension/sandbox/session.d.ts +85 -0
- package/dist/extension/sandbox/session.js +775 -0
- package/dist/extension/telemetry/attrs.d.ts +96 -0
- package/dist/extension/telemetry/attrs.js +149 -0
- package/dist/extension/telemetry/config.d.ts +99 -0
- package/dist/extension/telemetry/config.js +193 -0
- package/dist/extension/telemetry/index.d.ts +7 -0
- package/dist/extension/telemetry/index.js +7 -0
- package/dist/extension/telemetry/probe.d.ts +29 -0
- package/dist/extension/telemetry/probe.js +122 -0
- package/dist/extension/telemetry/register.d.ts +40 -0
- package/dist/extension/telemetry/register.js +192 -0
- package/dist/extension/telemetry/sdk.d.ts +63 -0
- package/dist/extension/telemetry/sdk.js +207 -0
- package/dist/extension/telemetry/tracker.d.ts +131 -0
- package/dist/extension/telemetry/tracker.js +551 -0
- package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
- package/dist/extension/vendor/ignore.d.ts +86 -0
- package/dist/extension/vendor/ignore.js +788 -0
- package/dist/goHeadless.d.ts +1 -1
- package/dist/goHeadless.js +2 -2
- package/dist/launch.d.ts +4 -3
- package/dist/launch.js +7 -4
- package/dist/mcpCommand.d.ts +122 -0
- package/dist/mcpCommand.js +787 -0
- package/dist/otel.d.ts +69 -63
- package/dist/otel.js +154 -119
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +18 -0
- package/dist/pluginCommand.d.ts +43 -0
- package/dist/pluginCommand.js +499 -0
- package/dist/pluginStore.d.ts +170 -0
- package/dist/pluginStore.js +554 -0
- package/dist/upgrade.d.ts +11 -2
- package/dist/upgrade.js +48 -8
- package/package.json +20 -3
- package/dist/extension/mcpTools.d.ts +0 -57
- package/dist/extension/mcpTools.js +0 -132
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yagni plugin …` — install, remove, and update Claude Code plugins from
|
|
3
|
+
* marketplaces without leaving YAGNI Code, mirroring Claude Code's own
|
|
4
|
+
* `claude plugin` surface (marketplace add/list/update/remove, install,
|
|
5
|
+
* uninstall, enable, disable, update, list).
|
|
6
|
+
*
|
|
7
|
+
* State lives in YAGNI's own ledger (pluginStore.ts). Plugins Claude Code
|
|
8
|
+
* installed are still mirrored read-only (claudePlugins.ts) and show up in
|
|
9
|
+
* `list` labelled as such, so a developer sees one honest inventory: what
|
|
10
|
+
* loads, where it came from, and which components (hooks) are not bridged.
|
|
11
|
+
*/
|
|
12
|
+
import { type PluginAssets } from "./claudePlugins.js";
|
|
13
|
+
import { type GitRunner, type PluginInstallScope } from "./pluginStore.js";
|
|
14
|
+
export interface PluginCommandDeps {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/** The user's home dir (`~`); the ledger lives under `$YAGNI_CODE_HOME` or `~/.yagni-code`. */
|
|
17
|
+
home?: string;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
git?: GitRunner;
|
|
20
|
+
stdout?: (text: string) => void;
|
|
21
|
+
stderr?: (text: string) => void;
|
|
22
|
+
/** pi agent dir holding trust.json (defaults to the active profile's). */
|
|
23
|
+
agentDir?: () => Promise<string>;
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedPluginArgs {
|
|
27
|
+
subcommand: string | undefined;
|
|
28
|
+
positionals: string[];
|
|
29
|
+
scope: PluginInstallScope;
|
|
30
|
+
scopeExplicit: boolean;
|
|
31
|
+
available: boolean;
|
|
32
|
+
json: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function parsePluginArgs(args: string[]): ParsedPluginArgs;
|
|
35
|
+
/** The repo root a project-scope install applies to: nearest `.git` ancestor, else cwd. */
|
|
36
|
+
export declare function resolveRepoRoot(cwd: string): string;
|
|
37
|
+
export declare function pluginCommand(args: string[], deps?: PluginCommandDeps): Promise<number>;
|
|
38
|
+
export declare function componentSummary(p: PluginAssets): string;
|
|
39
|
+
/** Whether a stdio `command` resolves on PATH (or is an existing path). Unknown when it has an unexpanded ${VAR}. */
|
|
40
|
+
export declare function commandOnPath(command: string, env: NodeJS.ProcessEnv): boolean | null;
|
|
41
|
+
/** Per-server notes for a plugin's MCP block: names, and a missing-binary warning for stdio servers. */
|
|
42
|
+
export declare function mcpNotes(p: PluginAssets, env: NodeJS.ProcessEnv): string[];
|
|
43
|
+
//# sourceMappingURL=pluginCommand.d.ts.map
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yagni plugin …` — install, remove, and update Claude Code plugins from
|
|
3
|
+
* marketplaces without leaving YAGNI Code, mirroring Claude Code's own
|
|
4
|
+
* `claude plugin` surface (marketplace add/list/update/remove, install,
|
|
5
|
+
* uninstall, enable, disable, update, list).
|
|
6
|
+
*
|
|
7
|
+
* State lives in YAGNI's own ledger (pluginStore.ts). Plugins Claude Code
|
|
8
|
+
* installed are still mirrored read-only (claudePlugins.ts) and show up in
|
|
9
|
+
* `list` labelled as such, so a developer sees one honest inventory: what
|
|
10
|
+
* loads, where it came from, and which components (hooks) are not bridged.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { delimiter, dirname, isAbsolute, join, sep } from "node:path";
|
|
15
|
+
import { pluginInventory, readTrustDecision } from "./claudeCompat.js";
|
|
16
|
+
import { discoverClaudePlugins } from "./claudePlugins.js";
|
|
17
|
+
import { agentDir } from "./credentials.js";
|
|
18
|
+
import { DISTRIBUTION } from "./distribution.js";
|
|
19
|
+
import { PluginStoreError, addMarketplace, catalog, describeSource, installPlugin, parseMarketplaceSource, pluginsHome, pruneCache, readKnownMarketplaces, readLedger, removeMarketplace, setPluginEnabled, uninstallPlugin, updateMarketplace, updatePlugins, } from "./pluginStore.js";
|
|
20
|
+
import { getActiveProfileName } from "./profiles.js";
|
|
21
|
+
const cmd = DISTRIBUTION.commandName;
|
|
22
|
+
const USAGE = `Usage:
|
|
23
|
+
${cmd} plugin marketplace add <owner/repo | git-url | path>
|
|
24
|
+
${cmd} plugin marketplace list
|
|
25
|
+
${cmd} plugin marketplace update [name]
|
|
26
|
+
${cmd} plugin marketplace remove <name>
|
|
27
|
+
${cmd} plugin install <plugin>[@marketplace] [-s user|project]
|
|
28
|
+
${cmd} plugin uninstall <plugin>[@marketplace] [-s user|project]
|
|
29
|
+
${cmd} plugin enable <plugin>[@marketplace]
|
|
30
|
+
${cmd} plugin disable <plugin>[@marketplace]
|
|
31
|
+
${cmd} plugin update [<plugin>[@marketplace]]
|
|
32
|
+
${cmd} plugin list [--available]
|
|
33
|
+
${cmd} plugin inventory --json (machine-readable; backs the in-session /plugin panel)
|
|
34
|
+
|
|
35
|
+
In a session: /plugin opens the same panel; /reload-plugins applies changes
|
|
36
|
+
without restarting.
|
|
37
|
+
|
|
38
|
+
Scopes (install/uninstall):
|
|
39
|
+
user available in all your projects (default)
|
|
40
|
+
project only inside this repo
|
|
41
|
+
|
|
42
|
+
What loads from a plugin: skills, commands, agents, and MCP servers
|
|
43
|
+
(.mcp.json at the plugin root). Hooks and LSP servers are not bridged;
|
|
44
|
+
\`plugin list\` says so per plugin. State: ~/.yagni-code/plugins.
|
|
45
|
+
Plugins installed with Claude Code itself are picked up too (read-only).
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
${cmd} plugin marketplace add acme/claude-plugins
|
|
49
|
+
${cmd} plugin install code-review@acme-plugins
|
|
50
|
+
${cmd} plugin update
|
|
51
|
+
`;
|
|
52
|
+
export function parsePluginArgs(args) {
|
|
53
|
+
const out = {
|
|
54
|
+
subcommand: undefined,
|
|
55
|
+
positionals: [],
|
|
56
|
+
scope: "user",
|
|
57
|
+
scopeExplicit: false,
|
|
58
|
+
available: false,
|
|
59
|
+
json: false,
|
|
60
|
+
};
|
|
61
|
+
const scopeFrom = (value) => {
|
|
62
|
+
if (value === "user" || value === "project")
|
|
63
|
+
return value;
|
|
64
|
+
throw new Error(`Invalid scope "${value ?? ""}" — expected user or project.`);
|
|
65
|
+
};
|
|
66
|
+
for (let i = 0; i < args.length; i++) {
|
|
67
|
+
const arg = args[i];
|
|
68
|
+
if (arg === "-s" || arg === "--scope") {
|
|
69
|
+
out.scope = scopeFrom(args[++i]);
|
|
70
|
+
out.scopeExplicit = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg.startsWith("--scope=")) {
|
|
74
|
+
out.scope = scopeFrom(arg.slice("--scope=".length));
|
|
75
|
+
out.scopeExplicit = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--available" || arg === "-a") {
|
|
79
|
+
out.available = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === "--json") {
|
|
83
|
+
out.json = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg === "-h" || arg === "--help") {
|
|
87
|
+
out.subcommand = "help";
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg.startsWith("-"))
|
|
91
|
+
throw new Error(`Unknown option "${arg}".`);
|
|
92
|
+
if (out.subcommand === undefined)
|
|
93
|
+
out.subcommand = arg;
|
|
94
|
+
else
|
|
95
|
+
out.positionals.push(arg);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/** The repo root a project-scope install applies to: nearest `.git` ancestor, else cwd. */
|
|
100
|
+
export function resolveRepoRoot(cwd) {
|
|
101
|
+
let current = cwd;
|
|
102
|
+
for (;;) {
|
|
103
|
+
if (existsSync(join(current, ".git")))
|
|
104
|
+
return current;
|
|
105
|
+
const parent = dirname(current);
|
|
106
|
+
if (parent === current)
|
|
107
|
+
return cwd;
|
|
108
|
+
current = parent;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export async function pluginCommand(args, deps = {}) {
|
|
112
|
+
const stdout = deps.stdout ?? ((t) => process.stdout.write(t));
|
|
113
|
+
const stderr = deps.stderr ?? ((t) => process.stderr.write(t));
|
|
114
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
115
|
+
const home = deps.home ?? homedir();
|
|
116
|
+
const env = deps.env ?? process.env;
|
|
117
|
+
const io = {
|
|
118
|
+
cwd,
|
|
119
|
+
home,
|
|
120
|
+
env,
|
|
121
|
+
store: { home: pluginsHome(env, home), ...(deps.git ? { git: deps.git } : {}), ...(deps.now ? { now: deps.now } : {}) },
|
|
122
|
+
stdout,
|
|
123
|
+
stderr,
|
|
124
|
+
agentDir: deps.agentDir ?? (async () => agentDir(await getActiveProfileName())),
|
|
125
|
+
};
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = parsePluginArgs(args);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
stderr(`${err instanceof Error ? err.message : String(err)}\n${USAGE}`);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
switch (parsed.subcommand) {
|
|
136
|
+
case undefined:
|
|
137
|
+
case "help":
|
|
138
|
+
stdout(USAGE);
|
|
139
|
+
return 0;
|
|
140
|
+
case "marketplace":
|
|
141
|
+
return await marketplaceCommand(parsed, io);
|
|
142
|
+
case "install":
|
|
143
|
+
case "add":
|
|
144
|
+
return await install(parsed, io);
|
|
145
|
+
case "uninstall":
|
|
146
|
+
case "remove":
|
|
147
|
+
return uninstall(parsed, io);
|
|
148
|
+
case "enable":
|
|
149
|
+
return toggle(parsed, true, io);
|
|
150
|
+
case "disable":
|
|
151
|
+
return toggle(parsed, false, io);
|
|
152
|
+
case "update":
|
|
153
|
+
return await update(parsed, io);
|
|
154
|
+
case "list":
|
|
155
|
+
return parsed.available ? listAvailable(io) : await list(io);
|
|
156
|
+
case "inventory":
|
|
157
|
+
return await inventory(io);
|
|
158
|
+
default:
|
|
159
|
+
stderr(`Unknown plugin subcommand "${parsed.subcommand}".\n${USAGE}`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
if (err instanceof PluginStoreError) {
|
|
165
|
+
stderr(`Error: ${err.message}\n`);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ── marketplace ─────────────────────────────────────────────────────────────
|
|
172
|
+
async function marketplaceCommand(parsed, io) {
|
|
173
|
+
const [action, target] = parsed.positionals;
|
|
174
|
+
switch (action) {
|
|
175
|
+
case "add": {
|
|
176
|
+
if (!target) {
|
|
177
|
+
io.stderr(`Error: a marketplace source is required.\nUsage: ${cmd} plugin marketplace add <owner/repo | git-url | path>\n`);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const source = parseMarketplaceSource(target, io.cwd);
|
|
181
|
+
if (!source) {
|
|
182
|
+
io.stderr(`Error: "${target}" is not a GitHub owner/repo, a git URL, or an existing directory.\n`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
if (source.source !== "directory")
|
|
186
|
+
io.stdout(`Cloning ${describeSource(source)}…\n`);
|
|
187
|
+
const added = await addMarketplace(source, io.store);
|
|
188
|
+
const names = added.marketplace.plugins.map((p) => p.name);
|
|
189
|
+
io.stdout(`Added marketplace "${added.name}" (${describeSource(source)}) with ${names.length} plugin(s)${names.length ? `: ${names.join(", ")}` : ""}.\n`);
|
|
190
|
+
if (names[0])
|
|
191
|
+
io.stdout(`Install one with: ${cmd} plugin install ${names[0]}@${added.name}\n`);
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
case "list":
|
|
195
|
+
case undefined: {
|
|
196
|
+
const known = readKnownMarketplaces(io.store.home);
|
|
197
|
+
const entries = Object.entries(known);
|
|
198
|
+
if (entries.length === 0) {
|
|
199
|
+
io.stdout(`No marketplaces added. Add one with: ${cmd} plugin marketplace add <owner/repo | git-url | path>\n`);
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
for (const [name, mp] of entries) {
|
|
203
|
+
io.stdout(`${name}\n Source: ${describeSource(mp.source)}\n Location: ${mp.installLocation}\n`);
|
|
204
|
+
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
case "update": {
|
|
208
|
+
const known = readKnownMarketplaces(io.store.home);
|
|
209
|
+
const names = target ? [target] : Object.keys(known);
|
|
210
|
+
if (names.length === 0) {
|
|
211
|
+
io.stdout("No marketplaces to update.\n");
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
let failed = false;
|
|
215
|
+
for (const name of names) {
|
|
216
|
+
try {
|
|
217
|
+
const { changed } = await updateMarketplace(name, io.store);
|
|
218
|
+
io.stdout(`${name}: ${changed ? "updated" : "already up to date"}\n`);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
failed = true;
|
|
222
|
+
io.stderr(`${name}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!failed)
|
|
226
|
+
io.stdout(`Installed plugins are re-synced by: ${cmd} plugin update\n`);
|
|
227
|
+
return failed ? 1 : 0;
|
|
228
|
+
}
|
|
229
|
+
case "remove": {
|
|
230
|
+
if (!target) {
|
|
231
|
+
io.stderr(`Error: a marketplace name is required.\nUsage: ${cmd} plugin marketplace remove <name>\n`);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
const { removedPlugins } = removeMarketplace(target, io.store);
|
|
235
|
+
pruneCache(io.store.home);
|
|
236
|
+
io.stdout(`Removed marketplace "${target}"${removedPlugins.length ? ` and its installed plugin(s): ${removedPlugins.join(", ")}` : ""}.\n`);
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
io.stderr(`Unknown marketplace action "${action}".\n${USAGE}`);
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ── install / uninstall / enable / disable / update ─────────────────────────
|
|
245
|
+
async function install(parsed, io) {
|
|
246
|
+
const spec = parsed.positionals[0];
|
|
247
|
+
if (!spec) {
|
|
248
|
+
io.stderr(`Error: a plugin is required.\nUsage: ${cmd} plugin install <plugin>[@marketplace] [-s user|project]\n`);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
const projectPath = parsed.scope === "project" ? resolveRepoRoot(io.cwd) : undefined;
|
|
252
|
+
const { resolved, installPath, replaced } = await installPlugin(spec, { scope: parsed.scope, projectPath }, io.store);
|
|
253
|
+
const assets = discoverClaudePlugins({ cwd: io.cwd, homeDir: io.home, yagniLedger: readLedger(io.store.home) }).user.find((p) => p.key === resolved.key);
|
|
254
|
+
const where = parsed.scope === "project" ? `project scope, ${projectPath}` : "user scope";
|
|
255
|
+
io.stdout(`${replaced ? "Reinstalled" : "Installed"} ${resolved.key} v${resolved.version} (${where}).\n`);
|
|
256
|
+
if (assets) {
|
|
257
|
+
io.stdout(` Loads: ${componentSummary(assets)}\n`);
|
|
258
|
+
for (const line of mcpNotes(assets, io.env))
|
|
259
|
+
io.stdout(` ${line}\n`);
|
|
260
|
+
if (assets.unsupported.length > 0)
|
|
261
|
+
io.stdout(` Not bridged: ${assets.unsupported.join(", ")}\n`);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
io.stdout(` Warning: nothing loadable found in ${installPath} (no skills, commands, agents, or MCP servers).\n`);
|
|
265
|
+
}
|
|
266
|
+
io.stdout(`Takes effect on the next ${cmd} launch.\n`);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
function uninstall(parsed, io) {
|
|
270
|
+
const spec = parsed.positionals[0];
|
|
271
|
+
if (!spec) {
|
|
272
|
+
io.stderr(`Error: a plugin is required.\nUsage: ${cmd} plugin uninstall <plugin>[@marketplace] [-s user|project]\n`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
const { key, removed } = uninstallPlugin(spec, { ...(parsed.scopeExplicit ? { scope: parsed.scope } : {}), projectPath: resolveRepoRoot(io.cwd) }, io.store);
|
|
276
|
+
pruneCache(io.store.home);
|
|
277
|
+
io.stdout(`Uninstalled ${key} (${removed.map((r) => r.scope).join(", ")} scope).\n`);
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
function toggle(parsed, enabled, io) {
|
|
281
|
+
const spec = parsed.positionals[0];
|
|
282
|
+
if (!spec) {
|
|
283
|
+
io.stderr(`Error: a plugin is required.\nUsage: ${cmd} plugin ${enabled ? "enable" : "disable"} <plugin>[@marketplace]\n`);
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
const { key } = setPluginEnabled(spec, enabled, io.store);
|
|
287
|
+
io.stdout(`${enabled ? "Enabled" : "Disabled"} ${key}. Takes effect on the next ${cmd} launch.\n`);
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
async function update(parsed, io) {
|
|
291
|
+
const spec = parsed.positionals[0];
|
|
292
|
+
if (!spec && Object.keys(readLedger(io.store.home).plugins).length === 0) {
|
|
293
|
+
io.stdout(`No plugins installed via ${cmd}. Install one with: ${cmd} plugin install <plugin>[@marketplace]\n`);
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
const report = await updatePlugins(spec, io.store);
|
|
297
|
+
for (const mp of report.marketplaces)
|
|
298
|
+
io.stdout(`Marketplace ${mp.name}: ${mp.changed ? "updated" : "already up to date"}\n`);
|
|
299
|
+
for (const p of report.plugins) {
|
|
300
|
+
io.stdout(`${p.key}: ${p.from === p.to ? `v${p.to} re-synced` : `v${p.from} → v${p.to}`}\n`);
|
|
301
|
+
}
|
|
302
|
+
for (const e of report.errors)
|
|
303
|
+
io.stderr(`Error: ${e}\n`);
|
|
304
|
+
pruneCache(io.store.home);
|
|
305
|
+
if (report.plugins.length > 0)
|
|
306
|
+
io.stdout(`Takes effect on the next ${cmd} launch.\n`);
|
|
307
|
+
return report.errors.length > 0 ? 1 : 0;
|
|
308
|
+
}
|
|
309
|
+
// ── inventory (machine-readable, for the in-session panel) ─────────────────
|
|
310
|
+
async function inventory(io) {
|
|
311
|
+
const result = pluginInventory({ cwd: io.cwd, agentDir: await io.agentDir(), homeDir: io.home, env: io.env });
|
|
312
|
+
io.stdout(`${JSON.stringify(result)}\n`);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
// ── list ────────────────────────────────────────────────────────────────────
|
|
316
|
+
async function list(io) {
|
|
317
|
+
const discovered = discoverClaudePlugins({ cwd: io.cwd, homeDir: io.home, yagniLedger: readLedger(io.store.home) });
|
|
318
|
+
const ledger = readLedger(io.store.home);
|
|
319
|
+
const repoRoot = resolveRepoRoot(io.cwd);
|
|
320
|
+
const all = [...discovered.user, ...discovered.project];
|
|
321
|
+
const disabled = Object.entries(ledger.plugins).filter(([, entries]) => entries.every((e) => !e.enabled));
|
|
322
|
+
const disabledLine = disabled.length > 0
|
|
323
|
+
? `Disabled: ${disabled.map(([key]) => key).join(", ")} (re-enable with \`${cmd} plugin enable <plugin>\`)\n`
|
|
324
|
+
: "";
|
|
325
|
+
if (all.length === 0) {
|
|
326
|
+
io.stdout("No plugins load here.\n");
|
|
327
|
+
if (disabledLine)
|
|
328
|
+
io.stdout(disabledLine);
|
|
329
|
+
else
|
|
330
|
+
io.stdout(`Add a marketplace with \`${cmd} plugin marketplace add <owner/repo | git-url | path>\`, then \`${cmd} plugin install <plugin>\`.\n`);
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
let trust = null;
|
|
334
|
+
try {
|
|
335
|
+
trust = readTrustDecision(await io.agentDir(), io.cwd);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
trust = null;
|
|
339
|
+
}
|
|
340
|
+
const describeOrigin = (p) => {
|
|
341
|
+
if (p.origin === "yagni") {
|
|
342
|
+
const entries = ledger.plugins[p.key] ?? [];
|
|
343
|
+
const scopes = entries
|
|
344
|
+
.filter((e) => e.scope === "user" || e.projectPath === repoRoot || (e.projectPath && repoRoot.startsWith(e.projectPath + sep)))
|
|
345
|
+
.map((e) => e.scope);
|
|
346
|
+
return `installed via ${cmd} (${[...new Set(scopes)].join(", ") || "user"})`;
|
|
347
|
+
}
|
|
348
|
+
if (p.origin === "claude-code")
|
|
349
|
+
return "mirrored from Claude Code";
|
|
350
|
+
return "from this repo's marketplace";
|
|
351
|
+
};
|
|
352
|
+
const render = (p, gated) => {
|
|
353
|
+
io.stdout(` ${p.key}${p.version ? ` v${p.version}` : ""} ${describeOrigin(p)}${gated && trust !== true ? " (loads once this folder is trusted)" : ""}\n`);
|
|
354
|
+
io.stdout(` ${componentSummary(p)}\n`);
|
|
355
|
+
for (const line of mcpNotes(p, io.env))
|
|
356
|
+
io.stdout(` ${line}\n`);
|
|
357
|
+
if (p.unsupported.length > 0)
|
|
358
|
+
io.stdout(` not bridged: ${p.unsupported.join(", ")}\n`);
|
|
359
|
+
};
|
|
360
|
+
if (discovered.user.length > 0) {
|
|
361
|
+
io.stdout(`Plugins (${discovered.user.length}):\n`);
|
|
362
|
+
for (const p of discovered.user)
|
|
363
|
+
render(p, false);
|
|
364
|
+
}
|
|
365
|
+
if (discovered.project.length > 0) {
|
|
366
|
+
io.stdout(`Plugins from this repo's config (${discovered.project.length}):\n`);
|
|
367
|
+
for (const p of discovered.project)
|
|
368
|
+
render(p, true);
|
|
369
|
+
}
|
|
370
|
+
if (disabledLine)
|
|
371
|
+
io.stdout(disabledLine);
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
374
|
+
function listAvailable(io) {
|
|
375
|
+
const cat = catalog(io.store);
|
|
376
|
+
if (cat.length === 0) {
|
|
377
|
+
io.stdout(`No marketplaces added. Add one with: ${cmd} plugin marketplace add <owner/repo | git-url | path>\n`);
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
const installed = new Set(Object.keys(readLedger(io.store.home).plugins));
|
|
381
|
+
for (const mp of cat) {
|
|
382
|
+
io.stdout(`${mp.marketplace}:\n`);
|
|
383
|
+
if (mp.entries.length === 0)
|
|
384
|
+
io.stdout(" (no plugins listed)\n");
|
|
385
|
+
for (const e of mp.entries) {
|
|
386
|
+
const key = `${e.name}@${mp.marketplace}`;
|
|
387
|
+
const flags = [installed.has(key) ? "installed" : null, e.installable ? null : "not installable (remote source)"].filter(Boolean);
|
|
388
|
+
io.stdout(` ${e.name}${e.version ? ` v${e.version}` : ""}${flags.length ? ` [${flags.join(", ")}]` : ""}\n`);
|
|
389
|
+
if (e.description)
|
|
390
|
+
io.stdout(` ${e.description}\n`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return 0;
|
|
394
|
+
}
|
|
395
|
+
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
396
|
+
function isDir(path) {
|
|
397
|
+
try {
|
|
398
|
+
return statSync(path).isDirectory();
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function countSkills(paths) {
|
|
405
|
+
let n = 0;
|
|
406
|
+
for (const path of paths) {
|
|
407
|
+
if (!isDir(path)) {
|
|
408
|
+
n += 1;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (existsSync(join(path, "SKILL.md"))) {
|
|
412
|
+
n += 1;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
for (const child of readdirSync(path)) {
|
|
417
|
+
if (existsSync(join(path, child, "SKILL.md")))
|
|
418
|
+
n += 1;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// unreadable dir → 0
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return n;
|
|
426
|
+
}
|
|
427
|
+
function countMarkdown(paths) {
|
|
428
|
+
let n = 0;
|
|
429
|
+
for (const path of paths) {
|
|
430
|
+
if (!isDir(path)) {
|
|
431
|
+
if (path.endsWith(".md"))
|
|
432
|
+
n += 1;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
n += readdirSync(path).filter((f) => f.endsWith(".md")).length;
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// unreadable dir → 0
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return n;
|
|
443
|
+
}
|
|
444
|
+
export function componentSummary(p) {
|
|
445
|
+
const parts = [];
|
|
446
|
+
const skills = countSkills(p.skillPaths);
|
|
447
|
+
const commands = countMarkdown(p.commandPaths);
|
|
448
|
+
const agents = countMarkdown(p.agentDirs);
|
|
449
|
+
const mcp = p.mcp ? Object.keys(p.mcp.servers).length : 0;
|
|
450
|
+
if (skills)
|
|
451
|
+
parts.push(`${skills} skill${skills === 1 ? "" : "s"}`);
|
|
452
|
+
if (commands)
|
|
453
|
+
parts.push(`${commands} command${commands === 1 ? "" : "s"}`);
|
|
454
|
+
if (agents)
|
|
455
|
+
parts.push(`${agents} agent${agents === 1 ? "" : "s"}`);
|
|
456
|
+
if (mcp)
|
|
457
|
+
parts.push(`${mcp} MCP server${mcp === 1 ? "" : "s"}`);
|
|
458
|
+
return parts.length ? parts.join(" · ") : "nothing loadable";
|
|
459
|
+
}
|
|
460
|
+
/** Whether a stdio `command` resolves on PATH (or is an existing path). Unknown when it has an unexpanded ${VAR}. */
|
|
461
|
+
export function commandOnPath(command, env) {
|
|
462
|
+
if (command.includes("${"))
|
|
463
|
+
return null;
|
|
464
|
+
if (isAbsolute(command) || command.includes("/") || command.includes("\\"))
|
|
465
|
+
return existsSync(command);
|
|
466
|
+
const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
|
|
467
|
+
for (const dir of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
468
|
+
for (const ext of exts) {
|
|
469
|
+
if (existsSync(join(dir, command + ext)))
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
/** Per-server notes for a plugin's MCP block: names, and a missing-binary warning for stdio servers. */
|
|
476
|
+
export function mcpNotes(p, env) {
|
|
477
|
+
if (!p.mcp)
|
|
478
|
+
return [];
|
|
479
|
+
const lines = [];
|
|
480
|
+
const names = Object.keys(p.mcp.servers);
|
|
481
|
+
lines.push(`MCP: ${names.join(", ")} (check with \`${cmd} mcp list\`)`);
|
|
482
|
+
const missing = new Set();
|
|
483
|
+
for (const value of Object.values(p.mcp.servers)) {
|
|
484
|
+
if (!value || typeof value !== "object")
|
|
485
|
+
continue;
|
|
486
|
+
const config = value;
|
|
487
|
+
if (config.type !== undefined && config.type !== "stdio")
|
|
488
|
+
continue;
|
|
489
|
+
if (typeof config.command !== "string")
|
|
490
|
+
continue;
|
|
491
|
+
if (commandOnPath(config.command, env) === false)
|
|
492
|
+
missing.add(config.command);
|
|
493
|
+
}
|
|
494
|
+
for (const command of missing) {
|
|
495
|
+
lines.push(`Note: "${command}" is not on your PATH — install it before launching or its server will fail to start.`);
|
|
496
|
+
}
|
|
497
|
+
return lines;
|
|
498
|
+
}
|
|
499
|
+
//# sourceMappingURL=pluginCommand.js.map
|