@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/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@actiondock/cli",
3
+ "version": "2.0.0",
4
+ "description": "ActionDock CLI facade for developing, testing, building, and exporting standalone Agent Actions and Skills",
5
+ "type": "module",
6
+ "bin": {
7
+ "ac": "./bin/ac.js"
8
+ },
9
+ "main": "./src/index.ts",
10
+ "module": "./src/index.ts",
11
+ "types": "./src/index.ts",
12
+ "files": [
13
+ "bin",
14
+ "src",
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "engines": {
19
+ "bun": ">=1.2.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "scripts": {
26
+ "test": "bun test",
27
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun"
28
+ },
29
+ "dependencies": {
30
+ "@actiondock/core": "^2.0.0",
31
+ "@actiondock/mcp": "^2.0.0",
32
+ "@actiondock/sdk": "^2.0.0",
33
+ "commander": "^13.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/bun": "latest",
37
+ "typescript": "^5.7.0"
38
+ },
39
+ "keywords": ["actiondock", "ac", "agent", "cli", "skill", "builder", "mcp"],
40
+ "author": "team4u",
41
+ "license": "Apache-2.0",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/team4u/actiondock.git",
45
+ "directory": "packages/cli"
46
+ },
47
+ "homepage": "https://github.com/team4u/actiondock#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/team4u/actiondock/issues"
50
+ }
51
+ }
@@ -0,0 +1,602 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import {
4
+ ActionRunner,
5
+ createStorage,
6
+ executeRemoteAction,
7
+ fetchRemoteActions,
8
+ fetchRemoteActionShow,
9
+ filterWithFallbackInfo,
10
+ findProjectRoot,
11
+ getPackageSlug,
12
+ listLinkedPackages,
13
+ loadActions,
14
+ loadProjectConfig,
15
+ resolveActionProject,
16
+ resolveTarget,
17
+ validateSchema,
18
+ } from "@actiondock/core";
19
+ import { Command } from "commander";
20
+ import { parseDuration } from "../utils/duration";
21
+ import { resolveIntent } from "../utils/filter";
22
+
23
+ /**
24
+ * 注册 Action 相关的 CLI 命令集合(包括 action list, action describe, action run, action new, action test 以及顶层 run 别名)。
25
+ *
26
+ * @param program Commander 根程序对象
27
+ */
28
+ export function registerActionCommands(program: Command): void {
29
+ const actionCmd = program
30
+ .command("action")
31
+ .description("Manage and execute Actions");
32
+
33
+ // action list
34
+ actionCmd
35
+ .command("list [patterns...]")
36
+ .description("List actions in current project, linked packages, or remote profile")
37
+ .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
38
+ .option("-p, --profile <name>", "Execute or query against a specific profile")
39
+ .option("-s, --server <url>", "Remote server URL")
40
+ .option("-t, --token <token>", "Auth token for remote server")
41
+ .option("--no-fallback", "Disable fallback to full list when no items match intent")
42
+ .option("--json", "Output as JSON")
43
+ .action(async (patterns, options) => {
44
+ try {
45
+ const effectiveIntent = resolveIntent(options.intent, patterns);
46
+ const shouldFallback = options.fallback !== false;
47
+
48
+ const target = resolveTarget({
49
+ profile: options.profile,
50
+ server: options.server,
51
+ token: options.token,
52
+ });
53
+
54
+ if (target.type === "remote") {
55
+ let list = await fetchRemoteActions(
56
+ target.serverUrl!,
57
+ target.token,
58
+ effectiveIntent
59
+ );
60
+
61
+ let isFallback = false;
62
+ if (list.length === 0 && effectiveIntent && shouldFallback) {
63
+ list = await fetchRemoteActions(target.serverUrl!, target.token);
64
+ isFallback = true;
65
+ }
66
+
67
+ if (options.json) {
68
+ console.log(JSON.stringify(list, null, 2));
69
+ } else {
70
+ console.log(
71
+ `Actions on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}:\n`
72
+ );
73
+ if (isFallback && effectiveIntent) {
74
+ console.log(`(No remote actions matched intent '${effectiveIntent}', showing all actions)\n`);
75
+ }
76
+ for (const a of list) {
77
+ console.log(` ${a.id.padEnd(28)} ${a.description}`);
78
+ }
79
+ }
80
+ return;
81
+ }
82
+
83
+ const root = findProjectRoot();
84
+ if (root) {
85
+ const config = loadProjectConfig(root);
86
+ const actions = await loadActions(root, config.actionsDir);
87
+ const rawList = Array.from(actions.values()).map((a) => ({
88
+ id: a.id,
89
+ description: a.description || "",
90
+ packageId: config.id,
91
+ }));
92
+
93
+ const filterRes = filterWithFallbackInfo(
94
+ rawList,
95
+ effectiveIntent,
96
+ [(a) => a.id, (a) => a.description, (a) => a.packageId],
97
+ shouldFallback
98
+ );
99
+
100
+ if (options.json) {
101
+ console.log(JSON.stringify(filterRes.items, null, 2));
102
+ } else {
103
+ console.log(`Actions in ${config.id} (${root}):\n`);
104
+ if (filterRes.isFallback && effectiveIntent) {
105
+ console.log(`(No actions matched intent '${effectiveIntent}', showing all actions)\n`);
106
+ }
107
+ for (const a of filterRes.items) {
108
+ console.log(` ${a.id.padEnd(28)} ${a.description}`);
109
+ }
110
+ }
111
+ } else {
112
+ // List actions across all linked packages
113
+ const linkedList = listLinkedPackages();
114
+ if (linkedList.length === 0) {
115
+ console.log("No ActionDock project in current directory, and no packages linked.");
116
+ console.log("Run 'ac link' inside an Action package to register it.");
117
+ return;
118
+ }
119
+
120
+ const aggregated: Array<{
121
+ packageId: string;
122
+ packageName: string;
123
+ path: string;
124
+ actions: Array<{ id: string; description: string }>;
125
+ }> = [];
126
+
127
+ for (const pkg of linkedList) {
128
+ if (!existsSync(pkg.path)) continue;
129
+ try {
130
+ const config = loadProjectConfig(pkg.path);
131
+ const actions = await loadActions(pkg.path, config.actionsDir);
132
+ const pkgActions = Array.from(actions.values()).map((a) => ({
133
+ id: a.id,
134
+ description: a.description || "",
135
+ }));
136
+
137
+ aggregated.push({
138
+ packageId: pkg.id,
139
+ packageName: pkg.name,
140
+ path: pkg.path,
141
+ actions: pkgActions,
142
+ });
143
+ } catch {
144
+ // Ignore broken linked package
145
+ }
146
+ }
147
+
148
+ let filteredPackages: typeof aggregated = [];
149
+ if (!effectiveIntent) {
150
+ filteredPackages = aggregated;
151
+ } else {
152
+ for (const pkg of aggregated) {
153
+ const pkgMatches = filterWithFallbackInfo(
154
+ [pkg],
155
+ effectiveIntent,
156
+ [(p) => p.packageId, (p) => p.packageName, (p) => p.path],
157
+ false
158
+ ).matchedCount > 0;
159
+
160
+ if (pkgMatches) {
161
+ filteredPackages.push(pkg);
162
+ } else {
163
+ const matchedActions = filterWithFallbackInfo(
164
+ pkg.actions,
165
+ effectiveIntent,
166
+ [(a) => a.id, (a) => a.description],
167
+ false
168
+ ).items;
169
+
170
+ if (matchedActions.length > 0) {
171
+ filteredPackages.push({
172
+ ...pkg,
173
+ actions: matchedActions,
174
+ });
175
+ }
176
+ }
177
+ }
178
+
179
+ if (filteredPackages.length === 0 && shouldFallback) {
180
+ filteredPackages = aggregated;
181
+ }
182
+ }
183
+
184
+ if (options.json) {
185
+ console.log(JSON.stringify(filteredPackages, null, 2));
186
+ } else {
187
+ console.log("Linked Action Packages:\n");
188
+ for (const pkg of filteredPackages) {
189
+ console.log(`* Package: ${pkg.packageId} (${pkg.path})`);
190
+ for (const a of pkg.actions) {
191
+ console.log(` - ${a.id.padEnd(26)} ${a.description}`);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ } catch (err: any) {
197
+ console.error(`Error: ${err.message}`);
198
+ process.exit(1);
199
+ }
200
+ });
201
+
202
+
203
+ // action create / new
204
+ actionCmd
205
+ .command("create <id>")
206
+ .alias("new")
207
+ .description("Scaffold a new Action definition file")
208
+ .option("-d, --desc <description>", "Action description")
209
+ .option("-f, --file <filePath>", "Target file path relative to actions dir")
210
+ .action(async (id, options) => {
211
+ const root = findProjectRoot();
212
+ if (!root) {
213
+ console.error("Error: Not in an ActionDock project (actiondock.json not found)");
214
+ process.exit(1);
215
+ }
216
+ try {
217
+ const config = loadProjectConfig(root);
218
+ const actionsDir = resolve(root, config.actionsDir || "actions");
219
+ if (!existsSync(actionsDir)) {
220
+ mkdirSync(actionsDir, { recursive: true });
221
+ }
222
+
223
+ const cleanName = getPackageSlug(id);
224
+ const targetRelFile = options.file || `${cleanName}.ts`;
225
+ const targetFullFile = resolve(actionsDir, targetRelFile);
226
+
227
+ if (existsSync(targetFullFile)) {
228
+ console.error(`Error: Target action file already exists at ${targetFullFile}`);
229
+ process.exit(1);
230
+ }
231
+
232
+ const desc = options.desc || `Action ${id}`;
233
+ const template = `import { defineAction } from "@actiondock/sdk";
234
+
235
+ export interface Input {
236
+ exampleParam?: string;
237
+ }
238
+
239
+ export interface Output {
240
+ success: boolean;
241
+ result?: unknown;
242
+ }
243
+
244
+ export default defineAction<Input, Output>({
245
+ id: "${id}",
246
+ description: "${desc}",
247
+
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ exampleParam: {
252
+ type: "string",
253
+ description: "Example parameter description",
254
+ },
255
+ },
256
+ required: [],
257
+ },
258
+
259
+ outputSchema: {
260
+ type: "object",
261
+ properties: {
262
+ success: { type: "boolean" },
263
+ result: {},
264
+ },
265
+ required: ["success"],
266
+ },
267
+
268
+ async run(input, ctx) {
269
+ ctx.log.info("Running ${id}", input);
270
+
271
+ // Access config: ctx.config.get("MY_CONFIG")
272
+ // Access state: await ctx.state.get("my_key") / await ctx.state.set("my_key", val)
273
+ // Call action: await ctx.actions.invoke(otherAction, input)
274
+
275
+ return {
276
+ success: true,
277
+ result: input.exampleParam || "done",
278
+ };
279
+ },
280
+ });
281
+ `;
282
+
283
+ writeFileSync(targetFullFile, template, "utf-8");
284
+ console.log(`[OK] Created Action '${id}' at ${targetFullFile}`);
285
+ console.log(`\nTo run this action:`);
286
+ console.log(` ac action run ${id} --input '{"exampleParam": "hello"}'`);
287
+ } catch (err: any) {
288
+ console.error(`Error: ${err.message}`);
289
+ process.exit(1);
290
+ }
291
+ });
292
+
293
+ // action show / describe
294
+ actionCmd
295
+ .command("show <id>")
296
+ .alias("describe")
297
+ .description("Show action definition, schema, and description")
298
+ .option("-p, --profile <name>", "Execute or query against a specific profile")
299
+ .option("-s, --server <url>", "Remote server URL")
300
+ .option("-t, --token <token>", "Auth token for remote server")
301
+ .option("--json", "Output as JSON")
302
+ .action(async (id, options) => {
303
+ try {
304
+ const target = resolveTarget({
305
+ profile: options.profile,
306
+ server: options.server,
307
+ token: options.token,
308
+ });
309
+
310
+ if (target.type === "remote") {
311
+ const detail = await fetchRemoteActionShow(
312
+ target.serverUrl!,
313
+ id,
314
+ target.token
315
+ );
316
+ if (options.json) {
317
+ console.log(JSON.stringify(detail, null, 2));
318
+ } else {
319
+ console.log(`Action: ${detail.id}`);
320
+ if (detail.packageId) console.log(`Package: ${detail.packageId}`);
321
+ if (detail.description) console.log(`Description: ${detail.description}`);
322
+ if (detail.inputSchema) {
323
+ console.log("\nInput Schema:");
324
+ console.log(JSON.stringify(detail.inputSchema, null, 2));
325
+ }
326
+ if (detail.outputSchema) {
327
+ console.log("\nOutput Schema:");
328
+ console.log(JSON.stringify(detail.outputSchema, null, 2));
329
+ }
330
+ }
331
+ return;
332
+ }
333
+
334
+ const resolved = await resolveActionProject(id);
335
+ const config = loadProjectConfig(resolved.projectRoot);
336
+ const actions = await loadActions(resolved.projectRoot, config.actionsDir);
337
+ const action = actions.get(resolved.actionId);
338
+ if (!action) {
339
+ console.error(`Error: Action '${resolved.actionId}' not found in package '${resolved.packageId}'`);
340
+ process.exit(1);
341
+ }
342
+
343
+ if (options.json) {
344
+ console.log(
345
+ JSON.stringify(
346
+ {
347
+ id: action.id,
348
+ packageId: resolved.packageId,
349
+ description: action.description,
350
+ inputSchema: action.inputSchema,
351
+ outputSchema: action.outputSchema,
352
+ },
353
+ null,
354
+ 2
355
+ )
356
+ );
357
+ } else {
358
+ console.log(`Action: ${action.id}`);
359
+ console.log(`Package: ${resolved.packageId} (${resolved.projectRoot})`);
360
+ if (action.description) console.log(`Description: ${action.description}`);
361
+ if (action.inputSchema) {
362
+ console.log("\nInput Schema:");
363
+ console.log(JSON.stringify(action.inputSchema, null, 2));
364
+ }
365
+ if (action.outputSchema) {
366
+ console.log("\nOutput Schema:");
367
+ console.log(JSON.stringify(action.outputSchema, null, 2));
368
+ }
369
+ }
370
+ } catch (err: any) {
371
+ console.error(`Error: ${err.message}`);
372
+ process.exit(1);
373
+ }
374
+ });
375
+
376
+ // action validate
377
+ actionCmd
378
+ .command("validate [id]")
379
+ .description("Validate action schemas and definitions")
380
+ .option("--json", "Output as JSON")
381
+ .action(async (id, options) => {
382
+ const root = findProjectRoot();
383
+ if (!root) {
384
+ console.error("Error: Not in an ActionDock project");
385
+ process.exit(1);
386
+ }
387
+ try {
388
+ const config = loadProjectConfig(root);
389
+ const actions = await loadActions(root, config.actionsDir);
390
+
391
+ const toValidate = id ? [actions.get(id)].filter(Boolean) : Array.from(actions.values());
392
+ if (id && toValidate.length === 0) {
393
+ console.error(`Error: Action '${id}' not found in project`);
394
+ process.exit(1);
395
+ }
396
+
397
+ const results: Array<{ id: string; valid: boolean; errors: string[] }> = [];
398
+
399
+ for (const act of toValidate as any[]) {
400
+ const errors: string[] = [];
401
+ if (!act.id) errors.push("Missing id property");
402
+ if (!act.run || typeof act.run !== "function") errors.push("Missing run method");
403
+ if (act.inputSchema) {
404
+ const vRes = validateSchema(act.inputSchema, {});
405
+ // Check if schema itself is valid
406
+ if (typeof act.inputSchema !== "object") {
407
+ errors.push("Invalid inputSchema object");
408
+ }
409
+ }
410
+ if (act.outputSchema) {
411
+ if (typeof act.outputSchema !== "object") {
412
+ errors.push("Invalid outputSchema object");
413
+ }
414
+ }
415
+ results.push({
416
+ id: act.id,
417
+ valid: errors.length === 0,
418
+ errors,
419
+ });
420
+ }
421
+
422
+ const allValid = results.every((r) => r.valid);
423
+ if (options.json) {
424
+ console.log(JSON.stringify({ valid: allValid, results }, null, 2));
425
+ } else {
426
+ for (const r of results) {
427
+ if (r.valid) {
428
+ console.log(`[OK] ${r.id}: Valid`);
429
+ } else {
430
+ console.log(`[FAIL] ${r.id}: ${r.errors.join(", ")}`);
431
+ }
432
+ }
433
+ }
434
+ if (!allValid) process.exit(1);
435
+ } catch (err: any) {
436
+ console.error(`Error: ${err.message}`);
437
+ process.exit(1);
438
+ }
439
+ });
440
+
441
+ // action run
442
+ actionCmd
443
+ .command("run <id>")
444
+ .description("Execute an action (from current project, linked packages, or remote profile)")
445
+ .option("-i, --input <json>", "Input as JSON string")
446
+ .option("-f, --input-file <path>", "Input from JSON file")
447
+ .option("-c, --config <key=value...>", "Temporary config override (repeatable or comma-separated)")
448
+ .option("-p, --profile <name>", "Execute against a specific profile")
449
+ .option("-s, --server <url>", "Remote server URL")
450
+ .option("-t, --token <token>", "Auth token for remote server")
451
+ .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
452
+ .option("--async", "Execute asynchronously in background (requires remote server or profile)")
453
+ .action(async (id, options) => {
454
+ await executeAction(id, options);
455
+ });
456
+
457
+ // Root level alias: ac run <id>
458
+ program
459
+ .command("run <id>")
460
+ .description("Alias for 'ac action run <id>'")
461
+ .option("-i, --input <json>", "Input as JSON string")
462
+ .option("-f, --input-file <path>", "Input from JSON file")
463
+ .option("-c, --config <key=value...>", "Temporary config override")
464
+ .option("-p, --profile <name>", "Execute against a specific profile")
465
+ .option("-s, --server <url>", "Remote server URL")
466
+ .option("-t, --token <token>", "Auth token for remote server")
467
+ .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
468
+ .option("--async", "Execute asynchronously in background (requires remote server or profile)")
469
+ .action(async (id, options) => {
470
+ await executeAction(id, options);
471
+ });
472
+ }
473
+
474
+ async function executeAction(id: string, options: any): Promise<void> {
475
+ let input: unknown = {};
476
+ if (options.input) {
477
+ try {
478
+ input = JSON.parse(options.input);
479
+ } catch (err: any) {
480
+ console.error(`Error parsing --input JSON: ${err.message}`);
481
+ process.exit(1);
482
+ }
483
+ } else if (options.inputFile) {
484
+ try {
485
+ input = JSON.parse(readFileSync(options.inputFile, "utf-8"));
486
+ } catch (err: any) {
487
+ console.error(`Error reading --input-file: ${err.message}`);
488
+ process.exit(1);
489
+ }
490
+ }
491
+
492
+ let timeoutMs: number | undefined;
493
+ if (options.timeout) {
494
+ try {
495
+ timeoutMs = parseDuration(options.timeout);
496
+ } catch (err: any) {
497
+ console.error(`Error: ${err.message}`);
498
+ process.exit(1);
499
+ }
500
+ }
501
+
502
+ const configOverrides: Record<string, unknown> = {};
503
+ if (options.config) {
504
+ const list = Array.isArray(options.config) ? options.config : [options.config];
505
+ for (const item of list) {
506
+ const [k, ...v] = item.split("=");
507
+ if (k) configOverrides[k] = v.join("=");
508
+ }
509
+ }
510
+
511
+ // Check target (remote profile vs local)
512
+ let target;
513
+ try {
514
+ target = resolveTarget({
515
+ profile: options.profile,
516
+ server: options.server,
517
+ token: options.token,
518
+ });
519
+ } catch (err: any) {
520
+ console.error(`Error: ${err.message}`);
521
+ process.exit(1);
522
+ }
523
+
524
+ const controller = new AbortController();
525
+ const sigintHandler = () => {
526
+ controller.abort(new Error("Interrupted by SIGINT"));
527
+ };
528
+ process.once("SIGINT", sigintHandler);
529
+
530
+ try {
531
+ if (target.type === "remote") {
532
+ try {
533
+ const result = await executeRemoteAction(target.serverUrl!, id, input, {
534
+ configOverrides,
535
+ token: target.token,
536
+ timeoutMs,
537
+ signal: controller.signal,
538
+ async: Boolean(options.async),
539
+ });
540
+ console.log(JSON.stringify(result, null, 2));
541
+ if (!result.ok) {
542
+ process.exit(1);
543
+ }
544
+ return;
545
+ } catch (err: any) {
546
+ console.error(`Error: ${err.message}`);
547
+ process.exit(1);
548
+ }
549
+ }
550
+
551
+ // Local execution
552
+ if (options.async) {
553
+ console.error(
554
+ "Error: Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start `ac serve`."
555
+ );
556
+ process.exit(1);
557
+ }
558
+
559
+ let resolvedProjectRoot: string;
560
+ let resolvedActionId: string;
561
+
562
+ try {
563
+ const resolved = await resolveActionProject(id);
564
+ resolvedProjectRoot = resolved.projectRoot;
565
+ resolvedActionId = resolved.actionId;
566
+ } catch (err: any) {
567
+ console.error(`Error: ${err.message}`);
568
+ process.exit(1);
569
+ }
570
+
571
+ try {
572
+ const config = loadProjectConfig(resolvedProjectRoot);
573
+ const actions = await loadActions(resolvedProjectRoot, config.actionsDir);
574
+ const storage = createStorage(config.id, { projectRoot: resolvedProjectRoot });
575
+
576
+ const runner = new ActionRunner({
577
+ packageId: config.id,
578
+ storage,
579
+ projectConfig: config,
580
+ configOverrides,
581
+ actions,
582
+ });
583
+
584
+ const result = await runner.execute(resolvedActionId, input, {
585
+ signal: controller.signal,
586
+ timeoutMs,
587
+ });
588
+ console.log(JSON.stringify(result, null, 2));
589
+ storage.close();
590
+
591
+ if (!result.ok) {
592
+ process.exit(1);
593
+ }
594
+ } catch (err: any) {
595
+ console.error(`Error: ${err.message}`);
596
+ process.exit(1);
597
+ }
598
+ } finally {
599
+ process.removeListener("SIGINT", sigintHandler);
600
+ }
601
+ }
602
+
@@ -0,0 +1,54 @@
1
+ import { buildProject, findProjectRoot, resolvePackageRoot } from "@actiondock/core";
2
+ import { Command } from "commander";
3
+
4
+ export function registerBuildCommand(program: Command): void {
5
+ program
6
+ .command("build")
7
+ .description("Build project actions into a single standalone executable")
8
+ .option("-P, --package <id>", "Target package ID or path")
9
+ .option("-t, --target <target>", "Target compilation platform (e.g. bun, linux-x64, darwin-arm64, windows-x64)")
10
+ .option("-o, --out <path>", "Output executable path")
11
+ .option("-a, --actions <actions...>", "Only build specific action(s) into the standalone binary")
12
+ .option("-m, --minify", "Minify bundled JavaScript (default: true)", true)
13
+ .option("--no-minify", "Disable JavaScript minification")
14
+ .option("--bytecode", "Compile JavaScript to bytecode for faster startup (default: true)", true)
15
+ .option("--no-bytecode", "Disable bytecode compilation")
16
+ .action(async (options) => {
17
+ const root = options.package
18
+ ? resolvePackageRoot(options.package)
19
+ : findProjectRoot();
20
+
21
+ if (!root) {
22
+ if (options.package) {
23
+ console.error(`Error: Package '${options.package}' not found in linked packages or path`);
24
+ } else {
25
+ console.error(
26
+ "Error: Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <id> or cd into a project directory."
27
+ );
28
+ }
29
+ process.exit(1);
30
+ }
31
+
32
+ try {
33
+ console.log("Building standalone executable...");
34
+ const result = await buildProject({
35
+ projectRoot: root,
36
+ target: options.target,
37
+ outfile: options.out,
38
+ minify: options.minify,
39
+ bytecode: options.bytecode,
40
+ actions: options.actions,
41
+ });
42
+
43
+ console.log(`[OK] Successfully compiled ${result.packageId} (v${result.version})`);
44
+ console.log(` Target: ${result.target}`);
45
+ console.log(` Actions: ${result.actions.join(", ")}`);
46
+ console.log(` Executable: ${result.executablePath}`);
47
+ console.log(` Metadata: ${result.metadataPath}`);
48
+ } catch (err: any) {
49
+ console.error(`Build failed: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+ });
53
+ }
54
+