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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -38
  3. package/dist/index.js +398 -162
  4. 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
- # agkit
1
+ <div align="center">
2
2
 
3
- > **AG**ent marketplace **KIT**
3
+ # **AG**ent marketplace **KIT**
4
4
 
5
- Scaffold and manage **plugin marketplaces for Claude Code, GitHub Copilot, OpenAI Codex, and Cursor**, distributed through **any Git host** — GitHub, GitLab, Bitbucket, Gitea, or a self-hosted forge.
5
+ **One canonical catalog every AI coding agent any Git forge.**
6
6
 
7
- 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.
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
+ [![npm version](https://img.shields.io/npm/v/agkit?color=cb3837&logo=npm&label=agkit)](https://www.npmjs.com/package/agkit)
11
+ [![npm downloads](https://img.shields.io/npm/dm/agkit?color=cb3837&logo=npm)](https://www.npmjs.com/package/agkit)
12
+ [![CI](https://github.com/Fairen/agkit/actions/workflows/ci.yml/badge.svg)](https://github.com/Fairen/agkit/actions/workflows/ci.yml)
13
+ [![Node.js](https://img.shields.io/node/v/agkit?color=339933&logo=node.js&logoColor=white)](https://nodejs.org)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ## How agents consume your marketplace
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
- 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:
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
- - **Native (no build step)** — **Claude Code** and **GitHub Copilot** read `.claude-plugin/marketplace.json` directly. Push the repo and it installs.
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
- You can target several agents from a single repository — the plugin content under `plugins/` is shared.
49
+ ## 🚀 Quick start
19
50
 
20
- ## Quick start
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
- The default init targets Claude Code and GitHub Copilot (both native). To target Codex or Cursor, see below.
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/&lt;name&gt;<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
- ## Create a marketplace for your agent
90
+ Choose targets at init with `--agents`. Every flow ends with a normal `git push`; distribution works from any forge.
34
91
 
35
- Choose targets at init with `--agents`. Every flow ends with a normal git push; distribution works from any forge.
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
- ### Claude Code
99
+ <details open>
100
+ <summary><b>🤖 Claude Code</b> — native, nothing to generate</summary>
38
101
 
39
- Native — nothing to generate.
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
- ### GitHub Copilot
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
- Native — same repository as Claude Code, no build. The default init already includes Copilot; to target it explicitly use `--agents copilot` (or `--agents claude-code,copilot`).
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
- ### OpenAI Codex
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
- ### Cursor
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
- ### Several agents at once
190
+ </details>
191
+
192
+ <details>
193
+ <summary><b>🌈 Several agents at once</b> — one repository serves them all</summary>
113
194
 
114
- One repository can serve every supported agent:
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
- ## Commands
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 [Target agents](#target-agents)). Non-interactive with `-y`. |
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 plugin table. |
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
- ## Target agents
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
- ## Keeping registries fresh
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
- ## Team templates (remote or local)
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
- ## Forge-agnostic by design
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
- ## Team setup (automatic installation)
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
- ## License
278
+ ---
279
+
280
+ ## 📄 License
281
+
282
+ [MIT](LICENSE) © fair3n
173
283
 
174
- MIT
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 = ["skill", "command", "agent", "hook", "mcp"];
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) + "\n");
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 fs2 from "fs";
153
+ import fs3 from "fs";
93
154
  import os from "os";
94
- import path2 from "path";
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 = fs2.existsSync(path2.join(dir, ".claude-plugin", "plugin.json.tpl")) || fs2.existsSync(path2.join(dir, ".claude-plugin", "plugin.json"));
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: path2.join(templatesDir(), "plugins", spec),
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("../") || path2.isAbsolute(spec) ? spec : void 0;
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 = path2.resolve(localPath);
144
- if (!fs2.existsSync(dir2)) throw new Error(`Template directory not found: ${dir2}`);
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 = fs2.mkdtempSync(path2.join(os.tmpdir(), "agkit-tpl-"));
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
- fs2.rmSync(tmp, { recursive: true, force: true });
223
+ fs3.rmSync(tmp, { recursive: true, force: true });
162
224
  const msg = err.stderr?.toString().trim();
163
- throw new Error(`git clone failed for ${git2.cloneUrl}${msg ? `:
164
- ${msg}` : ""}`);
225
+ throw new Error(
226
+ `git clone failed for ${git2.cloneUrl}${msg ? `:
227
+ ${msg}` : ""}`
228
+ );
165
229
  }
166
- const dir = git2.subdir ? path2.join(tmp, git2.subdir) : tmp;
167
- if (!fs2.existsSync(dir)) {
168
- fs2.rmSync(tmp, { recursive: true, force: true });
169
- throw new Error(`Subdirectory "${git2.subdir}" not found in ${git2.cloneUrl}`);
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
- fs2.rmSync(path2.join(tmp, ".git"), { recursive: true, force: true });
237
+ fs3.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
172
238
  try {
173
239
  assertTemplateDir(dir, spec);
174
240
  } catch (err) {
175
- fs2.rmSync(tmp, { recursive: true, force: true });
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/lib/marketplace.ts
182
- import fs3 from "fs";
183
- import path3 from "path";
184
- function marketplacePath(root) {
185
- return path3.join(root, MARKETPLACE_DIR, MARKETPLACE_FILE);
186
- }
187
- function findMarketplaceRoot(startDir) {
188
- let dir = path3.resolve(startDir);
189
- for (; ; ) {
190
- if (fs3.existsSync(marketplacePath(dir))) return dir;
191
- const parent = path3.dirname(dir);
192
- if (parent === dir) return void 0;
193
- dir = parent;
194
- }
195
- }
196
- function readMarketplace(root) {
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
- function writeMarketplace(root, data) {
200
- writeJson(marketplacePath(root), data);
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 pluginRootDir(root, mp) {
203
- const rel = mp.metadata?.pluginRoot ?? DEFAULT_PLUGIN_ROOT;
204
- return path3.resolve(root, rel);
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 resolveLocalPluginDir(root, mp, entry) {
207
- if (typeof entry.source !== "string") return void 0;
208
- if (entry.source.startsWith("./") || entry.source.startsWith("../")) {
209
- return path3.resolve(root, entry.source);
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 scanLocalPlugins(root, mp) {
214
- const base = pluginRootDir(root, mp);
215
- if (!fs3.existsSync(base)) return [];
216
- const result = [];
217
- for (const entry of fs3.readdirSync(base, { withFileTypes: true })) {
218
- if (!entry.isDirectory()) continue;
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
- return result;
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
- // src/commands/sync.ts
236
- import fs5 from "fs";
237
- import path6 from "path";
238
- import * as p2 from "@clack/prompts";
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) + "\n";
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
- { relPath: path4.posix.join(".cursor-plugin", "marketplace.json"), content: json(registry) }
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(pluginRootRel(mp), p8.dirName, ".cursor-plugin", "plugin.json"),
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(pluginRootRel(mp), p8.dirName, ".codex-plugin", "plugin.json"),
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("No .claude-plugin/marketplace.json found. Run `agkit init` first.");
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
- var README_START = "<!-- agkit:plugins:start -->";
424
- var README_END = "<!-- agkit:plugins:end -->";
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("No .claude-plugin/marketplace.json found. Run `agkit init` first.");
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: mp.metadata?.pluginRoot ? local.dirName : `./${path6.relative(root, local.dir).split(path6.sep).join("/")}`
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 ["version", "description", "author", "keywords", "homepage"]) {
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) changes.push(`~ refreshed ${id} tier-2 artifacts`);
467
- const readmePath = path6.join(root, "README.md");
468
- if (fs5.existsSync(readmePath)) {
469
- const readme = fs5.readFileSync(readmePath, "utf8");
470
- const start = readme.indexOf(README_START);
471
- const end = readme.indexOf(README_END);
472
- if (start !== -1 && end !== -1 && end > start) {
473
- const table = mp.plugins.length === 0 ? "_No plugins yet. Add one with `agkit add <template> <name>`._" : [
474
- "| Plugin | Version | Description |",
475
- "| :----- | :------ | :---------- |",
476
- ...mp.plugins.map(
477
- (e) => `| \`${e.name}\` | ${e.version ?? "\u2014"} | ${e.description ?? ""} |`
478
- )
479
- ].join("\n");
480
- const updated = readme.slice(0, start + README_START.length) + "\n" + table + "\n" + readme.slice(end);
481
- if (updated !== readme) {
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) fs6.rmSync(resolved.cleanup, { recursive: true, force: true });
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 = mp.metadata?.pluginRoot ? name : `./${path7.relative(root, destDir).split(path7.sep).join("/")}`;
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) return { level: void 0, commits: 0, reasons: [] };
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("No .claude-plugin/marketplace.json found. Run `agkit init` first.");
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(`No local plugin named "${name}". Known: ${locals.map((l) => l.manifest.name || l.dirName).join(", ")}`);
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(`Invalid level "${levelArg}". Use major, minor, patch, or auto.`);
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(`[dry-run] ${name}: ${current} -> ${pc3.green(next)} (${level})${opts.tag ? ` + tag ${tagName}` : ""}`);
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(local.dir, MARKETPLACE_DIR, PLUGIN_MANIFEST_FILE);
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(`Committed and tagged ${pc3.cyan(tagName)} \u2014 push with: git push --follow-tags`);
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(`Version bumped but commit/tag failed: ${err.message}`);
896
+ p4.log.warn(
897
+ `Version bumped but commit/tag failed: ${err.message}`
898
+ );
759
899
  }
760
900
  } else {
761
- p4.log.info("Commit the change, then push. Use --tag to commit and tag in one step.");
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 fs8 from "fs";
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: [`/plugin marketplace add ${arg}`, `/plugin install <plugin-name>@${name}`]
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: ["# add the marketplace in Cursor, then: /plugin install <plugin-name>"],
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 fs7 from "fs";
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 fs7.existsSync(path9.join(dir, ".git"));
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(/^(?:https?|ssh|git):\/\/(?:[\w.-]+@)?([\w.-]+(?::\d+)?)\/(.+?)(?:\.git)?\/?$/);
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 fs8.readFileSync(path10.join(templatesDir(), "marketplace", file), "utf8");
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 } : { source: "url", url: remote?.httpsUrl ?? "https://<git-host>/<owner>/<repo>.git" };
997
- return JSON.stringify(source, null, 2).split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
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 && fs8.existsSync(targetDir) ? getOriginUrl(targetDir) : void 0;
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(agentsRaw ?? DEFAULT_AGENTS.join(","));
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(`Could not parse remote URL "${repoUrl}"; install instructions will use placeholders.`);
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
- fs8.mkdirSync(targetDir, { recursive: true });
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(path10.join(targetDir, ".claude-plugin", "marketplace.json"), marketplace);
1121
- fs8.mkdirSync(path10.join(targetDir, "plugins"), { recursive: true });
1122
- fs8.writeFileSync(path10.join(targetDir, "plugins", ".gitkeep"), "");
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
- fs8.writeFileSync(
1302
+ fs9.writeFileSync(
1135
1303
  path10.join(targetDir, "README.md"),
1136
1304
  renderTemplate(marketplaceTpl("README.md.tpl"), vars)
1137
1305
  );
1138
- fs8.writeFileSync(
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
- fs8.mkdirSync(exampleDir, { recursive: true });
1144
- fs8.writeFileSync(
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
- fs8.mkdirSync(wfDir, { recursive: true });
1151
- fs8.writeFileSync(
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
- fs8.writeFileSync(
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
- fs8.writeFileSync(
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 fs9 from "fs";
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("No .claude-plugin/marketplace.json found. Run `agkit init` first.");
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({ level: "error", message: "Missing required field: name" });
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({ level: "error", message: "Missing required field: owner.name" });
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({ level: "error", message: "A plugin entry is missing its name" });
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({ level: "error", message: `Duplicate plugin name "${label}"` });
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({ level: "error", message: `Plugin "${label}" is missing source` });
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 (!fs9.existsSync(localDir)) {
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(localDir, MARKETPLACE_DIR, PLUGIN_MANIFEST_FILE);
1306
- if (!fs9.existsSync(manifestPath)) {
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(fs9.readFileSync(manifestPath, "utf8"));
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((e) => typeof e.source === "string");
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((id) => (getAdapter(id)?.tier ?? 1) > 1);
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("claude", ["plugin", "validate", root, ...opts.strict ? ["--strict"] : []], { stdio: "pipe" });
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 ? ":\n" + 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(pc6.red(`Validation failed: ${errors.length} error(s), ${warns.length} warning(s).`));
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("--agents <list>", "comma-separated target agents (e.g. claude-code,copilot,codex)").option("-y, --yes", "non-interactive, accept defaults").action(async (dir, opts) => {
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("[template]", "template name, local path, gh:/gl: shorthand, or git URL (see `agkit list`)").argument("[name]", "plugin name (kebab-case)").description("Scaffold a plugin from a template and register it in the catalog").option("-d, --description <text>", "one-line plugin description").action(async (template, name, opts) => {
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("Validate the catalog (local checks + `claude plugin validate` if available)").option("--strict", "treat warnings as errors in the official validator").action(async (opts) => {
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("[level]", "major | minor | patch | auto (default: auto, from conventional commits)").description("Bump a plugin version (conventional-commit aware) and sync the catalog").option("-t, --tag", "commit the bump and create a release tag <plugin>@<version>").option("--dry-run", "show what would change without writing").action(async (plugin, level, opts) => {
1411
- await bumpCommand(process.cwd(), plugin, level, { tag: opts.tag, dryRun: opts.dryRun });
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("Generate tier-2 agent artifacts (codex, cursor) from the catalog").option("--target <list>", "comma-separated tier-2 targets (default: those in metadata.targets)").option("--check", "verify generated artifacts are up to date (CI); non-zero exit on drift").action(async (opts) => {
1414
- await buildCommand(process.cwd(), { targets: opts.target, check: opts.check });
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.7.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
  }