agkit 0.7.0 → 0.8.0
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/LICENSE +21 -0
- package/README.md +150 -38
- package/dist/index.js +398 -162
- package/package.json +18 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fair3n
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,42 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# **AG**ent marketplace **KIT**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**One canonical catalog → every AI coding agent → any Git forge.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Scaffold and manage **plugin marketplaces for Claude Code · GitHub Copilot · OpenAI Codex · Cursor**,
|
|
8
|
+
distributed through **any Git host** — GitHub, GitLab, Bitbucket, Gitea, or a self‑hosted forge.
|
|
8
9
|
|
|
10
|
+
[](https://www.npmjs.com/package/agkit)
|
|
11
|
+
[](https://www.npmjs.com/package/agkit)
|
|
12
|
+
[](https://github.com/Fairen/agkit/actions/workflows/ci.yml)
|
|
13
|
+
[](https://nodejs.org)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
|
|
16
|
+
*`init` · `add` · `build` · `sync` · `bump` · `validate` · `list`*
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
> [!NOTE]
|
|
9
21
|
> Unofficial community tool, not affiliated with Anthropic, GitHub, OpenAI, or Cursor.
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
Think **`ng` for Angular, but for agent plugin marketplaces**: `init` gives you a push‑ready repository, and `add` / `build` / `sync` / `bump` / `validate` cover the whole life of the project afterwards.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ✨ Why agkit
|
|
28
|
+
|
|
29
|
+
- 🧩 **Multi‑agent** — one repository serves Claude Code, GitHub Copilot, OpenAI Codex, and Cursor.
|
|
30
|
+
- 🌐 **Forge‑agnostic** — distribute via GitHub, GitLab, Bitbucket, Gitea, or self‑hosted, with a plain `git push`.
|
|
31
|
+
- 📁 **One source of truth** — a single canonical `.claude-plugin/marketplace.json`; generated registries derive from it and never drift.
|
|
32
|
+
- 🔁 **Full lifecycle** — `init`, `add`, `build`, `sync`, `bump`, `validate`, `list`.
|
|
33
|
+
- 🧬 **Remote & local templates** — pull plugin templates from any git repo (`gh:` / `gl:` / URL) or a local path.
|
|
34
|
+
- ✅ **CI‑ready** — generated GitHub Actions / GitLab CI, with `validate` + `build --check` gates.
|
|
12
35
|
|
|
13
|
-
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 📦 Installation
|
|
39
|
+
|
|
40
|
+
Requires **Node.js ≥ 18**. Run it on demand with `npx` (nothing to install), or install the CLI globally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx agkit --help # run without installing
|
|
44
|
+
npm install -g agkit # or install the `agkit` command globally
|
|
45
|
+
```
|
|
14
46
|
|
|
15
|
-
|
|
16
|
-
- **Generated registry (`agkit build`)** — **Codex** and **Cursor** install from their own committed registry, which `agkit build` derives from the same canonical catalog. You run `agkit build` once to enable a target; after that `add`/`bump`/`sync` keep it fresh automatically.
|
|
47
|
+
---
|
|
17
48
|
|
|
18
|
-
|
|
49
|
+
## 🚀 Quick start
|
|
19
50
|
|
|
20
|
-
|
|
51
|
+
Create and distribute your first marketplace in **four steps**:
|
|
21
52
|
|
|
22
53
|
```bash
|
|
23
|
-
npx agkit init my-marketplace
|
|
54
|
+
npx agkit init my-marketplace # 1. scaffold a push-ready repository
|
|
24
55
|
cd my-marketplace
|
|
25
|
-
agkit add skill tdd-coach
|
|
26
|
-
agkit validate
|
|
56
|
+
agkit add skill tdd-coach # 2. add a plugin from a built-in template
|
|
57
|
+
agkit validate # 3. check the catalog is valid
|
|
27
58
|
git remote add origin <your-git-url>
|
|
28
|
-
git add -A && git commit -m "feat: initial marketplace" && git push -u origin main
|
|
59
|
+
git add -A && git commit -m "feat: initial marketplace" && git push -u origin main # 4. distribute via git
|
|
29
60
|
```
|
|
30
61
|
|
|
31
|
-
|
|
62
|
+
That's the whole loop — once pushed, anyone can install your plugins straight from the repo (the per‑agent install command is in the **Create a marketplace for your agent** section below). `init` targets **Claude Code** and **GitHub Copilot** by default; add `--agents codex,cursor` to serve more.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🧩 How agents consume your marketplace
|
|
67
|
+
|
|
68
|
+
agkit always writes **one canonical catalog** — `.claude-plugin/marketplace.json` — plus your plugins under `plugins/<name>/`. That catalog is the single source of truth. How each agent reads it falls into two groups:
|
|
69
|
+
|
|
70
|
+
```mermaid
|
|
71
|
+
flowchart TD
|
|
72
|
+
DEV["agkit init · add · bump · sync"] --> CAT[".claude-plugin/marketplace.json<br/>+ plugins/<name><br/><b>— canonical catalog —</b>"]
|
|
73
|
+
CAT -->|read as-is, no build| NATIVE["🟢 Native<br/>Claude Code · GitHub Copilot"]
|
|
74
|
+
CAT -->|agkit build| GEN["⚙️ Generated registries<br/>OpenAI Codex · Cursor"]
|
|
75
|
+
NATIVE --> PUSH["git push → any forge"]
|
|
76
|
+
GEN --> PUSH
|
|
77
|
+
PUSH --> INSTALL["🎉 consumers install your plugins"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- 🟢 **Native (no build step)** — **Claude Code** and **GitHub Copilot** read `.claude-plugin/marketplace.json` directly. Push the repo and it installs.
|
|
81
|
+
- ⚙️ **Generated registry (`agkit build`)** — **Codex** and **Cursor** install from their own committed registry, which `agkit build` derives from the same canonical catalog. You run `agkit build` once to enable a target; after that `add` / `bump` / `sync` keep it fresh automatically.
|
|
82
|
+
|
|
83
|
+
> [!TIP]
|
|
84
|
+
> You can target several agents from a single repository — the plugin content under `plugins/` is shared across all of them.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🎯 Create a marketplace for your agent
|
|
32
89
|
|
|
33
|
-
|
|
90
|
+
Choose targets at init with `--agents`. Every flow ends with a normal `git push`; distribution works from any forge.
|
|
34
91
|
|
|
35
|
-
|
|
92
|
+
| Agent | Type | `agkit build`? | Consumers install with |
|
|
93
|
+
| :---- | :--- | :------------: | :--------------------- |
|
|
94
|
+
| **Claude Code** | 🟢 Native | — | `/plugin marketplace add …` |
|
|
95
|
+
| **GitHub Copilot** | 🟢 Native | — | `copilot plugin marketplace add …` |
|
|
96
|
+
| **OpenAI Codex** | ⚙️ Generated | ✅ | `codex plugin marketplace add …` |
|
|
97
|
+
| **Cursor** | ⚙️ Generated | ✅ | add in Cursor, then install by name |
|
|
36
98
|
|
|
37
|
-
|
|
99
|
+
<details open>
|
|
100
|
+
<summary><b>🤖 Claude Code</b> — native, nothing to generate</summary>
|
|
38
101
|
|
|
39
|
-
|
|
102
|
+
<br/>
|
|
40
103
|
|
|
41
104
|
```bash
|
|
42
105
|
agkit init my-marketplace --agents claude-code
|
|
@@ -49,14 +112,19 @@ git add -A && git commit -m "feat: initial marketplace" && git push -u origin ma
|
|
|
49
112
|
|
|
50
113
|
Consumers install in a Claude Code session:
|
|
51
114
|
|
|
52
|
-
```
|
|
115
|
+
```text
|
|
53
116
|
/plugin marketplace add <owner/repo or git URL>
|
|
54
117
|
/plugin install my-skill@my-marketplace
|
|
55
118
|
```
|
|
56
119
|
|
|
57
|
-
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary><b>🐙 GitHub Copilot</b> — native, same repo as Claude Code, no build</summary>
|
|
124
|
+
|
|
125
|
+
<br/>
|
|
58
126
|
|
|
59
|
-
|
|
127
|
+
The default init already includes Copilot; to target it explicitly use `--agents copilot` (or `--agents claude-code,copilot`).
|
|
60
128
|
|
|
61
129
|
```bash
|
|
62
130
|
agkit init my-marketplace --agents claude-code,copilot
|
|
@@ -73,7 +141,12 @@ copilot plugin marketplace add <owner/repo or git URL>
|
|
|
73
141
|
copilot plugin install my-skill@my-marketplace
|
|
74
142
|
```
|
|
75
143
|
|
|
76
|
-
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
<details>
|
|
147
|
+
<summary><b>🧠 OpenAI Codex</b> — installs from a generated registry</summary>
|
|
148
|
+
|
|
149
|
+
<br/>
|
|
77
150
|
|
|
78
151
|
Codex installs from its own registry, which `agkit build` generates from the catalog.
|
|
79
152
|
|
|
@@ -94,7 +167,12 @@ codex plugin marketplace add <owner/repo or git URL>
|
|
|
94
167
|
|
|
95
168
|
The Codex registry format follows the official Codex plugin docs (`.agents/plugins/marketplace.json` with `source: { source: "local", path }`, `policy`, and `category`). Commit the generated files so consumers can install.
|
|
96
169
|
|
|
97
|
-
|
|
170
|
+
</details>
|
|
171
|
+
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>🖱️ Cursor</b> — installs from a generated registry</summary>
|
|
174
|
+
|
|
175
|
+
<br/>
|
|
98
176
|
|
|
99
177
|
Cursor installs from a committed `.cursor-plugin/` registry, also generated by `agkit build`.
|
|
100
178
|
|
|
@@ -109,9 +187,12 @@ git add -A && git commit -m "feat: initial marketplace" && git push -u origin ma
|
|
|
109
187
|
|
|
110
188
|
Consumers add the marketplace in Cursor, then install the plugin by name.
|
|
111
189
|
|
|
112
|
-
|
|
190
|
+
</details>
|
|
191
|
+
|
|
192
|
+
<details>
|
|
193
|
+
<summary><b>🌈 Several agents at once</b> — one repository serves them all</summary>
|
|
113
194
|
|
|
114
|
-
|
|
195
|
+
<br/>
|
|
115
196
|
|
|
116
197
|
```bash
|
|
117
198
|
agkit init my-marketplace --agents claude-code,copilot,codex,cursor
|
|
@@ -122,19 +203,25 @@ agkit validate
|
|
|
122
203
|
git add -A && git commit -m "feat: initial marketplace" && git push -u origin main
|
|
123
204
|
```
|
|
124
205
|
|
|
125
|
-
|
|
206
|
+
</details>
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 🛠️ Commands
|
|
126
211
|
|
|
127
212
|
| Command | What it does |
|
|
128
213
|
| :------ | :----------- |
|
|
129
|
-
| `agkit init [dir]` | Scaffolds a git-first marketplace: `.claude-plugin/marketplace.json` (with `$schema`, `pluginRoot`, `targets`), `plugins/`, `AGENTS.md`, a per-agent README, `examples/team-settings.json`, CI (GitHub Actions or GitLab CI), `.gitignore`, `git init`. Pick target agents with `--agents` (see
|
|
130
|
-
| `agkit add <template\|spec> <name>` | Scaffolds a plugin from a built-in template (`skill`, `command`, `agent`, `hook`, `mcp`) **or a remote/local one** — `gh:owner/repo/dir#ref`, `gl:owner/repo`, any git URL with `//subdir` and `#ref`, or a local path. Registers it in the catalog and refreshes the README
|
|
214
|
+
| `agkit init [dir]` | Scaffolds a git-first marketplace: `.claude-plugin/marketplace.json` (with `$schema`, `pluginRoot`, `targets`), `plugins/`, `AGENTS.md`, a per-agent README, `examples/team-settings.json`, CI (GitHub Actions or GitLab CI), `.gitignore`, `git init`. Pick target agents with `--agents` (see the **Target agents** section). Non-interactive with `-y`. |
|
|
215
|
+
| `agkit add <template\|spec> <name>` | Scaffolds a plugin from a built-in template (`skill`, `command`, `agent`, `hook`, `mcp`) **or a remote/local one** — `gh:owner/repo/dir#ref`, `gl:owner/repo`, any git URL with `//subdir` and `#ref`, or a local path. Registers it in the catalog and refreshes the README table and `AGENTS.md`. |
|
|
131
216
|
| `agkit build [--target] [--check]` | Generates the registries for **Codex** and **Cursor** from the catalog: their committed `marketplace.json` + per-plugin manifest mirrors. Default targets are the ones in `metadata.targets`; `--target codex,cursor` overrides. `--check` fails on drift (CI). |
|
|
132
|
-
| `agkit bump [plugin] [level]` | Bumps a plugin version from conventional commits scoped to its directory since the last `<plugin>@x.y.z` tag (`feat`→minor, breaking→major, else patch), or an explicit `major\|minor\|patch`. `--tag` commits and tags; `--dry-run` previews. Catalog, README, and any built registries stay in sync. |
|
|
133
|
-
| `agkit sync` | Reconciles the catalog with what's on disk. Source of truth: each plugin's `.claude-plugin/plugin.json`. Adds missing entries, fixes drift, flags orphans, regenerates the README table, and refreshes any already-built Codex/Cursor registry. |
|
|
217
|
+
| `agkit bump [plugin] [level]` | Bumps a plugin version from conventional commits scoped to its directory since the last `<plugin>@x.y.z` tag (`feat`→minor, breaking→major, else patch), or an explicit `major\|minor\|patch`. Prepends a dated entry to `plugins/<name>/CHANGELOG.md` from those commits. `--tag` commits and tags; `--dry-run` previews. Catalog, README, `AGENTS.md`, and any built registries stay in sync. |
|
|
218
|
+
| `agkit sync` | Reconciles the catalog with what's on disk. Source of truth: each plugin's `.claude-plugin/plugin.json`. Adds missing entries, fixes drift, flags orphans, regenerates the README table and the `AGENTS.md` plugin list, and refreshes any already-built Codex/Cursor registry. |
|
|
134
219
|
| `agkit validate [--strict]` | Local checks (JSON validity, kebab-case, reserved names, source resolution, manifest presence, version drift) plus delegation to `claude plugin validate` when the Claude Code CLI is installed (`--strict` forwarded). Non-zero exit on error — CI-ready. |
|
|
135
220
|
| `agkit list` | Lists available plugin templates. |
|
|
136
221
|
|
|
137
|
-
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🤖 Target agents
|
|
138
225
|
|
|
139
226
|
`agkit init --agents <list>` records the agents you want to serve and adapts the scaffold. Default: `claude-code,copilot`.
|
|
140
227
|
|
|
@@ -145,11 +232,26 @@ git add -A && git commit -m "feat: initial marketplace" && git push -u origin ma
|
|
|
145
232
|
|
|
146
233
|
Every init also writes a root `AGENTS.md`, read natively by most agents for repository context.
|
|
147
234
|
|
|
148
|
-
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 🔄 Keeping registries fresh
|
|
149
238
|
|
|
150
239
|
Generation is opt-in: run `agkit build` once to enable Codex/Cursor. From then on, `agkit add`, `agkit bump`, and `agkit sync` refresh the generated registries automatically, so they never drift as plugins change. In CI, run `agkit build --check` to fail the build if a committed registry is out of date (the generated GitHub Actions / GitLab CI workflows already do this).
|
|
151
240
|
|
|
152
|
-
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 📝 Generated documentation
|
|
244
|
+
|
|
245
|
+
agkit maintains a small set of human-facing docs derived from the canonical catalog, so they never drift:
|
|
246
|
+
|
|
247
|
+
- **README plugin table** and **`AGENTS.md` plugin list** — regenerated between `<!-- agkit:plugins:start -->` / `<!-- agkit:plugins:end -->` markers on every mutating command (`add`, `bump`, `sync`). The name links to a plugin's `homepage` when set, and a `Keywords` column appears when any plugin declares keywords. Drop the same markers into any Markdown file to have that list maintained there too.
|
|
248
|
+
- **Per-plugin `CHANGELOG.md`** — `agkit bump` prepends a dated, newest-on-top entry built from the conventional commits it analyses, so each plugin carries its own release history (committed alongside the bump with `--tag`).
|
|
249
|
+
|
|
250
|
+
Everything derives from `.claude-plugin/marketplace.json` and the per-plugin manifests — edit those (or run `agkit sync`) and the docs follow.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 🧬 Team templates (remote or local)
|
|
153
255
|
|
|
154
256
|
Version your team's plugin templates in any git repository and consume them from every marketplace:
|
|
155
257
|
|
|
@@ -161,14 +263,24 @@ agkit add ./shared-templates/hook-guard no-secrets
|
|
|
161
263
|
|
|
162
264
|
A template is any directory containing `.claude-plugin/plugin.json` (adopted, `name` rewritten) or `plugin.json.tpl`. Files ending in `.tpl` are rendered with `{{pluginName}}`, `{{pluginTitle}}`, `{{description}}`, `{{authorName}}` — in contents **and** in file/directory names. Executable bits are preserved.
|
|
163
265
|
|
|
164
|
-
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 🌐 Forge-agnostic by design
|
|
165
269
|
|
|
166
270
|
`init` parses your git remote (https, ssh, scp-style) regardless of host. On github.com it uses the `owner/repo` shorthand and `{"source": "github"}` team settings; everywhere else it emits the git URL form — which every supported agent accepts for any forge.
|
|
167
271
|
|
|
168
|
-
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 👥 Team setup (automatic installation)
|
|
169
275
|
|
|
170
276
|
`init` writes `examples/team-settings.json` with an `extraKnownMarketplaces` block. Drop it into the settings file for each agent so teammates get the marketplace automatically when they trust the repository — `.claude/settings.json` for Claude Code, `.github/copilot/settings.json` for GitHub Copilot.
|
|
171
277
|
|
|
172
|
-
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## 📄 License
|
|
281
|
+
|
|
282
|
+
[MIT](LICENSE) © fair3n
|
|
173
283
|
|
|
174
|
-
|
|
284
|
+
<div align="center">
|
|
285
|
+
<sub>Built for the age of AI coding agents. Ship a marketplace, not a config.</sub>
|
|
286
|
+
</div>
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,13 @@ var RESERVED_MARKETPLACE_NAMES = [
|
|
|
24
24
|
"anthropic-plugins"
|
|
25
25
|
];
|
|
26
26
|
var KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
27
|
-
var PLUGIN_TEMPLATES = [
|
|
27
|
+
var PLUGIN_TEMPLATES = [
|
|
28
|
+
"skill",
|
|
29
|
+
"command",
|
|
30
|
+
"agent",
|
|
31
|
+
"hook",
|
|
32
|
+
"mcp"
|
|
33
|
+
];
|
|
28
34
|
var TEMPLATE_DESCRIPTIONS = {
|
|
29
35
|
skill: "Plugin exposing an Agent Skill (SKILL.md): reference knowledge or a repeatable procedure Claude loads on demand.",
|
|
30
36
|
command: "Plugin exposing a slash command (commands/<name>.md): a prompt shortcut invoked as /<plugin>:<name>.",
|
|
@@ -84,14 +90,69 @@ function readJson(filePath) {
|
|
|
84
90
|
}
|
|
85
91
|
function writeJson(filePath, data) {
|
|
86
92
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
87
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2)
|
|
93
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}
|
|
94
|
+
`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/lib/marketplace.ts
|
|
98
|
+
import fs2 from "fs";
|
|
99
|
+
import path2 from "path";
|
|
100
|
+
function marketplacePath(root) {
|
|
101
|
+
return path2.join(root, MARKETPLACE_DIR, MARKETPLACE_FILE);
|
|
102
|
+
}
|
|
103
|
+
function findMarketplaceRoot(startDir) {
|
|
104
|
+
let dir = path2.resolve(startDir);
|
|
105
|
+
for (; ; ) {
|
|
106
|
+
if (fs2.existsSync(marketplacePath(dir))) return dir;
|
|
107
|
+
const parent = path2.dirname(dir);
|
|
108
|
+
if (parent === dir) return void 0;
|
|
109
|
+
dir = parent;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function readMarketplace(root) {
|
|
113
|
+
return readJson(marketplacePath(root));
|
|
114
|
+
}
|
|
115
|
+
function writeMarketplace(root, data) {
|
|
116
|
+
writeJson(marketplacePath(root), data);
|
|
117
|
+
}
|
|
118
|
+
function pluginRootDir(root, mp) {
|
|
119
|
+
const rel = mp.metadata?.pluginRoot ?? DEFAULT_PLUGIN_ROOT;
|
|
120
|
+
return path2.resolve(root, rel);
|
|
121
|
+
}
|
|
122
|
+
function resolveLocalPluginDir(root, mp, entry) {
|
|
123
|
+
if (typeof entry.source !== "string") return void 0;
|
|
124
|
+
if (entry.source.startsWith("./") || entry.source.startsWith("../")) {
|
|
125
|
+
return path2.resolve(root, entry.source);
|
|
126
|
+
}
|
|
127
|
+
return path2.join(pluginRootDir(root, mp), entry.source);
|
|
128
|
+
}
|
|
129
|
+
function scanLocalPlugins(root, mp) {
|
|
130
|
+
const base = pluginRootDir(root, mp);
|
|
131
|
+
if (!fs2.existsSync(base)) return [];
|
|
132
|
+
const result = [];
|
|
133
|
+
for (const entry of fs2.readdirSync(base, { withFileTypes: true })) {
|
|
134
|
+
if (!entry.isDirectory()) continue;
|
|
135
|
+
const manifestPath = path2.join(
|
|
136
|
+
base,
|
|
137
|
+
entry.name,
|
|
138
|
+
MARKETPLACE_DIR,
|
|
139
|
+
PLUGIN_MANIFEST_FILE
|
|
140
|
+
);
|
|
141
|
+
if (!fs2.existsSync(manifestPath)) continue;
|
|
142
|
+
result.push({
|
|
143
|
+
dir: path2.join(base, entry.name),
|
|
144
|
+
dirName: entry.name,
|
|
145
|
+
manifest: readJson(manifestPath)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
88
149
|
}
|
|
89
150
|
|
|
90
151
|
// src/lib/templates.ts
|
|
91
152
|
import { execFileSync } from "child_process";
|
|
92
|
-
import
|
|
153
|
+
import fs3 from "fs";
|
|
93
154
|
import os from "os";
|
|
94
|
-
import
|
|
155
|
+
import path3 from "path";
|
|
95
156
|
function parseGitSpec(spec) {
|
|
96
157
|
let ref;
|
|
97
158
|
let body = spec;
|
|
@@ -123,7 +184,7 @@ function parseGitSpec(spec) {
|
|
|
123
184
|
return void 0;
|
|
124
185
|
}
|
|
125
186
|
function assertTemplateDir(dir, label) {
|
|
126
|
-
const hasManifest =
|
|
187
|
+
const hasManifest = fs3.existsSync(path3.join(dir, ".claude-plugin", "plugin.json.tpl")) || fs3.existsSync(path3.join(dir, ".claude-plugin", "plugin.json"));
|
|
127
188
|
if (!hasManifest) {
|
|
128
189
|
throw new Error(
|
|
129
190
|
`"${label}" is not a plugin template: expected .claude-plugin/plugin.json(.tpl) in ${dir}`
|
|
@@ -133,15 +194,16 @@ function assertTemplateDir(dir, label) {
|
|
|
133
194
|
function resolveTemplate(spec) {
|
|
134
195
|
if (PLUGIN_TEMPLATES.includes(spec)) {
|
|
135
196
|
return {
|
|
136
|
-
dir:
|
|
197
|
+
dir: path3.join(templatesDir(), "plugins", spec),
|
|
137
198
|
label: `built-in:${spec}`,
|
|
138
199
|
builtin: true
|
|
139
200
|
};
|
|
140
201
|
}
|
|
141
|
-
const localPath = spec.startsWith("path:") ? spec.slice(5) : spec.startsWith("./") || spec.startsWith("../") ||
|
|
202
|
+
const localPath = spec.startsWith("path:") ? spec.slice(5) : spec.startsWith("./") || spec.startsWith("../") || path3.isAbsolute(spec) ? spec : void 0;
|
|
142
203
|
if (localPath !== void 0) {
|
|
143
|
-
const dir2 =
|
|
144
|
-
if (!
|
|
204
|
+
const dir2 = path3.resolve(localPath);
|
|
205
|
+
if (!fs3.existsSync(dir2))
|
|
206
|
+
throw new Error(`Template directory not found: ${dir2}`);
|
|
145
207
|
assertTemplateDir(dir2, spec);
|
|
146
208
|
return { dir: dir2, label: spec, builtin: false };
|
|
147
209
|
}
|
|
@@ -151,91 +213,106 @@ function resolveTemplate(spec) {
|
|
|
151
213
|
`Unrecognized template "${spec}". Use a built-in name (${PLUGIN_TEMPLATES.join(", ")}), a local path, gh:owner/repo[/dir][#ref], gl:owner/repo[/dir][#ref], or a git URL.`
|
|
152
214
|
);
|
|
153
215
|
}
|
|
154
|
-
const tmp =
|
|
216
|
+
const tmp = fs3.mkdtempSync(path3.join(os.tmpdir(), "agkit-tpl-"));
|
|
155
217
|
const args = ["clone", "--depth", "1"];
|
|
156
218
|
if (git2.ref) args.push("--branch", git2.ref);
|
|
157
219
|
args.push(git2.cloneUrl, tmp);
|
|
158
220
|
try {
|
|
159
221
|
execFileSync("git", args, { stdio: "pipe" });
|
|
160
222
|
} catch (err) {
|
|
161
|
-
|
|
223
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
162
224
|
const msg = err.stderr?.toString().trim();
|
|
163
|
-
throw new Error(
|
|
164
|
-
${msg
|
|
225
|
+
throw new Error(
|
|
226
|
+
`git clone failed for ${git2.cloneUrl}${msg ? `:
|
|
227
|
+
${msg}` : ""}`
|
|
228
|
+
);
|
|
165
229
|
}
|
|
166
|
-
const dir = git2.subdir ?
|
|
167
|
-
if (!
|
|
168
|
-
|
|
169
|
-
throw new Error(
|
|
230
|
+
const dir = git2.subdir ? path3.join(tmp, git2.subdir) : tmp;
|
|
231
|
+
if (!fs3.existsSync(dir)) {
|
|
232
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Subdirectory "${git2.subdir}" not found in ${git2.cloneUrl}`
|
|
235
|
+
);
|
|
170
236
|
}
|
|
171
|
-
|
|
237
|
+
fs3.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
|
|
172
238
|
try {
|
|
173
239
|
assertTemplateDir(dir, spec);
|
|
174
240
|
} catch (err) {
|
|
175
|
-
|
|
241
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
176
242
|
throw err;
|
|
177
243
|
}
|
|
178
244
|
return { dir, label: spec, cleanup: tmp, builtin: false };
|
|
179
245
|
}
|
|
180
246
|
|
|
181
|
-
// src/
|
|
182
|
-
import
|
|
183
|
-
import
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return readJson(marketplacePath(root));
|
|
247
|
+
// src/commands/sync.ts
|
|
248
|
+
import fs5 from "fs";
|
|
249
|
+
import path6 from "path";
|
|
250
|
+
import * as p2 from "@clack/prompts";
|
|
251
|
+
|
|
252
|
+
// src/lib/docs.ts
|
|
253
|
+
var PLUGINS_START = "<!-- agkit:plugins:start -->";
|
|
254
|
+
var PLUGINS_END = "<!-- agkit:plugins:end -->";
|
|
255
|
+
var CHANGELOG_HEADER = "# Changelog";
|
|
256
|
+
function replaceBetweenMarkers(content, start, end, body) {
|
|
257
|
+
const s = content.indexOf(start);
|
|
258
|
+
const e = content.indexOf(end);
|
|
259
|
+
if (s === -1 || e === -1 || e < s) return void 0;
|
|
260
|
+
return `${content.slice(0, s + start.length)}
|
|
261
|
+
${body}
|
|
262
|
+
${content.slice(e)}`;
|
|
198
263
|
}
|
|
199
|
-
|
|
200
|
-
|
|
264
|
+
var EMPTY_HINT = "_No plugins yet. Add one with `agkit add <template> <name>`._";
|
|
265
|
+
function renderPluginTable(plugins) {
|
|
266
|
+
if (plugins.length === 0) return EMPTY_HINT;
|
|
267
|
+
const withKeywords = plugins.some((p8) => (p8.keywords?.length ?? 0) > 0);
|
|
268
|
+
const header = withKeywords ? [
|
|
269
|
+
"| Plugin | Version | Description | Keywords |",
|
|
270
|
+
"| :----- | :------ | :---------- | :------- |"
|
|
271
|
+
] : [
|
|
272
|
+
"| Plugin | Version | Description |",
|
|
273
|
+
"| :----- | :------ | :---------- |"
|
|
274
|
+
];
|
|
275
|
+
const rows = plugins.map((e) => {
|
|
276
|
+
const name = e.homepage ? `[\`${e.name}\`](${e.homepage})` : `\`${e.name}\``;
|
|
277
|
+
const cells = [name, e.version ?? "\u2014", e.description ?? ""];
|
|
278
|
+
if (withKeywords) {
|
|
279
|
+
cells.push((e.keywords ?? []).map((k) => `\`${k}\``).join(" "));
|
|
280
|
+
}
|
|
281
|
+
return `| ${cells.join(" | ")} |`;
|
|
282
|
+
});
|
|
283
|
+
return [...header, ...rows].join("\n");
|
|
201
284
|
}
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
return
|
|
285
|
+
function renderAgentsPluginList(plugins) {
|
|
286
|
+
if (plugins.length === 0) return "_No plugins yet._";
|
|
287
|
+
return plugins.map((e) => {
|
|
288
|
+
const version2 = e.version ? ` (${e.version})` : "";
|
|
289
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
290
|
+
return `- \`${e.name}\`${version2}${desc}`;
|
|
291
|
+
}).join("\n");
|
|
205
292
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return path3.join(pluginRootDir(root, mp), entry.source);
|
|
293
|
+
function renderChangelogEntry(version2, subjects, date) {
|
|
294
|
+
const bullets = subjects.length > 0 ? subjects.map((s) => `- ${s}`).join("\n") : `- Release ${version2}`;
|
|
295
|
+
return `## ${version2} \u2014 ${date}
|
|
296
|
+
|
|
297
|
+
${bullets}`;
|
|
212
298
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const manifestPath = path3.join(
|
|
220
|
-
base,
|
|
221
|
-
entry.name,
|
|
222
|
-
MARKETPLACE_DIR,
|
|
223
|
-
PLUGIN_MANIFEST_FILE
|
|
224
|
-
);
|
|
225
|
-
if (!fs3.existsSync(manifestPath)) continue;
|
|
226
|
-
result.push({
|
|
227
|
-
dir: path3.join(base, entry.name),
|
|
228
|
-
dirName: entry.name,
|
|
229
|
-
manifest: readJson(manifestPath)
|
|
230
|
-
});
|
|
299
|
+
function prependChangelogEntry(existing, entry) {
|
|
300
|
+
if (!existing?.includes(CHANGELOG_HEADER)) {
|
|
301
|
+
return `${CHANGELOG_HEADER}
|
|
302
|
+
|
|
303
|
+
${entry}
|
|
304
|
+
`;
|
|
231
305
|
}
|
|
232
|
-
|
|
233
|
-
|
|
306
|
+
const idx = existing.indexOf(CHANGELOG_HEADER) + CHANGELOG_HEADER.length;
|
|
307
|
+
const before = existing.slice(0, idx);
|
|
308
|
+
const after = existing.slice(idx).replace(/^\n+/, "");
|
|
309
|
+
return `${`${before}
|
|
234
310
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
311
|
+
${entry}
|
|
312
|
+
|
|
313
|
+
${after}`.trimEnd()}
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
239
316
|
|
|
240
317
|
// src/commands/build.ts
|
|
241
318
|
import fs4 from "fs";
|
|
@@ -253,7 +330,8 @@ function sourcePath(mp, dirName) {
|
|
|
253
330
|
return `./${path4.posix.join(pluginRootRel(mp), dirName)}`;
|
|
254
331
|
}
|
|
255
332
|
function json(value) {
|
|
256
|
-
return JSON.stringify(value, null, 2)
|
|
333
|
+
return `${JSON.stringify(value, null, 2)}
|
|
334
|
+
`;
|
|
257
335
|
}
|
|
258
336
|
function manifestCopy(local) {
|
|
259
337
|
return json(local.manifest);
|
|
@@ -275,11 +353,19 @@ function generateCursor(ctx) {
|
|
|
275
353
|
}))
|
|
276
354
|
};
|
|
277
355
|
const files = [
|
|
278
|
-
{
|
|
356
|
+
{
|
|
357
|
+
relPath: path4.posix.join(".cursor-plugin", "marketplace.json"),
|
|
358
|
+
content: json(registry)
|
|
359
|
+
}
|
|
279
360
|
];
|
|
280
361
|
for (const p8 of plugins) {
|
|
281
362
|
files.push({
|
|
282
|
-
relPath: path4.posix.join(
|
|
363
|
+
relPath: path4.posix.join(
|
|
364
|
+
pluginRootRel(mp),
|
|
365
|
+
p8.dirName,
|
|
366
|
+
".cursor-plugin",
|
|
367
|
+
"plugin.json"
|
|
368
|
+
),
|
|
283
369
|
content: manifestCopy(p8)
|
|
284
370
|
});
|
|
285
371
|
}
|
|
@@ -304,7 +390,12 @@ function generateCodex(ctx) {
|
|
|
304
390
|
];
|
|
305
391
|
for (const p8 of plugins) {
|
|
306
392
|
files.push({
|
|
307
|
-
relPath: path4.posix.join(
|
|
393
|
+
relPath: path4.posix.join(
|
|
394
|
+
pluginRootRel(mp),
|
|
395
|
+
p8.dirName,
|
|
396
|
+
".codex-plugin",
|
|
397
|
+
"plugin.json"
|
|
398
|
+
),
|
|
308
399
|
content: manifestCopy(p8)
|
|
309
400
|
});
|
|
310
401
|
}
|
|
@@ -348,7 +439,9 @@ function computeFiles(root, mp, ids) {
|
|
|
348
439
|
async function buildCommand(startDir, opts = {}) {
|
|
349
440
|
const root = findMarketplaceRoot(startDir);
|
|
350
441
|
if (!root) {
|
|
351
|
-
p.log.error(
|
|
442
|
+
p.log.error(
|
|
443
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
444
|
+
);
|
|
352
445
|
process.exitCode = 1;
|
|
353
446
|
return;
|
|
354
447
|
}
|
|
@@ -420,12 +513,27 @@ function refreshBuiltTargets(root, mp) {
|
|
|
420
513
|
}
|
|
421
514
|
|
|
422
515
|
// src/commands/sync.ts
|
|
423
|
-
|
|
424
|
-
|
|
516
|
+
function refreshMarkedDoc(root, relPath, body, label, changes) {
|
|
517
|
+
const abs = path6.join(root, relPath);
|
|
518
|
+
if (!fs5.existsSync(abs)) return;
|
|
519
|
+
const content = fs5.readFileSync(abs, "utf8");
|
|
520
|
+
const updated = replaceBetweenMarkers(
|
|
521
|
+
content,
|
|
522
|
+
PLUGINS_START,
|
|
523
|
+
PLUGINS_END,
|
|
524
|
+
body
|
|
525
|
+
);
|
|
526
|
+
if (updated !== void 0 && updated !== content) {
|
|
527
|
+
fs5.writeFileSync(abs, updated);
|
|
528
|
+
changes.push(`~ refreshed ${label}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
425
531
|
async function syncCommand(startDir, opts = {}) {
|
|
426
532
|
const root = findMarketplaceRoot(startDir);
|
|
427
533
|
if (!root) {
|
|
428
|
-
p2.log.error(
|
|
534
|
+
p2.log.error(
|
|
535
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
536
|
+
);
|
|
429
537
|
process.exitCode = 1;
|
|
430
538
|
return;
|
|
431
539
|
}
|
|
@@ -439,12 +547,18 @@ async function syncCommand(startDir, opts = {}) {
|
|
|
439
547
|
if (!entry) {
|
|
440
548
|
entry = {
|
|
441
549
|
name: entryName,
|
|
442
|
-
source:
|
|
550
|
+
source: `./${path6.relative(root, local.dir).split(path6.sep).join("/")}`
|
|
443
551
|
};
|
|
444
552
|
mp.plugins.push(entry);
|
|
445
553
|
changes.push(`+ added "${entryName}" to the catalog`);
|
|
446
554
|
}
|
|
447
|
-
for (const field of [
|
|
555
|
+
for (const field of [
|
|
556
|
+
"version",
|
|
557
|
+
"description",
|
|
558
|
+
"author",
|
|
559
|
+
"keywords",
|
|
560
|
+
"homepage"
|
|
561
|
+
]) {
|
|
448
562
|
const value = manifest[field];
|
|
449
563
|
if (value !== void 0 && JSON.stringify(entry[field]) !== JSON.stringify(value)) {
|
|
450
564
|
entry[field] = value;
|
|
@@ -463,27 +577,22 @@ async function syncCommand(startDir, opts = {}) {
|
|
|
463
577
|
mp.plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
464
578
|
writeMarketplace(root, mp);
|
|
465
579
|
const refreshed = refreshBuiltTargets(root, mp);
|
|
466
|
-
for (const id of refreshed)
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
fs5.writeFileSync(readmePath, updated);
|
|
483
|
-
changes.push("~ refreshed plugin list in README.md");
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
580
|
+
for (const id of refreshed)
|
|
581
|
+
changes.push(`~ refreshed ${id} tier-2 artifacts`);
|
|
582
|
+
refreshMarkedDoc(
|
|
583
|
+
root,
|
|
584
|
+
"README.md",
|
|
585
|
+
renderPluginTable(mp.plugins),
|
|
586
|
+
"plugin list in README.md",
|
|
587
|
+
changes
|
|
588
|
+
);
|
|
589
|
+
refreshMarkedDoc(
|
|
590
|
+
root,
|
|
591
|
+
"AGENTS.md",
|
|
592
|
+
renderAgentsPluginList(mp.plugins),
|
|
593
|
+
"plugin list in AGENTS.md",
|
|
594
|
+
changes
|
|
595
|
+
);
|
|
487
596
|
if (!opts.quiet) {
|
|
488
597
|
if (changes.length === 0) {
|
|
489
598
|
p2.log.success("Catalog already in sync.");
|
|
@@ -576,7 +685,8 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
576
685
|
try {
|
|
577
686
|
copyTemplateDir(resolved.dir, destDir, vars);
|
|
578
687
|
} finally {
|
|
579
|
-
if (resolved.cleanup)
|
|
688
|
+
if (resolved.cleanup)
|
|
689
|
+
fs6.rmSync(resolved.cleanup, { recursive: true, force: true });
|
|
580
690
|
}
|
|
581
691
|
const manifestPath = path7.join(destDir, ".claude-plugin", "plugin.json");
|
|
582
692
|
if (!resolved.builtin && fs6.existsSync(manifestPath)) {
|
|
@@ -590,7 +700,7 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
590
700
|
}
|
|
591
701
|
}
|
|
592
702
|
const template = resolved.label;
|
|
593
|
-
const source =
|
|
703
|
+
const source = `./${path7.relative(root, destDir).split(path7.sep).join("/")}`;
|
|
594
704
|
mp.plugins.push({ name, source, description, version: "0.1.0" });
|
|
595
705
|
writeMarketplace(root, mp);
|
|
596
706
|
await syncCommand(root, { quiet: true });
|
|
@@ -611,6 +721,7 @@ function cancel2() {
|
|
|
611
721
|
|
|
612
722
|
// src/commands/bump.ts
|
|
613
723
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
724
|
+
import fs7 from "fs";
|
|
614
725
|
import path8 from "path";
|
|
615
726
|
import * as p4 from "@clack/prompts";
|
|
616
727
|
import pc3 from "picocolors";
|
|
@@ -656,7 +767,8 @@ function analyzeCommits(root, pluginDir, since) {
|
|
|
656
767
|
return { level: void 0, commits: 0, reasons: ["not a git repository"] };
|
|
657
768
|
}
|
|
658
769
|
const commits = raw.split("").map((c) => c.trim()).filter(Boolean);
|
|
659
|
-
if (commits.length === 0)
|
|
770
|
+
if (commits.length === 0)
|
|
771
|
+
return { level: void 0, commits: 0, reasons: [] };
|
|
660
772
|
let level = "patch";
|
|
661
773
|
const reasons = [];
|
|
662
774
|
for (const c of commits) {
|
|
@@ -678,7 +790,9 @@ function analyzeCommits(root, pluginDir, since) {
|
|
|
678
790
|
async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
679
791
|
const root = findMarketplaceRoot(startDir);
|
|
680
792
|
if (!root) {
|
|
681
|
-
p4.log.error(
|
|
793
|
+
p4.log.error(
|
|
794
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
795
|
+
);
|
|
682
796
|
process.exitCode = 1;
|
|
683
797
|
return;
|
|
684
798
|
}
|
|
@@ -707,20 +821,24 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
707
821
|
}
|
|
708
822
|
const local = locals.find((l) => (l.manifest.name || l.dirName) === name);
|
|
709
823
|
if (!local) {
|
|
710
|
-
p4.log.error(
|
|
824
|
+
p4.log.error(
|
|
825
|
+
`No local plugin named "${name}". Known: ${locals.map((l) => l.manifest.name || l.dirName).join(", ")}`
|
|
826
|
+
);
|
|
711
827
|
process.exitCode = 1;
|
|
712
828
|
return;
|
|
713
829
|
}
|
|
714
830
|
if (levelArg && !["major", "minor", "patch", "auto"].includes(levelArg)) {
|
|
715
|
-
p4.log.error(
|
|
831
|
+
p4.log.error(
|
|
832
|
+
`Invalid level "${levelArg}". Use major, minor, patch, or auto.`
|
|
833
|
+
);
|
|
716
834
|
process.exitCode = 1;
|
|
717
835
|
return;
|
|
718
836
|
}
|
|
719
837
|
const current = local.manifest.version ?? "0.1.0";
|
|
838
|
+
const sinceTag = lastReleaseTag(root, name);
|
|
839
|
+
const analysis = analyzeCommits(root, local.dir, sinceTag);
|
|
720
840
|
let level;
|
|
721
841
|
if (!levelArg || levelArg === "auto") {
|
|
722
|
-
const sinceTag = lastReleaseTag(root, name);
|
|
723
|
-
const analysis = analyzeCommits(root, local.dir, sinceTag);
|
|
724
842
|
if (!analysis.level) {
|
|
725
843
|
p4.log.info(
|
|
726
844
|
`No commits touching ${path8.relative(root, local.dir)}${sinceTag ? ` since ${sinceTag}` : ""} \u2014 nothing to bump.`
|
|
@@ -738,14 +856,32 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
738
856
|
}
|
|
739
857
|
const next = incrementSemver(current, level);
|
|
740
858
|
const tagName = `${name}@${next}`;
|
|
859
|
+
const subjects = analysis.commits > 0 ? analysis.reasons.map((r) => r.replace(/^(?:major|minor|patch)\s+/, "")) : [];
|
|
741
860
|
if (opts.dryRun) {
|
|
742
|
-
p4.log.info(
|
|
861
|
+
p4.log.info(
|
|
862
|
+
`[dry-run] ${name}: ${current} -> ${pc3.green(next)} (${level})${opts.tag ? ` + tag ${tagName}` : ""} + CHANGELOG.md entry`
|
|
863
|
+
);
|
|
743
864
|
return;
|
|
744
865
|
}
|
|
745
|
-
const manifestPath = path8.join(
|
|
866
|
+
const manifestPath = path8.join(
|
|
867
|
+
local.dir,
|
|
868
|
+
MARKETPLACE_DIR,
|
|
869
|
+
PLUGIN_MANIFEST_FILE
|
|
870
|
+
);
|
|
746
871
|
const manifest = readJson(manifestPath);
|
|
747
872
|
manifest.version = next;
|
|
748
873
|
writeJson(manifestPath, manifest);
|
|
874
|
+
const changelogPath = path8.join(local.dir, "CHANGELOG.md");
|
|
875
|
+
const existingChangelog = fs7.existsSync(changelogPath) ? fs7.readFileSync(changelogPath, "utf8") : void 0;
|
|
876
|
+
const entry = renderChangelogEntry(
|
|
877
|
+
next,
|
|
878
|
+
subjects,
|
|
879
|
+
(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
880
|
+
);
|
|
881
|
+
fs7.writeFileSync(
|
|
882
|
+
changelogPath,
|
|
883
|
+
prependChangelogEntry(existingChangelog, entry)
|
|
884
|
+
);
|
|
749
885
|
await syncCommand(root, { quiet: true });
|
|
750
886
|
p4.log.success(`${name}: ${current} -> ${pc3.green(next)} (${level})`);
|
|
751
887
|
if (opts.tag) {
|
|
@@ -753,17 +889,23 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
753
889
|
git(root, ["add", "-A"]);
|
|
754
890
|
git(root, ["commit", "-m", `chore(release): ${tagName}`]);
|
|
755
891
|
git(root, ["tag", tagName]);
|
|
756
|
-
p4.log.success(
|
|
892
|
+
p4.log.success(
|
|
893
|
+
`Committed and tagged ${pc3.cyan(tagName)} \u2014 push with: git push --follow-tags`
|
|
894
|
+
);
|
|
757
895
|
} catch (err) {
|
|
758
|
-
p4.log.warn(
|
|
896
|
+
p4.log.warn(
|
|
897
|
+
`Version bumped but commit/tag failed: ${err.message}`
|
|
898
|
+
);
|
|
759
899
|
}
|
|
760
900
|
} else {
|
|
761
|
-
p4.log.info(
|
|
901
|
+
p4.log.info(
|
|
902
|
+
"Commit the change, then push. Use --tag to commit and tag in one step."
|
|
903
|
+
);
|
|
762
904
|
}
|
|
763
905
|
}
|
|
764
906
|
|
|
765
907
|
// src/commands/init.ts
|
|
766
|
-
import
|
|
908
|
+
import fs9 from "fs";
|
|
767
909
|
import path10 from "path";
|
|
768
910
|
import * as p5 from "@clack/prompts";
|
|
769
911
|
import pc4 from "picocolors";
|
|
@@ -774,7 +916,10 @@ var claudeCode = {
|
|
|
774
916
|
label: "Claude Code",
|
|
775
917
|
tier: 1,
|
|
776
918
|
install: (arg, name) => ({
|
|
777
|
-
lines: [
|
|
919
|
+
lines: [
|
|
920
|
+
`/plugin marketplace add ${arg}`,
|
|
921
|
+
`/plugin install <plugin-name>@${name}`
|
|
922
|
+
]
|
|
778
923
|
}),
|
|
779
924
|
teamSettings: { path: ".claude/settings.json" }
|
|
780
925
|
};
|
|
@@ -805,7 +950,9 @@ var cursor = {
|
|
|
805
950
|
label: "Cursor",
|
|
806
951
|
tier: 2,
|
|
807
952
|
install: () => ({
|
|
808
|
-
lines: [
|
|
953
|
+
lines: [
|
|
954
|
+
"# add the marketplace in Cursor, then: /plugin install <plugin-name>"
|
|
955
|
+
],
|
|
809
956
|
note: "Requires a committed Cursor registry (.cursor-plugin/). Generate it with `agkit build --target cursor` (planned)."
|
|
810
957
|
})
|
|
811
958
|
};
|
|
@@ -911,6 +1058,12 @@ The catalog lives in \`.claude-plugin/marketplace.json\`; each plugin lives unde
|
|
|
911
1058
|
|
|
912
1059
|
Target agents: ${labels}.
|
|
913
1060
|
|
|
1061
|
+
## Plugins
|
|
1062
|
+
|
|
1063
|
+
${PLUGINS_START}
|
|
1064
|
+
${renderAgentsPluginList([])}
|
|
1065
|
+
${PLUGINS_END}
|
|
1066
|
+
|
|
914
1067
|
## For agents working in this repo
|
|
915
1068
|
|
|
916
1069
|
- Plugins are self-contained under \`plugins/<name>/\` with a \`.claude-plugin/plugin.json\` manifest and \`skills/\`, \`commands/\`, \`agents/\`, \`hooks/\` as needed.
|
|
@@ -921,7 +1074,7 @@ Target agents: ${labels}.
|
|
|
921
1074
|
|
|
922
1075
|
// src/lib/git.ts
|
|
923
1076
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
924
|
-
import
|
|
1077
|
+
import fs8 from "fs";
|
|
925
1078
|
import path9 from "path";
|
|
926
1079
|
function isGitAvailable() {
|
|
927
1080
|
try {
|
|
@@ -932,7 +1085,7 @@ function isGitAvailable() {
|
|
|
932
1085
|
}
|
|
933
1086
|
}
|
|
934
1087
|
function isGitRepo(dir) {
|
|
935
|
-
return
|
|
1088
|
+
return fs8.existsSync(path9.join(dir, ".git"));
|
|
936
1089
|
}
|
|
937
1090
|
function gitInit(dir) {
|
|
938
1091
|
execFileSync3("git", ["init", "--initial-branch=main"], {
|
|
@@ -954,7 +1107,9 @@ function parseRemoteUrl(raw) {
|
|
|
954
1107
|
let host;
|
|
955
1108
|
let repoPath;
|
|
956
1109
|
const scp = raw.match(/^(?:[\w.-]+)@([\w.-]+):(.+?)(?:\.git)?\/?$/);
|
|
957
|
-
const url = raw.match(
|
|
1110
|
+
const url = raw.match(
|
|
1111
|
+
/^(?:https?|ssh|git):\/\/(?:[\w.-]+@)?([\w.-]+(?::\d+)?)\/(.+?)(?:\.git)?\/?$/
|
|
1112
|
+
);
|
|
958
1113
|
if (scp) {
|
|
959
1114
|
host = scp[1];
|
|
960
1115
|
repoPath = scp[2];
|
|
@@ -986,22 +1141,28 @@ function validateName(name) {
|
|
|
986
1141
|
return void 0;
|
|
987
1142
|
}
|
|
988
1143
|
function marketplaceTpl(file) {
|
|
989
|
-
return
|
|
1144
|
+
return fs9.readFileSync(
|
|
1145
|
+
path10.join(templatesDir(), "marketplace", file),
|
|
1146
|
+
"utf8"
|
|
1147
|
+
);
|
|
990
1148
|
}
|
|
991
1149
|
function addArgument(remote) {
|
|
992
1150
|
if (!remote) return "<git-url-or-owner/repo>";
|
|
993
1151
|
return remote.isGitHub && remote.slug ? remote.slug : remote.httpsUrl;
|
|
994
1152
|
}
|
|
995
1153
|
function teamSource(remote) {
|
|
996
|
-
const source = remote?.isGitHub && remote.slug ? { source: "github", repo: remote.slug } : {
|
|
997
|
-
|
|
1154
|
+
const source = remote?.isGitHub && remote.slug ? { source: "github", repo: remote.slug } : {
|
|
1155
|
+
source: "url",
|
|
1156
|
+
url: remote?.httpsUrl ?? "https://<git-host>/<owner>/<repo>.git"
|
|
1157
|
+
};
|
|
1158
|
+
return JSON.stringify(source, null, 2).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
998
1159
|
}
|
|
999
1160
|
async function initCommand(dirArg, opts) {
|
|
1000
1161
|
p5.intro(pc4.bgCyan(pc4.black(" agkit init ")));
|
|
1001
1162
|
const targetDir = path10.resolve(dirArg ?? ".");
|
|
1002
1163
|
const defaultName = path10.basename(targetDir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1003
1164
|
const gitAvailable = isGitAvailable();
|
|
1004
|
-
const existingRemote = gitAvailable &&
|
|
1165
|
+
const existingRemote = gitAvailable && fs9.existsSync(targetDir) ? getOriginUrl(targetDir) : void 0;
|
|
1005
1166
|
let name = opts.name;
|
|
1006
1167
|
let owner = opts.owner;
|
|
1007
1168
|
let email = opts.email;
|
|
@@ -1091,7 +1252,9 @@ async function initCommand(dirArg, opts) {
|
|
|
1091
1252
|
}
|
|
1092
1253
|
owner = owner || "Unknown Owner";
|
|
1093
1254
|
ci = ci ?? "github";
|
|
1094
|
-
const { ids: agentIds, unknown } = parseAgentList(
|
|
1255
|
+
const { ids: agentIds, unknown } = parseAgentList(
|
|
1256
|
+
agentsRaw ?? DEFAULT_AGENTS.join(",")
|
|
1257
|
+
);
|
|
1095
1258
|
if (unknown.length > 0) {
|
|
1096
1259
|
p5.log.warn(
|
|
1097
1260
|
`Unknown agent(s) ignored: ${unknown.join(", ")}. Known: ${AGENT_ADAPTERS.map((a) => a.id).join(", ")}.`
|
|
@@ -1100,11 +1263,13 @@ async function initCommand(dirArg, opts) {
|
|
|
1100
1263
|
const targets = agentIds.length > 0 ? agentIds : [...DEFAULT_AGENTS];
|
|
1101
1264
|
const remote = repoUrl ? parseRemoteUrl(repoUrl) : void 0;
|
|
1102
1265
|
if (repoUrl && !remote) {
|
|
1103
|
-
p5.log.warn(
|
|
1266
|
+
p5.log.warn(
|
|
1267
|
+
`Could not parse remote URL "${repoUrl}"; install instructions will use placeholders.`
|
|
1268
|
+
);
|
|
1104
1269
|
}
|
|
1105
1270
|
const s = p5.spinner();
|
|
1106
1271
|
s.start("Scaffolding marketplace");
|
|
1107
|
-
|
|
1272
|
+
fs9.mkdirSync(targetDir, { recursive: true });
|
|
1108
1273
|
const marketplace = {
|
|
1109
1274
|
$schema: MARKETPLACE_SCHEMA_URL,
|
|
1110
1275
|
name,
|
|
@@ -1117,9 +1282,12 @@ async function initCommand(dirArg, opts) {
|
|
|
1117
1282
|
},
|
|
1118
1283
|
plugins: []
|
|
1119
1284
|
};
|
|
1120
|
-
writeJson(
|
|
1121
|
-
|
|
1122
|
-
|
|
1285
|
+
writeJson(
|
|
1286
|
+
path10.join(targetDir, ".claude-plugin", "marketplace.json"),
|
|
1287
|
+
marketplace
|
|
1288
|
+
);
|
|
1289
|
+
fs9.mkdirSync(path10.join(targetDir, "plugins"), { recursive: true });
|
|
1290
|
+
fs9.writeFileSync(path10.join(targetDir, "plugins", ".gitkeep"), "");
|
|
1123
1291
|
const desc = description || "A curated collection of agent plugins.";
|
|
1124
1292
|
const mktArg = addArgument(remote);
|
|
1125
1293
|
const teamBlock = teamSource(remote);
|
|
@@ -1131,34 +1299,34 @@ async function initCommand(dirArg, opts) {
|
|
|
1131
1299
|
installSections: renderInstallSections(targets, mktArg, name),
|
|
1132
1300
|
teamSections: renderTeamSections(targets, teamBlock, name)
|
|
1133
1301
|
};
|
|
1134
|
-
|
|
1302
|
+
fs9.writeFileSync(
|
|
1135
1303
|
path10.join(targetDir, "README.md"),
|
|
1136
1304
|
renderTemplate(marketplaceTpl("README.md.tpl"), vars)
|
|
1137
1305
|
);
|
|
1138
|
-
|
|
1306
|
+
fs9.writeFileSync(
|
|
1139
1307
|
path10.join(targetDir, "AGENTS.md"),
|
|
1140
1308
|
renderAgentsMd(name, desc, targets)
|
|
1141
1309
|
);
|
|
1142
1310
|
const exampleDir = path10.join(targetDir, "examples");
|
|
1143
|
-
|
|
1144
|
-
|
|
1311
|
+
fs9.mkdirSync(exampleDir, { recursive: true });
|
|
1312
|
+
fs9.writeFileSync(
|
|
1145
1313
|
path10.join(exampleDir, "team-settings.json"),
|
|
1146
1314
|
renderTemplate(marketplaceTpl("team-settings.json.tpl"), vars)
|
|
1147
1315
|
);
|
|
1148
1316
|
if (ci === "github") {
|
|
1149
1317
|
const wfDir = path10.join(targetDir, ".github", "workflows");
|
|
1150
|
-
|
|
1151
|
-
|
|
1318
|
+
fs9.mkdirSync(wfDir, { recursive: true });
|
|
1319
|
+
fs9.writeFileSync(
|
|
1152
1320
|
path10.join(wfDir, "validate.yml"),
|
|
1153
1321
|
renderTemplate(marketplaceTpl("github-validate.yml.tpl"), vars)
|
|
1154
1322
|
);
|
|
1155
1323
|
} else if (ci === "gitlab") {
|
|
1156
|
-
|
|
1324
|
+
fs9.writeFileSync(
|
|
1157
1325
|
path10.join(targetDir, ".gitlab-ci.yml"),
|
|
1158
1326
|
renderTemplate(marketplaceTpl("gitlab-ci.yml.tpl"), vars)
|
|
1159
1327
|
);
|
|
1160
1328
|
}
|
|
1161
|
-
|
|
1329
|
+
fs9.writeFileSync(
|
|
1162
1330
|
path10.join(targetDir, ".gitignore"),
|
|
1163
1331
|
["node_modules/", ".DS_Store", "*.log", ""].join("\n")
|
|
1164
1332
|
);
|
|
@@ -1229,14 +1397,16 @@ async function listCommand() {
|
|
|
1229
1397
|
|
|
1230
1398
|
// src/commands/validate.ts
|
|
1231
1399
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
1232
|
-
import
|
|
1400
|
+
import fs10 from "fs";
|
|
1233
1401
|
import path11 from "path";
|
|
1234
1402
|
import * as p7 from "@clack/prompts";
|
|
1235
1403
|
import pc6 from "picocolors";
|
|
1236
1404
|
async function validateCommand(startDir, opts = {}) {
|
|
1237
1405
|
const root = findMarketplaceRoot(startDir);
|
|
1238
1406
|
if (!root) {
|
|
1239
|
-
p7.log.error(
|
|
1407
|
+
p7.log.error(
|
|
1408
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
1409
|
+
);
|
|
1240
1410
|
process.exitCode = 1;
|
|
1241
1411
|
return;
|
|
1242
1412
|
}
|
|
@@ -1252,7 +1422,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1252
1422
|
}
|
|
1253
1423
|
if (mp) {
|
|
1254
1424
|
if (!mp.name) {
|
|
1255
|
-
findings.push({
|
|
1425
|
+
findings.push({
|
|
1426
|
+
level: "error",
|
|
1427
|
+
message: "Missing required field: name"
|
|
1428
|
+
});
|
|
1256
1429
|
} else {
|
|
1257
1430
|
if (!KEBAB_CASE_RE.test(mp.name)) {
|
|
1258
1431
|
findings.push({
|
|
@@ -1268,7 +1441,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1268
1441
|
}
|
|
1269
1442
|
}
|
|
1270
1443
|
if (!mp.owner?.name) {
|
|
1271
|
-
findings.push({
|
|
1444
|
+
findings.push({
|
|
1445
|
+
level: "error",
|
|
1446
|
+
message: "Missing required field: owner.name"
|
|
1447
|
+
});
|
|
1272
1448
|
}
|
|
1273
1449
|
if (!Array.isArray(mp.plugins)) {
|
|
1274
1450
|
findings.push({ level: "error", message: "plugins must be an array" });
|
|
@@ -1277,7 +1453,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1277
1453
|
for (const entry of mp.plugins ?? []) {
|
|
1278
1454
|
const label = entry.name ?? "<unnamed>";
|
|
1279
1455
|
if (!entry.name) {
|
|
1280
|
-
findings.push({
|
|
1456
|
+
findings.push({
|
|
1457
|
+
level: "error",
|
|
1458
|
+
message: "A plugin entry is missing its name"
|
|
1459
|
+
});
|
|
1281
1460
|
continue;
|
|
1282
1461
|
}
|
|
1283
1462
|
if (!KEBAB_CASE_RE.test(entry.name)) {
|
|
@@ -1287,30 +1466,42 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1287
1466
|
});
|
|
1288
1467
|
}
|
|
1289
1468
|
if (seen.has(entry.name)) {
|
|
1290
|
-
findings.push({
|
|
1469
|
+
findings.push({
|
|
1470
|
+
level: "error",
|
|
1471
|
+
message: `Duplicate plugin name "${label}"`
|
|
1472
|
+
});
|
|
1291
1473
|
}
|
|
1292
1474
|
seen.add(entry.name);
|
|
1293
1475
|
if (entry.source === void 0) {
|
|
1294
|
-
findings.push({
|
|
1476
|
+
findings.push({
|
|
1477
|
+
level: "error",
|
|
1478
|
+
message: `Plugin "${label}" is missing source`
|
|
1479
|
+
});
|
|
1295
1480
|
continue;
|
|
1296
1481
|
}
|
|
1297
1482
|
const localDir = resolveLocalPluginDir(root, mp, entry);
|
|
1298
1483
|
if (localDir) {
|
|
1299
|
-
if (!
|
|
1484
|
+
if (!fs10.existsSync(localDir)) {
|
|
1300
1485
|
findings.push({
|
|
1301
1486
|
level: "error",
|
|
1302
1487
|
message: `Plugin "${label}" source resolves to missing directory ${path11.relative(root, localDir)}`
|
|
1303
1488
|
});
|
|
1304
1489
|
} else {
|
|
1305
|
-
const manifestPath = path11.join(
|
|
1306
|
-
|
|
1490
|
+
const manifestPath = path11.join(
|
|
1491
|
+
localDir,
|
|
1492
|
+
MARKETPLACE_DIR,
|
|
1493
|
+
PLUGIN_MANIFEST_FILE
|
|
1494
|
+
);
|
|
1495
|
+
if (!fs10.existsSync(manifestPath)) {
|
|
1307
1496
|
findings.push({
|
|
1308
1497
|
level: "error",
|
|
1309
1498
|
message: `Plugin "${label}" has no ${MARKETPLACE_DIR}/${PLUGIN_MANIFEST_FILE}`
|
|
1310
1499
|
});
|
|
1311
1500
|
} else {
|
|
1312
1501
|
try {
|
|
1313
|
-
const manifest = JSON.parse(
|
|
1502
|
+
const manifest = JSON.parse(
|
|
1503
|
+
fs10.readFileSync(manifestPath, "utf8")
|
|
1504
|
+
);
|
|
1314
1505
|
if (manifest.name && manifest.name !== entry.name) {
|
|
1315
1506
|
findings.push({
|
|
1316
1507
|
level: "warn",
|
|
@@ -1333,7 +1524,9 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1333
1524
|
}
|
|
1334
1525
|
}
|
|
1335
1526
|
}
|
|
1336
|
-
const hasRelative = (mp.plugins ?? []).some(
|
|
1527
|
+
const hasRelative = (mp.plugins ?? []).some(
|
|
1528
|
+
(e) => typeof e.source === "string"
|
|
1529
|
+
);
|
|
1337
1530
|
if (hasRelative) {
|
|
1338
1531
|
p7.log.info(
|
|
1339
1532
|
"Note: relative plugin sources resolve only when the marketplace is added via Git (any forge), not via a direct URL to marketplace.json."
|
|
@@ -1341,7 +1534,9 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1341
1534
|
}
|
|
1342
1535
|
}
|
|
1343
1536
|
if (mp?.metadata?.targets) {
|
|
1344
|
-
const needBuild = mp.metadata.targets.filter(
|
|
1537
|
+
const needBuild = mp.metadata.targets.filter(
|
|
1538
|
+
(id) => (getAdapter(id)?.tier ?? 1) > 1
|
|
1539
|
+
);
|
|
1345
1540
|
if (needBuild.length > 0) {
|
|
1346
1541
|
p7.log.info(
|
|
1347
1542
|
`metadata.targets includes tier 2/3 agent(s): ${needBuild.join(", ")}. The Claude catalog validates here; run \`agkit build\` to (re)generate their registries and \`agkit build --check\` in CI.`
|
|
@@ -1350,7 +1545,11 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1350
1545
|
}
|
|
1351
1546
|
let officialRan = false;
|
|
1352
1547
|
try {
|
|
1353
|
-
execFileSync4(
|
|
1548
|
+
execFileSync4(
|
|
1549
|
+
"claude",
|
|
1550
|
+
["plugin", "validate", root, ...opts.strict ? ["--strict"] : []],
|
|
1551
|
+
{ stdio: "pipe" }
|
|
1552
|
+
);
|
|
1354
1553
|
officialRan = true;
|
|
1355
1554
|
p7.log.success("claude plugin validate: passed");
|
|
1356
1555
|
} catch (err) {
|
|
@@ -1364,7 +1563,8 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1364
1563
|
const out = [e.stdout?.toString(), e.stderr?.toString()].filter(Boolean).join("\n").trim();
|
|
1365
1564
|
findings.push({
|
|
1366
1565
|
level: "error",
|
|
1367
|
-
message: `claude plugin validate failed${out ?
|
|
1566
|
+
message: `claude plugin validate failed${out ? `:
|
|
1567
|
+
${out}` : ""}`
|
|
1368
1568
|
});
|
|
1369
1569
|
}
|
|
1370
1570
|
}
|
|
@@ -1373,7 +1573,11 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1373
1573
|
for (const f of warns) p7.log.warn(f.message);
|
|
1374
1574
|
for (const f of errors) p7.log.error(f.message);
|
|
1375
1575
|
if (errors.length > 0) {
|
|
1376
|
-
p7.log.error(
|
|
1576
|
+
p7.log.error(
|
|
1577
|
+
pc6.red(
|
|
1578
|
+
`Validation failed: ${errors.length} error(s), ${warns.length} warning(s).`
|
|
1579
|
+
)
|
|
1580
|
+
);
|
|
1377
1581
|
process.exitCode = 1;
|
|
1378
1582
|
} else {
|
|
1379
1583
|
p7.log.success(
|
|
@@ -1392,10 +1596,18 @@ var program = new Command();
|
|
|
1392
1596
|
program.name("agkit").description(
|
|
1393
1597
|
"Scaffold and manage plugin marketplaces for Claude Code and GitHub Copilot, distributed via any Git host."
|
|
1394
1598
|
).version(version);
|
|
1395
|
-
program.command("init").argument("[dir]", "target directory (default: current directory)").description("Scaffold a new plugin marketplace (git-first)").option("-n, --name <name>", "marketplace name (kebab-case)").option("-o, --owner <owner>", "owner name").option("-e, --email <email>", "owner email").option("-d, --description <text>", "marketplace description").option("-r, --repo <url>", "git remote URL (any forge)").option("--ci <ci>", "CI workflow: github | gitlab | none").option(
|
|
1599
|
+
program.command("init").argument("[dir]", "target directory (default: current directory)").description("Scaffold a new plugin marketplace (git-first)").option("-n, --name <name>", "marketplace name (kebab-case)").option("-o, --owner <owner>", "owner name").option("-e, --email <email>", "owner email").option("-d, --description <text>", "marketplace description").option("-r, --repo <url>", "git remote URL (any forge)").option("--ci <ci>", "CI workflow: github | gitlab | none").option(
|
|
1600
|
+
"--agents <list>",
|
|
1601
|
+
"comma-separated target agents (e.g. claude-code,copilot,codex)"
|
|
1602
|
+
).option("-y, --yes", "non-interactive, accept defaults").action(async (dir, opts) => {
|
|
1396
1603
|
await initCommand(dir, opts);
|
|
1397
1604
|
});
|
|
1398
|
-
program.command("add").argument(
|
|
1605
|
+
program.command("add").argument(
|
|
1606
|
+
"[template]",
|
|
1607
|
+
"template name, local path, gh:/gl: shorthand, or git URL (see `agkit list`)"
|
|
1608
|
+
).argument("[name]", "plugin name (kebab-case)").description(
|
|
1609
|
+
"Scaffold a plugin from a template and register it in the catalog"
|
|
1610
|
+
).option("-d, --description <text>", "one-line plugin description").action(async (template, name, opts) => {
|
|
1399
1611
|
await addPlugin(process.cwd(), template, name, {
|
|
1400
1612
|
description: opts.description,
|
|
1401
1613
|
interactive: opts.description === void 0
|
|
@@ -1404,14 +1616,38 @@ program.command("add").argument("[template]", "template name, local path, gh:/gl
|
|
|
1404
1616
|
program.command("sync").description("Reconcile marketplace.json and README with the plugins on disk").action(async () => {
|
|
1405
1617
|
await syncCommand(process.cwd());
|
|
1406
1618
|
});
|
|
1407
|
-
program.command("validate").description(
|
|
1619
|
+
program.command("validate").description(
|
|
1620
|
+
"Validate the catalog (local checks + `claude plugin validate` if available)"
|
|
1621
|
+
).option("--strict", "treat warnings as errors in the official validator").action(async (opts) => {
|
|
1408
1622
|
await validateCommand(process.cwd(), { strict: opts.strict });
|
|
1409
1623
|
});
|
|
1410
|
-
program.command("bump").argument("[plugin]", "plugin name (prompted if omitted)").argument(
|
|
1411
|
-
|
|
1624
|
+
program.command("bump").argument("[plugin]", "plugin name (prompted if omitted)").argument(
|
|
1625
|
+
"[level]",
|
|
1626
|
+
"major | minor | patch | auto (default: auto, from conventional commits)"
|
|
1627
|
+
).description(
|
|
1628
|
+
"Bump a plugin version (conventional-commit aware) and sync the catalog"
|
|
1629
|
+
).option(
|
|
1630
|
+
"-t, --tag",
|
|
1631
|
+
"commit the bump and create a release tag <plugin>@<version>"
|
|
1632
|
+
).option("--dry-run", "show what would change without writing").action(async (plugin, level, opts) => {
|
|
1633
|
+
await bumpCommand(process.cwd(), plugin, level, {
|
|
1634
|
+
tag: opts.tag,
|
|
1635
|
+
dryRun: opts.dryRun
|
|
1636
|
+
});
|
|
1412
1637
|
});
|
|
1413
|
-
program.command("build").description(
|
|
1414
|
-
|
|
1638
|
+
program.command("build").description(
|
|
1639
|
+
"Generate tier-2 agent artifacts (codex, cursor) from the catalog"
|
|
1640
|
+
).option(
|
|
1641
|
+
"--target <list>",
|
|
1642
|
+
"comma-separated tier-2 targets (default: those in metadata.targets)"
|
|
1643
|
+
).option(
|
|
1644
|
+
"--check",
|
|
1645
|
+
"verify generated artifacts are up to date (CI); non-zero exit on drift"
|
|
1646
|
+
).action(async (opts) => {
|
|
1647
|
+
await buildCommand(process.cwd(), {
|
|
1648
|
+
targets: opts.target,
|
|
1649
|
+
check: opts.check
|
|
1650
|
+
});
|
|
1415
1651
|
});
|
|
1416
1652
|
program.command("list").description("List available plugin templates").action(async () => {
|
|
1417
1653
|
await listCommand();
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Scaffold and manage plugin marketplaces for Claude Code and GitHub Copilot, distributed via any Git host (GitHub, GitLab, Bitbucket, Gitea, self-hosted). Init, add plugins from built-in or remote templates, sync, validate, bump.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": "fair3n <fairen.dev@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Fairen/agkit.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Fairen/agkit#readme",
|
|
12
|
+
"bugs": "https://github.com/Fairen/agkit/issues",
|
|
6
13
|
"type": "module",
|
|
7
14
|
"bin": {
|
|
8
15
|
"agkit": "./dist/index.js"
|
|
@@ -10,7 +17,8 @@
|
|
|
10
17
|
"files": [
|
|
11
18
|
"dist",
|
|
12
19
|
"templates",
|
|
13
|
-
"README.md"
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
14
22
|
],
|
|
15
23
|
"engines": {
|
|
16
24
|
"node": ">=18.0.0"
|
|
@@ -19,6 +27,11 @@
|
|
|
19
27
|
"build": "tsup",
|
|
20
28
|
"dev": "tsup --watch",
|
|
21
29
|
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "biome check",
|
|
31
|
+
"lint:fix": "biome check --write",
|
|
32
|
+
"format": "biome format --write",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
22
35
|
"prepublishOnly": "npm run build"
|
|
23
36
|
},
|
|
24
37
|
"keywords": [
|
|
@@ -39,8 +52,10 @@
|
|
|
39
52
|
"picocolors": "^1.1.0"
|
|
40
53
|
},
|
|
41
54
|
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "^2.5.2",
|
|
42
56
|
"@types/node": "^22.0.0",
|
|
43
57
|
"tsup": "^8.0.0",
|
|
44
|
-
"typescript": "^5.6.0"
|
|
58
|
+
"typescript": "^5.6.0",
|
|
59
|
+
"vitest": "^4.1.9"
|
|
45
60
|
}
|
|
46
61
|
}
|