@phnx-labs/agents-cli 1.20.3 → 1.20.5
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 +27 -0
- package/README.md +48 -17
- package/dist/commands/cli.js +1 -1
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +2 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +52 -16
- package/dist/commands/hooks.js +6 -6
- package/dist/commands/import.js +90 -37
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.js +590 -0
- package/dist/commands/mcp.js +17 -16
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -4
- package/dist/commands/permissions.js +13 -12
- package/dist/commands/plugins.d.ts +13 -0
- package/dist/commands/plugins.js +100 -11
- package/dist/commands/prune.js +3 -2
- package/dist/commands/pull.d.ts +12 -5
- package/dist/commands/pull.js +26 -422
- package/dist/commands/push.d.ts +14 -0
- package/dist/commands/push.js +30 -0
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +155 -112
- package/dist/commands/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +12 -3
- package/dist/commands/routines.js +32 -7
- package/dist/commands/rules.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.js +15 -15
- package/dist/commands/skills.js +6 -5
- package/dist/commands/subagents.js +5 -4
- package/dist/commands/sync.d.ts +18 -5
- package/dist/commands/sync.js +251 -65
- package/dist/commands/teams.js +1 -0
- package/dist/commands/tmux.d.ts +25 -0
- package/dist/commands/tmux.js +415 -0
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +1 -1
- package/dist/commands/versions.js +2 -2
- package/dist/commands/view.js +14 -4
- package/dist/commands/workflows.js +4 -3
- package/dist/commands/worktree.d.ts +4 -5
- package/dist/commands/worktree.js +4 -4
- package/dist/index.js +68 -20
- package/dist/lib/agents.d.ts +19 -10
- package/dist/lib/agents.js +102 -28
- package/dist/lib/auto-pull-worker.d.ts +1 -1
- package/dist/lib/auto-pull-worker.js +2 -2
- package/dist/lib/auto-pull.d.ts +1 -1
- package/dist/lib/auto-pull.js +1 -1
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.d.ts +28 -1
- package/dist/lib/commands.js +125 -20
- package/dist/lib/doctor-diff.js +2 -2
- package/dist/lib/exec.d.ts +14 -0
- package/dist/lib/exec.js +39 -5
- package/dist/lib/fuzzy.d.ts +12 -2
- package/dist/lib/fuzzy.js +29 -4
- package/dist/lib/git.js +8 -1
- package/dist/lib/hooks.d.ts +2 -2
- package/dist/lib/hooks.js +97 -10
- package/dist/lib/import.d.ts +21 -0
- package/dist/lib/import.js +55 -2
- package/dist/lib/mcp.js +32 -2
- package/dist/lib/migrate.d.ts +51 -0
- package/dist/lib/migrate.js +227 -1
- package/dist/lib/models.js +62 -15
- package/dist/lib/permissions.d.ts +36 -2
- package/dist/lib/permissions.js +217 -7
- package/dist/lib/plugin-marketplace.d.ts +108 -40
- package/dist/lib/plugin-marketplace.js +243 -94
- package/dist/lib/plugins.d.ts +21 -4
- package/dist/lib/plugins.js +130 -49
- package/dist/lib/profiles-presets.js +12 -12
- package/dist/lib/project-launch.d.ts +65 -0
- package/dist/lib/project-launch.js +367 -0
- package/dist/lib/pty-client.js +1 -1
- package/dist/lib/pty-server.d.ts +1 -1
- package/dist/lib/pty-server.js +28 -4
- package/dist/lib/refresh.d.ts +26 -0
- package/dist/lib/refresh.js +315 -0
- package/dist/lib/resource-patterns.d.ts +1 -1
- package/dist/lib/resource-patterns.js +1 -1
- package/dist/lib/resources/commands.js +2 -2
- package/dist/lib/resources/hooks.d.ts +1 -1
- package/dist/lib/resources/hooks.js +1 -1
- package/dist/lib/resources/mcp.d.ts +1 -1
- package/dist/lib/resources/mcp.js +5 -6
- package/dist/lib/resources/permissions.js +5 -2
- package/dist/lib/resources/rules.js +3 -2
- package/dist/lib/resources/skills.js +3 -2
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.js +2 -2
- package/dist/lib/rotate.d.ts +1 -1
- package/dist/lib/rotate.js +1 -1
- package/dist/lib/routines.d.ts +16 -4
- package/dist/lib/routines.js +67 -17
- package/dist/lib/rules/compile.js +22 -10
- package/dist/lib/rules/rules.js +3 -3
- package/dist/lib/runner.js +16 -3
- package/dist/lib/scheduler.js +15 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/secrets/linux.d.ts +44 -9
- package/dist/lib/secrets/linux.js +302 -48
- package/dist/lib/session/db.js +15 -2
- package/dist/lib/session/discover.js +118 -3
- package/dist/lib/session/parse.js +3 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +10 -9
- package/dist/lib/shims.js +101 -50
- package/dist/lib/skills.d.ts +1 -1
- package/dist/lib/skills.js +10 -9
- package/dist/lib/staleness/detectors/commands.d.ts +3 -0
- package/dist/lib/staleness/detectors/commands.js +46 -0
- package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
- package/dist/lib/staleness/detectors/hooks.js +44 -0
- package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
- package/dist/lib/staleness/detectors/mcp.js +31 -0
- package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
- package/dist/lib/staleness/detectors/permissions.js +201 -0
- package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
- package/dist/lib/staleness/detectors/plugins.js +23 -0
- package/dist/lib/staleness/detectors/rules.d.ts +3 -0
- package/dist/lib/staleness/detectors/rules.js +34 -0
- package/dist/lib/staleness/detectors/skills.d.ts +3 -0
- package/dist/lib/staleness/detectors/skills.js +71 -0
- package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
- package/dist/lib/staleness/detectors/subagents.js +50 -0
- package/dist/lib/staleness/detectors/types.d.ts +22 -0
- package/dist/lib/staleness/detectors/types.js +1 -0
- package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
- package/dist/lib/staleness/detectors/workflows.js +28 -0
- package/dist/lib/staleness/registry.d.ts +26 -0
- package/dist/lib/staleness/registry.js +123 -0
- package/dist/lib/staleness/writers/commands.d.ts +3 -0
- package/dist/lib/staleness/writers/commands.js +111 -0
- package/dist/lib/staleness/writers/hooks.d.ts +3 -0
- package/dist/lib/staleness/writers/hooks.js +47 -0
- package/dist/lib/staleness/writers/kinds.d.ts +10 -0
- package/dist/lib/staleness/writers/kinds.js +15 -0
- package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
- package/dist/lib/staleness/writers/lazy-map.js +19 -0
- package/dist/lib/staleness/writers/mcp.d.ts +10 -0
- package/dist/lib/staleness/writers/mcp.js +19 -0
- package/dist/lib/staleness/writers/permissions.d.ts +13 -0
- package/dist/lib/staleness/writers/permissions.js +26 -0
- package/dist/lib/staleness/writers/plugins.d.ts +7 -0
- package/dist/lib/staleness/writers/plugins.js +31 -0
- package/dist/lib/staleness/writers/rules.d.ts +7 -0
- package/dist/lib/staleness/writers/rules.js +55 -0
- package/dist/lib/staleness/writers/skills.d.ts +3 -0
- package/dist/lib/staleness/writers/skills.js +81 -0
- package/dist/lib/staleness/writers/sources.d.ts +16 -0
- package/dist/lib/staleness/writers/sources.js +72 -0
- package/dist/lib/staleness/writers/subagents.d.ts +3 -0
- package/dist/lib/staleness/writers/subagents.js +53 -0
- package/dist/lib/staleness/writers/types.d.ts +36 -0
- package/dist/lib/staleness/writers/types.js +1 -0
- package/dist/lib/staleness/writers/workflows.d.ts +7 -0
- package/dist/lib/staleness/writers/workflows.js +31 -0
- package/dist/lib/state.d.ts +34 -11
- package/dist/lib/state.js +58 -13
- package/dist/lib/subagents.d.ts +0 -2
- package/dist/lib/subagents.js +6 -6
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/binary.d.ts +67 -0
- package/dist/lib/tmux/binary.js +141 -0
- package/dist/lib/tmux/index.d.ts +8 -0
- package/dist/lib/tmux/index.js +8 -0
- package/dist/lib/tmux/paths.d.ts +17 -0
- package/dist/lib/tmux/paths.js +30 -0
- package/dist/lib/tmux/session.d.ts +122 -0
- package/dist/lib/tmux/session.js +305 -0
- package/dist/lib/types.d.ts +58 -7
- package/dist/lib/types.js +1 -1
- package/dist/lib/usage.js +1 -1
- package/dist/lib/versions.d.ts +4 -4
- package/dist/lib/versions.js +154 -491
- package/dist/lib/workflows.d.ts +2 -4
- package/dist/lib/workflows.js +3 -4
- package/package.json +7 -7
- package/scripts/postinstall.js +16 -63
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.js +0 -25
|
@@ -1,70 +1,222 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Native plugin
|
|
2
|
+
* Native plugin marketplaces for Claude / OpenClaw — one per DotAgents repo.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Every DotAgents repo that holds plugins synthesizes its OWN synthetic local
|
|
5
|
+
* marketplace under each version's plugin directory, named after the repo:
|
|
6
6
|
*
|
|
7
7
|
* <versionHome>/.{claude,openclaw}/plugins/
|
|
8
|
-
* known_marketplaces.json
|
|
9
|
-
* marketplaces/agents-cli/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* known_marketplaces.json # registers each repo's marketplace
|
|
9
|
+
* marketplaces/agents-cli/ # ~/.agents/plugins/* (user repo)
|
|
10
|
+
* marketplaces/agents-<alias>/ # ~/.agents-<alias>/plugins/* (extra repo)
|
|
11
|
+
* marketplaces/agents-project/ # <cwd>/.agents/plugins/* (project repo)
|
|
12
|
+
* .claude-plugin/marketplace.json # synthesized catalog
|
|
13
|
+
* plugins/<plugin>/ # copied plugin source
|
|
12
14
|
*
|
|
13
|
-
* Plus the version's settings.json gets
|
|
15
|
+
* Plus the version's settings.json gets
|
|
16
|
+
* `enabledPlugins["<plugin>@<marketplace>"] = true`.
|
|
14
17
|
*
|
|
15
|
-
* This produces native `/plugin:skill` slash namespacing, visibility in
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* This produces native `/plugin:skill` slash namespacing, visibility in
|
|
19
|
+
* `/plugins`, `/plugin enable|disable` support, AND honest attribution (the
|
|
20
|
+
* user can see which repo each plugin came from) — matching the Claude Code
|
|
21
|
+
* spec at https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
|
|
22
|
+
*
|
|
23
|
+
* The naming policy lives in one place — marketplaceNameFor(). Source-side
|
|
24
|
+
* discovery (discoverMarketplaces) and per-version synthesis (syncMarketplaceManifest
|
|
25
|
+
* / registerMarketplace / syncAllMarketplaces) all key off a MarketplaceSpec so
|
|
26
|
+
* the catalog name and on-disk layout are derived, never hard-coded per call.
|
|
18
27
|
*/
|
|
19
28
|
import * as fs from 'fs';
|
|
20
29
|
import * as path from 'path';
|
|
30
|
+
import { getPluginsDir, getEnabledExtraRepos, getProjectPluginsDir } from './state.js';
|
|
31
|
+
import { agentConfigDirName } from './agents.js';
|
|
32
|
+
/**
|
|
33
|
+
* Canonical name for the user-repo marketplace (~/.agents/plugins/). Kept as an
|
|
34
|
+
* exported constant for callers that operate on the user repo directly and for
|
|
35
|
+
* the `marketplaces/agents-cli/` on-disk path that existing installs already have.
|
|
36
|
+
*/
|
|
21
37
|
export const MARKETPLACE_NAME = 'agents-cli';
|
|
38
|
+
export const SYSTEM_MARKETPLACE_NAME = 'agents-system';
|
|
39
|
+
/** Marketplace name for <cwd>/.agents/plugins/*. */
|
|
40
|
+
export const PROJECT_MARKETPLACE_NAME = 'agents-project';
|
|
41
|
+
// ─── Naming policy (single source of truth) ──────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Map a MarketplaceSpec to its catalog name. This is the ONLY place that
|
|
44
|
+
* encodes the repo → name policy; every other function derives the name here.
|
|
45
|
+
*/
|
|
46
|
+
export function marketplaceNameFor(spec) {
|
|
47
|
+
switch (spec.kind) {
|
|
48
|
+
case 'user': return MARKETPLACE_NAME; // "agents-cli"
|
|
49
|
+
case 'extra': return `agents-${spec.alias}`; // e.g. "agents-extras"
|
|
50
|
+
case 'project': return PROJECT_MARKETPLACE_NAME; // "agents-project"
|
|
51
|
+
case 'system': return SYSTEM_MARKETPLACE_NAME; // "agents-system"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Resolve a spec-or-name argument to the bare marketplace name string. */
|
|
55
|
+
function nameOf(specOrName) {
|
|
56
|
+
return typeof specOrName === 'string' ? specOrName : marketplaceNameFor(specOrName);
|
|
57
|
+
}
|
|
58
|
+
function descriptionFor(spec) {
|
|
59
|
+
switch (spec.kind) {
|
|
60
|
+
case 'user': return 'Plugins from the user repo (~/.agents/plugins/)';
|
|
61
|
+
case 'extra': return `Plugins from extra repo "${spec.alias}" (~/.agents-${spec.alias}/plugins/)`;
|
|
62
|
+
case 'project': return 'Project-scoped plugins from <cwd>/.agents/plugins/';
|
|
63
|
+
case 'system': return 'Plugins from the system repo (~/.agents/.system/plugins/)';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ─── Source-side discovery ────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Discover every DotAgents repo that contributes plugins, in precedence order
|
|
69
|
+
* (user, then each enabled extra repo, then the project repo when cwd has one).
|
|
70
|
+
* No agent / version is involved — this walks source-side plugin roots only.
|
|
71
|
+
*
|
|
72
|
+
* A repo is included when its plugins/ directory exists on disk. The user repo
|
|
73
|
+
* is always probed; extras come from getEnabledExtraRepos() (already filtered to
|
|
74
|
+
* enabled + on-disk repos); the project repo is included only when
|
|
75
|
+
* <cwd>/.agents/plugins/ exists.
|
|
76
|
+
*/
|
|
77
|
+
export function discoverMarketplaces(opts = {}) {
|
|
78
|
+
const out = [];
|
|
79
|
+
// User repo — always the canonical "agents-cli" marketplace.
|
|
80
|
+
const userRoot = getPluginsDir();
|
|
81
|
+
if (dirExists(userRoot)) {
|
|
82
|
+
const spec = { kind: 'user' };
|
|
83
|
+
out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot: userRoot, description: descriptionFor(spec) });
|
|
84
|
+
}
|
|
85
|
+
// Extra repos — peer ~/.agents-<alias>/ clones (and user-owned path:-repos).
|
|
86
|
+
for (const extra of getEnabledExtraRepos()) {
|
|
87
|
+
const pluginsRoot = path.join(extra.dir, 'plugins');
|
|
88
|
+
if (!dirExists(pluginsRoot))
|
|
89
|
+
continue;
|
|
90
|
+
const spec = { kind: 'extra', alias: extra.alias, root: pluginsRoot };
|
|
91
|
+
out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot, description: descriptionFor(spec) });
|
|
92
|
+
}
|
|
93
|
+
// Project repo — <cwd>/.agents/plugins/.
|
|
94
|
+
const projectRoot = getProjectPluginsDir(opts.cwd ?? process.cwd());
|
|
95
|
+
if (projectRoot && dirExists(projectRoot)) {
|
|
96
|
+
const spec = { kind: 'project', root: projectRoot };
|
|
97
|
+
out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot: projectRoot, description: descriptionFor(spec) });
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function dirExists(p) {
|
|
102
|
+
try {
|
|
103
|
+
return fs.statSync(p).isDirectory();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ─── Per-version paths ────────────────────────────────────────────────────────
|
|
22
110
|
function pluginsRootForVersion(agent, versionHome) {
|
|
23
|
-
return path.join(versionHome,
|
|
111
|
+
return path.join(versionHome, agentConfigDirName(agent), 'plugins');
|
|
24
112
|
}
|
|
25
|
-
export function marketplaceRoot(agent, versionHome) {
|
|
26
|
-
return path.join(pluginsRootForVersion(agent, versionHome), 'marketplaces',
|
|
113
|
+
export function marketplaceRoot(specOrName, agent, versionHome) {
|
|
114
|
+
return path.join(pluginsRootForVersion(agent, versionHome), 'marketplaces', nameOf(specOrName));
|
|
27
115
|
}
|
|
28
|
-
export function marketplaceManifestPath(agent, versionHome) {
|
|
29
|
-
return path.join(marketplaceRoot(agent, versionHome), '.claude-plugin', 'marketplace.json');
|
|
116
|
+
export function marketplaceManifestPath(specOrName, agent, versionHome) {
|
|
117
|
+
return path.join(marketplaceRoot(specOrName, agent, versionHome), '.claude-plugin', 'marketplace.json');
|
|
30
118
|
}
|
|
31
|
-
export function pluginInstallDir(plugin, agent, versionHome) {
|
|
32
|
-
return path.join(marketplaceRoot(agent, versionHome), 'plugins', plugin.name);
|
|
119
|
+
export function pluginInstallDir(plugin, specOrName, agent, versionHome) {
|
|
120
|
+
return path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', plugin.name);
|
|
33
121
|
}
|
|
34
122
|
export function knownMarketplacesPath(agent, versionHome) {
|
|
35
123
|
return path.join(pluginsRootForVersion(agent, versionHome), 'known_marketplaces.json');
|
|
36
124
|
}
|
|
37
125
|
function settingsPath(agent, versionHome) {
|
|
38
|
-
return path.join(versionHome,
|
|
126
|
+
return path.join(versionHome, agentConfigDirName(agent), 'settings.json');
|
|
39
127
|
}
|
|
128
|
+
// ─── Copy plugin source into a marketplace ────────────────────────────────────
|
|
40
129
|
/**
|
|
41
|
-
* Copy plugin source into marketplace install dir.
|
|
42
|
-
* Source of truth remains
|
|
130
|
+
* Copy plugin source into the marketplace install dir for the given spec.
|
|
131
|
+
* Source of truth remains the plugin's source dir — this is a per-version snapshot.
|
|
132
|
+
*
|
|
133
|
+
* Symlinks pointing OUTSIDE the plugin source root are dropped. They show up
|
|
134
|
+
* when plugin authors (legitimately) link prompt-side references to sibling
|
|
135
|
+
* codebases — e.g. the rush plugin's `app -> ../../../rush/app` for @app/...
|
|
136
|
+
* autocomplete in user prompts. Faithfully copying those symlinks pollutes
|
|
137
|
+
* the marketplace with gigabytes of node_modules / .next / brand-asset video
|
|
138
|
+
* that the consumer (Claude Code, OpenClaw) then walks during plugin
|
|
139
|
+
* discovery — which is the documented cause of multi-minute startup hangs.
|
|
140
|
+
*
|
|
141
|
+
* Internal symlinks (target stays inside the plugin root) are preserved.
|
|
43
142
|
*/
|
|
44
|
-
export function copyPluginToMarketplace(plugin, agent, versionHome) {
|
|
45
|
-
const dest = pluginInstallDir(plugin, agent, versionHome);
|
|
143
|
+
export function copyPluginToMarketplace(plugin, spec, agent, versionHome) {
|
|
144
|
+
const dest = pluginInstallDir(plugin, spec, agent, versionHome);
|
|
46
145
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
47
146
|
if (fs.existsSync(dest)) {
|
|
48
147
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
49
148
|
}
|
|
50
|
-
|
|
149
|
+
const sourceRealRoot = (() => {
|
|
150
|
+
try {
|
|
151
|
+
return fs.realpathSync(plugin.root);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return plugin.root;
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
const skipped = [];
|
|
158
|
+
fs.cpSync(plugin.root, dest, {
|
|
159
|
+
recursive: true,
|
|
160
|
+
dereference: false,
|
|
161
|
+
filter: (src) => {
|
|
162
|
+
try {
|
|
163
|
+
const stat = fs.lstatSync(src);
|
|
164
|
+
if (!stat.isSymbolicLink())
|
|
165
|
+
return true;
|
|
166
|
+
const target = fs.realpathSync(src);
|
|
167
|
+
if (target === sourceRealRoot || target.startsWith(sourceRealRoot + path.sep)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
skipped.push(path.relative(plugin.root, src) || path.basename(src));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Dangling symlink or stat failure — drop it; it can't be useful in
|
|
175
|
+
// the marketplace and would error the consumer's walk anyway.
|
|
176
|
+
skipped.push(path.relative(plugin.root, src) || path.basename(src));
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
if (skipped.length > 0) {
|
|
182
|
+
process.stderr.write(`agents-cli: plugin '${plugin.name}' has ${skipped.length} symlink(s) ` +
|
|
183
|
+
`pointing outside its source root; not copied to marketplace ` +
|
|
184
|
+
`(would bloat consumer startup): ${skipped.join(', ')}\n`);
|
|
185
|
+
}
|
|
51
186
|
return dest;
|
|
52
187
|
}
|
|
188
|
+
// ─── Catalog synthesis ──────────────────────────────────────────────────────
|
|
53
189
|
/**
|
|
54
|
-
* Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the
|
|
55
|
-
*
|
|
56
|
-
* so the manifest stays in lockstep with on-disk contents.
|
|
190
|
+
* Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the plugins
|
|
191
|
+
* already installed under <marketplace>/plugins/. Always run after add or remove
|
|
192
|
+
* so the manifest stays in lockstep with on-disk contents. Returns the manifest
|
|
193
|
+
* it wrote, or null when the marketplace has no plugins dir yet.
|
|
57
194
|
*/
|
|
58
|
-
export function syncMarketplaceManifest(agent, versionHome) {
|
|
59
|
-
const
|
|
195
|
+
export function syncMarketplaceManifest(spec, agent, versionHome) {
|
|
196
|
+
const name = marketplaceNameFor(spec);
|
|
197
|
+
const root = marketplaceRoot(spec, agent, versionHome);
|
|
60
198
|
const pluginsDir = path.join(root, 'plugins');
|
|
61
199
|
if (!fs.existsSync(pluginsDir))
|
|
62
200
|
return null;
|
|
63
201
|
const entries = [];
|
|
64
202
|
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
65
|
-
if (
|
|
203
|
+
if (entry.name.startsWith('.'))
|
|
66
204
|
continue;
|
|
67
|
-
|
|
205
|
+
// Follow symlinks: Dirent.isDirectory() is false for a symlink even when the
|
|
206
|
+
// target is a directory. statSync follows the link.
|
|
207
|
+
const entryPath = path.join(pluginsDir, entry.name);
|
|
208
|
+
let isDir = entry.isDirectory();
|
|
209
|
+
if (!isDir && entry.isSymbolicLink()) {
|
|
210
|
+
try {
|
|
211
|
+
isDir = fs.statSync(entryPath).isDirectory();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
isDir = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!isDir)
|
|
218
|
+
continue;
|
|
219
|
+
const manifestFile = path.join(entryPath, '.claude-plugin', 'plugin.json');
|
|
68
220
|
if (!fs.existsSync(manifestFile))
|
|
69
221
|
continue;
|
|
70
222
|
let manifest;
|
|
@@ -84,22 +236,25 @@ export function syncMarketplaceManifest(agent, versionHome) {
|
|
|
84
236
|
}
|
|
85
237
|
const manifest = {
|
|
86
238
|
$schema: 'https://anthropic.com/claude-code/marketplace.schema.json',
|
|
87
|
-
name
|
|
88
|
-
description:
|
|
239
|
+
name,
|
|
240
|
+
description: descriptionFor(spec),
|
|
89
241
|
owner: { name: 'agents-cli' },
|
|
90
242
|
plugins: entries.sort((a, b) => a.name.localeCompare(b.name)),
|
|
91
243
|
};
|
|
92
|
-
const manifestPath = marketplaceManifestPath(agent, versionHome);
|
|
244
|
+
const manifestPath = marketplaceManifestPath(spec, agent, versionHome);
|
|
93
245
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
94
246
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
95
247
|
return manifest;
|
|
96
248
|
}
|
|
249
|
+
// ─── Registration in known_marketplaces.json ──────────────────────────────────
|
|
97
250
|
/**
|
|
98
|
-
* Register
|
|
99
|
-
*
|
|
251
|
+
* Register a marketplace in known_marketplaces.json so Claude Code discovers
|
|
252
|
+
* it on startup. Idempotent: re-running just refreshes lastUpdated. Other
|
|
253
|
+
* marketplaces' entries are preserved untouched.
|
|
100
254
|
*/
|
|
101
|
-
export function registerMarketplace(agent, versionHome) {
|
|
102
|
-
const
|
|
255
|
+
export function registerMarketplace(spec, agent, versionHome) {
|
|
256
|
+
const name = marketplaceNameFor(spec);
|
|
257
|
+
const root = marketplaceRoot(spec, agent, versionHome);
|
|
103
258
|
const knownPath = knownMarketplacesPath(agent, versionHome);
|
|
104
259
|
let known = {};
|
|
105
260
|
if (fs.existsSync(knownPath)) {
|
|
@@ -110,7 +265,7 @@ export function registerMarketplace(agent, versionHome) {
|
|
|
110
265
|
known = {};
|
|
111
266
|
}
|
|
112
267
|
}
|
|
113
|
-
known[
|
|
268
|
+
known[name] = {
|
|
114
269
|
source: { source: 'directory', path: root },
|
|
115
270
|
installLocation: root,
|
|
116
271
|
lastUpdated: new Date().toISOString(),
|
|
@@ -119,10 +274,12 @@ export function registerMarketplace(agent, versionHome) {
|
|
|
119
274
|
fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
|
|
120
275
|
}
|
|
121
276
|
/**
|
|
122
|
-
* Drop
|
|
123
|
-
*
|
|
277
|
+
* Drop a marketplace entry from known_marketplaces.json. Called when the last
|
|
278
|
+
* plugin under it is removed. Removes only its own entry; deletes the file only
|
|
279
|
+
* when no entries remain.
|
|
124
280
|
*/
|
|
125
|
-
export function unregisterMarketplace(agent, versionHome) {
|
|
281
|
+
export function unregisterMarketplace(specOrName, agent, versionHome) {
|
|
282
|
+
const name = nameOf(specOrName);
|
|
126
283
|
const knownPath = knownMarketplacesPath(agent, versionHome);
|
|
127
284
|
if (!fs.existsSync(knownPath))
|
|
128
285
|
return;
|
|
@@ -133,9 +290,9 @@ export function unregisterMarketplace(agent, versionHome) {
|
|
|
133
290
|
catch {
|
|
134
291
|
return;
|
|
135
292
|
}
|
|
136
|
-
if (!(
|
|
293
|
+
if (!(name in known))
|
|
137
294
|
return;
|
|
138
|
-
delete known[
|
|
295
|
+
delete known[name];
|
|
139
296
|
if (Object.keys(known).length === 0) {
|
|
140
297
|
try {
|
|
141
298
|
fs.unlinkSync(knownPath);
|
|
@@ -146,15 +303,38 @@ export function unregisterMarketplace(agent, versionHome) {
|
|
|
146
303
|
fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
|
|
147
304
|
}
|
|
148
305
|
}
|
|
306
|
+
// ─── Top-level orchestration ──────────────────────────────────────────────────
|
|
149
307
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
308
|
+
* Discover every source-side marketplace, then for each one re-synthesize its
|
|
309
|
+
* catalog from the plugins already copied under the version home and register
|
|
310
|
+
* it in known_marketplaces.json. Returns one result per marketplace that has at
|
|
311
|
+
* least one plugin installed.
|
|
312
|
+
*
|
|
313
|
+
* Copying plugin source into a marketplace is the caller's responsibility
|
|
314
|
+
* (copyPluginToMarketplace / syncPluginToVersion) — this reconciles catalogs +
|
|
315
|
+
* registrations across all repos once the copies are in place. Marketplaces
|
|
316
|
+
* whose version-home plugins dir is empty or absent are skipped, so we never
|
|
317
|
+
* register a known_marketplace pointing at a directory with no catalog.
|
|
153
318
|
*/
|
|
154
|
-
export function
|
|
155
|
-
|
|
156
|
-
|
|
319
|
+
export function syncAllMarketplaces(agent, versionHome, opts = {}) {
|
|
320
|
+
const results = [];
|
|
321
|
+
for (const dm of discoverMarketplaces(opts)) {
|
|
322
|
+
const manifest = syncMarketplaceManifest(dm.spec, agent, versionHome);
|
|
323
|
+
if (!manifest || manifest.plugins.length === 0)
|
|
324
|
+
continue;
|
|
325
|
+
registerMarketplace(dm.spec, agent, versionHome);
|
|
326
|
+
results.push({ spec: dm.spec, name: dm.name, plugins: manifest.plugins.length });
|
|
157
327
|
}
|
|
328
|
+
return results;
|
|
329
|
+
}
|
|
330
|
+
// ─── Per-plugin settings ops ──────────────────────────────────────────────────
|
|
331
|
+
/**
|
|
332
|
+
* Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
|
|
333
|
+
* enabledPlugins["<plugin>@<marketplace>"]: true. Reads, mutates, writes —
|
|
334
|
+
* preserving every other key. Trust/exec-surface gating is the caller's
|
|
335
|
+
* responsibility (plugins.ts owns plugin capability inspection).
|
|
336
|
+
*/
|
|
337
|
+
export function addPluginToSettings(pluginName, marketplaceName, agent, versionHome) {
|
|
158
338
|
const sPath = settingsPath(agent, versionHome);
|
|
159
339
|
let settings = {};
|
|
160
340
|
if (fs.existsSync(sPath)) {
|
|
@@ -169,49 +349,17 @@ export function enablePluginInSettings(pluginName, agent, versionHome, options =
|
|
|
169
349
|
settings.enabledPlugins = {};
|
|
170
350
|
}
|
|
171
351
|
const enabled = settings.enabledPlugins;
|
|
172
|
-
const key = `${pluginName}@${
|
|
352
|
+
const key = `${pluginName}@${marketplaceName}`;
|
|
173
353
|
if (enabled[key] === true)
|
|
174
354
|
return;
|
|
175
355
|
enabled[key] = true;
|
|
176
356
|
fs.mkdirSync(path.dirname(sPath), { recursive: true });
|
|
177
357
|
fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
178
358
|
}
|
|
179
|
-
function marketplacePluginHasExecSurfaces(pluginName, agent, versionHome) {
|
|
180
|
-
const root = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
|
|
181
|
-
if (fs.existsSync(path.join(root, '.mcp.json')))
|
|
182
|
-
return true;
|
|
183
|
-
for (const dir of ['bin', 'scripts', 'permissions']) {
|
|
184
|
-
if (fs.existsSync(path.join(root, dir)))
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
const hooksFile = path.join(root, 'hooks', 'hooks.json');
|
|
188
|
-
if (fs.existsSync(hooksFile))
|
|
189
|
-
return true;
|
|
190
|
-
const hooksDir = path.join(root, 'hooks');
|
|
191
|
-
if (fs.existsSync(hooksDir)) {
|
|
192
|
-
try {
|
|
193
|
-
if (fs.readdirSync(hooksDir).some((entry) => !entry.startsWith('.')))
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const settingsFile = path.join(root, 'settings.json');
|
|
201
|
-
if (!fs.existsSync(settingsFile))
|
|
202
|
-
return false;
|
|
203
|
-
try {
|
|
204
|
-
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
|
|
205
|
-
return Object.keys(settings).some((key) => key !== 'permissions') || 'permissions' in settings;
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
359
|
/**
|
|
212
|
-
* Remove the enabledPlugins key for this plugin. Inverse of
|
|
360
|
+
* Remove the enabledPlugins key for this plugin. Inverse of addPluginToSettings.
|
|
213
361
|
*/
|
|
214
|
-
export function
|
|
362
|
+
export function removePluginFromSettings(pluginName, marketplaceName, agent, versionHome) {
|
|
215
363
|
const sPath = settingsPath(agent, versionHome);
|
|
216
364
|
if (!fs.existsSync(sPath))
|
|
217
365
|
return;
|
|
@@ -225,7 +373,7 @@ export function disablePluginInSettings(pluginName, agent, versionHome) {
|
|
|
225
373
|
const enabled = settings.enabledPlugins;
|
|
226
374
|
if (!enabled)
|
|
227
375
|
return;
|
|
228
|
-
const key = `${pluginName}@${
|
|
376
|
+
const key = `${pluginName}@${marketplaceName}`;
|
|
229
377
|
if (!(key in enabled))
|
|
230
378
|
return;
|
|
231
379
|
delete enabled[key];
|
|
@@ -234,12 +382,13 @@ export function disablePluginInSettings(pluginName, agent, versionHome) {
|
|
|
234
382
|
}
|
|
235
383
|
fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
236
384
|
}
|
|
385
|
+
// ─── Marketplace teardown helpers ─────────────────────────────────────────────
|
|
237
386
|
/**
|
|
238
387
|
* Remove a plugin's installed marketplace directory. Returns true if the dir
|
|
239
388
|
* existed and was removed.
|
|
240
389
|
*/
|
|
241
|
-
export function removePluginFromMarketplace(pluginName, agent, versionHome) {
|
|
242
|
-
const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
|
|
390
|
+
export function removePluginFromMarketplace(pluginName, specOrName, agent, versionHome) {
|
|
391
|
+
const installed = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', pluginName);
|
|
243
392
|
if (!fs.existsSync(installed))
|
|
244
393
|
return false;
|
|
245
394
|
fs.rmSync(installed, { recursive: true, force: true });
|
|
@@ -248,8 +397,8 @@ export function removePluginFromMarketplace(pluginName, agent, versionHome) {
|
|
|
248
397
|
/**
|
|
249
398
|
* Return true if the marketplace has no plugins left under it.
|
|
250
399
|
*/
|
|
251
|
-
export function marketplaceIsEmpty(agent, versionHome) {
|
|
252
|
-
const pluginsDir = path.join(marketplaceRoot(agent, versionHome), 'plugins');
|
|
400
|
+
export function marketplaceIsEmpty(specOrName, agent, versionHome) {
|
|
401
|
+
const pluginsDir = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins');
|
|
253
402
|
if (!fs.existsSync(pluginsDir))
|
|
254
403
|
return true;
|
|
255
404
|
const remaining = fs.readdirSync(pluginsDir, { withFileTypes: true })
|
|
@@ -259,8 +408,8 @@ export function marketplaceIsEmpty(agent, versionHome) {
|
|
|
259
408
|
/**
|
|
260
409
|
* Drop the entire marketplace directory. Called after the last plugin removal.
|
|
261
410
|
*/
|
|
262
|
-
export function removeEmptyMarketplaceDir(agent, versionHome) {
|
|
263
|
-
const root = marketplaceRoot(agent, versionHome);
|
|
411
|
+
export function removeEmptyMarketplaceDir(specOrName, agent, versionHome) {
|
|
412
|
+
const root = marketplaceRoot(specOrName, agent, versionHome);
|
|
264
413
|
if (!fs.existsSync(root))
|
|
265
414
|
return;
|
|
266
415
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -268,7 +417,7 @@ export function removeEmptyMarketplaceDir(agent, versionHome) {
|
|
|
268
417
|
/**
|
|
269
418
|
* Detect whether a plugin is installed via the native marketplace path.
|
|
270
419
|
*/
|
|
271
|
-
export function isInstalledInMarketplace(pluginName, agent, versionHome) {
|
|
272
|
-
const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
|
|
420
|
+
export function isInstalledInMarketplace(pluginName, specOrName, agent, versionHome) {
|
|
421
|
+
const installed = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', pluginName);
|
|
273
422
|
return fs.existsSync(path.join(installed, '.claude-plugin', 'plugin.json'));
|
|
274
423
|
}
|
package/dist/lib/plugins.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* module discovers plugins, validates their manifests, and syncs their
|
|
9
9
|
* contents into agent version homes.
|
|
10
10
|
*/
|
|
11
|
-
import type { AgentId, DiscoveredPlugin, PluginManifest } from './types.js';
|
|
11
|
+
import type { AgentId, DiscoveredPlugin, PluginManifest, MarketplaceSpec } from './types.js';
|
|
12
12
|
export interface PluginCapabilities {
|
|
13
13
|
hasHooks: boolean;
|
|
14
14
|
hasMcp: boolean;
|
|
@@ -19,11 +19,28 @@ export interface PluginCapabilities {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const PLUGIN_EXEC_SURFACE_LABELS: Record<keyof PluginCapabilities, string>;
|
|
21
21
|
/**
|
|
22
|
-
* Discover all plugins in ~/.agents/plugins
|
|
22
|
+
* Discover all plugins in a given plugins directory (e.g. ~/.agents/plugins/,
|
|
23
|
+
* ~/.agents/.system/plugins/, <cwd>/.agents/plugins/, ~/.agents-<alias>/plugins/).
|
|
23
24
|
* A valid plugin has a .claude-plugin/plugin.json manifest.
|
|
25
|
+
*
|
|
26
|
+
* `spec` stamps marketplace provenance onto each discovered plugin. Callers that
|
|
27
|
+
* scan a single source dir without a marketplace identity (e.g. project-launch)
|
|
28
|
+
* may omit it; those plugins default to the user marketplace.
|
|
24
29
|
*/
|
|
25
|
-
export declare function
|
|
26
|
-
|
|
30
|
+
export declare function discoverPluginsInDir(pluginsDir: string, spec?: MarketplaceSpec): DiscoveredPlugin[];
|
|
31
|
+
/**
|
|
32
|
+
* Discover every plugin across ALL marketplaces — the user repo (~/.agents/),
|
|
33
|
+
* each enabled extra repo (~/.agents-<alias>/), and the project repo
|
|
34
|
+
* (<cwd>/.agents/) — stamping marketplace provenance onto each.
|
|
35
|
+
*
|
|
36
|
+
* Plugin names are NOT deduplicated across marketplaces: a `code` plugin in both
|
|
37
|
+
* the user repo and an extra repo yields two entries (`code@agents-cli` and
|
|
38
|
+
* `code@agents-<alias>`), each installing into its own marketplace directory.
|
|
39
|
+
*/
|
|
40
|
+
export declare function discoverPlugins(opts?: {
|
|
41
|
+
cwd?: string;
|
|
42
|
+
}): DiscoveredPlugin[];
|
|
43
|
+
export declare function buildDiscoveredPlugin(pluginRoot: string, manifest: PluginManifest, spec?: MarketplaceSpec): DiscoveredPlugin;
|
|
27
44
|
export declare function inspectPluginCapabilities(pluginRoot: string): PluginCapabilities;
|
|
28
45
|
export declare function hasPluginExecSurfaces(capabilities: PluginCapabilities): boolean;
|
|
29
46
|
export declare function pluginCapabilityLabels(capabilities: PluginCapabilities): string[];
|