nextclaw 0.33.2 → 0.35.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.
Files changed (54) hide show
  1. package/dist/cli/app/index.js +184 -33
  2. package/dist/cli/app/index.js.map +1 -1
  3. package/package.json +13 -12
  4. package/resources/USAGE.md +29 -0
  5. package/ui-dist/assets/{api-a_GaOFc7.js → api-Dket1cmq.js} +1 -1
  6. package/ui-dist/assets/{appearance-settings-page-KEYKkxHu.js → appearance-settings-page-CfwDf9ag.js} +1 -1
  7. package/ui-dist/assets/{channels-list-page-eXLXPgKT.js → channels-list-page-Dyn83ZlS.js} +2 -2
  8. package/ui-dist/assets/chat-page-CL0IMvgF.js +1 -0
  9. package/ui-dist/assets/{config-split-page-ps1Aod3g.js → config-split-page-D4D4HFhq.js} +1 -1
  10. package/ui-dist/assets/{confirm-dialog-DJSUNG33.js → confirm-dialog-Cv6-9Nnj.js} +1 -1
  11. package/ui-dist/assets/{desktop-update-config-C20U71k1.js → desktop-update-config-Bckgulvm.js} +1 -1
  12. package/ui-dist/assets/{dist-BkTYNzm_.js → dist-Bh_tj3FN.js} +1 -1
  13. package/ui-dist/assets/{dist-DkR7_Dpl.js → dist-BrRgjiq1.js} +1 -1
  14. package/ui-dist/assets/{doc-browser-Q67TOGVS.js → doc-browser-BlWjhmnM.js} +1 -1
  15. package/ui-dist/assets/doc-browser-Da9hDsoQ.js +1 -0
  16. package/ui-dist/assets/{doc-browser-context-BQim8SqD.js → doc-browser-context-NGYa66Xg.js} +1 -1
  17. package/ui-dist/assets/{form-actions-B2-bpFie.js → form-actions-CeKgh4SQ.js} +1 -1
  18. package/ui-dist/assets/{index-BP0Hd2jI.js → index-Bz5yNMXA.js} +44 -44
  19. package/ui-dist/assets/loader-circle-DLp2jNIf.js +1 -0
  20. package/ui-dist/assets/mcp-marketplace-page-CIWbjXM8.js +1 -0
  21. package/ui-dist/assets/{mcp-marketplace-page-w8panjZG.js → mcp-marketplace-page-DfNmMqKs.js} +1 -1
  22. package/ui-dist/assets/{model-config-page-CJCnaKOh.js → model-config-page-DHzyDV5v.js} +1 -1
  23. package/ui-dist/assets/{navigation-link-mpHFXwRr.js → navigation-link-CzGgQGi_.js} +1 -1
  24. package/ui-dist/assets/plus-Dfj3tJ_m.js +1 -0
  25. package/ui-dist/assets/{provider-scoped-model-input-Dqu-ZoWG.js → provider-scoped-model-input-CjrHwzFb.js} +1 -1
  26. package/ui-dist/assets/providers-config-page-ZPvJWHFN.js +1 -0
  27. package/ui-dist/assets/{react-BlXwvOLo.js → react-BXRf96P-.js} +1 -1
  28. package/ui-dist/assets/{refresh-cw-JFuyDj_P.js → refresh-cw-ChKuqgPR.js} +1 -1
  29. package/ui-dist/assets/remote-C2enAZbs.js +1 -0
  30. package/ui-dist/assets/rotate-cw-BGkZpcc7.js +1 -0
  31. package/ui-dist/assets/runtime-config-page-CGpPQD8K.js +1 -0
  32. package/ui-dist/assets/{save-BL8P8CLX.js → save-BWGNAKV5.js} +1 -1
  33. package/ui-dist/assets/{search-bZL75Pgc.js → search-DFcT_9wJ.js} +1 -1
  34. package/ui-dist/assets/{search-config-page-DhSFjYj4.js → search-config-page-DBBzVAQZ.js} +1 -1
  35. package/ui-dist/assets/{secrets-config-page-BLGcyb1D.js → secrets-config-page-bnJ8sIB7.js} +2 -2
  36. package/ui-dist/assets/security-config-CfbXSpgX.js +1 -0
  37. package/ui-dist/assets/{select-rUZ8nIZu.js → select-BnNrv8si.js} +1 -1
  38. package/ui-dist/assets/{settings-2-BI4SCkxQ.js → settings-2-CFHrgKTX.js} +1 -1
  39. package/ui-dist/assets/skeleton-tfWhGuJT.js +1 -0
  40. package/ui-dist/assets/{tag-chip-BdI_yIOA.js → tag-chip-L5w4xfS7.js} +1 -1
  41. package/ui-dist/assets/x-BCSE3LCQ.js +1 -0
  42. package/ui-dist/index.html +18 -18
  43. package/ui-dist/assets/chat-page-jfImLdsq.js +0 -1
  44. package/ui-dist/assets/doc-browser-DahzJEWj.js +0 -1
  45. package/ui-dist/assets/loader-circle-DyXZU92l.js +0 -1
  46. package/ui-dist/assets/mcp-marketplace-page-B0dBE5CV.js +0 -1
  47. package/ui-dist/assets/plus-Dhb4KuMH.js +0 -1
  48. package/ui-dist/assets/providers-config-page-C6fRN2rJ.js +0 -1
  49. package/ui-dist/assets/remote-Xyv2c_W9.js +0 -1
  50. package/ui-dist/assets/rotate-cw-DjbbiIW_.js +0 -1
  51. package/ui-dist/assets/runtime-config-page-CtypMgLd.js +0 -1
  52. package/ui-dist/assets/security-config-DmJw9QZA.js +0 -1
  53. package/ui-dist/assets/skeleton-CVmEPG43.js +0 -1
  54. package/ui-dist/assets/x-BcEayhV6.js +0 -1
@@ -4,14 +4,15 @@ import { createRequire } from "node:module";
4
4
  import { APP_NAME, APP_TAGLINE, getConfigPath, getDataDir, getRunPath, loadConfig, resolveConfigSecrets } from "@nextclaw/core";
5
5
  import { Command, InvalidArgumentError } from "commander";
6
6
  import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
- import { randomBytes } from "node:crypto";
7
+ import { createHash, randomBytes } from "node:crypto";
8
8
  import { execFile } from "node:child_process";
9
9
  import path, { join, resolve } from "node:path";
10
10
  import { tmpdir } from "node:os";
11
11
  import "@nextclaw/server";
12
12
  import { NextclawDistributionService, NextclawServiceRuntime, readLearningLoopRuntimeConfig } from "@nextclaw/service";
13
- import { access, mkdtemp, readFile, rm, stat } from "node:fs/promises";
13
+ import { AppHomeService, AppInstanceStorageService, AppPublishService, AppPublishValidationService } from "@nextclaw/app-runtime";
14
14
  import { McpServiceAppRuntimeService, buildServiceActionId, getServiceAppManifestPath, mergeServiceAppRuntimeActions, readServiceAppManifest } from "@nextclaw/kernel";
15
+ import { access, mkdtemp, readFile, rm, stat } from "node:fs/promises";
15
16
  import { promisify } from "node:util";
16
17
  var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
17
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -2019,7 +2020,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2019
2020
  const http$1 = __require("http");
2020
2021
  const net = __require("net");
2021
2022
  const tls = __require("tls");
2022
- const { randomBytes: randomBytes$1, createHash: createHash$1 } = __require("crypto");
2023
+ const { randomBytes: randomBytes$1, createHash: createHash$2 } = __require("crypto");
2023
2024
  const { Duplex: Duplex$2, Readable } = __require("stream");
2024
2025
  const { URL: URL$1 } = __require("url");
2025
2026
  const PerMessageDeflate = require_permessage_deflate();
@@ -2671,7 +2672,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2671
2672
  abortHandshake(websocket, socket, "Invalid Upgrade header");
2672
2673
  return;
2673
2674
  }
2674
- const digest = createHash$1("sha1").update(key + GUID).digest("base64");
2675
+ const digest = createHash$2("sha1").update(key + GUID).digest("base64");
2675
2676
  if (res.headers["sec-websocket-accept"] !== digest) {
2676
2677
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
2677
2678
  return;
@@ -3143,7 +3144,7 @@ var require_websocket_server = /* @__PURE__ */ __commonJSMin(((exports, module)
3143
3144
  const EventEmitter = __require("events");
3144
3145
  const http = __require("http");
3145
3146
  const { Duplex } = __require("stream");
3146
- const { createHash } = __require("crypto");
3147
+ const { createHash: createHash$1 } = __require("crypto");
3147
3148
  const extension = require_extension();
3148
3149
  const PerMessageDeflate = require_permessage_deflate();
3149
3150
  const subprotocol = require_subprotocol();
@@ -3402,7 +3403,7 @@ var require_websocket_server = /* @__PURE__ */ __commonJSMin(((exports, module)
3402
3403
  "HTTP/1.1 101 Switching Protocols",
3403
3404
  "Upgrade: websocket",
3404
3405
  "Connection: Upgrade",
3405
- `Sec-WebSocket-Accept: ${createHash("sha1").update(key + GUID).digest("base64")}`
3406
+ `Sec-WebSocket-Accept: ${createHash$1("sha1").update(key + GUID).digest("base64")}`
3406
3407
  ];
3407
3408
  const ws = new this.options.WebSocket(null, void 0, this.options);
3408
3409
  if (protocols.size) {
@@ -3632,6 +3633,7 @@ const registerHostServiceControls = ({ program, nextclaw }) => {
3632
3633
  //#endregion
3633
3634
  //#region src/cli/app/services/service-app-dev.service.ts
3634
3635
  var ServiceAppDevService = class {
3636
+ instanceStorageService = new AppInstanceStorageService();
3635
3637
  constructor(params = {}) {
3636
3638
  this.params = params;
3637
3639
  }
@@ -3642,14 +3644,14 @@ var ServiceAppDevService = class {
3642
3644
  if (!loaded) return this.buildDevReport(appPath, void 0, [], issues);
3643
3645
  if (this.hasErrors(issues)) return this.buildDevReport(appPath, this.toServiceAppRecord(appPath, loaded.manifest, this.idleRuntimeStatus), [], issues);
3644
3646
  const runtime = this.createRuntimeService();
3645
- const dataDirectory = await this.createDevDataDirectory();
3647
+ const storage = await this.createDevStorage(appPath, loaded.manifest.id);
3646
3648
  try {
3647
- const startRecord = this.toServiceAppRecord(appPath, loaded.manifest, runtime, dataDirectory);
3649
+ const startRecord = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3648
3650
  const runtimeActions = await runtime.listActions({
3649
3651
  app: startRecord,
3650
3652
  manifest: loaded.manifest
3651
3653
  });
3652
- const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, dataDirectory);
3654
+ const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3653
3655
  const actions = mergeServiceAppRuntimeActions({
3654
3656
  record,
3655
3657
  manifest: loaded.manifest,
@@ -3658,14 +3660,7 @@ var ServiceAppDevService = class {
3658
3660
  this.collectRuntimeIssues(record, actions, issues);
3659
3661
  return this.buildDevReport(appPath, record, actions, issues);
3660
3662
  } finally {
3661
- try {
3662
- await runtime.dispose();
3663
- } finally {
3664
- await rm(dataDirectory, {
3665
- recursive: true,
3666
- force: true
3667
- });
3668
- }
3663
+ await runtime.dispose();
3669
3664
  }
3670
3665
  };
3671
3666
  call = async (target, actionName, input) => {
@@ -3685,19 +3680,19 @@ var ServiceAppDevService = class {
3685
3680
  return this.buildCallReport(appPath, record, void 0, void 0, issues);
3686
3681
  }
3687
3682
  const runtime = this.createRuntimeService();
3688
- const dataDirectory = await this.createDevDataDirectory();
3683
+ const storage = await this.createDevStorage(appPath, loaded.manifest.id);
3689
3684
  try {
3690
- const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, dataDirectory);
3685
+ const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3691
3686
  const result = await runtime.invokeAction({
3692
3687
  app: record,
3693
3688
  manifest: loaded.manifest,
3694
3689
  actionName: action,
3695
3690
  input
3696
3691
  });
3697
- const nextRecord = this.toServiceAppRecord(appPath, loaded.manifest, runtime, dataDirectory);
3692
+ const nextRecord = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3698
3693
  return this.buildCallReport(appPath, nextRecord, buildServiceActionId(loaded.manifest.id, action), result, issues);
3699
3694
  } catch (error) {
3700
- const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, dataDirectory);
3695
+ const record = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3701
3696
  issues.push({
3702
3697
  severity: "error",
3703
3698
  code: "service.runtime.callFailed",
@@ -3705,14 +3700,7 @@ var ServiceAppDevService = class {
3705
3700
  });
3706
3701
  return this.buildCallReport(appPath, record, buildServiceActionId(loaded.manifest.id, action), void 0, issues);
3707
3702
  } finally {
3708
- try {
3709
- await runtime.dispose();
3710
- } finally {
3711
- await rm(dataDirectory, {
3712
- recursive: true,
3713
- force: true
3714
- });
3715
- }
3703
+ await runtime.dispose();
3716
3704
  }
3717
3705
  };
3718
3706
  loadServiceApp = async (appPath, issues) => {
@@ -3744,8 +3732,16 @@ var ServiceAppDevService = class {
3744
3732
  return resolveConfigSecrets(loadConfig(configPath), { configPath });
3745
3733
  };
3746
3734
  idleRuntimeStatus = { getStatus: () => ({ status: "idle" }) };
3747
- createDevDataDirectory = async () => await mkdtemp(path.join(tmpdir(), "nextclaw-service-app-dev-data-"));
3748
- toServiceAppRecord = (dirPath, manifest, runtime, dataDirectory) => {
3735
+ createDevStorage = async (appPath, appId) => {
3736
+ const sourceId = createHash("sha256").update(appPath).digest("hex").slice(0, 16);
3737
+ const instanceDirectory = path.join(new AppHomeService().getAppHomeDirectory(), "dev-instances", appId, sourceId, "default");
3738
+ return (await this.instanceStorageService.materialize({
3739
+ appId,
3740
+ instanceId: "default",
3741
+ instanceDirectory
3742
+ })).storage;
3743
+ };
3744
+ toServiceAppRecord = (dirPath, manifest, runtime, storage) => {
3749
3745
  const runtimeStatus = runtime.getStatus(manifest.id);
3750
3746
  const record = {
3751
3747
  id: manifest.id,
@@ -3758,7 +3754,10 @@ var ServiceAppDevService = class {
3758
3754
  enabled: manifest.enabled,
3759
3755
  protocol: manifest.protocol,
3760
3756
  status: manifest.enabled ? runtimeStatus.status : "stopped",
3761
- dataDirectory
3757
+ dataDirectory: storage?.dataDirectory,
3758
+ instanceId: storage?.instanceId,
3759
+ storage,
3760
+ isolation: "full-user"
3762
3761
  };
3763
3762
  if (manifest.description) record.description = manifest.description;
3764
3763
  if (runtimeStatus.lastError) record.lastError = runtimeStatus.lastError;
@@ -4607,13 +4606,165 @@ var AppRestartCommandController = class {
4607
4606
  };
4608
4607
  };
4609
4608
  //#endregion
4609
+ //#region src/cli/app/services/app-publishing.service.ts
4610
+ var AppPublishingService = class {
4611
+ constructor(validationService = new AppPublishValidationService(), publishService = new AppPublishService()) {
4612
+ this.validationService = validationService;
4613
+ this.publishService = publishService;
4614
+ }
4615
+ validate = async (params) => {
4616
+ const result = await this.validationService.validate({
4617
+ appDirectory: params.appDirectory,
4618
+ metadataPath: params.metadataPath,
4619
+ mode: "bundle"
4620
+ });
4621
+ if (result.profile !== "components") throw new Error("nextclaw app publish 只支持由 Panel App 或 Service App 组成的 schema v2 Mini App。");
4622
+ if (result.distributionMode !== "bundle") throw new Error("NextClaw Mini App 只支持 bundle 分发。");
4623
+ return result;
4624
+ };
4625
+ publish = async (params) => {
4626
+ const validation = await this.validate(params);
4627
+ if (validation.warnings.length > 0 && !params.allowWarnings) {
4628
+ const warningList = validation.warnings.map((warning) => `[${warning.code}] ${warning.message}`).join("; ");
4629
+ throw new Error(`发布校验包含警告:${warningList}。确认后可使用 --allow-warnings 继续。`);
4630
+ }
4631
+ const tempDirectory = await mkdtemp(path.join(tmpdir(), "nextclaw-app-publish-"));
4632
+ try {
4633
+ const result = await this.publishService.publish({
4634
+ appDirectory: params.appDirectory,
4635
+ metadataPath: params.metadataPath,
4636
+ bundleOutputPath: path.join(tempDirectory, "artifact.napp"),
4637
+ mode: "bundle"
4638
+ });
4639
+ const { item } = result;
4640
+ return {
4641
+ validation,
4642
+ publish: {
4643
+ created: result.created,
4644
+ item: {
4645
+ slug: item.slug,
4646
+ appId: item.appId,
4647
+ ownerScope: item.ownerScope,
4648
+ appName: item.appName,
4649
+ publishStatus: item.publishStatus,
4650
+ name: item.name,
4651
+ latestVersion: item.latestVersion,
4652
+ webUrl: item.publishStatus === "published" ? item.webUrl : void 0
4653
+ },
4654
+ fileCount: result.fileCount
4655
+ }
4656
+ };
4657
+ } catch (error) {
4658
+ const message = error instanceof Error ? error.message : String(error);
4659
+ throw new Error(message.replace(/缺少 marketplace publish token。请先登录 NextClaw,或传入 --token。?/g, "发布需要 NextClaw 平台登录态。请先运行 nextclaw login。").replace(/,或传入 --token。?/g, "。"));
4660
+ } finally {
4661
+ await rm(tempDirectory, {
4662
+ recursive: true,
4663
+ force: true
4664
+ });
4665
+ }
4666
+ };
4667
+ };
4668
+ //#endregion
4669
+ //#region src/cli/app/controllers/app-publish-command.controller.ts
4670
+ const PLATFORM_APPS_URL = "https://platform.nextclaw.io/apps";
4671
+ var AppPublishCommandController = class {
4672
+ constructor(appPublishingService = new AppPublishingService()) {
4673
+ this.appPublishingService = appPublishingService;
4674
+ }
4675
+ publish = async (target, options) => {
4676
+ const { allowWarnings, json, meta } = options;
4677
+ try {
4678
+ const result = await this.appPublishingService.publish({
4679
+ appDirectory: target,
4680
+ metadataPath: meta,
4681
+ allowWarnings
4682
+ });
4683
+ process.stdout.write(json ? `${JSON.stringify({
4684
+ ok: true,
4685
+ ...result
4686
+ }, null, 2)}\n` : this.format(result));
4687
+ } catch (error) {
4688
+ this.writeError(error, Boolean(json));
4689
+ process.exitCode = 1;
4690
+ }
4691
+ };
4692
+ format = (result) => {
4693
+ const { item } = result.publish;
4694
+ if (item.publishStatus === "pending") return [
4695
+ `Submitted ${item.name} (${item.appId}) ${item.latestVersion} for review.`,
4696
+ "Status: pending",
4697
+ "The app will appear in the App Marketplace after approval.",
4698
+ `Manage submissions: ${PLATFORM_APPS_URL}`,
4699
+ ""
4700
+ ].join("\n");
4701
+ return [
4702
+ `${result.publish.created ? "Published" : "Updated"} ${item.name} (${item.appId}) ${item.latestVersion}.`,
4703
+ "Status: published",
4704
+ item.webUrl ? `Details: ${item.webUrl}` : "",
4705
+ `Manage apps: ${PLATFORM_APPS_URL}`,
4706
+ ""
4707
+ ].filter((line, index, lines) => line || index === lines.length - 1).join("\n");
4708
+ };
4709
+ writeError = (error, json) => {
4710
+ const message = error instanceof Error ? error.message : String(error);
4711
+ process.stdout.write(json ? `${JSON.stringify({
4712
+ ok: false,
4713
+ error: { message }
4714
+ }, null, 2)}\n` : `Mini App publish failed: ${message}\n`);
4715
+ };
4716
+ };
4717
+ //#endregion
4718
+ //#region src/cli/app/controllers/app-validate-publish-command.controller.ts
4719
+ var AppValidatePublishCommandController = class {
4720
+ constructor(appPublishingService = new AppPublishingService()) {
4721
+ this.appPublishingService = appPublishingService;
4722
+ }
4723
+ validate = async (target, options) => {
4724
+ try {
4725
+ const validation = await this.appPublishingService.validate({
4726
+ appDirectory: target,
4727
+ metadataPath: options.meta
4728
+ });
4729
+ process.stdout.write(options.json ? `${JSON.stringify({
4730
+ ok: true,
4731
+ validation
4732
+ }, null, 2)}\n` : this.format(validation));
4733
+ } catch (error) {
4734
+ this.writeError(error, Boolean(options.json));
4735
+ process.exitCode = 1;
4736
+ }
4737
+ };
4738
+ format = (validation) => {
4739
+ const lines = [
4740
+ `Mini App publish validation passed: ${validation.appId}@${validation.version}`,
4741
+ `Components: ${validation.componentCount ?? 0}`,
4742
+ `Bundle size: ${validation.bundleSizeBytes} bytes`,
4743
+ `Metadata: ${validation.metadataPath}`
4744
+ ];
4745
+ for (const warning of validation.warnings) lines.push(`Warning [${warning.code}]: ${warning.message}`);
4746
+ return `${lines.join("\n")}\n`;
4747
+ };
4748
+ writeError = (error, json) => {
4749
+ const message = error instanceof Error ? error.message : String(error);
4750
+ process.stdout.write(json ? `${JSON.stringify({
4751
+ ok: false,
4752
+ error: { message }
4753
+ }, null, 2)}\n` : `Mini App publish validation failed: ${message}\n`);
4754
+ };
4755
+ };
4756
+ //#endregion
4610
4757
  //#region src/cli/app/register-app-commands.ts
4611
4758
  function registerAppCommands(program) {
4612
- const app = program.command("app").description("Inspect and validate lightweight NextClaw apps");
4759
+ const app = program.command("app").description("Develop, validate, and publish NextClaw apps");
4613
4760
  const appCheck = new AppCheckCommandController();
4614
4761
  const appDev = new AppDevCommandController();
4615
4762
  const appCall = new AppCallCommandController();
4616
4763
  const appRestart = new AppRestartCommandController();
4764
+ const appValidatePublish = new AppValidatePublishCommandController();
4765
+ const appPublish = new AppPublishCommandController();
4766
+ app.command("validate-publish <app-dir>").description("Validate a NextClaw Mini App before Marketplace submission").option("--meta <path>", "Use a custom marketplace metadata file").option("--json", "Output JSON", false).action(async (target, opts) => appValidatePublish.validate(target, opts));
4767
+ app.command("publish <app-dir>").description("Submit a NextClaw Mini App to the App Marketplace").option("--meta <path>", "Use a custom marketplace metadata file").option("--allow-warnings", "Submit after reviewing validation warnings", false).option("--json", "Output JSON", false).action(async (target, opts) => appPublish.publish(target, opts));
4617
4768
  app.command("check <app-dir>").description("Check a Panel App or Service App directory").option("--json", "Output JSON", false).action(async (target, opts) => appCheck.check(target, opts));
4618
4769
  app.command("dev <service-app-dir>").description("Start a Service App through the real runtime and inspect its actions").option("--json", "Output JSON", false).action(async (target, opts) => appDev.dev(target, opts));
4619
4770
  app.command("call <service-app-dir> <action-name>").description("Call a Service App action through the real runtime").option("--input <json>", "JSON object input for the action").option("--json", "Output JSON", false).action(async (target, actionName, opts) => appCall.call(target, actionName, opts));