@actiondock/cli 2.0.1 → 2.0.3

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.
@@ -191,16 +191,6 @@ export function registerInfoCommand(program: Command): void {
191
191
  .option("--json", "Output information as JSON")
192
192
  .action(async (patterns: string[] = [], options: any) => {
193
193
  try {
194
- if (options.tree) {
195
- const status = getRegistryStatus();
196
- if (options.json) {
197
- console.log(JSON.stringify(status, null, 2));
198
- } else {
199
- printRegistryTree(status);
200
- }
201
- return;
202
- }
203
-
204
194
  const effectiveIntent = resolveIntent(options.intent, patterns);
205
195
  const shouldFallback = options.fallback !== false;
206
196
 
@@ -214,121 +204,110 @@ export function registerInfoCommand(program: Command): void {
214
204
  if (target.type === "remote") {
215
205
  const remoteInfo = await fetchRemoteInfo(
216
206
  target.serverUrl!,
217
- target.token
207
+ target.token,
208
+ {
209
+ intent: effectiveIntent,
210
+ package: options.package,
211
+ tree: Boolean(options.tree),
212
+ }
218
213
  );
219
214
 
220
215
  if (options.json) {
221
- if (Array.isArray(remoteInfo.linkedPackages) && effectiveIntent) {
222
- const filterRes = filterWithFallbackInfo(
223
- remoteInfo.linkedPackages,
224
- effectiveIntent,
225
- [
226
- (p: any) => p.id,
227
- (p: any) => p.name,
228
- (p: any) => p.description,
229
- (p: any) => p.path,
230
- (p: any) => p.actions,
231
- (p: any) => p.playbooks,
232
- ],
233
- shouldFallback
234
- );
235
- console.log(
236
- JSON.stringify(
237
- {
238
- ...remoteInfo,
239
- linkedPackages: filterRes.items,
240
- isFallback: filterRes.isFallback,
241
- },
242
- null,
243
- 2
244
- )
245
- );
246
- } else {
247
- console.log(JSON.stringify(remoteInfo, null, 2));
216
+ console.log(JSON.stringify(remoteInfo, null, 2));
217
+ return;
218
+ }
219
+
220
+ console.log(
221
+ `Remote ActionDock Server: ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}\n`
222
+ );
223
+
224
+ // Remote Tree
225
+ if (remoteInfo.type === "tree" || (options.tree && remoteInfo.workspaces)) {
226
+ printRegistryTree(remoteInfo);
227
+ return;
228
+ }
229
+
230
+ // Remote Package Detail
231
+ if (remoteInfo.type === "package_detail" || (remoteInfo.id && !remoteInfo.packages)) {
232
+ console.log(`ActionDock Project: ${remoteInfo.name || remoteInfo.id} (${remoteInfo.id})`);
233
+ console.log(`Version: ${remoteInfo.version || "unknown"}`);
234
+ if (remoteInfo.description) console.log(`Description: ${remoteInfo.description}`);
235
+ if (remoteInfo.path || remoteInfo.projectRoot) {
236
+ console.log(`Root: ${remoteInfo.path || remoteInfo.projectRoot}`);
248
237
  }
249
- } else {
250
- console.log(
251
- `Remote ActionDock Server: ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}`
252
- );
253
- if (remoteInfo.id) {
254
- if (effectiveIntent) {
255
- const match = filterWithFallbackInfo(
256
- [remoteInfo],
257
- effectiveIntent,
258
- [
259
- (r: any) => r.id,
260
- (r: any) => r.name,
261
- (r: any) => r.description,
262
- (r: any) => r.actions,
263
- (r: any) => r.playbooks,
264
- ],
265
- shouldFallback
266
- );
267
- if (match.matchedCount === 0 && !shouldFallback) {
268
- console.error(
269
- `Error: Remote project '${remoteInfo.id}' did not match intent '${effectiveIntent}'`
270
- );
271
- process.exit(1);
272
- }
273
- }
274
- console.log(
275
- `Project: ${remoteInfo.name || remoteInfo.id} (${remoteInfo.id})`
276
- );
277
- console.log(`Version: ${remoteInfo.version || "unknown"}`);
278
- if (remoteInfo.description) {
279
- console.log(`Description: ${remoteInfo.description}`);
280
- }
281
- if (Array.isArray(remoteInfo.actions)) {
282
- console.log(`\nActions (${remoteInfo.actions.length}):`);
283
- for (const actId of remoteInfo.actions) {
284
- console.log(` - ${actId}`);
285
- }
286
- }
287
- if (Array.isArray(remoteInfo.playbooks)) {
288
- console.log(`\nPlaybooks (${remoteInfo.playbooks.length}):`);
289
- for (const pbId of remoteInfo.playbooks) {
290
- console.log(` - ${pbId}`);
291
- }
238
+
239
+ const actions = remoteInfo.actionsDetail || remoteInfo.actions || [];
240
+ console.log(`\nActions (${remoteInfo.actionsCount || actions.length}):`);
241
+ for (const act of actions) {
242
+ if (typeof act === "string") {
243
+ console.log(` - ${act}`);
244
+ } else {
245
+ console.log(` - ${(act.id || "").padEnd(28)} ${act.description || ""}`);
292
246
  }
293
- } else if (Array.isArray(remoteInfo.linkedPackages)) {
294
- let list = remoteInfo.linkedPackages;
295
- let isFallback = false;
296
- if (effectiveIntent) {
297
- const filterRes = filterWithFallbackInfo(
298
- list,
299
- effectiveIntent,
300
- [
301
- (p: any) => p.id,
302
- (p: any) => p.name,
303
- (p: any) => p.description,
304
- (p: any) => p.path,
305
- (p: any) => p.actions,
306
- (p: any) => p.playbooks,
307
- ],
308
- shouldFallback
309
- );
310
- if (filterRes.matchedCount === 0 && !shouldFallback) {
311
- console.error(
312
- `Error: No remote packages matched intent '${effectiveIntent}'`
313
- );
314
- process.exit(1);
315
- }
316
- list = filterRes.items;
317
- isFallback = filterRes.isFallback;
247
+ }
248
+
249
+ const playbooks = remoteInfo.playbooksDetail || remoteInfo.playbooks || [];
250
+ console.log(`\nPlaybooks (${remoteInfo.playbooksCount || playbooks.length}):`);
251
+ for (const pb of playbooks) {
252
+ if (typeof pb === "string") {
253
+ console.log(` - ${pb}`);
254
+ } else {
255
+ console.log(` - ${(pb.id || "").padEnd(28)} ${pb.description || ""}`);
318
256
  }
319
- console.log(`Version: ${remoteInfo.version || "2.0.0"}`);
320
- if (isFallback && effectiveIntent) {
257
+ }
258
+
259
+ const declared = remoteInfo.configDeclared || {};
260
+ const declaredKeys = Object.keys(declared);
261
+ if (declaredKeys.length > 0) {
262
+ console.log(`\nDeclared Config Keys:`);
263
+ for (const k of declaredKeys) {
264
+ const item = declared[k];
265
+ const isSec = item?.secret ? " [secret]" : "";
266
+ const def =
267
+ item?.default !== undefined
268
+ ? ` (default: ${JSON.stringify(item.default)})`
269
+ : "";
321
270
  console.log(
322
- `\n(No remote packages matched intent '${effectiveIntent}', showing all linked packages)`
271
+ ` - ${k.padEnd(24)} ${item?.description || ""}${def}${isSec}`
323
272
  );
324
273
  }
325
- console.log(`\nLinked Packages (${list.length}):`);
326
- for (const pkg of list) {
327
- console.log(` - ${pkg.id} (${pkg.path})`);
328
- }
329
- } else {
330
- console.log(JSON.stringify(remoteInfo, null, 2));
331
274
  }
275
+ return;
276
+ }
277
+
278
+ // Remote Package List
279
+ const packages = remoteInfo.packages || remoteInfo.linkedPackages || [];
280
+ if (Array.isArray(packages)) {
281
+ console.log(`Remote ActionDock Packages (${packages.length}):\n`);
282
+ for (const p of packages) {
283
+ console.log(`* ${p.name || p.id} (${p.id}) v${p.version || "unknown"}`);
284
+ if (p.path) console.log(` Path: ${p.path}`);
285
+ if (p.description) console.log(` Desc: ${p.description}`);
286
+ const actNames = Array.isArray(p.actions)
287
+ ? p.actions.map((a: any) => (typeof a === "string" ? a : a.id)).join(", ")
288
+ : "";
289
+ const pbNames = Array.isArray(p.playbooks)
290
+ ? p.playbooks.map((pb: any) => (typeof pb === "string" ? pb : pb.id)).join(", ")
291
+ : "";
292
+ console.log(` Actions (${p.actionsCount || (p.actions ? p.actions.length : 0)}): ${actNames || "(none)"}`);
293
+ console.log(` Playbooks (${p.playbooksCount || (p.playbooks ? p.playbooks.length : 0)}): ${pbNames || "(none)"}`);
294
+ console.log("");
295
+ }
296
+ console.log("Tip: Run 'ad info <package-id> --server <url>' to view detailed package configuration and schema.");
297
+ return;
298
+ }
299
+
300
+ console.log(JSON.stringify(remoteInfo, null, 2));
301
+ return;
302
+ }
303
+
304
+ // Local Tree
305
+ if (options.tree) {
306
+ const status = getRegistryStatus();
307
+ if (options.json) {
308
+ console.log(JSON.stringify(status, null, 2));
309
+ } else {
310
+ printRegistryTree(status);
332
311
  }
333
312
  return;
334
313
  }
@@ -1,13 +1,18 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import {
4
+ fetchRemotePlaybooks,
5
+ fetchRemotePlaybookShow,
4
6
  filterWithFallbackInfo,
5
7
  findProjectRoot,
6
8
  listLinkedPackages,
7
9
  loadActions,
10
+ loadManifest,
8
11
  loadPlaybooks,
9
12
  loadProjectConfig,
13
+ resolveActionProject,
10
14
  resolvePlaybookProject,
15
+ resolveTarget,
11
16
  } from "@actiondock/core";
12
17
  import type { PlaybookDefinition } from "@actiondock/core";
13
18
  import { Command } from "commander";
@@ -23,13 +28,45 @@ export function registerPlaybookCommands(program: Command): void {
23
28
  .command("list [patterns...]")
24
29
  .description("List playbooks in current project or linked packages")
25
30
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
31
+ .option("-P, --package <id>", "Target package ID or path")
32
+ .option("-p, --profile <name>", "Query against a specific profile")
33
+ .option("-s, --server <url>", "Remote server URL")
34
+ .option("-t, --token <token>", "Auth token for remote server")
26
35
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
27
36
  .option("--json", "Output as JSON")
28
- .action((patterns, options) => {
37
+ .action(async (patterns, options) => {
29
38
  try {
30
39
  const effectiveIntent = resolveIntent(options.intent, patterns);
31
40
  const shouldFallback = options.fallback !== false;
32
41
 
42
+ const target = resolveTarget({
43
+ profile: options.profile,
44
+ server: options.server,
45
+ token: options.token,
46
+ });
47
+
48
+ if (target.type === "remote") {
49
+ const remotePbs = await fetchRemotePlaybooks(target.serverUrl!, target.token, {
50
+ intent: effectiveIntent,
51
+ package: options.package,
52
+ });
53
+ if (options.json) {
54
+ console.log(JSON.stringify(remotePbs, null, 2));
55
+ return;
56
+ }
57
+ console.log(
58
+ `Playbooks on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}:\n`
59
+ );
60
+ if (remotePbs.length === 0) {
61
+ console.log(" (No playbooks found)");
62
+ } else {
63
+ for (const p of remotePbs) {
64
+ console.log(` ${p.id.padEnd(24)} ${p.description} (Package: ${p.packageId})`);
65
+ }
66
+ }
67
+ return;
68
+ }
69
+
33
70
  const root = findProjectRoot();
34
71
  if (root) {
35
72
  const config = loadProjectConfig(root);
@@ -228,9 +265,35 @@ This playbook provides task execution guidance for AI Agents.
228
265
  pbCmd
229
266
  .command("show <id>")
230
267
  .description("Show playbook content and metadata (from current project or linked packages)")
268
+ .option("-p, --profile <name>", "Query against a specific profile")
269
+ .option("-s, --server <url>", "Remote server URL")
270
+ .option("-t, --token <token>", "Auth token for remote server")
231
271
  .option("--json", "Output as JSON")
232
- .action((id, options) => {
272
+ .action(async (id, options) => {
233
273
  try {
274
+ const target = resolveTarget({
275
+ profile: options.profile,
276
+ server: options.server,
277
+ token: options.token,
278
+ });
279
+
280
+ if (target.type === "remote") {
281
+ const pb = await fetchRemotePlaybookShow(target.serverUrl!, id, target.token);
282
+ if (options.json) {
283
+ console.log(JSON.stringify(pb, null, 2));
284
+ return;
285
+ }
286
+ console.log(`Playbook: ${pb.id} (Package: ${pb.packageId})`);
287
+ if (pb.description) console.log(`Description: ${pb.description}`);
288
+ if (pb.actions && pb.actions.length > 0) {
289
+ console.log(`Actions: ${pb.actions.join(", ")}`);
290
+ }
291
+ if (pb.filePath) console.log(`File: ${pb.filePath}\n`);
292
+ console.log("--- Content ---");
293
+ console.log(pb.content);
294
+ return;
295
+ }
296
+
234
297
  const resolved = resolvePlaybookProject(id);
235
298
  const pb = resolved.playbook;
236
299
 
@@ -307,7 +370,18 @@ This playbook provides task execution guidance for AI Agents.
307
370
 
308
371
  for (const target of targets) {
309
372
  const config = loadProjectConfig(target.root);
310
- const actions = await loadActions(target.root, config.actionsDir);
373
+ let actionIds = new Set<string>();
374
+ const manifest = loadManifest(target.root);
375
+ if (manifest?.actions) {
376
+ actionIds = new Set(Object.keys(manifest.actions));
377
+ } else {
378
+ try {
379
+ const actions = await loadActions(target.root, config.actionsDir, { autoInstall: false });
380
+ actionIds = new Set(actions.keys());
381
+ } catch {
382
+ // Ignore action loading failure during validation
383
+ }
384
+ }
311
385
 
312
386
  for (const pb of target.playbooks) {
313
387
  if (!pb) continue;
@@ -319,8 +393,13 @@ This playbook provides task execution guidance for AI Agents.
319
393
 
320
394
  if (pb.actions) {
321
395
  for (const actId of pb.actions) {
322
- if (!actions.has(actId)) {
323
- warnings.push(`Referenced action '${actId}' not found in package '${target.packageId}'`);
396
+ if (actionIds.has(actId)) {
397
+ continue;
398
+ }
399
+ try {
400
+ await resolveActionProject(actId, target.root);
401
+ } catch (err: any) {
402
+ warnings.push(err.message);
324
403
  }
325
404
  }
326
405
  }
@@ -235,7 +235,7 @@ export function registerProfileCommands(program: Command): void {
235
235
  if (options.json) {
236
236
  console.log(JSON.stringify({ ok: true, type: "local", message: "Local execution" }, null, 2));
237
237
  } else {
238
- console.log(`Target profile '${target.profileName || "local"}' is local (runs in local Bun runtime).`);
238
+ console.log(`Target profile '${target.profileName || "local"}' is local (runs in local runtime).`);
239
239
  }
240
240
  return;
241
241
  }
@@ -1,8 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import {
3
3
  cancelRemoteRun,
4
+ clearRemoteRuns,
4
5
  createStorage,
5
6
  fetchRemoteRun,
7
+ fetchRemoteRuns,
6
8
  filterWithFallbackInfo,
7
9
  findProjectRoot,
8
10
  listLinkedPackages,
@@ -49,14 +51,50 @@ export function registerRunsCommands(program: Command): void {
49
51
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
50
52
  .option("-a, --action <actionId>", "Filter by action ID")
51
53
  .option("-n, --limit <count>", "Maximum number of records to return", "20")
54
+ .option("-p, --profile <name>", "Query against a specific profile")
55
+ .option("-s, --server <url>", "Remote server URL")
56
+ .option("-t, --token <token>", "Auth token for remote server")
52
57
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
53
58
  .option("--json", "Output as JSON")
54
- .action((patterns, options) => {
59
+ .action(async (patterns, options) => {
55
60
  try {
56
61
  const effectiveIntent = resolveIntent(options.intent, patterns);
57
62
  const shouldFallback = options.fallback !== false;
58
63
  const limit = Number.parseInt(options.limit, 10) || 20;
59
64
 
65
+ const target = resolveTarget({
66
+ profile: options.profile,
67
+ server: options.server,
68
+ token: options.token,
69
+ });
70
+
71
+ if (target.type === "remote") {
72
+ const res = await fetchRemoteRuns(target.serverUrl!, target.token, {
73
+ packageId: options.package,
74
+ actionId: options.action,
75
+ intent: effectiveIntent,
76
+ limit,
77
+ });
78
+ if (options.json) {
79
+ console.log(JSON.stringify(res.items, null, 2));
80
+ return;
81
+ }
82
+ console.log(
83
+ `Execution Runs on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""} (${res.items.length}):\n`
84
+ );
85
+ console.log(
86
+ ` ${"RUN ID".padEnd(38)} ${"ACTION".padEnd(24)} ${"STATUS".padEnd(10)} ${"STARTED"}`
87
+ );
88
+ console.log(" " + "-".repeat(90));
89
+ for (const r of res.items) {
90
+ const time = (r.startedAt || "").replace("T", " ").slice(0, 19);
91
+ console.log(
92
+ ` ${r.id.padEnd(38)} ${r.actionId.padEnd(24)} ${r.status.padEnd(10)} ${time}`
93
+ );
94
+ }
95
+ return;
96
+ }
97
+
60
98
  const targetRoot = options.package
61
99
  ? resolvePackageRoot(options.package)
62
100
  : findProjectRoot();
@@ -315,5 +353,57 @@ export function registerRunsCommands(program: Command): void {
315
353
  process.exit(1);
316
354
  }
317
355
  });
356
+
357
+ // runs clear
358
+ runsCmd
359
+ .command("clear")
360
+ .description("Clear execution run records (locally or on remote server)")
361
+ .option("-P, --package <id>", "Target package ID or path")
362
+ .option("-a, --action <actionId>", "Filter by action ID")
363
+ .option("-p, --profile <name>", "Target profile")
364
+ .option("-s, --server <url>", "Remote server URL")
365
+ .option("-t, --token <token>", "Auth token for remote server")
366
+ .option("--json", "Output as JSON")
367
+ .action(async (options) => {
368
+ try {
369
+ const target = resolveTarget({
370
+ profile: options.profile,
371
+ server: options.server,
372
+ token: options.token,
373
+ });
374
+
375
+ if (target.type === "remote") {
376
+ const res = await clearRemoteRuns(target.serverUrl!, target.token, {
377
+ packageId: options.package,
378
+ actionId: options.action,
379
+ });
380
+ if (options.json) {
381
+ console.log(JSON.stringify(res, null, 2));
382
+ } else {
383
+ console.log(`Cleared ${res.clearedCount} execution run(s) on remote server.`);
384
+ }
385
+ return;
386
+ }
387
+
388
+ const targetRoot = options.package
389
+ ? resolvePackageRoot(options.package)
390
+ : findProjectRoot();
391
+
392
+ if (targetRoot) {
393
+ const projConfig = loadProjectConfig(targetRoot);
394
+ const storage = createStorage(projConfig.id, { projectRoot: targetRoot });
395
+ const count = storage.clearRuns({ actionId: options.action });
396
+ storage.close();
397
+ if (options.json) {
398
+ console.log(JSON.stringify({ ok: true, clearedCount: count }, null, 2));
399
+ } else {
400
+ console.log(`Cleared ${count} execution run(s) in package '${projConfig.id}'.`);
401
+ }
402
+ }
403
+ } catch (err: any) {
404
+ console.error(`Error: ${err.message}`);
405
+ process.exit(1);
406
+ }
407
+ });
318
408
  }
319
409
 
@@ -1,5 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { findProjectRoot, loadProjectConfig, startActionDockServer } from "@actiondock/core";
3
+ import { createActionDockMcpServer } from "@actiondock/mcp";
4
+ import { createMcpHandler } from "@modelcontextprotocol/server";
3
5
  import { Command } from "commander";
4
6
  import { parseByteSize } from "../utils/bytes";
5
7
 
@@ -19,6 +21,7 @@ export function registerServeCommand(program: Command): void {
19
21
  )
20
22
  .option("--max-body <size>", "Maximum allowed JSON request body size (e.g. 1mb, 500kb)", "1mb")
21
23
  .option("--expose-debug-info", "Expose project root path in health and info responses")
24
+ .option("--no-mcp", "Disable unified MCP protocol endpoint at /mcp")
22
25
  .option("-d, --dir <path>", "Project root directory (default: current working directory)")
23
26
  .action(async (options) => {
24
27
  const port = parseInt(options.port, 10) || 5177;
@@ -55,6 +58,30 @@ export function registerServeCommand(program: Command): void {
55
58
  }
56
59
  }
57
60
 
61
+ let mcpHandler: ((req: Request) => Promise<Response | null | undefined>) | undefined;
62
+ const enableMcp = options.mcp !== false;
63
+ if (enableMcp) {
64
+ try {
65
+ const handler = createMcpHandler(
66
+ () => {
67
+ return createActionDockMcpServer({
68
+ projectRoot: projectRoot || undefined,
69
+ });
70
+ },
71
+ {
72
+ onerror: (err) => {
73
+ process.stderr.write(`[MCP HTTP Error] ${err?.message || String(err)}\n`);
74
+ },
75
+ }
76
+ );
77
+ mcpHandler = async (req: Request) => {
78
+ return handler.fetch(req);
79
+ };
80
+ } catch {
81
+ // 忽略 MCP 初始化异常
82
+ }
83
+ }
84
+
58
85
  try {
59
86
  const server = startActionDockServer({
60
87
  port,
@@ -64,6 +91,8 @@ export function registerServeCommand(program: Command): void {
64
91
  corsOrigins,
65
92
  maxBodyBytes,
66
93
  exposeDebugInfo,
94
+ enableMcp,
95
+ mcpHandler,
67
96
  projectRoot: projectRoot || undefined,
68
97
  });
69
98
 
@@ -79,6 +108,9 @@ export function registerServeCommand(program: Command): void {
79
108
  console.log(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`);
80
109
  console.log(` * Max Body Size: ${options.maxBody || "1mb"}`);
81
110
  console.log(` * Health Endpoint: http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${server.port}/api/v1/health`);
111
+ if (enableMcp) {
112
+ console.log(` * MCP Endpoint: http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${server.port}/mcp`);
113
+ }
82
114
  console.log(`======================================================\n`);
83
115
  console.log(`Server is ready to accept remote 'ad run' requests.`);
84
116
  console.log(`Press Ctrl+C to terminate.\n`);