@actiondock/cli 2.0.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/README.md +85 -0
- package/bin/ac.js +4 -0
- package/dist/index.js +44620 -0
- package/package.json +51 -0
- package/src/commands/action.ts +602 -0
- package/src/commands/build.ts +54 -0
- package/src/commands/config.ts +398 -0
- package/src/commands/doctor.ts +64 -0
- package/src/commands/export.ts +70 -0
- package/src/commands/index.ts +43 -0
- package/src/commands/info.ts +539 -0
- package/src/commands/init.ts +24 -0
- package/src/commands/link.ts +74 -0
- package/src/commands/mcp.ts +199 -0
- package/src/commands/playbook.ts +359 -0
- package/src/commands/profile.ts +263 -0
- package/src/commands/runs.ts +319 -0
- package/src/commands/serve.ts +101 -0
- package/src/commands/state.ts +396 -0
- package/src/commands/test.ts +26 -0
- package/src/index.ts +20 -0
- package/src/utils/bytes.ts +36 -0
- package/src/utils/duration.ts +1 -0
- package/src/utils/filter.ts +24 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
fetchRemoteInfo,
|
|
4
|
+
filterWithFallbackInfo,
|
|
5
|
+
findProjectRoot,
|
|
6
|
+
getRegistryStatus,
|
|
7
|
+
listLinkedPackages,
|
|
8
|
+
loadActions,
|
|
9
|
+
loadPlaybooks,
|
|
10
|
+
loadProjectConfig,
|
|
11
|
+
resolvePackageRoot,
|
|
12
|
+
resolveTarget,
|
|
13
|
+
} from "@actiondock/core";
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import { resolveIntent } from "../utils/filter";
|
|
16
|
+
|
|
17
|
+
interface AggregatedPackage {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
path: string;
|
|
23
|
+
actionsCount: number;
|
|
24
|
+
playbooksCount: number;
|
|
25
|
+
actions: string[];
|
|
26
|
+
playbooks: string[];
|
|
27
|
+
configDeclared: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function getProjectDetailInfo(root: string) {
|
|
31
|
+
const config = loadProjectConfig(root);
|
|
32
|
+
const actions = await loadActions(root, config.actionsDir);
|
|
33
|
+
const playbooks = loadPlaybooks(root, config.playbooksDir);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
id: config.id,
|
|
37
|
+
name: config.name,
|
|
38
|
+
version: config.version,
|
|
39
|
+
description: config.description,
|
|
40
|
+
projectRoot: root,
|
|
41
|
+
actionsDir: config.actionsDir || "actions",
|
|
42
|
+
playbooksDir: config.playbooksDir || "playbooks",
|
|
43
|
+
actionsCount: actions.size,
|
|
44
|
+
playbooksCount: playbooks.size,
|
|
45
|
+
actions: Array.from(actions.keys()),
|
|
46
|
+
playbooks: Array.from(playbooks.keys()),
|
|
47
|
+
configDeclared: config.config ? Object.keys(config.config) : [],
|
|
48
|
+
configDef: config.config,
|
|
49
|
+
actionsMap: actions,
|
|
50
|
+
playbooksMap: playbooks,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function projectDetailToJson(info: Awaited<ReturnType<typeof getProjectDetailInfo>>) {
|
|
55
|
+
return {
|
|
56
|
+
id: info.id,
|
|
57
|
+
name: info.name,
|
|
58
|
+
version: info.version,
|
|
59
|
+
description: info.description,
|
|
60
|
+
projectRoot: info.projectRoot,
|
|
61
|
+
actionsDir: info.actionsDir,
|
|
62
|
+
playbooksDir: info.playbooksDir,
|
|
63
|
+
actionsCount: info.actionsCount,
|
|
64
|
+
playbooksCount: info.playbooksCount,
|
|
65
|
+
actions: info.actions,
|
|
66
|
+
playbooks: info.playbooks,
|
|
67
|
+
configDeclared: info.configDeclared,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function printProjectDetail(info: Awaited<ReturnType<typeof getProjectDetailInfo>>) {
|
|
72
|
+
console.log(`ActionDock Project: ${info.name} (${info.id})`);
|
|
73
|
+
console.log(`Version: ${info.version}`);
|
|
74
|
+
if (info.description) console.log(`Description: ${info.description}`);
|
|
75
|
+
console.log(`Root: ${info.projectRoot}`);
|
|
76
|
+
console.log(`\nActions (${info.actionsCount}):`);
|
|
77
|
+
for (const [id, act] of info.actionsMap.entries()) {
|
|
78
|
+
console.log(` - ${id.padEnd(28)} ${act.description || ""}`);
|
|
79
|
+
}
|
|
80
|
+
console.log(`\nPlaybooks (${info.playbooksCount}):`);
|
|
81
|
+
for (const [id, pb] of info.playbooksMap.entries()) {
|
|
82
|
+
console.log(` - ${id.padEnd(28)} ${pb.description || ""}`);
|
|
83
|
+
}
|
|
84
|
+
if (info.configDeclared.length > 0) {
|
|
85
|
+
console.log(`\nDeclared Config Keys:`);
|
|
86
|
+
for (const k of info.configDeclared) {
|
|
87
|
+
const item = info.configDef?.[k];
|
|
88
|
+
const isSec = item?.secret ? " [secret]" : "";
|
|
89
|
+
const def =
|
|
90
|
+
item?.default !== undefined
|
|
91
|
+
? ` (default: ${JSON.stringify(item.default)})`
|
|
92
|
+
: "";
|
|
93
|
+
console.log(
|
|
94
|
+
` - ${k.padEnd(24)} ${item?.description || ""}${def}${isSec}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function printAggregatedPackages(
|
|
101
|
+
packages: AggregatedPackage[],
|
|
102
|
+
options?: { header?: string; showTip?: boolean }
|
|
103
|
+
) {
|
|
104
|
+
if (options?.header) {
|
|
105
|
+
console.log(options.header);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`ActionDock Linked Packages (${packages.length}):\n`);
|
|
108
|
+
}
|
|
109
|
+
for (const p of packages) {
|
|
110
|
+
console.log(`* ${p.name} (${p.id}) v${p.version}`);
|
|
111
|
+
console.log(` Path: ${p.path}`);
|
|
112
|
+
if (p.description) {
|
|
113
|
+
console.log(` Desc: ${p.description}`);
|
|
114
|
+
}
|
|
115
|
+
console.log(
|
|
116
|
+
` Actions (${p.actionsCount}): ${p.actions.join(", ") || "(none)"}`
|
|
117
|
+
);
|
|
118
|
+
console.log(
|
|
119
|
+
` Playbooks (${p.playbooksCount}): ${p.playbooks.join(", ") || "(none)"}`
|
|
120
|
+
);
|
|
121
|
+
console.log("");
|
|
122
|
+
}
|
|
123
|
+
if (options?.showTip !== false) {
|
|
124
|
+
console.log(
|
|
125
|
+
"Tip: Run 'ac info <package-id>' to view detailed package configuration and schema."
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function printRegistryTree(status: ReturnType<typeof getRegistryStatus>): void {
|
|
131
|
+
const hasWorkspaces = status.workspaces.length > 0;
|
|
132
|
+
const hasPackages = status.packages.length > 0;
|
|
133
|
+
|
|
134
|
+
if (!hasWorkspaces && !hasPackages) {
|
|
135
|
+
console.log("[INFO] No ActionDock packages or workspaces currently linked.");
|
|
136
|
+
console.log(" Run 'ac link' inside an Action package or workspace to register it.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log("[ActionDock Workspace & Package Tree]\n");
|
|
141
|
+
|
|
142
|
+
if (hasWorkspaces) {
|
|
143
|
+
console.log("Workspaces:");
|
|
144
|
+
for (const ws of status.workspaces) {
|
|
145
|
+
const tag = ws.status === "active" ? "[OK]" : "[STALE]";
|
|
146
|
+
console.log(` ${tag} ${ws.path} (${ws.packagesCount} package${ws.packagesCount === 1 ? "" : "s"})`);
|
|
147
|
+
if (ws.children && ws.children.length > 0) {
|
|
148
|
+
ws.children.forEach((child: any, idx: number) => {
|
|
149
|
+
const isLast = idx === ws.children!.length - 1;
|
|
150
|
+
const prefix = isLast ? " +-- " : " |-- ";
|
|
151
|
+
console.log(`${prefix}${child.id} (v${child.version}) -> ${child.path}`);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (hasPackages) {
|
|
158
|
+
if (hasWorkspaces) console.log("");
|
|
159
|
+
console.log("Standalone Packages:");
|
|
160
|
+
for (const pkg of status.packages) {
|
|
161
|
+
const tag = pkg.status === "active" ? "[OK]" : "[STALE]";
|
|
162
|
+
console.log(` ${tag} ${pkg.id} (v${pkg.version || "unknown"}) -> ${pkg.path}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`\n[Summary] Total: ${status.totalPackagesCount} active package(s), ${status.workspaces.length} workspace(s)`);
|
|
167
|
+
if (status.staleCount > 0) {
|
|
168
|
+
console.log(`[WARN] ${status.staleCount} stale entry/entries detected. Run 'ac unlink --prune' to clean up.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function registerInfoCommand(program: Command): void {
|
|
173
|
+
program
|
|
174
|
+
.command("info [patterns...]")
|
|
175
|
+
.description(
|
|
176
|
+
"Display information about current project, linked package, or remote target"
|
|
177
|
+
)
|
|
178
|
+
.option(
|
|
179
|
+
"-i, --intent <pattern>",
|
|
180
|
+
"Regex or fuzzy intent filter; falls back to full list when no match"
|
|
181
|
+
)
|
|
182
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
183
|
+
.option("--tree", "Display packages in hierarchical tree view grouped by workspace")
|
|
184
|
+
.option("-p, --profile <name>", "Query against a specific profile")
|
|
185
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
186
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
187
|
+
.option(
|
|
188
|
+
"--no-fallback",
|
|
189
|
+
"Disable fallback to full list when no items match intent"
|
|
190
|
+
)
|
|
191
|
+
.option("--json", "Output information as JSON")
|
|
192
|
+
.action(async (patterns: string[] = [], options: any) => {
|
|
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
|
+
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
205
|
+
const shouldFallback = options.fallback !== false;
|
|
206
|
+
|
|
207
|
+
const target = resolveTarget({
|
|
208
|
+
profile: options.profile,
|
|
209
|
+
server: options.server,
|
|
210
|
+
token: options.token,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// 1. Remote target handling
|
|
214
|
+
if (target.type === "remote") {
|
|
215
|
+
const remoteInfo = await fetchRemoteInfo(
|
|
216
|
+
target.serverUrl!,
|
|
217
|
+
target.token
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
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));
|
|
248
|
+
}
|
|
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
|
+
}
|
|
292
|
+
}
|
|
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;
|
|
318
|
+
}
|
|
319
|
+
console.log(`Version: ${remoteInfo.version || "2.0.0"}`);
|
|
320
|
+
if (isFallback && effectiveIntent) {
|
|
321
|
+
console.log(
|
|
322
|
+
`\n(No remote packages matched intent '${effectiveIntent}', showing all linked packages)`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
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
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 2. Explicit package option (-P, --package)
|
|
337
|
+
if (options.package) {
|
|
338
|
+
const directRoot = resolvePackageRoot(options.package);
|
|
339
|
+
if (directRoot) {
|
|
340
|
+
const detail = await getProjectDetailInfo(directRoot);
|
|
341
|
+
if (options.json) {
|
|
342
|
+
console.log(JSON.stringify(projectDetailToJson(detail), null, 2));
|
|
343
|
+
} else {
|
|
344
|
+
printProjectDetail(detail);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
console.error(
|
|
349
|
+
`Error: Package '${options.package}' not found in linked packages or path`
|
|
350
|
+
);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 3. Collect local candidate packages (current project + linked packages)
|
|
355
|
+
const currentRoot = findProjectRoot();
|
|
356
|
+
const linkedList = listLinkedPackages();
|
|
357
|
+
const aggregated: AggregatedPackage[] = [];
|
|
358
|
+
const seenPaths = new Set<string>();
|
|
359
|
+
|
|
360
|
+
if (currentRoot) {
|
|
361
|
+
try {
|
|
362
|
+
const config = loadProjectConfig(currentRoot);
|
|
363
|
+
const actions = await loadActions(currentRoot, config.actionsDir);
|
|
364
|
+
const playbooks = loadPlaybooks(currentRoot, config.playbooksDir);
|
|
365
|
+
aggregated.push({
|
|
366
|
+
id: config.id,
|
|
367
|
+
name: config.name,
|
|
368
|
+
version: config.version,
|
|
369
|
+
description: config.description,
|
|
370
|
+
path: currentRoot,
|
|
371
|
+
actionsCount: actions.size,
|
|
372
|
+
playbooksCount: playbooks.size,
|
|
373
|
+
actions: Array.from(actions.keys()),
|
|
374
|
+
playbooks: Array.from(playbooks.keys()),
|
|
375
|
+
configDeclared: config.config ? Object.keys(config.config) : [],
|
|
376
|
+
});
|
|
377
|
+
seenPaths.add(currentRoot);
|
|
378
|
+
} catch {
|
|
379
|
+
// Ignore broken project root
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for (const pkg of linkedList) {
|
|
384
|
+
if (!existsSync(pkg.path)) continue;
|
|
385
|
+
if (seenPaths.has(pkg.path)) continue;
|
|
386
|
+
try {
|
|
387
|
+
const config = loadProjectConfig(pkg.path);
|
|
388
|
+
const actions = await loadActions(pkg.path, config.actionsDir);
|
|
389
|
+
const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
|
|
390
|
+
|
|
391
|
+
aggregated.push({
|
|
392
|
+
id: config.id,
|
|
393
|
+
name: config.name,
|
|
394
|
+
version: config.version,
|
|
395
|
+
description: config.description,
|
|
396
|
+
path: pkg.path,
|
|
397
|
+
actionsCount: actions.size,
|
|
398
|
+
playbooksCount: playbooks.size,
|
|
399
|
+
actions: Array.from(actions.keys()),
|
|
400
|
+
playbooks: Array.from(playbooks.keys()),
|
|
401
|
+
configDeclared: config.config ? Object.keys(config.config) : [],
|
|
402
|
+
});
|
|
403
|
+
seenPaths.add(pkg.path);
|
|
404
|
+
} catch {
|
|
405
|
+
// Ignore broken linked package
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 4. Case: No pattern / intent specified
|
|
410
|
+
if (!effectiveIntent) {
|
|
411
|
+
if (currentRoot) {
|
|
412
|
+
const detail = await getProjectDetailInfo(currentRoot);
|
|
413
|
+
if (options.json) {
|
|
414
|
+
console.log(JSON.stringify(projectDetailToJson(detail), null, 2));
|
|
415
|
+
} else {
|
|
416
|
+
printProjectDetail(detail);
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (aggregated.length === 0) {
|
|
422
|
+
console.log(
|
|
423
|
+
"No ActionDock project in current directory, and no packages linked."
|
|
424
|
+
);
|
|
425
|
+
console.log(
|
|
426
|
+
"Run 'ac link' inside an Action package to register it."
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (options.json) {
|
|
432
|
+
console.log(
|
|
433
|
+
JSON.stringify({ linkedPackages: aggregated }, null, 2)
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
printAggregatedPackages(aggregated);
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 5. Case: Pattern / intent specified
|
|
442
|
+
// 5.1 Check if single positional argument directly matches an exact path or package ID/slug
|
|
443
|
+
if (patterns.length === 1 && !options.intent) {
|
|
444
|
+
const directRoot = resolvePackageRoot(patterns[0]);
|
|
445
|
+
if (directRoot) {
|
|
446
|
+
const detail = await getProjectDetailInfo(directRoot);
|
|
447
|
+
if (options.json) {
|
|
448
|
+
console.log(JSON.stringify(projectDetailToJson(detail), null, 2));
|
|
449
|
+
} else {
|
|
450
|
+
printProjectDetail(detail);
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (aggregated.length === 0) {
|
|
457
|
+
console.error(
|
|
458
|
+
`Error: No ActionDock project or linked packages available to match '${effectiveIntent}'`
|
|
459
|
+
);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// 5.2 Intent filtering across candidate packages
|
|
464
|
+
const filterRes = filterWithFallbackInfo(
|
|
465
|
+
aggregated,
|
|
466
|
+
effectiveIntent,
|
|
467
|
+
[
|
|
468
|
+
(p) => p.id,
|
|
469
|
+
(p) => p.name,
|
|
470
|
+
(p) => p.description,
|
|
471
|
+
(p) => p.path,
|
|
472
|
+
(p) => p.actions,
|
|
473
|
+
(p) => p.playbooks,
|
|
474
|
+
],
|
|
475
|
+
shouldFallback
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
// Subcase A: 0 items matched
|
|
479
|
+
if (filterRes.matchedCount === 0) {
|
|
480
|
+
if (!shouldFallback) {
|
|
481
|
+
if (options.json) {
|
|
482
|
+
console.log(JSON.stringify({ linkedPackages: [] }, null, 2));
|
|
483
|
+
} else {
|
|
484
|
+
console.error(
|
|
485
|
+
`Error: No packages matched intent '${effectiveIntent}'`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Fallback enabled: display all packages with a notice
|
|
492
|
+
if (options.json) {
|
|
493
|
+
console.log(
|
|
494
|
+
JSON.stringify(
|
|
495
|
+
{ linkedPackages: filterRes.items, isFallback: true },
|
|
496
|
+
null,
|
|
497
|
+
2
|
|
498
|
+
)
|
|
499
|
+
);
|
|
500
|
+
} else {
|
|
501
|
+
console.log(
|
|
502
|
+
`(No linked packages matched intent '${effectiveIntent}', showing all packages)\n`
|
|
503
|
+
);
|
|
504
|
+
printAggregatedPackages(filterRes.items);
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Subcase B: Exactly 1 item matched -> Display its full project detail
|
|
510
|
+
if (filterRes.matchedCount === 1 && !filterRes.isFallback) {
|
|
511
|
+
const matchedPkg = filterRes.items[0];
|
|
512
|
+
const detail = await getProjectDetailInfo(matchedPkg.path);
|
|
513
|
+
if (options.json) {
|
|
514
|
+
console.log(JSON.stringify(projectDetailToJson(detail), null, 2));
|
|
515
|
+
} else {
|
|
516
|
+
printProjectDetail(detail);
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Subcase C: Multiple items matched -> Display filtered summary list
|
|
522
|
+
if (options.json) {
|
|
523
|
+
console.log(
|
|
524
|
+
JSON.stringify({ linkedPackages: filterRes.items }, null, 2)
|
|
525
|
+
);
|
|
526
|
+
} else {
|
|
527
|
+
printAggregatedPackages(filterRes.items, {
|
|
528
|
+
header: `ActionDock Linked Packages (${filterRes.matchedCount} matches for '${effectiveIntent}'):\n`,
|
|
529
|
+
showTip: true,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
} catch (err: any) {
|
|
533
|
+
console.error(`Error: ${err.message}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { initProject } from "@actiondock/core";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
export function registerInitCommand(program: Command): void {
|
|
5
|
+
program
|
|
6
|
+
.command("init [directory]")
|
|
7
|
+
.description("Initialize a new ActionDock project")
|
|
8
|
+
.option("-i, --id <id>", "Project identifier")
|
|
9
|
+
.option("-n, --name <name>", "Project display name")
|
|
10
|
+
.option("-d, --desc <description>", "Project description")
|
|
11
|
+
.action((dir = ".", options) => {
|
|
12
|
+
try {
|
|
13
|
+
initProject(dir, {
|
|
14
|
+
id: options.id,
|
|
15
|
+
name: options.name,
|
|
16
|
+
description: options.desc,
|
|
17
|
+
});
|
|
18
|
+
console.log(`Successfully initialized ActionDock project in ${dir}`);
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
console.error(`Error: ${err.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
linkPackage,
|
|
3
|
+
pruneRegistry,
|
|
4
|
+
unlinkPackage,
|
|
5
|
+
} from "@actiondock/core";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
|
|
8
|
+
export function registerLinkCommands(program: Command): void {
|
|
9
|
+
// ac link [path]
|
|
10
|
+
program
|
|
11
|
+
.command("link [path]")
|
|
12
|
+
.option("-r, --recursive", "Recursively discover and link packages in subdirectories")
|
|
13
|
+
.description("Link local Action package(s) or workspace directory into global registry for instant cross-directory execution")
|
|
14
|
+
.action(async (targetPath, options) => {
|
|
15
|
+
try {
|
|
16
|
+
const result = linkPackage(targetPath || process.cwd(), undefined, {
|
|
17
|
+
recursive: options.recursive,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (result.isWorkspace) {
|
|
21
|
+
console.log(`[OK] Linked workspace '${result.path}' (${result.entries.length} package${result.entries.length > 1 ? "s" : ""}):`);
|
|
22
|
+
for (const e of result.entries) {
|
|
23
|
+
console.log(` - ${e.id} (v${e.version}) -> ${e.path}`);
|
|
24
|
+
}
|
|
25
|
+
console.log("[INFO] Sub-packages added to this workspace will be automatically discovered.");
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`[OK] Linked package '${result.id}' (v${result.version}) from ${result.path}`);
|
|
28
|
+
}
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
console.error(`Error: ${err.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ac unlink [id|path]
|
|
36
|
+
program
|
|
37
|
+
.command("unlink [identifier]")
|
|
38
|
+
.option("-p, --prune", "Automatically scan and prune any stale/missing paths from registry")
|
|
39
|
+
.description("Unlink a package, workspace, or prune stale entries from global developer registry")
|
|
40
|
+
.action((identifier, options) => {
|
|
41
|
+
try {
|
|
42
|
+
if (options.prune || identifier === "--prune") {
|
|
43
|
+
const result = pruneRegistry();
|
|
44
|
+
const totalPruned = result.prunedPackages.length + result.prunedWorkspaces.length;
|
|
45
|
+
if (totalPruned === 0) {
|
|
46
|
+
console.log("[OK] No stale registry entries found.");
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`[OK] Pruned ${result.prunedWorkspaces.length} workspace(s) and ${result.prunedPackages.length} package(s) from registry.`);
|
|
49
|
+
for (const ws of result.prunedWorkspaces) {
|
|
50
|
+
console.log(` - [workspace] ${ws.path}`);
|
|
51
|
+
}
|
|
52
|
+
for (const pkg of result.prunedPackages) {
|
|
53
|
+
console.log(` - [package] ${pkg.id} (${pkg.path})`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const removed = unlinkPackage(identifier || process.cwd());
|
|
60
|
+
if (removed) {
|
|
61
|
+
if (removed.type === "workspace") {
|
|
62
|
+
console.log(`[OK] Unlinked workspace '${removed.path}' (${removed.packagesCount || 0} package${(removed.packagesCount || 0) > 1 ? "s" : ""} unlinked)`);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(`[OK] Unlinked package '${removed.id}' (${removed.path})`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
console.log(`Package or workspace '${identifier || process.cwd()}' was not linked in registry`);
|
|
68
|
+
}
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
console.error(`Error: ${err.message}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|