nextclaw 0.35.0 → 0.36.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 (55) hide show
  1. package/dist/cli/app/index.js +163 -82
  2. package/dist/cli/app/index.js.map +1 -1
  3. package/package.json +14 -14
  4. package/resources/USAGE.md +63 -1
  5. package/ui-dist/assets/api-CVjdSvJk.js +13 -0
  6. package/ui-dist/assets/{appearance-settings-page-CfwDf9ag.js → appearance-settings-page-BOorfaUx.js} +1 -1
  7. package/ui-dist/assets/{channels-list-page-Dyn83ZlS.js → channels-list-page-DuZJvYB6.js} +1 -1
  8. package/ui-dist/assets/{chat-page-CL0IMvgF.js → chat-page-8PTMgI0t.js} +1 -1
  9. package/ui-dist/assets/{config-split-page-D4D4HFhq.js → config-split-page-UuhsVcQD.js} +1 -1
  10. package/ui-dist/assets/{confirm-dialog-Cv6-9Nnj.js → confirm-dialog-Bnosa8UF.js} +1 -1
  11. package/ui-dist/assets/{desktop-update-config-Bckgulvm.js → desktop-update-config-CM4lsX4R.js} +1 -1
  12. package/ui-dist/assets/{dist-BrRgjiq1.js → dist-D17RZAsz.js} +1 -1
  13. package/ui-dist/assets/{dist-Bh_tj3FN.js → dist-_zZ8P38T.js} +1 -1
  14. package/ui-dist/assets/{doc-browser-BlWjhmnM.js → doc-browser-CiLvybSC.js} +1 -1
  15. package/ui-dist/assets/{doc-browser-context-NGYa66Xg.js → doc-browser-context-CerkiK_N.js} +1 -1
  16. package/ui-dist/assets/doc-browser-z7vdH3Ez.js +1 -0
  17. package/ui-dist/assets/{form-actions-CeKgh4SQ.js → form-actions-Cmv23dN4.js} +1 -1
  18. package/ui-dist/assets/index-CWdgBpSc.css +1 -0
  19. package/ui-dist/assets/index-DlL8uKXu.js +109 -0
  20. package/ui-dist/assets/loader-circle-vUsFtTfb.js +1 -0
  21. package/ui-dist/assets/mcp-marketplace-page-BV8WB3_L.js +1 -0
  22. package/ui-dist/assets/{mcp-marketplace-page-DfNmMqKs.js → mcp-marketplace-page-DN_fqcLd.js} +1 -1
  23. package/ui-dist/assets/{model-config-page-DHzyDV5v.js → model-config-page-BxaeCMbJ.js} +1 -1
  24. package/ui-dist/assets/{navigation-link-CzGgQGi_.js → navigation-link-k9rsmtC4.js} +1 -1
  25. package/ui-dist/assets/plus-Dc760kHj.js +1 -0
  26. package/ui-dist/assets/{provider-scoped-model-input-CjrHwzFb.js → provider-scoped-model-input-DLSAB6a5.js} +1 -1
  27. package/ui-dist/assets/{providers-config-page-ZPvJWHFN.js → providers-config-page-DM5N0aeS.js} +1 -1
  28. package/ui-dist/assets/{react-BXRf96P-.js → react-BgRLh3ee.js} +1 -1
  29. package/ui-dist/assets/{refresh-cw-ChKuqgPR.js → refresh-cw-axvJLmRG.js} +1 -1
  30. package/ui-dist/assets/remote-Cj1GBMaN.js +1 -0
  31. package/ui-dist/assets/rotate-cw-CFByP2O8.js +1 -0
  32. package/ui-dist/assets/{runtime-config-page-CGpPQD8K.js → runtime-config-page-WNzZyjMW.js} +1 -1
  33. package/ui-dist/assets/{save-BWGNAKV5.js → save-DbR4SGk8.js} +1 -1
  34. package/ui-dist/assets/{search-DFcT_9wJ.js → search-M9lrb8id.js} +1 -1
  35. package/ui-dist/assets/{search-config-page-DBBzVAQZ.js → search-config-page-DmsID8FQ.js} +1 -1
  36. package/ui-dist/assets/{secrets-config-page-bnJ8sIB7.js → secrets-config-page-C10OwI0b.js} +1 -1
  37. package/ui-dist/assets/security-config-CvU5JxDT.js +1 -0
  38. package/ui-dist/assets/{select-BnNrv8si.js → select-D_YBzMdv.js} +1 -1
  39. package/ui-dist/assets/{settings-2-CFHrgKTX.js → settings-2-zy40UwTo.js} +1 -1
  40. package/ui-dist/assets/skeleton-BmAtzjDM.js +1 -0
  41. package/ui-dist/assets/{tag-chip-L5w4xfS7.js → tag-chip-D7QRdcph.js} +1 -1
  42. package/ui-dist/assets/x-D9Y9hf_r.js +1 -0
  43. package/ui-dist/index.html +19 -19
  44. package/ui-dist/assets/api-Dket1cmq.js +0 -13
  45. package/ui-dist/assets/doc-browser-Da9hDsoQ.js +0 -1
  46. package/ui-dist/assets/index-Bz5yNMXA.js +0 -109
  47. package/ui-dist/assets/index-DSLK7zgR.css +0 -1
  48. package/ui-dist/assets/loader-circle-DLp2jNIf.js +0 -1
  49. package/ui-dist/assets/mcp-marketplace-page-CIWbjXM8.js +0 -1
  50. package/ui-dist/assets/plus-Dfj3tJ_m.js +0 -1
  51. package/ui-dist/assets/remote-C2enAZbs.js +0 -1
  52. package/ui-dist/assets/rotate-cw-BGkZpcc7.js +0 -1
  53. package/ui-dist/assets/security-config-CfbXSpgX.js +0 -1
  54. package/ui-dist/assets/skeleton-tfWhGuJT.js +0 -1
  55. package/ui-dist/assets/x-BCSE3LCQ.js +0 -1
@@ -10,9 +10,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 { AppHomeService, AppInstanceStorageService, AppPublishService, AppPublishValidationService } from "@nextclaw/app-runtime";
14
- import { McpServiceAppRuntimeService, buildServiceActionId, getServiceAppManifestPath, mergeServiceAppRuntimeActions, readServiceAppManifest } from "@nextclaw/kernel";
15
13
  import { access, mkdtemp, readFile, rm, stat } from "node:fs/promises";
14
+ import { AppHomeService, AppInstanceInventoryService, AppInstanceStorageService, AppPublishService, AppPublishValidationService } from "@nextclaw/app-runtime";
15
+ import { McpServiceAppRuntimeService, buildServiceActionId, getServiceAppManifestPath, mergeServiceAppRuntimeActions, readServiceAppManifest } from "@nextclaw/kernel";
16
16
  import { promisify } from "node:util";
17
17
  var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
18
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -3633,18 +3633,24 @@ const registerHostServiceControls = ({ program, nextclaw }) => {
3633
3633
  //#endregion
3634
3634
  //#region src/cli/app/services/service-app-dev.service.ts
3635
3635
  var ServiceAppDevService = class {
3636
+ instanceInventoryService = new AppInstanceInventoryService();
3636
3637
  instanceStorageService = new AppInstanceStorageService();
3637
3638
  constructor(params = {}) {
3638
3639
  this.params = params;
3639
3640
  }
3640
- inspect = async (target) => {
3641
+ inspect = async (target, options = {}) => {
3641
3642
  const appPath = path.resolve(target);
3642
3643
  const issues = [];
3643
3644
  const loaded = await this.loadServiceApp(appPath, issues);
3644
3645
  if (!loaded) return this.buildDevReport(appPath, void 0, [], issues);
3646
+ if (options.resetData && options.confirmAppId?.trim() !== loaded.manifest.id) issues.push({
3647
+ severity: "error",
3648
+ code: "service.data.confirmationMismatch",
3649
+ message: `--reset-data requires --confirm ${loaded.manifest.id}.`
3650
+ });
3645
3651
  if (this.hasErrors(issues)) return this.buildDevReport(appPath, this.toServiceAppRecord(appPath, loaded.manifest, this.idleRuntimeStatus), [], issues);
3646
3652
  const runtime = this.createRuntimeService();
3647
- const storage = await this.createDevStorage(appPath, loaded.manifest.id);
3653
+ const storage = await this.createDevStorage(appPath, loaded.manifest.id, options.resetData === true);
3648
3654
  try {
3649
3655
  const startRecord = this.toServiceAppRecord(appPath, loaded.manifest, runtime, storage);
3650
3656
  const runtimeActions = await runtime.listActions({
@@ -3732,15 +3738,36 @@ var ServiceAppDevService = class {
3732
3738
  return resolveConfigSecrets(loadConfig(configPath), { configPath });
3733
3739
  };
3734
3740
  idleRuntimeStatus = { getStatus: () => ({ status: "idle" }) };
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");
3741
+ createDevStorage = async (appPath, appId, resetData = false) => {
3742
+ const sourceId = this.getSourceId(appPath);
3743
+ const appHomeDirectory = new AppHomeService().getAppHomeDirectory();
3744
+ const instanceDirectory = path.join(appHomeDirectory, "dev-instances", appId, sourceId, "default");
3745
+ if (resetData && await this.pathExists(instanceDirectory)) await this.instanceInventoryService.purgeNested({
3746
+ instancesRoot: appHomeDirectory,
3747
+ pathSegments: [
3748
+ "dev-instances",
3749
+ appId,
3750
+ sourceId,
3751
+ "default"
3752
+ ],
3753
+ appId,
3754
+ instanceId: "default"
3755
+ });
3738
3756
  return (await this.instanceStorageService.materialize({
3739
3757
  appId,
3740
3758
  instanceId: "default",
3741
3759
  instanceDirectory
3742
3760
  })).storage;
3743
3761
  };
3762
+ getSourceId = (appPath) => createHash("sha256").update(appPath).digest("hex").slice(0, 16);
3763
+ pathExists = async (targetPath) => {
3764
+ try {
3765
+ await access(targetPath);
3766
+ return true;
3767
+ } catch {
3768
+ return false;
3769
+ }
3770
+ };
3744
3771
  toServiceAppRecord = (dirPath, manifest, runtime, storage) => {
3745
3772
  const runtimeStatus = runtime.getStatus(manifest.id);
3746
3773
  const record = {
@@ -4417,7 +4444,10 @@ var AppDevCommandController = class {
4417
4444
  this.serviceAppDevService = serviceAppDevService;
4418
4445
  }
4419
4446
  dev = async (target, options) => {
4420
- const report = await this.serviceAppDevService.inspect(target);
4447
+ const report = await this.serviceAppDevService.inspect(target, {
4448
+ resetData: options.resetData,
4449
+ confirmAppId: options.confirm
4450
+ });
4421
4451
  process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : this.format(report));
4422
4452
  if (!report.ok) process.exitCode = 1;
4423
4453
  };
@@ -4440,6 +4470,125 @@ var AppDevCommandController = class {
4440
4470
  };
4441
4471
  };
4442
4472
  //#endregion
4473
+ //#region src/cli/app/services/local-api/local-ui-api-client.service.ts
4474
+ function createLocalUiApiClient() {
4475
+ const apiBase = resolveLocalUiApiBase();
4476
+ return apiBase ? new LocalUiApiClient(apiBase) : null;
4477
+ }
4478
+ var LocalUiApiClient = class {
4479
+ cookie;
4480
+ constructor(apiBase) {
4481
+ this.apiBase = apiBase;
4482
+ }
4483
+ request = async (params) => {
4484
+ const { body, method, path } = params;
4485
+ const cookie = await this.getCookie();
4486
+ const headers = {};
4487
+ if (body) headers["Content-Type"] = "application/json";
4488
+ if (cookie) headers.Cookie = cookie;
4489
+ const response = await fetch(`${this.apiBase}${path}`, {
4490
+ method: method ?? "GET",
4491
+ headers,
4492
+ ...body ? { body: JSON.stringify(body) } : {}
4493
+ });
4494
+ if (!response.ok) throw new Error(`api request failed with status ${response.status}`);
4495
+ const payload = await response.json();
4496
+ if (!payload.ok) throw new Error(payload.error?.message ?? "api request failed");
4497
+ return payload.data;
4498
+ };
4499
+ getCookie = async () => {
4500
+ if (this.cookie !== void 0) return this.cookie;
4501
+ const response = await fetch(`${this.apiBase}/api/auth/bridge`, {
4502
+ method: "POST",
4503
+ headers: { "x-nextclaw-ui-bridge-secret": ensureUiBridgeSecret() }
4504
+ });
4505
+ if (!response.ok) throw new Error(`bridge auth failed with status ${response.status}`);
4506
+ const payload = await response.json();
4507
+ if (!payload.ok) throw new Error(payload.error?.message ?? "bridge auth failed");
4508
+ this.cookie = typeof payload.data.cookie === "string" && payload.data.cookie.trim() ? payload.data.cookie.trim() : null;
4509
+ return this.cookie;
4510
+ };
4511
+ };
4512
+ function resolveLocalUiApiBase() {
4513
+ const state = readRunningRuntimeState(resolve(getRunPath(), "ui-runtime.json")) ?? readRunningRuntimeState(resolve(getRunPath(), "service.json"));
4514
+ if (!state) return null;
4515
+ if (typeof state.uiUrl === "string" && state.uiUrl.trim()) return state.uiUrl.replace(/\/+$/, "");
4516
+ if (typeof state.apiUrl === "string" && state.apiUrl.trim()) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
4517
+ return null;
4518
+ }
4519
+ function readRunningRuntimeState(statePath) {
4520
+ if (!existsSync(statePath)) return null;
4521
+ try {
4522
+ const state = JSON.parse(readFileSync(statePath, "utf-8"));
4523
+ return typeof state.pid === "number" && isProcessRunning(state.pid) ? state : null;
4524
+ } catch {
4525
+ return null;
4526
+ }
4527
+ }
4528
+ function isProcessRunning(pid) {
4529
+ try {
4530
+ process.kill(pid, 0);
4531
+ return true;
4532
+ } catch {
4533
+ return false;
4534
+ }
4535
+ }
4536
+ function ensureUiBridgeSecret() {
4537
+ const dirPath = join(getDataDir(), "remote");
4538
+ const secretPath = join(dirPath, "ui-bridge-secret");
4539
+ if (existsSync(secretPath)) {
4540
+ const existing = readFileSync(secretPath, "utf-8").trim();
4541
+ if (existing) return existing;
4542
+ }
4543
+ mkdirSync(dirPath, { recursive: true });
4544
+ const secret = randomBytes(24).toString("hex");
4545
+ writeFileSync(secretPath, `${secret}\n`, "utf-8");
4546
+ return secret;
4547
+ }
4548
+ //#endregion
4549
+ //#region src/cli/app/services/local-api/app-data-live.service.ts
4550
+ var AppDataLiveService = class {
4551
+ constructor(params = {}) {
4552
+ this.params = params;
4553
+ }
4554
+ list = async () => await this.requireApiClient().request({ path: "/api/app-data" });
4555
+ deleteRetained = async (dataId, confirmAppId) => {
4556
+ const normalizedDataId = dataId.trim();
4557
+ const normalizedAppId = confirmAppId.trim();
4558
+ if (!normalizedDataId) throw new Error("App data id is required.");
4559
+ if (!normalizedAppId) throw new Error("--confirm <app-id> is required.");
4560
+ return await this.requireApiClient().request({
4561
+ path: `/api/app-data/${encodeURIComponent(normalizedDataId)}`,
4562
+ method: "DELETE",
4563
+ body: { confirmAppId: normalizedAppId }
4564
+ });
4565
+ };
4566
+ requireApiClient = () => {
4567
+ const client = this.params.createApiClient ? this.params.createApiClient() : createLocalUiApiClient();
4568
+ if (!client) throw new Error("NextClaw UI runtime is not running; start NextClaw before managing App data.");
4569
+ return client;
4570
+ };
4571
+ };
4572
+ //#endregion
4573
+ //#region src/cli/app/controllers/app-data-command.controller.ts
4574
+ var AppDataCommandController = class {
4575
+ constructor(service = new AppDataLiveService()) {
4576
+ this.service = service;
4577
+ }
4578
+ list = async (options) => {
4579
+ const result = await this.service.list();
4580
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : this.formatList(result));
4581
+ };
4582
+ deleteRetained = async (dataId, options) => {
4583
+ const result = await this.service.deleteRetained(dataId, options.confirm);
4584
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : `Deleted App data: ${result.appId}/${result.instanceId}\n`);
4585
+ };
4586
+ formatList = (result) => {
4587
+ if (result.entries.length === 0) return "App data: none\n";
4588
+ return `${result.entries.map((entry) => `${entry.id}\t${entry.lifecycle}\t${entry.source}\t${entry.appId}\t${entry.usage.totalBytes} B\t${entry.storage.instanceDirectory}`).join("\n")}\n`;
4589
+ };
4590
+ };
4591
+ //#endregion
4443
4592
  //#region src/cli/app/services/service-app-live-runtime.service.ts
4444
4593
  function readErrorMessage(error) {
4445
4594
  if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
@@ -4502,81 +4651,9 @@ var ServiceAppLiveRuntimeService = class {
4502
4651
  };
4503
4652
  createApiClient = () => {
4504
4653
  if (this.params.createApiClient) return this.params.createApiClient();
4505
- const apiBase = resolveLocalUiApiBase();
4506
- return apiBase ? new LocalUiApiClient(apiBase) : null;
4654
+ return createLocalUiApiClient();
4507
4655
  };
4508
4656
  };
4509
- var LocalUiApiClient = class {
4510
- cookie;
4511
- constructor(apiBase) {
4512
- this.apiBase = apiBase;
4513
- }
4514
- request = async (params) => {
4515
- const { body, method, path } = params;
4516
- const cookie = await this.getCookie();
4517
- const headers = {};
4518
- if (body) headers["Content-Type"] = "application/json";
4519
- if (cookie) headers.Cookie = cookie;
4520
- const requestInit = {
4521
- method: method ?? "GET",
4522
- headers
4523
- };
4524
- if (body) requestInit.body = JSON.stringify(body);
4525
- const response = await fetch(`${this.apiBase}${path}`, requestInit);
4526
- if (!response.ok) throw new Error(`api request failed with status ${response.status}`);
4527
- const payload = await response.json();
4528
- if (!payload.ok) throw new Error(payload.error?.message ?? "api request failed");
4529
- return payload.data;
4530
- };
4531
- getCookie = async () => {
4532
- if (this.cookie !== void 0) return this.cookie;
4533
- const response = await fetch(`${this.apiBase}/api/auth/bridge`, {
4534
- method: "POST",
4535
- headers: { "x-nextclaw-ui-bridge-secret": ensureUiBridgeSecret() }
4536
- });
4537
- if (!response.ok) throw new Error(`bridge auth failed with status ${response.status}`);
4538
- const payload = await response.json();
4539
- if (!payload.ok) throw new Error(payload.error?.message ?? "bridge auth failed");
4540
- this.cookie = typeof payload.data.cookie === "string" && payload.data.cookie.trim().length > 0 ? payload.data.cookie.trim() : null;
4541
- return this.cookie;
4542
- };
4543
- };
4544
- function resolveLocalUiApiBase() {
4545
- const state = readRunningRuntimeState(resolve(getRunPath(), "ui-runtime.json")) ?? readRunningRuntimeState(resolve(getRunPath(), "service.json"));
4546
- if (!state) return null;
4547
- if (typeof state.uiUrl === "string" && state.uiUrl.trim().length > 0) return state.uiUrl.replace(/\/+$/, "");
4548
- if (typeof state.apiUrl === "string" && state.apiUrl.trim().length > 0) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
4549
- return null;
4550
- }
4551
- function readRunningRuntimeState(statePath) {
4552
- if (!existsSync(statePath)) return null;
4553
- try {
4554
- const state = JSON.parse(readFileSync(statePath, "utf-8"));
4555
- return typeof state.pid === "number" && isProcessRunning(state.pid) ? state : null;
4556
- } catch {
4557
- return null;
4558
- }
4559
- }
4560
- function isProcessRunning(pid) {
4561
- try {
4562
- process.kill(pid, 0);
4563
- return true;
4564
- } catch {
4565
- return false;
4566
- }
4567
- }
4568
- function ensureUiBridgeSecret() {
4569
- const dirPath = join(getDataDir(), "remote");
4570
- const secretPath = join(dirPath, "ui-bridge-secret");
4571
- if (existsSync(secretPath)) {
4572
- const existing = readFileSync(secretPath, "utf-8").trim();
4573
- if (existing.length > 0) return existing;
4574
- }
4575
- mkdirSync(dirPath, { recursive: true });
4576
- const secret = randomBytes(24).toString("hex");
4577
- writeFileSync(secretPath, `${secret}\n`, "utf-8");
4578
- return secret;
4579
- }
4580
4657
  //#endregion
4581
4658
  //#region src/cli/app/controllers/app-restart-command.controller.ts
4582
4659
  var AppRestartCommandController = class {
@@ -4759,6 +4836,7 @@ function registerAppCommands(program) {
4759
4836
  const app = program.command("app").description("Develop, validate, and publish NextClaw apps");
4760
4837
  const appCheck = new AppCheckCommandController();
4761
4838
  const appDev = new AppDevCommandController();
4839
+ const appData = new AppDataCommandController();
4762
4840
  const appCall = new AppCallCommandController();
4763
4841
  const appRestart = new AppRestartCommandController();
4764
4842
  const appValidatePublish = new AppValidatePublishCommandController();
@@ -4766,7 +4844,10 @@ function registerAppCommands(program) {
4766
4844
  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
4845
  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));
4768
4846
  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));
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));
4847
+ app.command("dev <service-app-dir>").description("Start a Service App through the real runtime and inspect its actions").option("--json", "Output JSON", false).option("--reset-data", "Reset the exact development instance before starting", false).option("--confirm <app-id>", "Confirm the Service App id when resetting data").action(async (target, opts) => appDev.dev(target, opts));
4848
+ const data = app.command("data").description("Inspect and manage NextClaw App data");
4849
+ data.command("list").description("List active and retained App data through the running NextClaw host").option("--json", "Output JSON", false).action(async (opts) => appData.list(opts));
4850
+ data.command("delete <data-id>").description("Permanently delete a retained App data instance").requiredOption("--confirm <app-id>", "Confirm the exact App id").option("--json", "Output JSON", false).action(async (dataId, opts) => appData.deleteRetained(dataId, opts));
4770
4851
  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));
4771
4852
  app.command("restart <app-id>").description("Restart a live Service App runtime in the running NextClaw UI").option("--json", "Output JSON", false).action(async (appId, opts) => appRestart.restart(appId, opts));
4772
4853
  }