agkit 0.7.0 → 0.9.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 +231 -43
- package/dist/index.js +656 -170
- package/package.json +19 -4
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;
|
|
@@ -122,8 +183,75 @@ function parseGitSpec(spec) {
|
|
|
122
183
|
}
|
|
123
184
|
return void 0;
|
|
124
185
|
}
|
|
186
|
+
function isRemoteSpec(spec) {
|
|
187
|
+
if (PLUGIN_TEMPLATES.includes(spec)) return false;
|
|
188
|
+
return !(spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("path:") || path3.isAbsolute(spec));
|
|
189
|
+
}
|
|
190
|
+
function resolveRemoteSource(spec, opts = {}) {
|
|
191
|
+
if (spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("path:") || path3.isAbsolute(spec)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`--link needs a remote git source, but "${spec}" is a local path. Drop --link to scaffold from a local template.`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (PLUGIN_TEMPLATES.includes(spec)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`--link needs a remote git source, but "${spec}" is a built-in template. Drop --link to scaffold it locally.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (opts.sha && !/^[0-9a-f]{40}$/i.test(opts.sha)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`--sha must be a full 40-character commit hash: "${opts.sha}"`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
let body = spec;
|
|
207
|
+
let hashRef;
|
|
208
|
+
const hash = body.lastIndexOf("#");
|
|
209
|
+
if (hash > 0) {
|
|
210
|
+
hashRef = body.slice(hash + 1) || void 0;
|
|
211
|
+
body = body.slice(0, hash);
|
|
212
|
+
}
|
|
213
|
+
const git2 = parseGitSpec(spec);
|
|
214
|
+
const ref = opts.ref ?? git2?.ref ?? hashRef;
|
|
215
|
+
const pin = (base) => {
|
|
216
|
+
if (ref) base.ref = ref;
|
|
217
|
+
if (opts.sha) base.sha = opts.sha;
|
|
218
|
+
return base;
|
|
219
|
+
};
|
|
220
|
+
if (git2) {
|
|
221
|
+
if (git2.subdir) {
|
|
222
|
+
return {
|
|
223
|
+
source: pin({
|
|
224
|
+
source: "git-subdir",
|
|
225
|
+
url: git2.cloneUrl,
|
|
226
|
+
path: git2.subdir
|
|
227
|
+
}),
|
|
228
|
+
label: spec
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const gh = git2.cloneUrl.match(
|
|
232
|
+
/^https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/
|
|
233
|
+
);
|
|
234
|
+
if (gh) {
|
|
235
|
+
return {
|
|
236
|
+
source: pin({ source: "github", repo: `${gh[1]}/${gh[2]}` }),
|
|
237
|
+
label: spec
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { source: pin({ source: "url", url: git2.cloneUrl }), label: spec };
|
|
241
|
+
}
|
|
242
|
+
const bare = body.match(/^([\w][\w.-]*)\/([\w][\w.-]*)$/);
|
|
243
|
+
if (bare) {
|
|
244
|
+
return {
|
|
245
|
+
source: pin({ source: "github", repo: `${bare[1]}/${bare[2]}` }),
|
|
246
|
+
label: spec
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Unrecognized remote source "${spec}". Use owner/repo, gh:owner/repo[/dir], gl:owner/repo[/dir], or a git URL[//dir][#ref].`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
125
253
|
function assertTemplateDir(dir, label) {
|
|
126
|
-
const hasManifest =
|
|
254
|
+
const hasManifest = fs3.existsSync(path3.join(dir, ".claude-plugin", "plugin.json.tpl")) || fs3.existsSync(path3.join(dir, ".claude-plugin", "plugin.json"));
|
|
127
255
|
if (!hasManifest) {
|
|
128
256
|
throw new Error(
|
|
129
257
|
`"${label}" is not a plugin template: expected .claude-plugin/plugin.json(.tpl) in ${dir}`
|
|
@@ -133,15 +261,16 @@ function assertTemplateDir(dir, label) {
|
|
|
133
261
|
function resolveTemplate(spec) {
|
|
134
262
|
if (PLUGIN_TEMPLATES.includes(spec)) {
|
|
135
263
|
return {
|
|
136
|
-
dir:
|
|
264
|
+
dir: path3.join(templatesDir(), "plugins", spec),
|
|
137
265
|
label: `built-in:${spec}`,
|
|
138
266
|
builtin: true
|
|
139
267
|
};
|
|
140
268
|
}
|
|
141
|
-
const localPath = spec.startsWith("path:") ? spec.slice(5) : spec.startsWith("./") || spec.startsWith("../") ||
|
|
269
|
+
const localPath = spec.startsWith("path:") ? spec.slice(5) : spec.startsWith("./") || spec.startsWith("../") || path3.isAbsolute(spec) ? spec : void 0;
|
|
142
270
|
if (localPath !== void 0) {
|
|
143
|
-
const dir2 =
|
|
144
|
-
if (!
|
|
271
|
+
const dir2 = path3.resolve(localPath);
|
|
272
|
+
if (!fs3.existsSync(dir2))
|
|
273
|
+
throw new Error(`Template directory not found: ${dir2}`);
|
|
145
274
|
assertTemplateDir(dir2, spec);
|
|
146
275
|
return { dir: dir2, label: spec, builtin: false };
|
|
147
276
|
}
|
|
@@ -151,91 +280,106 @@ function resolveTemplate(spec) {
|
|
|
151
280
|
`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
281
|
);
|
|
153
282
|
}
|
|
154
|
-
const tmp =
|
|
283
|
+
const tmp = fs3.mkdtempSync(path3.join(os.tmpdir(), "agkit-tpl-"));
|
|
155
284
|
const args = ["clone", "--depth", "1"];
|
|
156
285
|
if (git2.ref) args.push("--branch", git2.ref);
|
|
157
286
|
args.push(git2.cloneUrl, tmp);
|
|
158
287
|
try {
|
|
159
288
|
execFileSync("git", args, { stdio: "pipe" });
|
|
160
289
|
} catch (err) {
|
|
161
|
-
|
|
290
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
162
291
|
const msg = err.stderr?.toString().trim();
|
|
163
|
-
throw new Error(
|
|
164
|
-
${msg
|
|
292
|
+
throw new Error(
|
|
293
|
+
`git clone failed for ${git2.cloneUrl}${msg ? `:
|
|
294
|
+
${msg}` : ""}`
|
|
295
|
+
);
|
|
165
296
|
}
|
|
166
|
-
const dir = git2.subdir ?
|
|
167
|
-
if (!
|
|
168
|
-
|
|
169
|
-
throw new Error(
|
|
297
|
+
const dir = git2.subdir ? path3.join(tmp, git2.subdir) : tmp;
|
|
298
|
+
if (!fs3.existsSync(dir)) {
|
|
299
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Subdirectory "${git2.subdir}" not found in ${git2.cloneUrl}`
|
|
302
|
+
);
|
|
170
303
|
}
|
|
171
|
-
|
|
304
|
+
fs3.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
|
|
172
305
|
try {
|
|
173
306
|
assertTemplateDir(dir, spec);
|
|
174
307
|
} catch (err) {
|
|
175
|
-
|
|
308
|
+
fs3.rmSync(tmp, { recursive: true, force: true });
|
|
176
309
|
throw err;
|
|
177
310
|
}
|
|
178
311
|
return { dir, label: spec, cleanup: tmp, builtin: false };
|
|
179
312
|
}
|
|
180
313
|
|
|
181
|
-
// src/
|
|
182
|
-
import
|
|
183
|
-
import
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return readJson(marketplacePath(root));
|
|
314
|
+
// src/commands/sync.ts
|
|
315
|
+
import fs5 from "fs";
|
|
316
|
+
import path6 from "path";
|
|
317
|
+
import * as p2 from "@clack/prompts";
|
|
318
|
+
|
|
319
|
+
// src/lib/docs.ts
|
|
320
|
+
var PLUGINS_START = "<!-- agkit:plugins:start -->";
|
|
321
|
+
var PLUGINS_END = "<!-- agkit:plugins:end -->";
|
|
322
|
+
var CHANGELOG_HEADER = "# Changelog";
|
|
323
|
+
function replaceBetweenMarkers(content, start, end, body) {
|
|
324
|
+
const s = content.indexOf(start);
|
|
325
|
+
const e = content.indexOf(end);
|
|
326
|
+
if (s === -1 || e === -1 || e < s) return void 0;
|
|
327
|
+
return `${content.slice(0, s + start.length)}
|
|
328
|
+
${body}
|
|
329
|
+
${content.slice(e)}`;
|
|
198
330
|
}
|
|
199
|
-
|
|
200
|
-
|
|
331
|
+
var EMPTY_HINT = "_No plugins yet. Add one with `agkit add <template> <name>`._";
|
|
332
|
+
function renderPluginTable(plugins) {
|
|
333
|
+
if (plugins.length === 0) return EMPTY_HINT;
|
|
334
|
+
const withKeywords = plugins.some((p8) => (p8.keywords?.length ?? 0) > 0);
|
|
335
|
+
const header = withKeywords ? [
|
|
336
|
+
"| Plugin | Version | Description | Keywords |",
|
|
337
|
+
"| :----- | :------ | :---------- | :------- |"
|
|
338
|
+
] : [
|
|
339
|
+
"| Plugin | Version | Description |",
|
|
340
|
+
"| :----- | :------ | :---------- |"
|
|
341
|
+
];
|
|
342
|
+
const rows = plugins.map((e) => {
|
|
343
|
+
const name = e.homepage ? `[\`${e.name}\`](${e.homepage})` : `\`${e.name}\``;
|
|
344
|
+
const cells = [name, e.version ?? "\u2014", e.description ?? ""];
|
|
345
|
+
if (withKeywords) {
|
|
346
|
+
cells.push((e.keywords ?? []).map((k) => `\`${k}\``).join(" "));
|
|
347
|
+
}
|
|
348
|
+
return `| ${cells.join(" | ")} |`;
|
|
349
|
+
});
|
|
350
|
+
return [...header, ...rows].join("\n");
|
|
201
351
|
}
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
return
|
|
352
|
+
function renderAgentsPluginList(plugins) {
|
|
353
|
+
if (plugins.length === 0) return "_No plugins yet._";
|
|
354
|
+
return plugins.map((e) => {
|
|
355
|
+
const version2 = e.version ? ` (${e.version})` : "";
|
|
356
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
357
|
+
return `- \`${e.name}\`${version2}${desc}`;
|
|
358
|
+
}).join("\n");
|
|
205
359
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return path3.join(pluginRootDir(root, mp), entry.source);
|
|
360
|
+
function renderChangelogEntry(version2, subjects, date) {
|
|
361
|
+
const bullets = subjects.length > 0 ? subjects.map((s) => `- ${s}`).join("\n") : `- Release ${version2}`;
|
|
362
|
+
return `## ${version2} \u2014 ${date}
|
|
363
|
+
|
|
364
|
+
${bullets}`;
|
|
212
365
|
}
|
|
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
|
-
});
|
|
366
|
+
function prependChangelogEntry(existing, entry) {
|
|
367
|
+
if (!existing?.includes(CHANGELOG_HEADER)) {
|
|
368
|
+
return `${CHANGELOG_HEADER}
|
|
369
|
+
|
|
370
|
+
${entry}
|
|
371
|
+
`;
|
|
231
372
|
}
|
|
232
|
-
|
|
233
|
-
|
|
373
|
+
const idx = existing.indexOf(CHANGELOG_HEADER) + CHANGELOG_HEADER.length;
|
|
374
|
+
const before = existing.slice(0, idx);
|
|
375
|
+
const after = existing.slice(idx).replace(/^\n+/, "");
|
|
376
|
+
return `${`${before}
|
|
234
377
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
378
|
+
${entry}
|
|
379
|
+
|
|
380
|
+
${after}`.trimEnd()}
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
239
383
|
|
|
240
384
|
// src/commands/build.ts
|
|
241
385
|
import fs4 from "fs";
|
|
@@ -253,7 +397,8 @@ function sourcePath(mp, dirName) {
|
|
|
253
397
|
return `./${path4.posix.join(pluginRootRel(mp), dirName)}`;
|
|
254
398
|
}
|
|
255
399
|
function json(value) {
|
|
256
|
-
return JSON.stringify(value, null, 2)
|
|
400
|
+
return `${JSON.stringify(value, null, 2)}
|
|
401
|
+
`;
|
|
257
402
|
}
|
|
258
403
|
function manifestCopy(local) {
|
|
259
404
|
return json(local.manifest);
|
|
@@ -275,11 +420,19 @@ function generateCursor(ctx) {
|
|
|
275
420
|
}))
|
|
276
421
|
};
|
|
277
422
|
const files = [
|
|
278
|
-
{
|
|
423
|
+
{
|
|
424
|
+
relPath: path4.posix.join(".cursor-plugin", "marketplace.json"),
|
|
425
|
+
content: json(registry)
|
|
426
|
+
}
|
|
279
427
|
];
|
|
280
428
|
for (const p8 of plugins) {
|
|
281
429
|
files.push({
|
|
282
|
-
relPath: path4.posix.join(
|
|
430
|
+
relPath: path4.posix.join(
|
|
431
|
+
pluginRootRel(mp),
|
|
432
|
+
p8.dirName,
|
|
433
|
+
".cursor-plugin",
|
|
434
|
+
"plugin.json"
|
|
435
|
+
),
|
|
283
436
|
content: manifestCopy(p8)
|
|
284
437
|
});
|
|
285
438
|
}
|
|
@@ -304,7 +457,12 @@ function generateCodex(ctx) {
|
|
|
304
457
|
];
|
|
305
458
|
for (const p8 of plugins) {
|
|
306
459
|
files.push({
|
|
307
|
-
relPath: path4.posix.join(
|
|
460
|
+
relPath: path4.posix.join(
|
|
461
|
+
pluginRootRel(mp),
|
|
462
|
+
p8.dirName,
|
|
463
|
+
".codex-plugin",
|
|
464
|
+
"plugin.json"
|
|
465
|
+
),
|
|
308
466
|
content: manifestCopy(p8)
|
|
309
467
|
});
|
|
310
468
|
}
|
|
@@ -348,7 +506,9 @@ function computeFiles(root, mp, ids) {
|
|
|
348
506
|
async function buildCommand(startDir, opts = {}) {
|
|
349
507
|
const root = findMarketplaceRoot(startDir);
|
|
350
508
|
if (!root) {
|
|
351
|
-
p.log.error(
|
|
509
|
+
p.log.error(
|
|
510
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
511
|
+
);
|
|
352
512
|
process.exitCode = 1;
|
|
353
513
|
return;
|
|
354
514
|
}
|
|
@@ -420,12 +580,27 @@ function refreshBuiltTargets(root, mp) {
|
|
|
420
580
|
}
|
|
421
581
|
|
|
422
582
|
// src/commands/sync.ts
|
|
423
|
-
|
|
424
|
-
|
|
583
|
+
function refreshMarkedDoc(root, relPath, body, label, changes) {
|
|
584
|
+
const abs = path6.join(root, relPath);
|
|
585
|
+
if (!fs5.existsSync(abs)) return;
|
|
586
|
+
const content = fs5.readFileSync(abs, "utf8");
|
|
587
|
+
const updated = replaceBetweenMarkers(
|
|
588
|
+
content,
|
|
589
|
+
PLUGINS_START,
|
|
590
|
+
PLUGINS_END,
|
|
591
|
+
body
|
|
592
|
+
);
|
|
593
|
+
if (updated !== void 0 && updated !== content) {
|
|
594
|
+
fs5.writeFileSync(abs, updated);
|
|
595
|
+
changes.push(`~ refreshed ${label}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
425
598
|
async function syncCommand(startDir, opts = {}) {
|
|
426
599
|
const root = findMarketplaceRoot(startDir);
|
|
427
600
|
if (!root) {
|
|
428
|
-
p2.log.error(
|
|
601
|
+
p2.log.error(
|
|
602
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
603
|
+
);
|
|
429
604
|
process.exitCode = 1;
|
|
430
605
|
return;
|
|
431
606
|
}
|
|
@@ -439,12 +614,18 @@ async function syncCommand(startDir, opts = {}) {
|
|
|
439
614
|
if (!entry) {
|
|
440
615
|
entry = {
|
|
441
616
|
name: entryName,
|
|
442
|
-
source:
|
|
617
|
+
source: `./${path6.relative(root, local.dir).split(path6.sep).join("/")}`
|
|
443
618
|
};
|
|
444
619
|
mp.plugins.push(entry);
|
|
445
620
|
changes.push(`+ added "${entryName}" to the catalog`);
|
|
446
621
|
}
|
|
447
|
-
for (const field of [
|
|
622
|
+
for (const field of [
|
|
623
|
+
"version",
|
|
624
|
+
"description",
|
|
625
|
+
"author",
|
|
626
|
+
"keywords",
|
|
627
|
+
"homepage"
|
|
628
|
+
]) {
|
|
448
629
|
const value = manifest[field];
|
|
449
630
|
if (value !== void 0 && JSON.stringify(entry[field]) !== JSON.stringify(value)) {
|
|
450
631
|
entry[field] = value;
|
|
@@ -463,27 +644,22 @@ async function syncCommand(startDir, opts = {}) {
|
|
|
463
644
|
mp.plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
464
645
|
writeMarketplace(root, mp);
|
|
465
646
|
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
|
-
}
|
|
647
|
+
for (const id of refreshed)
|
|
648
|
+
changes.push(`~ refreshed ${id} tier-2 artifacts`);
|
|
649
|
+
refreshMarkedDoc(
|
|
650
|
+
root,
|
|
651
|
+
"README.md",
|
|
652
|
+
renderPluginTable(mp.plugins),
|
|
653
|
+
"plugin list in README.md",
|
|
654
|
+
changes
|
|
655
|
+
);
|
|
656
|
+
refreshMarkedDoc(
|
|
657
|
+
root,
|
|
658
|
+
"AGENTS.md",
|
|
659
|
+
renderAgentsPluginList(mp.plugins),
|
|
660
|
+
"plugin list in AGENTS.md",
|
|
661
|
+
changes
|
|
662
|
+
);
|
|
487
663
|
if (!opts.quiet) {
|
|
488
664
|
if (changes.length === 0) {
|
|
489
665
|
p2.log.success("Catalog already in sync.");
|
|
@@ -521,6 +697,7 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
521
697
|
if (p3.isCancel(answer)) return cancel2();
|
|
522
698
|
templateSpec = answer;
|
|
523
699
|
}
|
|
700
|
+
const reference = isRemoteSpec(templateSpec) && opts.vendor !== true;
|
|
524
701
|
let name = nameArg;
|
|
525
702
|
if (!name) {
|
|
526
703
|
const answer = await p3.text({
|
|
@@ -542,12 +719,6 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
542
719
|
process.exitCode = 1;
|
|
543
720
|
return;
|
|
544
721
|
}
|
|
545
|
-
const destDir = path7.join(pluginRootDir(root, mp), name);
|
|
546
|
-
if (fs6.existsSync(destDir)) {
|
|
547
|
-
p3.log.error(`Directory already exists: ${destDir}`);
|
|
548
|
-
process.exitCode = 1;
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
722
|
let description = opts.description;
|
|
552
723
|
if (description === void 0 && opts.interactive !== false) {
|
|
553
724
|
const answer = await p3.text({
|
|
@@ -559,6 +730,39 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
559
730
|
description = answer;
|
|
560
731
|
}
|
|
561
732
|
description = description || `TODO: describe what ${name} does.`;
|
|
733
|
+
if (reference) {
|
|
734
|
+
let remote;
|
|
735
|
+
try {
|
|
736
|
+
remote = resolveRemoteSource(templateSpec, {
|
|
737
|
+
ref: opts.ref,
|
|
738
|
+
sha: opts.sha
|
|
739
|
+
});
|
|
740
|
+
} catch (err) {
|
|
741
|
+
p3.log.error(err.message);
|
|
742
|
+
process.exitCode = 1;
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
mp.plugins.push({ name, source: remote.source, description });
|
|
746
|
+
writeMarketplace(root, mp);
|
|
747
|
+
await syncCommand(root, { quiet: true });
|
|
748
|
+
p3.log.success(
|
|
749
|
+
`Registered ${pc2.cyan(name)} as a remote plugin (${remote.label}) in marketplace.json \u2014 nothing was cloned.`
|
|
750
|
+
);
|
|
751
|
+
p3.log.info(
|
|
752
|
+
`Claude Code fetches it from the source at install time:
|
|
753
|
+
claude
|
|
754
|
+
/plugin marketplace add ${root}
|
|
755
|
+
/plugin install ${name}@${mp.name}
|
|
756
|
+
(pass --vendor to clone it into ${mp.metadata?.pluginRoot ?? "./plugins"} instead)`
|
|
757
|
+
);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const destDir = path7.join(pluginRootDir(root, mp), name);
|
|
761
|
+
if (fs6.existsSync(destDir)) {
|
|
762
|
+
p3.log.error(`Directory already exists: ${destDir}`);
|
|
763
|
+
process.exitCode = 1;
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
562
766
|
const vars = {
|
|
563
767
|
pluginName: name,
|
|
564
768
|
pluginTitle: titleCase(name),
|
|
@@ -576,7 +780,8 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
576
780
|
try {
|
|
577
781
|
copyTemplateDir(resolved.dir, destDir, vars);
|
|
578
782
|
} finally {
|
|
579
|
-
if (resolved.cleanup)
|
|
783
|
+
if (resolved.cleanup)
|
|
784
|
+
fs6.rmSync(resolved.cleanup, { recursive: true, force: true });
|
|
580
785
|
}
|
|
581
786
|
const manifestPath = path7.join(destDir, ".claude-plugin", "plugin.json");
|
|
582
787
|
if (!resolved.builtin && fs6.existsSync(manifestPath)) {
|
|
@@ -590,7 +795,7 @@ async function addPlugin(startDir, templateArg, nameArg, opts = {}) {
|
|
|
590
795
|
}
|
|
591
796
|
}
|
|
592
797
|
const template = resolved.label;
|
|
593
|
-
const source =
|
|
798
|
+
const source = `./${path7.relative(root, destDir).split(path7.sep).join("/")}`;
|
|
594
799
|
mp.plugins.push({ name, source, description, version: "0.1.0" });
|
|
595
800
|
writeMarketplace(root, mp);
|
|
596
801
|
await syncCommand(root, { quiet: true });
|
|
@@ -611,6 +816,7 @@ function cancel2() {
|
|
|
611
816
|
|
|
612
817
|
// src/commands/bump.ts
|
|
613
818
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
819
|
+
import fs7 from "fs";
|
|
614
820
|
import path8 from "path";
|
|
615
821
|
import * as p4 from "@clack/prompts";
|
|
616
822
|
import pc3 from "picocolors";
|
|
@@ -656,7 +862,8 @@ function analyzeCommits(root, pluginDir, since) {
|
|
|
656
862
|
return { level: void 0, commits: 0, reasons: ["not a git repository"] };
|
|
657
863
|
}
|
|
658
864
|
const commits = raw.split("").map((c) => c.trim()).filter(Boolean);
|
|
659
|
-
if (commits.length === 0)
|
|
865
|
+
if (commits.length === 0)
|
|
866
|
+
return { level: void 0, commits: 0, reasons: [] };
|
|
660
867
|
let level = "patch";
|
|
661
868
|
const reasons = [];
|
|
662
869
|
for (const c of commits) {
|
|
@@ -678,7 +885,9 @@ function analyzeCommits(root, pluginDir, since) {
|
|
|
678
885
|
async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
679
886
|
const root = findMarketplaceRoot(startDir);
|
|
680
887
|
if (!root) {
|
|
681
|
-
p4.log.error(
|
|
888
|
+
p4.log.error(
|
|
889
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
890
|
+
);
|
|
682
891
|
process.exitCode = 1;
|
|
683
892
|
return;
|
|
684
893
|
}
|
|
@@ -707,20 +916,24 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
707
916
|
}
|
|
708
917
|
const local = locals.find((l) => (l.manifest.name || l.dirName) === name);
|
|
709
918
|
if (!local) {
|
|
710
|
-
p4.log.error(
|
|
919
|
+
p4.log.error(
|
|
920
|
+
`No local plugin named "${name}". Known: ${locals.map((l) => l.manifest.name || l.dirName).join(", ")}`
|
|
921
|
+
);
|
|
711
922
|
process.exitCode = 1;
|
|
712
923
|
return;
|
|
713
924
|
}
|
|
714
925
|
if (levelArg && !["major", "minor", "patch", "auto"].includes(levelArg)) {
|
|
715
|
-
p4.log.error(
|
|
926
|
+
p4.log.error(
|
|
927
|
+
`Invalid level "${levelArg}". Use major, minor, patch, or auto.`
|
|
928
|
+
);
|
|
716
929
|
process.exitCode = 1;
|
|
717
930
|
return;
|
|
718
931
|
}
|
|
719
932
|
const current = local.manifest.version ?? "0.1.0";
|
|
933
|
+
const sinceTag = lastReleaseTag(root, name);
|
|
934
|
+
const analysis = analyzeCommits(root, local.dir, sinceTag);
|
|
720
935
|
let level;
|
|
721
936
|
if (!levelArg || levelArg === "auto") {
|
|
722
|
-
const sinceTag = lastReleaseTag(root, name);
|
|
723
|
-
const analysis = analyzeCommits(root, local.dir, sinceTag);
|
|
724
937
|
if (!analysis.level) {
|
|
725
938
|
p4.log.info(
|
|
726
939
|
`No commits touching ${path8.relative(root, local.dir)}${sinceTag ? ` since ${sinceTag}` : ""} \u2014 nothing to bump.`
|
|
@@ -738,14 +951,32 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
738
951
|
}
|
|
739
952
|
const next = incrementSemver(current, level);
|
|
740
953
|
const tagName = `${name}@${next}`;
|
|
954
|
+
const subjects = analysis.commits > 0 ? analysis.reasons.map((r) => r.replace(/^(?:major|minor|patch)\s+/, "")) : [];
|
|
741
955
|
if (opts.dryRun) {
|
|
742
|
-
p4.log.info(
|
|
956
|
+
p4.log.info(
|
|
957
|
+
`[dry-run] ${name}: ${current} -> ${pc3.green(next)} (${level})${opts.tag ? ` + tag ${tagName}` : ""} + CHANGELOG.md entry`
|
|
958
|
+
);
|
|
743
959
|
return;
|
|
744
960
|
}
|
|
745
|
-
const manifestPath = path8.join(
|
|
961
|
+
const manifestPath = path8.join(
|
|
962
|
+
local.dir,
|
|
963
|
+
MARKETPLACE_DIR,
|
|
964
|
+
PLUGIN_MANIFEST_FILE
|
|
965
|
+
);
|
|
746
966
|
const manifest = readJson(manifestPath);
|
|
747
967
|
manifest.version = next;
|
|
748
968
|
writeJson(manifestPath, manifest);
|
|
969
|
+
const changelogPath = path8.join(local.dir, "CHANGELOG.md");
|
|
970
|
+
const existingChangelog = fs7.existsSync(changelogPath) ? fs7.readFileSync(changelogPath, "utf8") : void 0;
|
|
971
|
+
const entry = renderChangelogEntry(
|
|
972
|
+
next,
|
|
973
|
+
subjects,
|
|
974
|
+
(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
975
|
+
);
|
|
976
|
+
fs7.writeFileSync(
|
|
977
|
+
changelogPath,
|
|
978
|
+
prependChangelogEntry(existingChangelog, entry)
|
|
979
|
+
);
|
|
749
980
|
await syncCommand(root, { quiet: true });
|
|
750
981
|
p4.log.success(`${name}: ${current} -> ${pc3.green(next)} (${level})`);
|
|
751
982
|
if (opts.tag) {
|
|
@@ -753,17 +984,23 @@ async function bumpCommand(startDir, pluginName, levelArg, opts = {}) {
|
|
|
753
984
|
git(root, ["add", "-A"]);
|
|
754
985
|
git(root, ["commit", "-m", `chore(release): ${tagName}`]);
|
|
755
986
|
git(root, ["tag", tagName]);
|
|
756
|
-
p4.log.success(
|
|
987
|
+
p4.log.success(
|
|
988
|
+
`Committed and tagged ${pc3.cyan(tagName)} \u2014 push with: git push --follow-tags`
|
|
989
|
+
);
|
|
757
990
|
} catch (err) {
|
|
758
|
-
p4.log.warn(
|
|
991
|
+
p4.log.warn(
|
|
992
|
+
`Version bumped but commit/tag failed: ${err.message}`
|
|
993
|
+
);
|
|
759
994
|
}
|
|
760
995
|
} else {
|
|
761
|
-
p4.log.info(
|
|
996
|
+
p4.log.info(
|
|
997
|
+
"Commit the change, then push. Use --tag to commit and tag in one step."
|
|
998
|
+
);
|
|
762
999
|
}
|
|
763
1000
|
}
|
|
764
1001
|
|
|
765
1002
|
// src/commands/init.ts
|
|
766
|
-
import
|
|
1003
|
+
import fs9 from "fs";
|
|
767
1004
|
import path10 from "path";
|
|
768
1005
|
import * as p5 from "@clack/prompts";
|
|
769
1006
|
import pc4 from "picocolors";
|
|
@@ -774,7 +1011,10 @@ var claudeCode = {
|
|
|
774
1011
|
label: "Claude Code",
|
|
775
1012
|
tier: 1,
|
|
776
1013
|
install: (arg, name) => ({
|
|
777
|
-
lines: [
|
|
1014
|
+
lines: [
|
|
1015
|
+
`/plugin marketplace add ${arg}`,
|
|
1016
|
+
`/plugin install <plugin-name>@${name}`
|
|
1017
|
+
]
|
|
778
1018
|
}),
|
|
779
1019
|
teamSettings: { path: ".claude/settings.json" }
|
|
780
1020
|
};
|
|
@@ -805,7 +1045,9 @@ var cursor = {
|
|
|
805
1045
|
label: "Cursor",
|
|
806
1046
|
tier: 2,
|
|
807
1047
|
install: () => ({
|
|
808
|
-
lines: [
|
|
1048
|
+
lines: [
|
|
1049
|
+
"# add the marketplace in Cursor, then: /plugin install <plugin-name>"
|
|
1050
|
+
],
|
|
809
1051
|
note: "Requires a committed Cursor registry (.cursor-plugin/). Generate it with `agkit build --target cursor` (planned)."
|
|
810
1052
|
})
|
|
811
1053
|
};
|
|
@@ -911,6 +1153,12 @@ The catalog lives in \`.claude-plugin/marketplace.json\`; each plugin lives unde
|
|
|
911
1153
|
|
|
912
1154
|
Target agents: ${labels}.
|
|
913
1155
|
|
|
1156
|
+
## Plugins
|
|
1157
|
+
|
|
1158
|
+
${PLUGINS_START}
|
|
1159
|
+
${renderAgentsPluginList([])}
|
|
1160
|
+
${PLUGINS_END}
|
|
1161
|
+
|
|
914
1162
|
## For agents working in this repo
|
|
915
1163
|
|
|
916
1164
|
- Plugins are self-contained under \`plugins/<name>/\` with a \`.claude-plugin/plugin.json\` manifest and \`skills/\`, \`commands/\`, \`agents/\`, \`hooks/\` as needed.
|
|
@@ -921,7 +1169,7 @@ Target agents: ${labels}.
|
|
|
921
1169
|
|
|
922
1170
|
// src/lib/git.ts
|
|
923
1171
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
924
|
-
import
|
|
1172
|
+
import fs8 from "fs";
|
|
925
1173
|
import path9 from "path";
|
|
926
1174
|
function isGitAvailable() {
|
|
927
1175
|
try {
|
|
@@ -932,7 +1180,16 @@ function isGitAvailable() {
|
|
|
932
1180
|
}
|
|
933
1181
|
}
|
|
934
1182
|
function isGitRepo(dir) {
|
|
935
|
-
return
|
|
1183
|
+
return fs8.existsSync(path9.join(dir, ".git"));
|
|
1184
|
+
}
|
|
1185
|
+
function isInsideGitRepo(dir) {
|
|
1186
|
+
let d = path9.resolve(dir);
|
|
1187
|
+
for (; ; ) {
|
|
1188
|
+
if (fs8.existsSync(path9.join(d, ".git"))) return true;
|
|
1189
|
+
const parent = path9.dirname(d);
|
|
1190
|
+
if (parent === d) return false;
|
|
1191
|
+
d = parent;
|
|
1192
|
+
}
|
|
936
1193
|
}
|
|
937
1194
|
function gitInit(dir) {
|
|
938
1195
|
execFileSync3("git", ["init", "--initial-branch=main"], {
|
|
@@ -954,7 +1211,9 @@ function parseRemoteUrl(raw) {
|
|
|
954
1211
|
let host;
|
|
955
1212
|
let repoPath;
|
|
956
1213
|
const scp = raw.match(/^(?:[\w.-]+)@([\w.-]+):(.+?)(?:\.git)?\/?$/);
|
|
957
|
-
const url = raw.match(
|
|
1214
|
+
const url = raw.match(
|
|
1215
|
+
/^(?:https?|ssh|git):\/\/(?:[\w.-]+@)?([\w.-]+(?::\d+)?)\/(.+?)(?:\.git)?\/?$/
|
|
1216
|
+
);
|
|
958
1217
|
if (scp) {
|
|
959
1218
|
host = scp[1];
|
|
960
1219
|
repoPath = scp[2];
|
|
@@ -976,6 +1235,9 @@ function parseRemoteUrl(raw) {
|
|
|
976
1235
|
}
|
|
977
1236
|
|
|
978
1237
|
// src/commands/init.ts
|
|
1238
|
+
function titleCase2(kebab) {
|
|
1239
|
+
return kebab.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1240
|
+
}
|
|
979
1241
|
function validateName(name) {
|
|
980
1242
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
981
1243
|
return "Name must be kebab-case (lowercase letters, digits, hyphens)";
|
|
@@ -986,22 +1248,151 @@ function validateName(name) {
|
|
|
986
1248
|
return void 0;
|
|
987
1249
|
}
|
|
988
1250
|
function marketplaceTpl(file) {
|
|
989
|
-
return
|
|
1251
|
+
return fs9.readFileSync(
|
|
1252
|
+
path10.join(templatesDir(), "marketplace", file),
|
|
1253
|
+
"utf8"
|
|
1254
|
+
);
|
|
990
1255
|
}
|
|
991
1256
|
function addArgument(remote) {
|
|
992
1257
|
if (!remote) return "<git-url-or-owner/repo>";
|
|
993
1258
|
return remote.isGitHub && remote.slug ? remote.slug : remote.httpsUrl;
|
|
994
1259
|
}
|
|
995
1260
|
function teamSource(remote) {
|
|
996
|
-
const source = remote?.isGitHub && remote.slug ? { source: "github", repo: remote.slug } : {
|
|
997
|
-
|
|
1261
|
+
const source = remote?.isGitHub && remote.slug ? { source: "github", repo: remote.slug } : {
|
|
1262
|
+
source: "url",
|
|
1263
|
+
url: remote?.httpsUrl ?? "https://<git-host>/<owner>/<repo>.git"
|
|
1264
|
+
};
|
|
1265
|
+
return JSON.stringify(source, null, 2).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
1266
|
+
}
|
|
1267
|
+
async function initPlugin(targetDir, templateSpec, opts) {
|
|
1268
|
+
p5.intro(pc4.bgCyan(pc4.black(" agkit init --plugin ")));
|
|
1269
|
+
const defaultName = path10.basename(targetDir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1270
|
+
let spec = typeof templateSpec === "string" ? templateSpec : void 0;
|
|
1271
|
+
if (!spec) {
|
|
1272
|
+
if (opts.yes) {
|
|
1273
|
+
spec = "skill";
|
|
1274
|
+
} else {
|
|
1275
|
+
const answer = await p5.select({
|
|
1276
|
+
message: "Plugin template",
|
|
1277
|
+
options: PLUGIN_TEMPLATES.map((t) => ({
|
|
1278
|
+
value: t,
|
|
1279
|
+
label: t,
|
|
1280
|
+
hint: TEMPLATE_DESCRIPTIONS[t]
|
|
1281
|
+
}))
|
|
1282
|
+
});
|
|
1283
|
+
if (p5.isCancel(answer)) return cancel5();
|
|
1284
|
+
spec = answer;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
let name = opts.name;
|
|
1288
|
+
if (!name && !opts.yes) {
|
|
1289
|
+
const answer = await p5.text({
|
|
1290
|
+
message: "Plugin name (kebab-case)",
|
|
1291
|
+
initialValue: defaultName,
|
|
1292
|
+
validate: (v) => KEBAB_CASE_RE.test(v) ? void 0 : "Must be kebab-case (lowercase, digits, hyphens)"
|
|
1293
|
+
});
|
|
1294
|
+
if (p5.isCancel(answer)) return cancel5();
|
|
1295
|
+
name = answer;
|
|
1296
|
+
}
|
|
1297
|
+
name = name || defaultName;
|
|
1298
|
+
if (!KEBAB_CASE_RE.test(name)) {
|
|
1299
|
+
p5.log.error(`Plugin name "${name}" must be kebab-case.`);
|
|
1300
|
+
process.exitCode = 1;
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
let description = opts.description;
|
|
1304
|
+
if (description === void 0 && !opts.yes) {
|
|
1305
|
+
const answer = await p5.text({
|
|
1306
|
+
message: "One-line description",
|
|
1307
|
+
defaultValue: "",
|
|
1308
|
+
placeholder: `What does ${name} do?`
|
|
1309
|
+
});
|
|
1310
|
+
if (p5.isCancel(answer)) return cancel5();
|
|
1311
|
+
description = answer;
|
|
1312
|
+
}
|
|
1313
|
+
description = description || `TODO: describe what ${name} does.`;
|
|
1314
|
+
const manifestPath = path10.join(targetDir, ".claude-plugin", "plugin.json");
|
|
1315
|
+
if (fs9.existsSync(manifestPath)) {
|
|
1316
|
+
p5.log.error(`A plugin already exists here: ${manifestPath}`);
|
|
1317
|
+
process.exitCode = 1;
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
const s = p5.spinner();
|
|
1321
|
+
s.start("Scaffolding plugin");
|
|
1322
|
+
fs9.mkdirSync(targetDir, { recursive: true });
|
|
1323
|
+
const vars = {
|
|
1324
|
+
pluginName: name,
|
|
1325
|
+
pluginTitle: titleCase2(name),
|
|
1326
|
+
description,
|
|
1327
|
+
authorName: opts.owner || "Unknown"
|
|
1328
|
+
};
|
|
1329
|
+
let resolved;
|
|
1330
|
+
try {
|
|
1331
|
+
resolved = resolveTemplate(spec);
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
s.stop("Scaffolding failed");
|
|
1334
|
+
p5.log.error(err.message);
|
|
1335
|
+
process.exitCode = 1;
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
try {
|
|
1339
|
+
copyTemplateDir(resolved.dir, targetDir, vars);
|
|
1340
|
+
} finally {
|
|
1341
|
+
if (resolved.cleanup)
|
|
1342
|
+
fs9.rmSync(resolved.cleanup, { recursive: true, force: true });
|
|
1343
|
+
}
|
|
1344
|
+
if (!resolved.builtin && fs9.existsSync(manifestPath)) {
|
|
1345
|
+
try {
|
|
1346
|
+
const manifest = readJson(manifestPath);
|
|
1347
|
+
manifest.name = name;
|
|
1348
|
+
if (opts.description) manifest.description = description;
|
|
1349
|
+
writeJson(manifestPath, manifest);
|
|
1350
|
+
} catch {
|
|
1351
|
+
p5.log.warn("Template plugin.json could not be parsed; left as-is.");
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const standalone = !isInsideGitRepo(targetDir);
|
|
1355
|
+
if (standalone) {
|
|
1356
|
+
const gitignore = path10.join(targetDir, ".gitignore");
|
|
1357
|
+
if (!fs9.existsSync(gitignore)) {
|
|
1358
|
+
fs9.writeFileSync(
|
|
1359
|
+
gitignore,
|
|
1360
|
+
["node_modules/", ".DS_Store", "*.log", ""].join("\n")
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
if (isGitAvailable()) {
|
|
1364
|
+
try {
|
|
1365
|
+
gitInit(targetDir);
|
|
1366
|
+
} catch {
|
|
1367
|
+
p5.log.warn("git init failed; initialize the repository manually.");
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
s.stop("Plugin scaffolded");
|
|
1372
|
+
const rel = path10.relative(process.cwd(), targetDir) || ".";
|
|
1373
|
+
p5.note(
|
|
1374
|
+
[
|
|
1375
|
+
`Standalone plugin "${name}" (${resolved.label}) \u2014 no marketplace was created.`,
|
|
1376
|
+
"",
|
|
1377
|
+
"Distribute it either way:",
|
|
1378
|
+
" \u2022 Push this folder to its own git repo, then reference it from a",
|
|
1379
|
+
` marketplace: agkit add <owner/repo> ${name}`,
|
|
1380
|
+
" \u2022 Or drop it into an existing marketplace's plugins/ and run:",
|
|
1381
|
+
" agkit sync"
|
|
1382
|
+
].join("\n"),
|
|
1383
|
+
`Next steps (cd ${rel})`
|
|
1384
|
+
);
|
|
1385
|
+
p5.outro("Done \u2714");
|
|
998
1386
|
}
|
|
999
1387
|
async function initCommand(dirArg, opts) {
|
|
1000
|
-
p5.intro(pc4.bgCyan(pc4.black(" agkit init ")));
|
|
1001
1388
|
const targetDir = path10.resolve(dirArg ?? ".");
|
|
1389
|
+
if (opts.plugin) {
|
|
1390
|
+
return initPlugin(targetDir, opts.plugin, opts);
|
|
1391
|
+
}
|
|
1392
|
+
p5.intro(pc4.bgCyan(pc4.black(" agkit init ")));
|
|
1002
1393
|
const defaultName = path10.basename(targetDir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1003
1394
|
const gitAvailable = isGitAvailable();
|
|
1004
|
-
const existingRemote = gitAvailable &&
|
|
1395
|
+
const existingRemote = gitAvailable && fs9.existsSync(targetDir) ? getOriginUrl(targetDir) : void 0;
|
|
1005
1396
|
let name = opts.name;
|
|
1006
1397
|
let owner = opts.owner;
|
|
1007
1398
|
let email = opts.email;
|
|
@@ -1091,7 +1482,9 @@ async function initCommand(dirArg, opts) {
|
|
|
1091
1482
|
}
|
|
1092
1483
|
owner = owner || "Unknown Owner";
|
|
1093
1484
|
ci = ci ?? "github";
|
|
1094
|
-
const { ids: agentIds, unknown } = parseAgentList(
|
|
1485
|
+
const { ids: agentIds, unknown } = parseAgentList(
|
|
1486
|
+
agentsRaw ?? DEFAULT_AGENTS.join(",")
|
|
1487
|
+
);
|
|
1095
1488
|
if (unknown.length > 0) {
|
|
1096
1489
|
p5.log.warn(
|
|
1097
1490
|
`Unknown agent(s) ignored: ${unknown.join(", ")}. Known: ${AGENT_ADAPTERS.map((a) => a.id).join(", ")}.`
|
|
@@ -1100,11 +1493,13 @@ async function initCommand(dirArg, opts) {
|
|
|
1100
1493
|
const targets = agentIds.length > 0 ? agentIds : [...DEFAULT_AGENTS];
|
|
1101
1494
|
const remote = repoUrl ? parseRemoteUrl(repoUrl) : void 0;
|
|
1102
1495
|
if (repoUrl && !remote) {
|
|
1103
|
-
p5.log.warn(
|
|
1496
|
+
p5.log.warn(
|
|
1497
|
+
`Could not parse remote URL "${repoUrl}"; install instructions will use placeholders.`
|
|
1498
|
+
);
|
|
1104
1499
|
}
|
|
1105
1500
|
const s = p5.spinner();
|
|
1106
1501
|
s.start("Scaffolding marketplace");
|
|
1107
|
-
|
|
1502
|
+
fs9.mkdirSync(targetDir, { recursive: true });
|
|
1108
1503
|
const marketplace = {
|
|
1109
1504
|
$schema: MARKETPLACE_SCHEMA_URL,
|
|
1110
1505
|
name,
|
|
@@ -1117,9 +1512,12 @@ async function initCommand(dirArg, opts) {
|
|
|
1117
1512
|
},
|
|
1118
1513
|
plugins: []
|
|
1119
1514
|
};
|
|
1120
|
-
writeJson(
|
|
1121
|
-
|
|
1122
|
-
|
|
1515
|
+
writeJson(
|
|
1516
|
+
path10.join(targetDir, ".claude-plugin", "marketplace.json"),
|
|
1517
|
+
marketplace
|
|
1518
|
+
);
|
|
1519
|
+
fs9.mkdirSync(path10.join(targetDir, "plugins"), { recursive: true });
|
|
1520
|
+
fs9.writeFileSync(path10.join(targetDir, "plugins", ".gitkeep"), "");
|
|
1123
1521
|
const desc = description || "A curated collection of agent plugins.";
|
|
1124
1522
|
const mktArg = addArgument(remote);
|
|
1125
1523
|
const teamBlock = teamSource(remote);
|
|
@@ -1131,34 +1529,34 @@ async function initCommand(dirArg, opts) {
|
|
|
1131
1529
|
installSections: renderInstallSections(targets, mktArg, name),
|
|
1132
1530
|
teamSections: renderTeamSections(targets, teamBlock, name)
|
|
1133
1531
|
};
|
|
1134
|
-
|
|
1532
|
+
fs9.writeFileSync(
|
|
1135
1533
|
path10.join(targetDir, "README.md"),
|
|
1136
1534
|
renderTemplate(marketplaceTpl("README.md.tpl"), vars)
|
|
1137
1535
|
);
|
|
1138
|
-
|
|
1536
|
+
fs9.writeFileSync(
|
|
1139
1537
|
path10.join(targetDir, "AGENTS.md"),
|
|
1140
1538
|
renderAgentsMd(name, desc, targets)
|
|
1141
1539
|
);
|
|
1142
1540
|
const exampleDir = path10.join(targetDir, "examples");
|
|
1143
|
-
|
|
1144
|
-
|
|
1541
|
+
fs9.mkdirSync(exampleDir, { recursive: true });
|
|
1542
|
+
fs9.writeFileSync(
|
|
1145
1543
|
path10.join(exampleDir, "team-settings.json"),
|
|
1146
1544
|
renderTemplate(marketplaceTpl("team-settings.json.tpl"), vars)
|
|
1147
1545
|
);
|
|
1148
1546
|
if (ci === "github") {
|
|
1149
1547
|
const wfDir = path10.join(targetDir, ".github", "workflows");
|
|
1150
|
-
|
|
1151
|
-
|
|
1548
|
+
fs9.mkdirSync(wfDir, { recursive: true });
|
|
1549
|
+
fs9.writeFileSync(
|
|
1152
1550
|
path10.join(wfDir, "validate.yml"),
|
|
1153
1551
|
renderTemplate(marketplaceTpl("github-validate.yml.tpl"), vars)
|
|
1154
1552
|
);
|
|
1155
1553
|
} else if (ci === "gitlab") {
|
|
1156
|
-
|
|
1554
|
+
fs9.writeFileSync(
|
|
1157
1555
|
path10.join(targetDir, ".gitlab-ci.yml"),
|
|
1158
1556
|
renderTemplate(marketplaceTpl("gitlab-ci.yml.tpl"), vars)
|
|
1159
1557
|
);
|
|
1160
1558
|
}
|
|
1161
|
-
|
|
1559
|
+
fs9.writeFileSync(
|
|
1162
1560
|
path10.join(targetDir, ".gitignore"),
|
|
1163
1561
|
["node_modules/", ".DS_Store", "*.log", ""].join("\n")
|
|
1164
1562
|
);
|
|
@@ -1229,14 +1627,16 @@ async function listCommand() {
|
|
|
1229
1627
|
|
|
1230
1628
|
// src/commands/validate.ts
|
|
1231
1629
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
1232
|
-
import
|
|
1630
|
+
import fs10 from "fs";
|
|
1233
1631
|
import path11 from "path";
|
|
1234
1632
|
import * as p7 from "@clack/prompts";
|
|
1235
1633
|
import pc6 from "picocolors";
|
|
1236
1634
|
async function validateCommand(startDir, opts = {}) {
|
|
1237
1635
|
const root = findMarketplaceRoot(startDir);
|
|
1238
1636
|
if (!root) {
|
|
1239
|
-
p7.log.error(
|
|
1637
|
+
p7.log.error(
|
|
1638
|
+
"No .claude-plugin/marketplace.json found. Run `agkit init` first."
|
|
1639
|
+
);
|
|
1240
1640
|
process.exitCode = 1;
|
|
1241
1641
|
return;
|
|
1242
1642
|
}
|
|
@@ -1252,7 +1652,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1252
1652
|
}
|
|
1253
1653
|
if (mp) {
|
|
1254
1654
|
if (!mp.name) {
|
|
1255
|
-
findings.push({
|
|
1655
|
+
findings.push({
|
|
1656
|
+
level: "error",
|
|
1657
|
+
message: "Missing required field: name"
|
|
1658
|
+
});
|
|
1256
1659
|
} else {
|
|
1257
1660
|
if (!KEBAB_CASE_RE.test(mp.name)) {
|
|
1258
1661
|
findings.push({
|
|
@@ -1268,7 +1671,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1268
1671
|
}
|
|
1269
1672
|
}
|
|
1270
1673
|
if (!mp.owner?.name) {
|
|
1271
|
-
findings.push({
|
|
1674
|
+
findings.push({
|
|
1675
|
+
level: "error",
|
|
1676
|
+
message: "Missing required field: owner.name"
|
|
1677
|
+
});
|
|
1272
1678
|
}
|
|
1273
1679
|
if (!Array.isArray(mp.plugins)) {
|
|
1274
1680
|
findings.push({ level: "error", message: "plugins must be an array" });
|
|
@@ -1277,7 +1683,10 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1277
1683
|
for (const entry of mp.plugins ?? []) {
|
|
1278
1684
|
const label = entry.name ?? "<unnamed>";
|
|
1279
1685
|
if (!entry.name) {
|
|
1280
|
-
findings.push({
|
|
1686
|
+
findings.push({
|
|
1687
|
+
level: "error",
|
|
1688
|
+
message: "A plugin entry is missing its name"
|
|
1689
|
+
});
|
|
1281
1690
|
continue;
|
|
1282
1691
|
}
|
|
1283
1692
|
if (!KEBAB_CASE_RE.test(entry.name)) {
|
|
@@ -1287,30 +1696,42 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1287
1696
|
});
|
|
1288
1697
|
}
|
|
1289
1698
|
if (seen.has(entry.name)) {
|
|
1290
|
-
findings.push({
|
|
1699
|
+
findings.push({
|
|
1700
|
+
level: "error",
|
|
1701
|
+
message: `Duplicate plugin name "${label}"`
|
|
1702
|
+
});
|
|
1291
1703
|
}
|
|
1292
1704
|
seen.add(entry.name);
|
|
1293
1705
|
if (entry.source === void 0) {
|
|
1294
|
-
findings.push({
|
|
1706
|
+
findings.push({
|
|
1707
|
+
level: "error",
|
|
1708
|
+
message: `Plugin "${label}" is missing source`
|
|
1709
|
+
});
|
|
1295
1710
|
continue;
|
|
1296
1711
|
}
|
|
1297
1712
|
const localDir = resolveLocalPluginDir(root, mp, entry);
|
|
1298
1713
|
if (localDir) {
|
|
1299
|
-
if (!
|
|
1714
|
+
if (!fs10.existsSync(localDir)) {
|
|
1300
1715
|
findings.push({
|
|
1301
1716
|
level: "error",
|
|
1302
1717
|
message: `Plugin "${label}" source resolves to missing directory ${path11.relative(root, localDir)}`
|
|
1303
1718
|
});
|
|
1304
1719
|
} else {
|
|
1305
|
-
const manifestPath = path11.join(
|
|
1306
|
-
|
|
1720
|
+
const manifestPath = path11.join(
|
|
1721
|
+
localDir,
|
|
1722
|
+
MARKETPLACE_DIR,
|
|
1723
|
+
PLUGIN_MANIFEST_FILE
|
|
1724
|
+
);
|
|
1725
|
+
if (!fs10.existsSync(manifestPath)) {
|
|
1307
1726
|
findings.push({
|
|
1308
1727
|
level: "error",
|
|
1309
1728
|
message: `Plugin "${label}" has no ${MARKETPLACE_DIR}/${PLUGIN_MANIFEST_FILE}`
|
|
1310
1729
|
});
|
|
1311
1730
|
} else {
|
|
1312
1731
|
try {
|
|
1313
|
-
const manifest = JSON.parse(
|
|
1732
|
+
const manifest = JSON.parse(
|
|
1733
|
+
fs10.readFileSync(manifestPath, "utf8")
|
|
1734
|
+
);
|
|
1314
1735
|
if (manifest.name && manifest.name !== entry.name) {
|
|
1315
1736
|
findings.push({
|
|
1316
1737
|
level: "warn",
|
|
@@ -1333,7 +1754,9 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1333
1754
|
}
|
|
1334
1755
|
}
|
|
1335
1756
|
}
|
|
1336
|
-
const hasRelative = (mp.plugins ?? []).some(
|
|
1757
|
+
const hasRelative = (mp.plugins ?? []).some(
|
|
1758
|
+
(e) => typeof e.source === "string"
|
|
1759
|
+
);
|
|
1337
1760
|
if (hasRelative) {
|
|
1338
1761
|
p7.log.info(
|
|
1339
1762
|
"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 +1764,9 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1341
1764
|
}
|
|
1342
1765
|
}
|
|
1343
1766
|
if (mp?.metadata?.targets) {
|
|
1344
|
-
const needBuild = mp.metadata.targets.filter(
|
|
1767
|
+
const needBuild = mp.metadata.targets.filter(
|
|
1768
|
+
(id) => (getAdapter(id)?.tier ?? 1) > 1
|
|
1769
|
+
);
|
|
1345
1770
|
if (needBuild.length > 0) {
|
|
1346
1771
|
p7.log.info(
|
|
1347
1772
|
`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 +1775,11 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1350
1775
|
}
|
|
1351
1776
|
let officialRan = false;
|
|
1352
1777
|
try {
|
|
1353
|
-
execFileSync4(
|
|
1778
|
+
execFileSync4(
|
|
1779
|
+
"claude",
|
|
1780
|
+
["plugin", "validate", root, ...opts.strict ? ["--strict"] : []],
|
|
1781
|
+
{ stdio: "pipe" }
|
|
1782
|
+
);
|
|
1354
1783
|
officialRan = true;
|
|
1355
1784
|
p7.log.success("claude plugin validate: passed");
|
|
1356
1785
|
} catch (err) {
|
|
@@ -1364,7 +1793,8 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1364
1793
|
const out = [e.stdout?.toString(), e.stderr?.toString()].filter(Boolean).join("\n").trim();
|
|
1365
1794
|
findings.push({
|
|
1366
1795
|
level: "error",
|
|
1367
|
-
message: `claude plugin validate failed${out ?
|
|
1796
|
+
message: `claude plugin validate failed${out ? `:
|
|
1797
|
+
${out}` : ""}`
|
|
1368
1798
|
});
|
|
1369
1799
|
}
|
|
1370
1800
|
}
|
|
@@ -1373,7 +1803,11 @@ async function validateCommand(startDir, opts = {}) {
|
|
|
1373
1803
|
for (const f of warns) p7.log.warn(f.message);
|
|
1374
1804
|
for (const f of errors) p7.log.error(f.message);
|
|
1375
1805
|
if (errors.length > 0) {
|
|
1376
|
-
p7.log.error(
|
|
1806
|
+
p7.log.error(
|
|
1807
|
+
pc6.red(
|
|
1808
|
+
`Validation failed: ${errors.length} error(s), ${warns.length} warning(s).`
|
|
1809
|
+
)
|
|
1810
|
+
);
|
|
1377
1811
|
process.exitCode = 1;
|
|
1378
1812
|
} else {
|
|
1379
1813
|
p7.log.success(
|
|
@@ -1392,26 +1826,78 @@ var program = new Command();
|
|
|
1392
1826
|
program.name("agkit").description(
|
|
1393
1827
|
"Scaffold and manage plugin marketplaces for Claude Code and GitHub Copilot, distributed via any Git host."
|
|
1394
1828
|
).version(version);
|
|
1395
|
-
program.command("init").argument("[dir]", "target directory (default: current directory)").description(
|
|
1829
|
+
program.command("init").argument("[dir]", "target directory (default: current directory)").description(
|
|
1830
|
+
"Scaffold a new plugin marketplace (git-first), or a standalone plugin with --plugin"
|
|
1831
|
+
).option(
|
|
1832
|
+
"-n, --name <name>",
|
|
1833
|
+
"marketplace (or plugin, with --plugin) name (kebab-case)"
|
|
1834
|
+
).option("-o, --owner <owner>", "owner / plugin author name").option("-e, --email <email>", "owner email").option(
|
|
1835
|
+
"-d, --description <text>",
|
|
1836
|
+
"marketplace (or plugin, with --plugin) description"
|
|
1837
|
+
).option("-r, --repo <url>", "git remote URL (any forge)").option("--ci <ci>", "CI workflow: github | gitlab | none").option(
|
|
1838
|
+
"--agents <list>",
|
|
1839
|
+
"comma-separated target agents (e.g. claude-code,copilot,codex)"
|
|
1840
|
+
).option(
|
|
1841
|
+
"--plugin [template]",
|
|
1842
|
+
"scaffold a standalone plugin (skill|command|agent|hook|mcp, or a template spec) in [dir] instead of a marketplace"
|
|
1843
|
+
).option("-y, --yes", "non-interactive, accept defaults").action(async (dir, opts) => {
|
|
1396
1844
|
await initCommand(dir, opts);
|
|
1397
1845
|
});
|
|
1398
|
-
program.command("add").argument(
|
|
1846
|
+
program.command("add").argument(
|
|
1847
|
+
"[template]",
|
|
1848
|
+
"built-in template name, local path, or remote git source (owner/repo, gh:/gl: shorthand, git URL). Remote sources are referenced, not cloned (see --vendor)."
|
|
1849
|
+
).argument("[name]", "plugin name (kebab-case)").description(
|
|
1850
|
+
"Register a plugin in the catalog: reference a remote git repo, or scaffold from a built-in/local template"
|
|
1851
|
+
).option("-d, --description <text>", "one-line plugin description").option(
|
|
1852
|
+
"--vendor",
|
|
1853
|
+
"clone the remote repo and scaffold from its files into the marketplace, instead of referencing it"
|
|
1854
|
+
).option("--ref <ref>", "branch or tag to pin a referenced remote source to").option(
|
|
1855
|
+
"--sha <sha>",
|
|
1856
|
+
"commit hash (40 hex) to pin a referenced remote source to"
|
|
1857
|
+
).action(async (template, name, opts) => {
|
|
1399
1858
|
await addPlugin(process.cwd(), template, name, {
|
|
1400
1859
|
description: opts.description,
|
|
1401
|
-
interactive: opts.description === void 0
|
|
1860
|
+
interactive: opts.description === void 0,
|
|
1861
|
+
vendor: opts.vendor,
|
|
1862
|
+
ref: opts.ref,
|
|
1863
|
+
sha: opts.sha
|
|
1402
1864
|
});
|
|
1403
1865
|
});
|
|
1404
1866
|
program.command("sync").description("Reconcile marketplace.json and README with the plugins on disk").action(async () => {
|
|
1405
1867
|
await syncCommand(process.cwd());
|
|
1406
1868
|
});
|
|
1407
|
-
program.command("validate").description(
|
|
1869
|
+
program.command("validate").description(
|
|
1870
|
+
"Validate the catalog (local checks + `claude plugin validate` if available)"
|
|
1871
|
+
).option("--strict", "treat warnings as errors in the official validator").action(async (opts) => {
|
|
1408
1872
|
await validateCommand(process.cwd(), { strict: opts.strict });
|
|
1409
1873
|
});
|
|
1410
|
-
program.command("bump").argument("[plugin]", "plugin name (prompted if omitted)").argument(
|
|
1411
|
-
|
|
1874
|
+
program.command("bump").argument("[plugin]", "plugin name (prompted if omitted)").argument(
|
|
1875
|
+
"[level]",
|
|
1876
|
+
"major | minor | patch | auto (default: auto, from conventional commits)"
|
|
1877
|
+
).description(
|
|
1878
|
+
"Bump a plugin version (conventional-commit aware) and sync the catalog"
|
|
1879
|
+
).option(
|
|
1880
|
+
"-t, --tag",
|
|
1881
|
+
"commit the bump and create a release tag <plugin>@<version>"
|
|
1882
|
+
).option("--dry-run", "show what would change without writing").action(async (plugin, level, opts) => {
|
|
1883
|
+
await bumpCommand(process.cwd(), plugin, level, {
|
|
1884
|
+
tag: opts.tag,
|
|
1885
|
+
dryRun: opts.dryRun
|
|
1886
|
+
});
|
|
1412
1887
|
});
|
|
1413
|
-
program.command("build").description(
|
|
1414
|
-
|
|
1888
|
+
program.command("build").description(
|
|
1889
|
+
"Generate tier-2 agent artifacts (codex, cursor) from the catalog"
|
|
1890
|
+
).option(
|
|
1891
|
+
"--target <list>",
|
|
1892
|
+
"comma-separated tier-2 targets (default: those in metadata.targets)"
|
|
1893
|
+
).option(
|
|
1894
|
+
"--check",
|
|
1895
|
+
"verify generated artifacts are up to date (CI); non-zero exit on drift"
|
|
1896
|
+
).action(async (opts) => {
|
|
1897
|
+
await buildCommand(process.cwd(), {
|
|
1898
|
+
targets: opts.target,
|
|
1899
|
+
check: opts.check
|
|
1900
|
+
});
|
|
1415
1901
|
});
|
|
1416
1902
|
program.command("list").description("List available plugin templates").action(async () => {
|
|
1417
1903
|
await listCommand();
|