@phnx-labs/agents-cli 1.18.2 → 1.18.3
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 +10 -0
- package/dist/commands/plugins.js +58 -14
- package/dist/commands/view.js +16 -7
- package/dist/lib/plugin-marketplace.d.ts +93 -0
- package/dist/lib/plugin-marketplace.js +239 -0
- package/dist/lib/plugins.d.ts +25 -13
- package/dist/lib/plugins.js +350 -566
- package/dist/lib/types.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.18.3
|
|
4
|
+
|
|
5
|
+
**Plugins** ([#22](https://github.com/phnx-labs/agents-cli/issues/22))
|
|
6
|
+
|
|
7
|
+
- `agents plugins sync` now installs plugins via Claude Code's native marketplace path — `<versionHome>/.{claude,openclaw}/plugins/marketplaces/agents-cli/plugins/<name>/` — instead of flattening contents into `~/.claude/skills/<plugin>--<skill>/`. Skills resolve as `/plugin:skill` (the documented form) instead of `/plugin--skill`. Plugins appear in Claude's `/plugins` UI under Installed and respond to `/plugin enable`, `/plugin disable`.
|
|
8
|
+
- A synthetic `agents-cli` marketplace is materialized per version: `.claude-plugin/marketplace.json` is synthesized from discovered plugins, an entry is added to `<versionHome>/.claude/plugins/known_marketplaces.json`, and `settings.json#enabledPlugins["<plugin>@agents-cli"]` is flipped to `true`. Removal is symmetric — last plugin out drops the marketplace dir and the known_marketplaces entry.
|
|
9
|
+
- The sync now copies the whole plugin tree verbatim (single `fs.cpSync`) instead of re-implementing per-feature merges into `settings.json`. Every Claude plugin feature — skills, commands, subagents, hooks, `.mcp.json`, `.lsp.json`, `monitors/monitors.json`, `bin/`, `settings.json` — is preserved end-to-end. `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}` are left intact so Claude can expand them at runtime; only `${user_config.*}` (agents-cli-specific) is pre-expanded in copied text files.
|
|
10
|
+
- Legacy dual-dash layout from prior versions is auto-migrated at sync time — `~/.claude/skills/<plugin>--*`, `~/.claude/commands/<plugin>--*.md`, `~/.claude/agents/<plugin>--*.md`, `plugin-bin/<plugin>/`, and namespaced `mcpServers["<plugin>--*"]` entries are removed after the marketplace install succeeds.
|
|
11
|
+
- `agents plugins view <name>` surfaces every feature the plugin ships: Skills, Commands, Subagents, Hooks, MCP Servers, LSP Servers, Monitors, Bin, Scripts, Settings. The `agents view <agent>@<version>` Plugins section gains MCP/LSP/Monitor/Bin/Settings counts. New `discoverPluginMcpServers`, `discoverPluginLspServers`, `discoverPluginMonitors` helpers parse `.mcp.json`, `.lsp.json`, and `monitors/monitors.json`.
|
|
12
|
+
|
|
3
13
|
## 1.18.2
|
|
4
14
|
|
|
5
15
|
**Teams**
|
package/dist/commands/plugins.js
CHANGED
|
@@ -149,7 +149,19 @@ Examples:
|
|
|
149
149
|
if (plugin.skills.length > 0) {
|
|
150
150
|
console.log(chalk.bold('\n Skills'));
|
|
151
151
|
for (const skill of plugin.skills) {
|
|
152
|
-
console.log(` ${chalk.cyan(
|
|
152
|
+
console.log(` ${chalk.cyan(`/${plugin.name}:${skill}`)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (plugin.commands.length > 0) {
|
|
156
|
+
console.log(chalk.bold('\n Commands'));
|
|
157
|
+
for (const cmd of plugin.commands) {
|
|
158
|
+
console.log(` ${chalk.cyan(`/${plugin.name}:${cmd}`)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (plugin.agentDefs.length > 0) {
|
|
162
|
+
console.log(chalk.bold('\n Subagents'));
|
|
163
|
+
for (const a of plugin.agentDefs) {
|
|
164
|
+
console.log(` ${chalk.magenta(a)}`);
|
|
153
165
|
}
|
|
154
166
|
}
|
|
155
167
|
if (plugin.hooks.length > 0) {
|
|
@@ -158,12 +170,40 @@ Examples:
|
|
|
158
170
|
console.log(` ${chalk.yellow(hook)}`);
|
|
159
171
|
}
|
|
160
172
|
}
|
|
173
|
+
if (plugin.mcpServers.length > 0) {
|
|
174
|
+
console.log(chalk.bold('\n MCP Servers'));
|
|
175
|
+
for (const s of plugin.mcpServers) {
|
|
176
|
+
console.log(` ${chalk.green(s)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (plugin.lspServers.length > 0) {
|
|
180
|
+
console.log(chalk.bold('\n LSP Servers'));
|
|
181
|
+
for (const s of plugin.lspServers) {
|
|
182
|
+
console.log(` ${chalk.green(s)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (plugin.monitors.length > 0) {
|
|
186
|
+
console.log(chalk.bold('\n Monitors'));
|
|
187
|
+
for (const m of plugin.monitors) {
|
|
188
|
+
console.log(` ${chalk.blue(m)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (plugin.bin.length > 0) {
|
|
192
|
+
console.log(chalk.bold('\n Bin'));
|
|
193
|
+
for (const b of plugin.bin) {
|
|
194
|
+
console.log(` ${chalk.white(b)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
161
197
|
if (plugin.scripts.length > 0) {
|
|
162
198
|
console.log(chalk.bold('\n Scripts'));
|
|
163
199
|
for (const script of plugin.scripts) {
|
|
164
200
|
console.log(` ${chalk.gray(script)}`);
|
|
165
201
|
}
|
|
166
202
|
}
|
|
203
|
+
if (plugin.hasSettings) {
|
|
204
|
+
console.log(chalk.bold('\n Settings'));
|
|
205
|
+
console.log(` ${chalk.gray('settings.json')}`);
|
|
206
|
+
}
|
|
167
207
|
// Show installation status per agent version
|
|
168
208
|
console.log(chalk.bold('\n Installation Status'));
|
|
169
209
|
let anyInstalled = false;
|
|
@@ -572,20 +612,24 @@ function formatPluginDetail(plugin, targets) {
|
|
|
572
612
|
lines.push(' ' + chalk.gray('Supports: ') + supported.join(chalk.gray(' · ')));
|
|
573
613
|
}
|
|
574
614
|
lines.push(' ' + chalk.gray(formatPath(plugin.root)));
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
lines.push(' ' + plugin.skills.map((s) => chalk.cyan(s)).join(chalk.gray(', ')));
|
|
579
|
-
}
|
|
580
|
-
if (plugin.hooks.length > 0) {
|
|
581
|
-
lines.push('');
|
|
582
|
-
lines.push(chalk.bold(' Hooks'));
|
|
583
|
-
lines.push(' ' + plugin.hooks.map((h) => chalk.yellow(h)).join(chalk.gray(', ')));
|
|
584
|
-
}
|
|
585
|
-
if (plugin.scripts.length > 0) {
|
|
615
|
+
const section = (label, items, colorFn) => {
|
|
616
|
+
if (items.length === 0)
|
|
617
|
+
return;
|
|
586
618
|
lines.push('');
|
|
587
|
-
lines.push(chalk.bold(
|
|
588
|
-
lines.push(' ' +
|
|
619
|
+
lines.push(chalk.bold(` ${label}`));
|
|
620
|
+
lines.push(' ' + items.map(colorFn).join(chalk.gray(', ')));
|
|
621
|
+
};
|
|
622
|
+
section('Skills', plugin.skills.map((s) => `/${plugin.name}:${s}`), chalk.cyan);
|
|
623
|
+
section('Commands', plugin.commands.map((c) => `/${plugin.name}:${c}`), chalk.cyan);
|
|
624
|
+
section('Subagents', plugin.agentDefs, chalk.magenta);
|
|
625
|
+
section('Hooks', plugin.hooks, chalk.yellow);
|
|
626
|
+
section('MCP Servers', plugin.mcpServers, chalk.green);
|
|
627
|
+
section('LSP Servers', plugin.lspServers, chalk.green);
|
|
628
|
+
section('Monitors', plugin.monitors, chalk.blue);
|
|
629
|
+
section('Bin', plugin.bin, chalk.white);
|
|
630
|
+
section('Scripts', plugin.scripts, chalk.white);
|
|
631
|
+
if (plugin.hasSettings) {
|
|
632
|
+
section('Settings', ['settings.json'], chalk.gray);
|
|
589
633
|
}
|
|
590
634
|
if (targets.length > 0) {
|
|
591
635
|
lines.push('');
|
package/dist/commands/view.js
CHANGED
|
@@ -604,19 +604,28 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
604
604
|
const versionStr = agentData.version ? ` (${agentData.version})` : '';
|
|
605
605
|
const agentHeader = home ? termLink(agentData.agentName, home) : agentData.agentName;
|
|
606
606
|
console.log(` ${chalk.bold(agentHeader)}${chalk.gray(versionStr)}:`);
|
|
607
|
+
const pluralize = (n, singular) => `${n} ${singular}${n === 1 ? '' : 's'}`;
|
|
607
608
|
for (const p of plugins) {
|
|
608
609
|
const linkedName = termLink(p.name, linkTarget(p.root));
|
|
609
610
|
const parts = [];
|
|
610
611
|
if (p.skills.length > 0)
|
|
611
|
-
parts.push(
|
|
612
|
+
parts.push(pluralize(p.skills.length, 'skill'));
|
|
612
613
|
if (p.commands.length > 0)
|
|
613
|
-
parts.push(
|
|
614
|
-
if (p.hooks.length > 0)
|
|
615
|
-
parts.push(`${p.hooks.length} hook${p.hooks.length === 1 ? '' : 's'}`);
|
|
614
|
+
parts.push(pluralize(p.commands.length, 'command'));
|
|
616
615
|
if (p.agentDefs.length > 0)
|
|
617
|
-
parts.push(
|
|
618
|
-
if (p.
|
|
619
|
-
parts.push('
|
|
616
|
+
parts.push(pluralize(p.agentDefs.length, 'subagent'));
|
|
617
|
+
if (p.hooks.length > 0)
|
|
618
|
+
parts.push(pluralize(p.hooks.length, 'hook'));
|
|
619
|
+
if (p.mcpServers.length > 0)
|
|
620
|
+
parts.push(`${p.mcpServers.length} MCP`);
|
|
621
|
+
if (p.lspServers.length > 0)
|
|
622
|
+
parts.push(`${p.lspServers.length} LSP`);
|
|
623
|
+
if (p.monitors.length > 0)
|
|
624
|
+
parts.push(pluralize(p.monitors.length, 'monitor'));
|
|
625
|
+
if (p.bin.length > 0)
|
|
626
|
+
parts.push(pluralize(p.bin.length, 'bin'));
|
|
627
|
+
if (p.hasSettings)
|
|
628
|
+
parts.push('settings');
|
|
620
629
|
const contents = parts.length > 0 ? chalk.gray(` (${parts.join(', ')})`) : '';
|
|
621
630
|
console.log(` ${chalk.cyan(linkedName)}${contents} ${chalk.cyan('[user]')}`);
|
|
622
631
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native plugin marketplace install path for Claude / OpenClaw.
|
|
3
|
+
*
|
|
4
|
+
* Plugins managed by agents-cli are exposed as a synthetic local marketplace
|
|
5
|
+
* named "agents-cli" under each version's plugin directory:
|
|
6
|
+
*
|
|
7
|
+
* <versionHome>/.{claude,openclaw}/plugins/
|
|
8
|
+
* known_marketplaces.json # registers the "agents-cli" marketplace
|
|
9
|
+
* marketplaces/agents-cli/
|
|
10
|
+
* .claude-plugin/marketplace.json # synthesized catalog
|
|
11
|
+
* plugins/<plugin>/ # copied plugin source
|
|
12
|
+
*
|
|
13
|
+
* Plus the version's settings.json gets `enabledPlugins["<plugin>@agents-cli"] = true`.
|
|
14
|
+
*
|
|
15
|
+
* This produces native `/plugin:skill` slash namespacing, visibility in `/plugins`,
|
|
16
|
+
* and `/plugin enable|disable` support — matching the Claude Code spec at
|
|
17
|
+
* https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
|
|
18
|
+
*/
|
|
19
|
+
import type { AgentId, DiscoveredPlugin } from './types.js';
|
|
20
|
+
export declare const MARKETPLACE_NAME = "agents-cli";
|
|
21
|
+
interface MarketplacePluginEntry {
|
|
22
|
+
name: string;
|
|
23
|
+
source: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
author?: {
|
|
27
|
+
name: string;
|
|
28
|
+
email?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
interface MarketplaceManifest {
|
|
32
|
+
$schema?: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
owner: {
|
|
36
|
+
name: string;
|
|
37
|
+
email?: string;
|
|
38
|
+
};
|
|
39
|
+
plugins: MarketplacePluginEntry[];
|
|
40
|
+
}
|
|
41
|
+
export declare function marketplaceRoot(agent: AgentId, versionHome: string): string;
|
|
42
|
+
export declare function marketplaceManifestPath(agent: AgentId, versionHome: string): string;
|
|
43
|
+
export declare function pluginInstallDir(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): string;
|
|
44
|
+
export declare function knownMarketplacesPath(agent: AgentId, versionHome: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Copy plugin source into marketplace install dir.
|
|
47
|
+
* Source of truth remains ~/.agents/plugins/<name>/ — this is a per-version snapshot.
|
|
48
|
+
*/
|
|
49
|
+
export declare function copyPluginToMarketplace(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the list of
|
|
52
|
+
* plugins installed under <marketplace>/plugins/. Always run after add or remove
|
|
53
|
+
* so the manifest stays in lockstep with on-disk contents.
|
|
54
|
+
*/
|
|
55
|
+
export declare function syncMarketplaceManifest(agent: AgentId, versionHome: string): MarketplaceManifest | null;
|
|
56
|
+
/**
|
|
57
|
+
* Register the agents-cli marketplace in known_marketplaces.json so Claude Code
|
|
58
|
+
* discovers it on startup. Idempotent: re-running just refreshes lastUpdated.
|
|
59
|
+
*/
|
|
60
|
+
export declare function registerMarketplace(agent: AgentId, versionHome: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Drop the agents-cli marketplace entry from known_marketplaces.json.
|
|
63
|
+
* Called when the last plugin under it is removed.
|
|
64
|
+
*/
|
|
65
|
+
export declare function unregisterMarketplace(agent: AgentId, versionHome: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
|
|
68
|
+
* enabledPlugins["<plugin>@agents-cli"]: true. Reads, mutates, writes —
|
|
69
|
+
* preserving every other key.
|
|
70
|
+
*/
|
|
71
|
+
export declare function enablePluginInSettings(pluginName: string, agent: AgentId, versionHome: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Remove the enabledPlugins key for this plugin. Inverse of enablePluginInSettings.
|
|
74
|
+
*/
|
|
75
|
+
export declare function disablePluginInSettings(pluginName: string, agent: AgentId, versionHome: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Remove a plugin's installed marketplace directory. Returns true if the dir
|
|
78
|
+
* existed and was removed.
|
|
79
|
+
*/
|
|
80
|
+
export declare function removePluginFromMarketplace(pluginName: string, agent: AgentId, versionHome: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Return true if the marketplace has no plugins left under it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function marketplaceIsEmpty(agent: AgentId, versionHome: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Drop the entire marketplace directory. Called after the last plugin removal.
|
|
87
|
+
*/
|
|
88
|
+
export declare function removeEmptyMarketplaceDir(agent: AgentId, versionHome: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Detect whether a plugin is installed via the native marketplace path.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isInstalledInMarketplace(pluginName: string, agent: AgentId, versionHome: string): boolean;
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native plugin marketplace install path for Claude / OpenClaw.
|
|
3
|
+
*
|
|
4
|
+
* Plugins managed by agents-cli are exposed as a synthetic local marketplace
|
|
5
|
+
* named "agents-cli" under each version's plugin directory:
|
|
6
|
+
*
|
|
7
|
+
* <versionHome>/.{claude,openclaw}/plugins/
|
|
8
|
+
* known_marketplaces.json # registers the "agents-cli" marketplace
|
|
9
|
+
* marketplaces/agents-cli/
|
|
10
|
+
* .claude-plugin/marketplace.json # synthesized catalog
|
|
11
|
+
* plugins/<plugin>/ # copied plugin source
|
|
12
|
+
*
|
|
13
|
+
* Plus the version's settings.json gets `enabledPlugins["<plugin>@agents-cli"] = true`.
|
|
14
|
+
*
|
|
15
|
+
* This produces native `/plugin:skill` slash namespacing, visibility in `/plugins`,
|
|
16
|
+
* and `/plugin enable|disable` support — matching the Claude Code spec at
|
|
17
|
+
* https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
export const MARKETPLACE_NAME = 'agents-cli';
|
|
22
|
+
function pluginsRootForVersion(agent, versionHome) {
|
|
23
|
+
return path.join(versionHome, `.${agent}`, 'plugins');
|
|
24
|
+
}
|
|
25
|
+
export function marketplaceRoot(agent, versionHome) {
|
|
26
|
+
return path.join(pluginsRootForVersion(agent, versionHome), 'marketplaces', MARKETPLACE_NAME);
|
|
27
|
+
}
|
|
28
|
+
export function marketplaceManifestPath(agent, versionHome) {
|
|
29
|
+
return path.join(marketplaceRoot(agent, versionHome), '.claude-plugin', 'marketplace.json');
|
|
30
|
+
}
|
|
31
|
+
export function pluginInstallDir(plugin, agent, versionHome) {
|
|
32
|
+
return path.join(marketplaceRoot(agent, versionHome), 'plugins', plugin.name);
|
|
33
|
+
}
|
|
34
|
+
export function knownMarketplacesPath(agent, versionHome) {
|
|
35
|
+
return path.join(pluginsRootForVersion(agent, versionHome), 'known_marketplaces.json');
|
|
36
|
+
}
|
|
37
|
+
function settingsPath(agent, versionHome) {
|
|
38
|
+
return path.join(versionHome, `.${agent}`, 'settings.json');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Copy plugin source into marketplace install dir.
|
|
42
|
+
* Source of truth remains ~/.agents/plugins/<name>/ — this is a per-version snapshot.
|
|
43
|
+
*/
|
|
44
|
+
export function copyPluginToMarketplace(plugin, agent, versionHome) {
|
|
45
|
+
const dest = pluginInstallDir(plugin, agent, versionHome);
|
|
46
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
47
|
+
if (fs.existsSync(dest)) {
|
|
48
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
fs.cpSync(plugin.root, dest, { recursive: true, dereference: false });
|
|
51
|
+
return dest;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the list of
|
|
55
|
+
* plugins installed under <marketplace>/plugins/. Always run after add or remove
|
|
56
|
+
* so the manifest stays in lockstep with on-disk contents.
|
|
57
|
+
*/
|
|
58
|
+
export function syncMarketplaceManifest(agent, versionHome) {
|
|
59
|
+
const root = marketplaceRoot(agent, versionHome);
|
|
60
|
+
const pluginsDir = path.join(root, 'plugins');
|
|
61
|
+
if (!fs.existsSync(pluginsDir))
|
|
62
|
+
return null;
|
|
63
|
+
const entries = [];
|
|
64
|
+
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
65
|
+
if (!entry.isDirectory() || entry.name.startsWith('.'))
|
|
66
|
+
continue;
|
|
67
|
+
const manifestFile = path.join(pluginsDir, entry.name, '.claude-plugin', 'plugin.json');
|
|
68
|
+
if (!fs.existsSync(manifestFile))
|
|
69
|
+
continue;
|
|
70
|
+
let manifest;
|
|
71
|
+
try {
|
|
72
|
+
manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
entries.push({
|
|
78
|
+
name: manifest.name,
|
|
79
|
+
source: `./plugins/${manifest.name}`,
|
|
80
|
+
description: manifest.description,
|
|
81
|
+
version: manifest.version,
|
|
82
|
+
...(manifest.author ? { author: manifest.author } : {}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const manifest = {
|
|
86
|
+
$schema: 'https://anthropic.com/claude-code/marketplace.schema.json',
|
|
87
|
+
name: MARKETPLACE_NAME,
|
|
88
|
+
description: 'Plugins managed by agents-cli',
|
|
89
|
+
owner: { name: 'agents-cli' },
|
|
90
|
+
plugins: entries.sort((a, b) => a.name.localeCompare(b.name)),
|
|
91
|
+
};
|
|
92
|
+
const manifestPath = marketplaceManifestPath(agent, versionHome);
|
|
93
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
94
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
95
|
+
return manifest;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Register the agents-cli marketplace in known_marketplaces.json so Claude Code
|
|
99
|
+
* discovers it on startup. Idempotent: re-running just refreshes lastUpdated.
|
|
100
|
+
*/
|
|
101
|
+
export function registerMarketplace(agent, versionHome) {
|
|
102
|
+
const root = marketplaceRoot(agent, versionHome);
|
|
103
|
+
const knownPath = knownMarketplacesPath(agent, versionHome);
|
|
104
|
+
let known = {};
|
|
105
|
+
if (fs.existsSync(knownPath)) {
|
|
106
|
+
try {
|
|
107
|
+
known = JSON.parse(fs.readFileSync(knownPath, 'utf-8'));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
known = {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
known[MARKETPLACE_NAME] = {
|
|
114
|
+
source: { source: 'local', path: root },
|
|
115
|
+
installLocation: root,
|
|
116
|
+
lastUpdated: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
fs.mkdirSync(path.dirname(knownPath), { recursive: true });
|
|
119
|
+
fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Drop the agents-cli marketplace entry from known_marketplaces.json.
|
|
123
|
+
* Called when the last plugin under it is removed.
|
|
124
|
+
*/
|
|
125
|
+
export function unregisterMarketplace(agent, versionHome) {
|
|
126
|
+
const knownPath = knownMarketplacesPath(agent, versionHome);
|
|
127
|
+
if (!fs.existsSync(knownPath))
|
|
128
|
+
return;
|
|
129
|
+
let known;
|
|
130
|
+
try {
|
|
131
|
+
known = JSON.parse(fs.readFileSync(knownPath, 'utf-8'));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!(MARKETPLACE_NAME in known))
|
|
137
|
+
return;
|
|
138
|
+
delete known[MARKETPLACE_NAME];
|
|
139
|
+
if (Object.keys(known).length === 0) {
|
|
140
|
+
try {
|
|
141
|
+
fs.unlinkSync(knownPath);
|
|
142
|
+
}
|
|
143
|
+
catch { /* ignore */ }
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
|
|
151
|
+
* enabledPlugins["<plugin>@agents-cli"]: true. Reads, mutates, writes —
|
|
152
|
+
* preserving every other key.
|
|
153
|
+
*/
|
|
154
|
+
export function enablePluginInSettings(pluginName, agent, versionHome) {
|
|
155
|
+
const sPath = settingsPath(agent, versionHome);
|
|
156
|
+
let settings = {};
|
|
157
|
+
if (fs.existsSync(sPath)) {
|
|
158
|
+
try {
|
|
159
|
+
settings = JSON.parse(fs.readFileSync(sPath, 'utf-8'));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
settings = {};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== 'object') {
|
|
166
|
+
settings.enabledPlugins = {};
|
|
167
|
+
}
|
|
168
|
+
const enabled = settings.enabledPlugins;
|
|
169
|
+
const key = `${pluginName}@${MARKETPLACE_NAME}`;
|
|
170
|
+
if (enabled[key] === true)
|
|
171
|
+
return;
|
|
172
|
+
enabled[key] = true;
|
|
173
|
+
fs.mkdirSync(path.dirname(sPath), { recursive: true });
|
|
174
|
+
fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remove the enabledPlugins key for this plugin. Inverse of enablePluginInSettings.
|
|
178
|
+
*/
|
|
179
|
+
export function disablePluginInSettings(pluginName, agent, versionHome) {
|
|
180
|
+
const sPath = settingsPath(agent, versionHome);
|
|
181
|
+
if (!fs.existsSync(sPath))
|
|
182
|
+
return;
|
|
183
|
+
let settings;
|
|
184
|
+
try {
|
|
185
|
+
settings = JSON.parse(fs.readFileSync(sPath, 'utf-8'));
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const enabled = settings.enabledPlugins;
|
|
191
|
+
if (!enabled)
|
|
192
|
+
return;
|
|
193
|
+
const key = `${pluginName}@${MARKETPLACE_NAME}`;
|
|
194
|
+
if (!(key in enabled))
|
|
195
|
+
return;
|
|
196
|
+
delete enabled[key];
|
|
197
|
+
if (Object.keys(enabled).length === 0) {
|
|
198
|
+
delete settings.enabledPlugins;
|
|
199
|
+
}
|
|
200
|
+
fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Remove a plugin's installed marketplace directory. Returns true if the dir
|
|
204
|
+
* existed and was removed.
|
|
205
|
+
*/
|
|
206
|
+
export function removePluginFromMarketplace(pluginName, agent, versionHome) {
|
|
207
|
+
const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
|
|
208
|
+
if (!fs.existsSync(installed))
|
|
209
|
+
return false;
|
|
210
|
+
fs.rmSync(installed, { recursive: true, force: true });
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Return true if the marketplace has no plugins left under it.
|
|
215
|
+
*/
|
|
216
|
+
export function marketplaceIsEmpty(agent, versionHome) {
|
|
217
|
+
const pluginsDir = path.join(marketplaceRoot(agent, versionHome), 'plugins');
|
|
218
|
+
if (!fs.existsSync(pluginsDir))
|
|
219
|
+
return true;
|
|
220
|
+
const remaining = fs.readdirSync(pluginsDir, { withFileTypes: true })
|
|
221
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('.'));
|
|
222
|
+
return remaining.length === 0;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Drop the entire marketplace directory. Called after the last plugin removal.
|
|
226
|
+
*/
|
|
227
|
+
export function removeEmptyMarketplaceDir(agent, versionHome) {
|
|
228
|
+
const root = marketplaceRoot(agent, versionHome);
|
|
229
|
+
if (!fs.existsSync(root))
|
|
230
|
+
return;
|
|
231
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Detect whether a plugin is installed via the native marketplace path.
|
|
235
|
+
*/
|
|
236
|
+
export function isInstalledInMarketplace(pluginName, agent, versionHome) {
|
|
237
|
+
const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
|
|
238
|
+
return fs.existsSync(path.join(installed, '.claude-plugin', 'plugin.json'));
|
|
239
|
+
}
|
package/dist/lib/plugins.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare function discoverPluginCommands(pluginRoot: string): string[];
|
|
|
35
35
|
export declare function discoverPluginAgentDefs(pluginRoot: string): string[];
|
|
36
36
|
/** Discover executable files in a plugin's bin/ directory. */
|
|
37
37
|
export declare function discoverPluginBin(pluginRoot: string): string[];
|
|
38
|
+
/** Discover MCP server names from .mcp.json at the plugin root. */
|
|
39
|
+
export declare function discoverPluginMcpServers(pluginRoot: string): string[];
|
|
40
|
+
/** Discover LSP server keys from .lsp.json at the plugin root. */
|
|
41
|
+
export declare function discoverPluginLspServers(pluginRoot: string): string[];
|
|
42
|
+
/** Discover monitor names from monitors/monitors.json. */
|
|
43
|
+
export declare function discoverPluginMonitors(pluginRoot: string): string[];
|
|
38
44
|
/**
|
|
39
45
|
* Expand plugin variables in a string.
|
|
40
46
|
*
|
|
@@ -60,15 +66,18 @@ export declare function checkPluginDependencies(manifest: PluginManifest): strin
|
|
|
60
66
|
/**
|
|
61
67
|
* Sync a plugin to a specific agent version's home directory.
|
|
62
68
|
*
|
|
63
|
-
* For
|
|
64
|
-
* 1. Copy plugin
|
|
65
|
-
* 2.
|
|
66
|
-
* 3.
|
|
67
|
-
* 4.
|
|
68
|
-
* 5.
|
|
69
|
-
* 6.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* For plugins-capable agents (claude, openclaw):
|
|
70
|
+
* 1. Copy plugin source into <versionHome>/.<agent>/plugins/marketplaces/agents-cli/plugins/<name>/
|
|
71
|
+
* 2. Pre-expand ${user_config.*} variables in copied text files (Claude doesn't know this var).
|
|
72
|
+
* 3. (Re-)synthesize the marketplace.json catalog from the installed plugins.
|
|
73
|
+
* 4. Register the synthetic marketplace in known_marketplaces.json.
|
|
74
|
+
* 5. Mark <plugin>@agents-cli enabled in settings.json#enabledPlugins.
|
|
75
|
+
* 6. Migrate (remove) legacy dual-dash skills/commands/agents/bin/hooks/mcp entries.
|
|
76
|
+
*
|
|
77
|
+
* Claude/OpenClaw natively handle the plugin's skills, commands, agents, hooks,
|
|
78
|
+
* MCP servers, bin/, settings.json, and permissions once the plugin lives at the
|
|
79
|
+
* native install path and is marked enabled — see
|
|
80
|
+
* https://code.claude.com/docs/en/plugins.
|
|
72
81
|
*/
|
|
73
82
|
export declare function syncPluginToVersion(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): {
|
|
74
83
|
success: boolean;
|
|
@@ -82,8 +91,9 @@ export declare function syncPluginToVersion(plugin: DiscoveredPlugin, agent: Age
|
|
|
82
91
|
settings: boolean;
|
|
83
92
|
};
|
|
84
93
|
/**
|
|
85
|
-
* Check if a plugin is synced to a version
|
|
86
|
-
*
|
|
94
|
+
* Check if a plugin is synced to a version. True when the plugin lives at the
|
|
95
|
+
* native marketplace install path. Legacy dual-dash entries are not counted —
|
|
96
|
+
* they're treated as stale and migrated away on the next sync.
|
|
87
97
|
*/
|
|
88
98
|
export declare function isPluginSynced(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): boolean;
|
|
89
99
|
/**
|
|
@@ -102,8 +112,10 @@ export declare function removePluginFromVersion(pluginName: string, pluginRoot:
|
|
|
102
112
|
mcp: number;
|
|
103
113
|
};
|
|
104
114
|
/**
|
|
105
|
-
* Remove orphaned plugin
|
|
106
|
-
* Soft-deletes
|
|
115
|
+
* Remove orphaned plugin entries from a version home. An entry is "orphan" if
|
|
116
|
+
* its plugin name is not in the active plugin set. Soft-deletes the affected
|
|
117
|
+
* marketplace plugin dir to ~/.agents/.trash/plugins/. Also cleans up any
|
|
118
|
+
* legacy dual-dash skills/ directories from older agents-cli versions.
|
|
107
119
|
*/
|
|
108
120
|
export declare function cleanOrphanedPluginSkills(agent: AgentId, versionHome: string, activePluginNames: Set<string>, version?: string): string[];
|
|
109
121
|
export interface VersionPluginDiff {
|