agileflow 4.0.0-alpha.29 → 4.0.0-alpha.30
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/package.json +1 -1
- package/src/cli/commands/plugins.js +263 -47
- package/src/cli/index.js +6 -2
package/package.json
CHANGED
|
@@ -2,71 +2,79 @@
|
|
|
2
2
|
* `agileflow plugins <action>` — inspect the plugin registry.
|
|
3
3
|
*
|
|
4
4
|
* Actions:
|
|
5
|
-
* list
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* list Print all discoverable plugins with enabled status, skill
|
|
6
|
+
* count, and description. Supports --enabled / --disabled
|
|
7
|
+
* filters.
|
|
8
|
+
* search <query> Find plugins whose id, name, description, or provided
|
|
9
|
+
* skill ids contain the query (case-insensitive substring).
|
|
10
|
+
* show <id> Print full detail for a single plugin: version, depends,
|
|
11
|
+
* and the complete provides breakdown.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* All actions support --json for machine-readable output. The enable state
|
|
14
|
+
* is resolved from agileflow.config.json when present, otherwise from each
|
|
15
|
+
* plugin's `enabledByDefault` / `cannotDisable` flags.
|
|
13
16
|
*/
|
|
14
|
-
const path = require("path");
|
|
15
17
|
const { loadConfig } = require("../../runtime/config/loader.js");
|
|
16
18
|
const { discoverPlugins } = require("../../runtime/plugins/registry.js");
|
|
17
19
|
const { InvalidArgumentError, fail } = require("../../lib/errors.js");
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* Resolve enabled state for a plugin given the user's config and the plugin's
|
|
23
|
+
* own defaults. Mirrors the rules used by the original `list` action.
|
|
22
24
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
suggestion: "agileflow plugins list",
|
|
28
|
-
}),
|
|
29
|
-
{ command: "plugins" },
|
|
30
|
-
);
|
|
25
|
+
function resolveEnabled(plugin, configPlugins) {
|
|
26
|
+
const cfgEntry = configPlugins[plugin.id];
|
|
27
|
+
if (cfgEntry !== undefined) {
|
|
28
|
+
return Boolean(cfgEntry && cfgEntry.enabled);
|
|
31
29
|
}
|
|
30
|
+
return Boolean(plugin.enabledByDefault) || Boolean(plugin.cannotDisable);
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
+
function buildRow(plugin, configPlugins) {
|
|
34
|
+
const enabled = resolveEnabled(plugin, configPlugins);
|
|
35
|
+
const skillCount =
|
|
36
|
+
plugin.provides && plugin.provides.skills
|
|
37
|
+
? plugin.provides.skills.length
|
|
38
|
+
: 0;
|
|
39
|
+
const description = (plugin.description || "")
|
|
40
|
+
.replace(/\n/g, " ")
|
|
41
|
+
.trim()
|
|
42
|
+
.slice(0, 72);
|
|
43
|
+
return {
|
|
44
|
+
id: plugin.id,
|
|
45
|
+
enabled,
|
|
46
|
+
cannotDisable: Boolean(plugin.cannotDisable),
|
|
47
|
+
skills: skillCount,
|
|
48
|
+
description,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function loadContext() {
|
|
33
53
|
const cwd = process.cwd();
|
|
34
54
|
const { config, source } = await loadConfig(cwd);
|
|
35
55
|
const configPlugins = source === "file" ? config.plugins || {} : {};
|
|
36
|
-
|
|
37
56
|
const discovered = discoverPlugins();
|
|
57
|
+
return { source, configPlugins, discovered };
|
|
58
|
+
}
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
enabled = Boolean(p.enabledByDefault) || Boolean(p.cannotDisable);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const skillCount =
|
|
49
|
-
p.provides && p.provides.skills ? p.provides.skills.length : 0;
|
|
50
|
-
const description = (p.description || "")
|
|
51
|
-
.replace(/\n/g, " ")
|
|
52
|
-
.trim()
|
|
53
|
-
.slice(0, 72);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
id: p.id,
|
|
57
|
-
enabled,
|
|
58
|
-
cannotDisable: Boolean(p.cannotDisable),
|
|
59
|
-
skills: skillCount,
|
|
60
|
-
description,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
|
|
60
|
+
function printList(
|
|
61
|
+
rows,
|
|
62
|
+
source,
|
|
63
|
+
options,
|
|
64
|
+
{ heading = "Available plugins:" } = {},
|
|
65
|
+
) {
|
|
64
66
|
if (options.json) {
|
|
65
67
|
// eslint-disable-next-line no-console
|
|
66
68
|
console.log(JSON.stringify({ source, plugins: rows }, null, 2));
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
if (rows.length === 0) {
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.log("\nNo plugins match the current filter.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
const idW = Math.max(6, ...rows.map((r) => r.id.length));
|
|
71
79
|
const statusW = 8; // "disabled" length
|
|
72
80
|
const skillsW = 6; // "skills" length
|
|
@@ -74,8 +82,10 @@ async function plugins(action, options = {}) {
|
|
|
74
82
|
const header = `${"Plugin".padEnd(idW)} ${"Status".padEnd(statusW)} ${"Skills".padEnd(skillsW)} Description`;
|
|
75
83
|
const sep = `${"-".repeat(idW)} ${"-".repeat(statusW)} ${"-".repeat(skillsW)} ${"-".repeat(40)}`;
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
if (heading) {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.log(`\n${heading}\n`);
|
|
88
|
+
}
|
|
79
89
|
// eslint-disable-next-line no-console
|
|
80
90
|
console.log(header);
|
|
81
91
|
// eslint-disable-next-line no-console
|
|
@@ -96,7 +106,7 @@ async function plugins(action, options = {}) {
|
|
|
96
106
|
|
|
97
107
|
const enabledCount = rows.filter((r) => r.enabled || r.cannotDisable).length;
|
|
98
108
|
// eslint-disable-next-line no-console
|
|
99
|
-
console.log(`\n${rows.length} plugin(s)
|
|
109
|
+
console.log(`\n${rows.length} plugin(s) shown, ${enabledCount} enabled.`);
|
|
100
110
|
if (source === "defaults") {
|
|
101
111
|
// eslint-disable-next-line no-console
|
|
102
112
|
console.log(
|
|
@@ -110,4 +120,210 @@ async function plugins(action, options = {}) {
|
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
async function listAction(options) {
|
|
124
|
+
const { source, configPlugins, discovered } = await loadContext();
|
|
125
|
+
let rows = discovered.map((p) => buildRow(p, configPlugins));
|
|
126
|
+
|
|
127
|
+
if (options.enabled && options.disabled) {
|
|
128
|
+
fail(
|
|
129
|
+
new InvalidArgumentError(
|
|
130
|
+
"--enabled and --disabled are mutually exclusive",
|
|
131
|
+
{ suggestion: "use one filter at a time" },
|
|
132
|
+
),
|
|
133
|
+
{ command: "plugins" },
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (options.enabled) {
|
|
137
|
+
rows = rows.filter((r) => r.enabled || r.cannotDisable);
|
|
138
|
+
} else if (options.disabled) {
|
|
139
|
+
rows = rows.filter((r) => !r.enabled && !r.cannotDisable);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
printList(rows, source, options);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function matchesQuery(plugin, query) {
|
|
146
|
+
const q = query.toLowerCase();
|
|
147
|
+
if (plugin.id.toLowerCase().includes(q)) return true;
|
|
148
|
+
if ((plugin.name || "").toLowerCase().includes(q)) return true;
|
|
149
|
+
if ((plugin.description || "").toLowerCase().includes(q)) return true;
|
|
150
|
+
const skills = (plugin.provides && plugin.provides.skills) || [];
|
|
151
|
+
return skills.some((s) => (s.id || "").toLowerCase().includes(q));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function searchAction(query, options) {
|
|
155
|
+
if (!query || !query.trim()) {
|
|
156
|
+
fail(
|
|
157
|
+
new InvalidArgumentError("search requires a query string", {
|
|
158
|
+
suggestion: "agileflow plugins search <query>",
|
|
159
|
+
}),
|
|
160
|
+
{ command: "plugins" },
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { source, configPlugins, discovered } = await loadContext();
|
|
165
|
+
const matches = discovered.filter((p) => matchesQuery(p, query));
|
|
166
|
+
const rows = matches.map((p) => buildRow(p, configPlugins));
|
|
167
|
+
|
|
168
|
+
if (options.json) {
|
|
169
|
+
// eslint-disable-next-line no-console
|
|
170
|
+
console.log(JSON.stringify({ source, query, plugins: rows }, null, 2));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (rows.length === 0) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.log(`\nNo plugins match "${query}".`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
printList(rows, source, options, {
|
|
181
|
+
heading: `Plugins matching "${query}":`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function formatProvides(provides) {
|
|
186
|
+
const sections = [
|
|
187
|
+
["Skills", provides.skills],
|
|
188
|
+
["Agents", provides.agents],
|
|
189
|
+
["Hooks", provides.hooks],
|
|
190
|
+
["Templates", provides.templates],
|
|
191
|
+
];
|
|
192
|
+
const lines = [];
|
|
193
|
+
for (const [label, items] of sections) {
|
|
194
|
+
const list = Array.isArray(items) ? items : [];
|
|
195
|
+
lines.push(` ${label} (${list.length}):`);
|
|
196
|
+
if (list.length === 0) {
|
|
197
|
+
lines.push(" (none)");
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (const item of list) {
|
|
201
|
+
const id = item.id || item.name || "(unnamed)";
|
|
202
|
+
const dir = item.dir ? ` ${item.dir}` : "";
|
|
203
|
+
lines.push(` - ${id}${dir}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return lines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function showAction(id, options) {
|
|
210
|
+
if (!id || !id.trim()) {
|
|
211
|
+
fail(
|
|
212
|
+
new InvalidArgumentError("show requires a plugin id", {
|
|
213
|
+
suggestion: "agileflow plugins show <id>",
|
|
214
|
+
}),
|
|
215
|
+
{ command: "plugins" },
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { source, configPlugins, discovered } = await loadContext();
|
|
220
|
+
const plugin = discovered.find((p) => p.id === id);
|
|
221
|
+
if (!plugin) {
|
|
222
|
+
fail(
|
|
223
|
+
new InvalidArgumentError(`plugin "${id}" not found`, {
|
|
224
|
+
suggestion: "agileflow plugins list",
|
|
225
|
+
}),
|
|
226
|
+
{ command: "plugins" },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const enabled = resolveEnabled(plugin, configPlugins);
|
|
231
|
+
const provides = plugin.provides || {
|
|
232
|
+
skills: [],
|
|
233
|
+
agents: [],
|
|
234
|
+
hooks: [],
|
|
235
|
+
templates: [],
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
if (options.json) {
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.log(
|
|
241
|
+
JSON.stringify(
|
|
242
|
+
{
|
|
243
|
+
source,
|
|
244
|
+
plugin: {
|
|
245
|
+
id: plugin.id,
|
|
246
|
+
name: plugin.name,
|
|
247
|
+
description: plugin.description,
|
|
248
|
+
version: plugin.version,
|
|
249
|
+
enabled,
|
|
250
|
+
cannotDisable: Boolean(plugin.cannotDisable),
|
|
251
|
+
enabledByDefault: Boolean(plugin.enabledByDefault),
|
|
252
|
+
depends: plugin.depends || [],
|
|
253
|
+
provides,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
null,
|
|
257
|
+
2,
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const status = plugin.cannotDisable
|
|
264
|
+
? "always (cannot disable)"
|
|
265
|
+
: enabled
|
|
266
|
+
? "enabled"
|
|
267
|
+
: "disabled";
|
|
268
|
+
const depends =
|
|
269
|
+
plugin.depends && plugin.depends.length
|
|
270
|
+
? plugin.depends.join(", ")
|
|
271
|
+
: "(none)";
|
|
272
|
+
|
|
273
|
+
// eslint-disable-next-line no-console
|
|
274
|
+
console.log(`\n${plugin.name} (${plugin.id})`);
|
|
275
|
+
// eslint-disable-next-line no-console
|
|
276
|
+
console.log(` Version: ${plugin.version}`);
|
|
277
|
+
// eslint-disable-next-line no-console
|
|
278
|
+
console.log(` Status: ${status}`);
|
|
279
|
+
// eslint-disable-next-line no-console
|
|
280
|
+
console.log(` Depends: ${depends}`);
|
|
281
|
+
// eslint-disable-next-line no-console
|
|
282
|
+
console.log(` Description: ${plugin.description}`);
|
|
283
|
+
// eslint-disable-next-line no-console
|
|
284
|
+
console.log("\n Provides:");
|
|
285
|
+
// eslint-disable-next-line no-console
|
|
286
|
+
console.log(formatProvides(provides));
|
|
287
|
+
// eslint-disable-next-line no-console
|
|
288
|
+
console.log("");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @param {string} action - 'list' | 'search' | 'show'
|
|
293
|
+
* @param {string} [arg] - query for search, id for show
|
|
294
|
+
* @param {{ json?: boolean, enabled?: boolean, disabled?: boolean }} options
|
|
295
|
+
*/
|
|
296
|
+
async function plugins(action, arg, options = {}) {
|
|
297
|
+
// Commander invokes action handlers with (...args, command). When no
|
|
298
|
+
// <arg> is declared on a subcommand, the second positional may be the
|
|
299
|
+
// options object instead of a string. Normalize defensively so direct
|
|
300
|
+
// calls and commander dispatch both work.
|
|
301
|
+
if (
|
|
302
|
+
arg &&
|
|
303
|
+
typeof arg === "object" &&
|
|
304
|
+
options &&
|
|
305
|
+
Object.keys(options).length === 0
|
|
306
|
+
) {
|
|
307
|
+
options = arg;
|
|
308
|
+
arg = undefined;
|
|
309
|
+
}
|
|
310
|
+
options = options || {};
|
|
311
|
+
|
|
312
|
+
switch (action) {
|
|
313
|
+
case "list":
|
|
314
|
+
return listAction(options);
|
|
315
|
+
case "search":
|
|
316
|
+
return searchAction(arg, options);
|
|
317
|
+
case "show":
|
|
318
|
+
return showAction(arg, options);
|
|
319
|
+
default:
|
|
320
|
+
fail(
|
|
321
|
+
new InvalidArgumentError(`unknown action "${action}"`, {
|
|
322
|
+
suggestion: "agileflow plugins list | search <query> | show <id>",
|
|
323
|
+
}),
|
|
324
|
+
{ command: "plugins" },
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
113
329
|
module.exports = plugins;
|
package/src/cli/index.js
CHANGED
|
@@ -98,9 +98,13 @@ function buildProgram() {
|
|
|
98
98
|
.action(skills);
|
|
99
99
|
|
|
100
100
|
program
|
|
101
|
-
.command("plugins <action>")
|
|
102
|
-
.description(
|
|
101
|
+
.command("plugins <action> [arg]")
|
|
102
|
+
.description(
|
|
103
|
+
"inspect the plugin registry (actions: list, search <query>, show <id>)",
|
|
104
|
+
)
|
|
103
105
|
.option("--json", "output as JSON")
|
|
106
|
+
.option("--enabled", "list: show only enabled plugins")
|
|
107
|
+
.option("--disabled", "list: show only disabled plugins")
|
|
104
108
|
.action(plugins);
|
|
105
109
|
|
|
106
110
|
program
|