@nextclaw/app-runtime 0.3.0 → 0.4.1

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 CHANGED
@@ -9,6 +9,7 @@
9
9
  - `napp run <app-dir|app-id>`:启动本地宿主,支持目录运行和已安装应用运行
10
10
  - `napp dev <app-dir>`:当前等价于 `run`
11
11
  - `napp pack <app-dir>`:把应用目录打成 `.napp` bundle
12
+ - `napp publish <app-dir>`:把应用目录发布到官方 apps registry
12
13
  - `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
13
14
  - `napp update <app-id>`:更新已安装应用
14
15
  - `napp uninstall <app-id>`:卸载已安装应用
@@ -57,7 +58,7 @@ assets/
57
58
  - 用户数据目录:`~/.nextclaw/apps/data/<app-id>/`
58
59
  - 本地 registry:`~/.nextclaw/apps/registry.json`
59
60
  - 本地 config:`~/.nextclaw/apps/config.json`
60
- - 默认 registry:`https://registry.nextclaw.com/`
61
+ - 默认 registry:`https://apps-registry.nextclaw.io/api/v1/apps/registry/`
61
62
 
62
63
  ## 当前 MVP 工作流
63
64
 
@@ -67,6 +68,7 @@ assets/
67
68
  napp create ./my-first-napp
68
69
  napp inspect ./my-first-napp
69
70
  napp pack ./my-first-napp
71
+ napp publish ./my-first-napp
70
72
  ```
71
73
 
72
74
  本地安装工作流:
@@ -115,6 +117,7 @@ napp run nextclaw.my-first-napp
115
117
  ```bash
116
118
  napp inspect ./apps/examples/hello-notes
117
119
  napp pack ./apps/examples/hello-notes
120
+ napp publish ./apps/examples/hello-notes
118
121
  napp install ./apps/examples/hello-notes
119
122
  napp grant nextclaw.hello-notes --document notes=/absolute/path/to/notes
120
123
  napp run ./apps/examples/hello-notes --document notes=/absolute/path/to/notes
@@ -129,6 +132,11 @@ napp install nextclaw.hello-notes
129
132
  napp update nextclaw.hello-notes
130
133
  ```
131
134
 
135
+ 官方 apps 入口:
136
+
137
+ - Web:`https://apps.nextclaw.io`
138
+ - Registry/API:`https://apps-registry.nextclaw.io`
139
+
132
140
  ## Bundle 结构
133
141
 
134
142
  `.napp` bundle 的最小结构如下:
@@ -11,6 +11,7 @@ declare class AppRuntimeCliService {
11
11
  private handleRun;
12
12
  private handleDev;
13
13
  private handlePack;
14
+ private handlePublish;
14
15
  private handleInstall;
15
16
  private handleUpdate;
16
17
  private handleUninstall;
@@ -9,6 +9,7 @@ import { InstallCommand } from "../commands/install.controller.js";
9
9
  import { ListCommand } from "../commands/list.controller.js";
10
10
  import { PackCommand } from "../commands/pack.controller.js";
11
11
  import { PermissionsCommand } from "../commands/permissions.controller.js";
12
+ import { PublishCommand } from "../commands/publish.controller.js";
12
13
  import { RegistryCommand } from "../commands/registry.controller.js";
13
14
  import { RevokeCommand } from "../commands/revoke.controller.js";
14
15
  import { UninstallCommand } from "../commands/uninstall.controller.js";
@@ -52,6 +53,9 @@ var AppRuntimeCliService = class {
52
53
  case "pack":
53
54
  await this.handlePack(restArgs);
54
55
  return;
56
+ case "publish":
57
+ await this.handlePublish(restArgs);
58
+ return;
55
59
  case "install":
56
60
  await this.handleInstall(restArgs);
57
61
  return;
@@ -136,6 +140,18 @@ var AppRuntimeCliService = class {
136
140
  write: this.write
137
141
  });
138
142
  };
143
+ handlePublish = async (restArgs) => {
144
+ const { target, optionArgs } = this.optionsService.readTarget("publish", restArgs);
145
+ const options = this.optionsService.readPublishOptions(optionArgs);
146
+ await new PublishCommand().run({
147
+ appDirectory: target,
148
+ metadataPath: options.metadataPath,
149
+ apiBaseUrl: options.apiBaseUrl,
150
+ token: options.token,
151
+ json: options.json,
152
+ write: this.write
153
+ });
154
+ };
139
155
  handleInstall = async (restArgs) => {
140
156
  const { target, optionArgs } = this.optionsService.readTarget("install", restArgs);
141
157
  const options = this.optionsService.readInstallOptions(optionArgs);
@@ -259,6 +275,7 @@ var AppRuntimeCliService = class {
259
275
  this.write(" napp inspect <app-dir> [--json]\n");
260
276
  this.write(" napp <run|dev> <app-dir|app-id> [--host 127.0.0.1] [--port 3100] [--json] [--document scope=/path]\n");
261
277
  this.write(" napp pack <app-dir> [--out bundle.napp] [--json]\n");
278
+ this.write(" napp publish <app-dir> [--meta marketplace.json] [--api-base <url>] [--token <token>] [--json]\n");
262
279
  this.write(" napp install <app-dir|bundle.napp|app-id[@version]> [--registry <url>] [--json]\n");
263
280
  this.write(" napp update <app-id> [--version <version>] [--registry <url>] [--json]\n");
264
281
  this.write(" napp uninstall <app-id> [--purge-data] [--json]\n");
@@ -38,6 +38,12 @@ type RevokeCliOptions = {
38
38
  json: boolean;
39
39
  documentScopeIds: string[];
40
40
  };
41
+ type PublishCliOptions = {
42
+ json: boolean;
43
+ metadataPath?: string;
44
+ apiBaseUrl?: string;
45
+ token?: string;
46
+ };
41
47
  declare class AppRuntimeOptionsService {
42
48
  readTarget: (command: string, rawArgs: string[]) => {
43
49
  target: string;
@@ -52,9 +58,10 @@ declare class AppRuntimeOptionsService {
52
58
  readRuntimeOptions: (rawArgs: string[]) => RuntimeCliOptions;
53
59
  readGrantOptions: (rawArgs: string[]) => GrantCliOptions;
54
60
  readRevokeOptions: (rawArgs: string[]) => RevokeCliOptions;
61
+ readPublishOptions: (rawArgs: string[]) => PublishCliOptions;
55
62
  private assignDocumentGrant;
56
63
  private readDocumentScopeId;
57
64
  private requireOptionValue;
58
65
  }
59
66
  //#endregion
60
- export { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions };
67
+ export { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, PublishCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions };
@@ -193,6 +193,33 @@ var AppRuntimeOptionsService = class {
193
193
  if (options.documentScopeIds.length === 0) throw new Error("revoke 至少需要一个 --document scope。");
194
194
  return options;
195
195
  };
196
+ readPublishOptions = (rawArgs) => {
197
+ const options = { json: false };
198
+ for (let index = 0; index < rawArgs.length; index += 1) {
199
+ const current = rawArgs[index];
200
+ if (!current?.startsWith("--")) throw new Error(`未知参数:${current}`);
201
+ const nextValue = rawArgs[index + 1];
202
+ switch (current) {
203
+ case "--json":
204
+ options.json = true;
205
+ break;
206
+ case "--meta":
207
+ options.metadataPath = this.requireOptionValue(current, nextValue);
208
+ index += 1;
209
+ break;
210
+ case "--api-base":
211
+ options.apiBaseUrl = this.requireOptionValue(current, nextValue);
212
+ index += 1;
213
+ break;
214
+ case "--token":
215
+ options.token = this.requireOptionValue(current, nextValue);
216
+ index += 1;
217
+ break;
218
+ default: throw new Error(`未知参数:${current}`);
219
+ }
220
+ }
221
+ return options;
222
+ };
196
223
  assignDocumentGrant = (documentGrantMap, rawGrant) => {
197
224
  const delimiterIndex = rawGrant.indexOf("=");
198
225
  if (delimiterIndex < 1) throw new Error("--document 必须使用 scopeId=/absolute/or/relative/path 格式。");
@@ -0,0 +1,17 @@
1
+ import { AppPublishService } from "../publish/app-publish.service.js";
2
+
3
+ //#region src/commands/publish.controller.d.ts
4
+ declare class PublishCommand {
5
+ private readonly publishService;
6
+ constructor(publishService?: AppPublishService);
7
+ run: (params: {
8
+ appDirectory: string;
9
+ metadataPath?: string;
10
+ apiBaseUrl?: string;
11
+ token?: string;
12
+ json: boolean;
13
+ write: (text: string) => void;
14
+ }) => Promise<void>;
15
+ }
16
+ //#endregion
17
+ export { PublishCommand };
@@ -0,0 +1,29 @@
1
+ import { AppPublishService } from "../publish/app-publish.service.js";
2
+ //#region src/commands/publish.controller.ts
3
+ var PublishCommand = class {
4
+ constructor(publishService = new AppPublishService()) {
5
+ this.publishService = publishService;
6
+ }
7
+ run = async (params) => {
8
+ const { appDirectory, metadataPath, apiBaseUrl, token, json, write } = params;
9
+ const result = await this.publishService.publish({
10
+ appDirectory,
11
+ metadataPath,
12
+ apiBaseUrl,
13
+ token
14
+ });
15
+ if (json) {
16
+ write(`${JSON.stringify({
17
+ ok: true,
18
+ publish: result
19
+ }, null, 2)}\n`);
20
+ return;
21
+ }
22
+ write(`${result.created ? "Published" : "Updated"} ${result.item.name} (${result.item.appId}) ${result.item.latestVersion}\n`);
23
+ write(`Bundle: ${result.bundle.path}\n`);
24
+ if (result.item.webUrl) write(`Details: ${result.item.webUrl}\n`);
25
+ write(`Install: ${result.item.install.command}\n`);
26
+ };
27
+ };
28
+ //#endregion
29
+ export { PublishCommand };
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ import { WasmSidecarClientService } from "./sidecar/wasm-sidecar-client.service.
10
10
  import { WasmMainRunnerService } from "./runtime/wasm-main-runner.service.js";
11
11
  import { AppInstanceService, AppRunResult } from "./host/app-instance.service.js";
12
12
  import { HostBridgeServer } from "./bridge/host-bridge.service.js";
13
- import { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions } from "./cli/app-runtime-options.service.js";
13
+ import { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, PublishCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions } from "./cli/app-runtime-options.service.js";
14
14
  import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
15
15
  import { CreateCommand } from "./commands/create.controller.js";
16
16
  import { AppHomeService } from "./paths/app-home.service.js";
@@ -28,10 +28,16 @@ import { InstallCommand } from "./commands/install.controller.js";
28
28
  import { ListCommand } from "./commands/list.controller.js";
29
29
  import { PackCommand } from "./commands/pack.controller.js";
30
30
  import { PermissionsCommand } from "./commands/permissions.controller.js";
31
+ import { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./publish/app-publish.types.js";
32
+ import { AppMarketplaceClientService } from "./publish/app-marketplace-client.service.js";
33
+ import { AppMarketplaceMetadataService } from "./publish/app-marketplace-metadata.service.js";
34
+ import { PlatformAuthStateService, PlatformPublishAuthState } from "./publish/platform-auth-state.service.js";
35
+ import { AppPublishService } from "./publish/app-publish.service.js";
36
+ import { PublishCommand } from "./commands/publish.controller.js";
31
37
  import { RegistryCommand } from "./commands/registry.controller.js";
32
38
  import { RevokeCommand } from "./commands/revoke.controller.js";
33
39
  import { UpdateCommand } from "./commands/update.controller.js";
34
40
  import { UninstallCommand } from "./commands/uninstall.controller.js";
35
41
  import { AppHostHandle, AppHostService, AppHostStartOptions } from "./host/app-host.service.js";
36
42
  import { UiServerService } from "./ui/ui-server.service.js";
37
- export { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppRunResult, AppRuntimeCliService, AppRuntimeOptionsService, AppUiManifest, AppUninstallResult, AppUpdateResult, CreateCliOptions, CreateCommand, DEFAULT_APP_REGISTRY_URL, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService };
43
+ export { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppRunResult, AppRuntimeCliService, AppRuntimeOptionsService, AppUiManifest, AppUninstallResult, AppUpdateResult, CreateCliOptions, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService };
package/dist/index.js CHANGED
@@ -22,10 +22,16 @@ import { InstallCommand } from "./commands/install.controller.js";
22
22
  import { ListCommand } from "./commands/list.controller.js";
23
23
  import { PackCommand } from "./commands/pack.controller.js";
24
24
  import { PermissionsCommand } from "./commands/permissions.controller.js";
25
+ import { DEFAULT_APP_MARKETPLACE_API_BASE } from "./publish/app-publish.types.js";
26
+ import { AppMarketplaceClientService } from "./publish/app-marketplace-client.service.js";
27
+ import { AppMarketplaceMetadataService } from "./publish/app-marketplace-metadata.service.js";
28
+ import { PlatformAuthStateService } from "./publish/platform-auth-state.service.js";
29
+ import { AppPublishService } from "./publish/app-publish.service.js";
30
+ import { PublishCommand } from "./commands/publish.controller.js";
25
31
  import { RegistryCommand } from "./commands/registry.controller.js";
26
32
  import { RevokeCommand } from "./commands/revoke.controller.js";
27
33
  import { UninstallCommand } from "./commands/uninstall.controller.js";
28
34
  import { UpdateCommand } from "./commands/update.controller.js";
29
35
  import { AppRuntimeOptionsService } from "./cli/app-runtime-options.service.js";
30
36
  import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
31
- export { AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppPermissionsService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, CreateCommand, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, WasmMainRunnerService, WasmSidecarClientService };
37
+ export { AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, WasmMainRunnerService, WasmSidecarClientService };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.3.0";
2
+ var version = "0.4.1";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,13 @@
1
+ import { AppPublishPayload, AppPublishResult } from "./app-publish.types.js";
2
+
3
+ //#region src/publish/app-marketplace-client.service.d.ts
4
+ declare class AppMarketplaceClientService {
5
+ publish: (params: {
6
+ payload: AppPublishPayload;
7
+ apiBaseUrl?: string;
8
+ token?: string;
9
+ }) => Promise<AppPublishResult>;
10
+ private normalizeApiBase;
11
+ }
12
+ //#endregion
13
+ export { AppMarketplaceClientService };
@@ -0,0 +1,31 @@
1
+ import "./app-publish.types.js";
2
+ //#region src/publish/app-marketplace-client.service.ts
3
+ var AppMarketplaceClientService = class {
4
+ publish = async (params) => {
5
+ const apiBaseUrl = this.normalizeApiBase(params.apiBaseUrl ?? "https://apps-registry.nextclaw.io");
6
+ const token = params.token?.trim();
7
+ if (!token) throw new Error("缺少 marketplace publish token。请先登录 NextClaw,或传入 --token。");
8
+ const response = await fetch(`${apiBaseUrl}/api/v1/apps/publish`, {
9
+ method: "POST",
10
+ headers: {
11
+ "content-type": "application/json",
12
+ authorization: `Bearer ${token}`,
13
+ accept: "application/json"
14
+ },
15
+ body: JSON.stringify(params.payload)
16
+ });
17
+ const payload = await response.json().catch(() => null);
18
+ if (!response.ok) {
19
+ const message = typeof payload === "object" && payload && "error" in payload && typeof payload.error?.message === "string" ? payload.error.message : `${response.status} ${response.statusText}`;
20
+ throw new Error(`发布 app 失败:${message}`);
21
+ }
22
+ const data = typeof payload === "object" && payload && "data" in payload && typeof payload.data === "object" ? payload.data : null;
23
+ if (!data) throw new Error("marketplace publish 返回格式无效。");
24
+ return data;
25
+ };
26
+ normalizeApiBase = (apiBaseUrl) => {
27
+ return new URL(apiBaseUrl).toString().replace(/\/+$/, "");
28
+ };
29
+ };
30
+ //#endregion
31
+ export { AppMarketplaceClientService };
@@ -0,0 +1,27 @@
1
+ import { AppManifest } from "../manifest/app-manifest.types.js";
2
+ import { AppMarketplaceMetadata } from "./app-publish.types.js";
3
+
4
+ //#region src/publish/app-marketplace-metadata.service.d.ts
5
+ declare class AppMarketplaceMetadataService {
6
+ load: (params: {
7
+ appDirectory: string;
8
+ manifest: AppManifest;
9
+ metadataPath?: string;
10
+ }) => Promise<AppMarketplaceMetadata>;
11
+ collectPublishFiles: (params: {
12
+ appDirectory: string;
13
+ metadataPath?: string;
14
+ }) => Promise<Array<{
15
+ path: string;
16
+ bytes: Buffer;
17
+ }>>;
18
+ private parseMetadata;
19
+ private parsePublisher;
20
+ private readRequiredString;
21
+ private readOptionalString;
22
+ private readStringArray;
23
+ private readOptionalBoolean;
24
+ private readLocalizedTextMap;
25
+ }
26
+ //#endregion
27
+ export { AppMarketplaceMetadataService };
@@ -0,0 +1,84 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ //#region src/publish/app-marketplace-metadata.service.ts
5
+ var AppMarketplaceMetadataService = class {
6
+ load = async (params) => {
7
+ const { appDirectory, manifest, metadataPath: customMetadataPath } = params;
8
+ const metadataPath = customMetadataPath ? path.resolve(customMetadataPath) : path.join(path.resolve(appDirectory), "marketplace.json");
9
+ const raw = JSON.parse(await readFile(metadataPath, "utf-8"));
10
+ return this.parseMetadata(raw, manifest);
11
+ };
12
+ collectPublishFiles = async (params) => {
13
+ const appDirectory = path.resolve(params.appDirectory);
14
+ const publishFiles = [];
15
+ const metadataPath = params.metadataPath ? path.resolve(params.metadataPath) : path.join(appDirectory, "marketplace.json");
16
+ publishFiles.push({
17
+ path: "marketplace.json",
18
+ bytes: Buffer.from(await readFile(metadataPath))
19
+ });
20
+ const readmePath = path.join(appDirectory, "README.md");
21
+ if (existsSync(readmePath)) publishFiles.push({
22
+ path: "README.md",
23
+ bytes: Buffer.from(await readFile(readmePath))
24
+ });
25
+ return publishFiles;
26
+ };
27
+ parseMetadata = (rawMetadata, manifest) => {
28
+ if (!rawMetadata || typeof rawMetadata !== "object" || Array.isArray(rawMetadata)) throw new Error("marketplace.json 必须是对象。");
29
+ const candidate = rawMetadata;
30
+ const slug = this.readRequiredString(candidate.slug, "slug");
31
+ const summary = this.readRequiredString(candidate.summary, "summary");
32
+ const description = this.readOptionalString(candidate.description, "description");
33
+ const author = this.readOptionalString(candidate.author, "author") ?? manifest.name;
34
+ const publisher = this.parsePublisher(candidate.publisher);
35
+ return {
36
+ slug,
37
+ summary,
38
+ summaryI18n: this.readLocalizedTextMap(candidate.summaryI18n, "summaryI18n", summary),
39
+ description,
40
+ descriptionI18n: description ? this.readLocalizedTextMap(candidate.descriptionI18n, "descriptionI18n", description) : void 0,
41
+ author,
42
+ tags: this.readStringArray(candidate.tags, "tags"),
43
+ sourceRepo: this.readOptionalString(candidate.sourceRepo, "sourceRepo"),
44
+ homepage: this.readOptionalString(candidate.homepage, "homepage"),
45
+ featured: this.readOptionalBoolean(candidate.featured, "featured") ?? false,
46
+ publisher
47
+ };
48
+ };
49
+ parsePublisher = (rawPublisher) => {
50
+ if (rawPublisher === void 0) return;
51
+ if (!rawPublisher || typeof rawPublisher !== "object" || Array.isArray(rawPublisher)) throw new Error("publisher 必须是对象。");
52
+ const candidate = rawPublisher;
53
+ return {
54
+ id: this.readRequiredString(candidate.id, "publisher.id"),
55
+ name: this.readRequiredString(candidate.name, "publisher.name"),
56
+ url: this.readOptionalString(candidate.url, "publisher.url")
57
+ };
58
+ };
59
+ readRequiredString = (value, fieldName) => {
60
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${fieldName} 必须是非空字符串。`);
61
+ return value.trim();
62
+ };
63
+ readOptionalString = (value, fieldName) => {
64
+ if (value === void 0) return;
65
+ return this.readRequiredString(value, fieldName);
66
+ };
67
+ readStringArray = (value, fieldName) => {
68
+ if (!Array.isArray(value) || value.length === 0) throw new Error(`${fieldName} 必须是非空字符串数组。`);
69
+ return value.map((item, index) => this.readRequiredString(item, `${fieldName}[${index}]`));
70
+ };
71
+ readOptionalBoolean = (value, fieldName) => {
72
+ if (value === void 0) return;
73
+ if (typeof value !== "boolean") throw new Error(`${fieldName} 必须是布尔值。`);
74
+ return value;
75
+ };
76
+ readLocalizedTextMap = (value, fieldName, fallbackEn) => {
77
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${fieldName} 必须是对象。`);
78
+ const normalized = Object.fromEntries(Object.entries(value).map(([locale, localeValue]) => [locale, this.readRequiredString(localeValue, `${fieldName}.${locale}`)]));
79
+ if (!normalized.en) normalized.en = fallbackEn;
80
+ return normalized;
81
+ };
82
+ };
83
+ //#endregion
84
+ export { AppMarketplaceMetadataService };
@@ -0,0 +1,29 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppPublishResult } from "./app-publish.types.js";
4
+ import { AppMarketplaceClientService } from "./app-marketplace-client.service.js";
5
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
6
+ import { PlatformAuthStateService } from "./platform-auth-state.service.js";
7
+
8
+ //#region src/publish/app-publish.service.d.ts
9
+ declare class AppPublishService {
10
+ private readonly manifestService;
11
+ private readonly bundleService;
12
+ private readonly metadataService;
13
+ private readonly marketplaceClient;
14
+ private readonly authStateService;
15
+ constructor(manifestService?: AppManifestService, bundleService?: AppBundleService, metadataService?: AppMarketplaceMetadataService, marketplaceClient?: AppMarketplaceClientService, authStateService?: PlatformAuthStateService);
16
+ publish: (params: {
17
+ appDirectory: string;
18
+ metadataPath?: string;
19
+ apiBaseUrl?: string;
20
+ token?: string;
21
+ }) => Promise<AppPublishResult>;
22
+ private resolvePublishActor;
23
+ private fetchCurrentPlatformUser;
24
+ private resolvePlatformApiBase;
25
+ private buildUserPublisher;
26
+ private buildOfficialPublisher;
27
+ }
28
+ //#endregion
29
+ export { AppPublishService };
@@ -0,0 +1,154 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppMarketplaceClientService } from "./app-marketplace-client.service.js";
4
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
5
+ import { PlatformAuthStateService } from "./platform-auth-state.service.js";
6
+ import { readFile } from "node:fs/promises";
7
+ import { createHash } from "node:crypto";
8
+ import path from "node:path";
9
+ //#region src/publish/app-publish.service.ts
10
+ const DEFAULT_PLATFORM_API_BASE = "https://ai-gateway-api.nextclaw.io";
11
+ var AppPublishService = class {
12
+ constructor(manifestService = new AppManifestService(), bundleService = new AppBundleService(), metadataService = new AppMarketplaceMetadataService(), marketplaceClient = new AppMarketplaceClientService(), authStateService = new PlatformAuthStateService()) {
13
+ this.manifestService = manifestService;
14
+ this.bundleService = bundleService;
15
+ this.metadataService = metadataService;
16
+ this.marketplaceClient = marketplaceClient;
17
+ this.authStateService = authStateService;
18
+ }
19
+ publish = async (params) => {
20
+ const { appDirectory: inputAppDirectory, metadataPath, apiBaseUrl, token } = params;
21
+ const appDirectory = path.resolve(inputAppDirectory);
22
+ const manifestBundle = await this.manifestService.load(appDirectory);
23
+ const metadata = await this.metadataService.load({
24
+ appDirectory,
25
+ manifest: manifestBundle.manifest,
26
+ metadataPath
27
+ });
28
+ const actor = await this.resolvePublishActor({
29
+ apiBaseUrl,
30
+ explicitToken: token,
31
+ appId: manifestBundle.manifest.id
32
+ });
33
+ const bundle = await this.bundleService.packAppDirectory({ appDirectory });
34
+ const bundleBytes = Buffer.from(await readFile(bundle.bundlePath));
35
+ const bundleSha256 = createHash("sha256").update(bundleBytes).digest("hex");
36
+ const publishFiles = await this.metadataService.collectPublishFiles({
37
+ appDirectory,
38
+ metadataPath
39
+ });
40
+ const payload = {
41
+ slug: metadata.slug,
42
+ appId: manifestBundle.manifest.id,
43
+ name: manifestBundle.manifest.name,
44
+ version: manifestBundle.manifest.version,
45
+ summary: metadata.summary,
46
+ summaryI18n: metadata.summaryI18n,
47
+ description: metadata.description ?? manifestBundle.manifest.description,
48
+ descriptionI18n: metadata.descriptionI18n,
49
+ author: metadata.author,
50
+ tags: metadata.tags,
51
+ sourceRepo: metadata.sourceRepo,
52
+ homepage: metadata.homepage,
53
+ featured: metadata.featured ?? false,
54
+ publisher: actor.publisher,
55
+ manifest: manifestBundle.manifest,
56
+ permissions: manifestBundle.manifest.permissions ?? {},
57
+ bundleBase64: bundleBytes.toString("base64"),
58
+ bundleSha256,
59
+ files: publishFiles.map((file) => ({
60
+ path: file.path,
61
+ contentBase64: file.bytes.toString("base64")
62
+ }))
63
+ };
64
+ return {
65
+ ...await this.marketplaceClient.publish({
66
+ payload,
67
+ apiBaseUrl,
68
+ token: actor.token
69
+ }),
70
+ bundle: {
71
+ path: bundle.bundlePath,
72
+ sha256: bundleSha256
73
+ }
74
+ };
75
+ };
76
+ resolvePublishActor = async (params) => {
77
+ const explicitToken = params.explicitToken?.trim();
78
+ const envAdminToken = process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();
79
+ if (explicitToken) {
80
+ const token = explicitToken;
81
+ if (!token) throw new Error("缺少 publish token。");
82
+ const me = await this.fetchCurrentPlatformUser({
83
+ token,
84
+ platformApiBase: this.resolvePlatformApiBase()
85
+ });
86
+ return {
87
+ token,
88
+ publisher: this.buildUserPublisher(me, params.appId)
89
+ };
90
+ }
91
+ const authState = this.authStateService.readCurrentAuthState();
92
+ const platformToken = authState.token?.trim();
93
+ if (platformToken) {
94
+ const me = await this.fetchCurrentPlatformUser({
95
+ token: platformToken,
96
+ platformApiBase: this.resolvePlatformApiBase(authState.apiBaseUrl)
97
+ });
98
+ return {
99
+ token: platformToken,
100
+ publisher: this.buildUserPublisher(me, params.appId)
101
+ };
102
+ }
103
+ if (envAdminToken) return {
104
+ token: envAdminToken,
105
+ publisher: this.buildOfficialPublisher()
106
+ };
107
+ throw new Error("发布需要 NextClaw 平台登录态。请先运行 nextclaw login,或传入 --token。");
108
+ };
109
+ fetchCurrentPlatformUser = async (params) => {
110
+ const response = await fetch(`${params.platformApiBase}/platform/auth/me`, { headers: {
111
+ authorization: `Bearer ${params.token}`,
112
+ accept: "application/json"
113
+ } });
114
+ const payload = await response.json().catch(() => null);
115
+ if (!response.ok) {
116
+ const message = typeof payload === "object" && payload && "error" in payload && typeof payload.error?.message === "string" ? payload.error.message : `${response.status} ${response.statusText}`;
117
+ throw new Error(`读取 NextClaw 登录态失败:${message}`);
118
+ }
119
+ const user = typeof payload === "object" && payload && "data" in payload && typeof payload.data?.user === "object" && payload.data.user ? payload.data.user : null;
120
+ const id = typeof user?.id === "string" ? user.id.trim() : "";
121
+ const username = typeof user?.username === "string" ? user.username.trim() : "";
122
+ const role = user?.role === "admin" ? "admin" : "user";
123
+ if (!id) throw new Error("平台登录态缺少用户 id。");
124
+ return {
125
+ id,
126
+ username: username || null,
127
+ role
128
+ };
129
+ };
130
+ resolvePlatformApiBase = (configuredApiBase) => {
131
+ const source = configuredApiBase?.trim() || process.env.NEXTCLAW_PLATFORM_API_BASE?.trim() || DEFAULT_PLATFORM_API_BASE;
132
+ const normalized = new URL(source);
133
+ normalized.pathname = normalized.pathname.replace(/\/v1\/?$/, "/");
134
+ return normalized.toString().replace(/\/+$/, "");
135
+ };
136
+ buildUserPublisher = (user, appId) => {
137
+ if (appId.startsWith("nextclaw.") && user.role === "admin") return this.buildOfficialPublisher();
138
+ if (!user.username) throw new Error("当前 NextClaw 账号还没有 username,无法发布个人 scope app。请先在平台账号页设置用户名。");
139
+ return {
140
+ id: user.username,
141
+ name: user.username,
142
+ url: `https://platform.nextclaw.io/account`
143
+ };
144
+ };
145
+ buildOfficialPublisher = () => {
146
+ return {
147
+ id: "nextclaw",
148
+ name: "NextClaw",
149
+ url: "https://nextclaw.io"
150
+ };
151
+ };
152
+ };
153
+ //#endregion
154
+ export { AppPublishService };
@@ -0,0 +1,66 @@
1
+ import { AppManifest, AppPermissions } from "../manifest/app-manifest.types.js";
2
+ import { AppPublisher } from "../registry/app-remote-registry.types.js";
3
+
4
+ //#region src/publish/app-publish.types.d.ts
5
+ declare const DEFAULT_APP_MARKETPLACE_API_BASE = "https://apps-registry.nextclaw.io";
6
+ type AppMarketplaceMetadata = {
7
+ slug: string;
8
+ summary: string;
9
+ summaryI18n: Record<string, string>;
10
+ description?: string;
11
+ descriptionI18n?: Record<string, string>;
12
+ author: string;
13
+ tags: string[];
14
+ sourceRepo?: string;
15
+ homepage?: string;
16
+ featured?: boolean;
17
+ publisher?: AppPublisher;
18
+ };
19
+ type AppPublishFile = {
20
+ path: string;
21
+ contentBase64: string;
22
+ };
23
+ type AppPublishPayload = {
24
+ slug: string;
25
+ appId: string;
26
+ name: string;
27
+ version: string;
28
+ summary: string;
29
+ summaryI18n: Record<string, string>;
30
+ description?: string;
31
+ descriptionI18n?: Record<string, string>;
32
+ author: string;
33
+ tags: string[];
34
+ sourceRepo?: string;
35
+ homepage?: string;
36
+ featured: boolean;
37
+ publisher: AppPublisher;
38
+ manifest: AppManifest;
39
+ permissions: AppPermissions;
40
+ bundleBase64: string;
41
+ bundleSha256: string;
42
+ files: AppPublishFile[];
43
+ };
44
+ type AppPublishResult = {
45
+ created: boolean;
46
+ item: {
47
+ slug: string;
48
+ appId: string;
49
+ name: string;
50
+ latestVersion: string;
51
+ webUrl?: string;
52
+ install: {
53
+ kind: "registry";
54
+ spec: string;
55
+ command: string;
56
+ registry: string;
57
+ };
58
+ };
59
+ bundle: {
60
+ path: string;
61
+ sha256: string;
62
+ };
63
+ fileCount: number;
64
+ };
65
+ //#endregion
66
+ export { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE };
@@ -0,0 +1,4 @@
1
+ //#region src/publish/app-publish.types.ts
2
+ const DEFAULT_APP_MARKETPLACE_API_BASE = "https://apps-registry.nextclaw.io";
3
+ //#endregion
4
+ export { DEFAULT_APP_MARKETPLACE_API_BASE };
@@ -0,0 +1,11 @@
1
+ //#region src/publish/platform-auth-state.service.d.ts
2
+ type PlatformPublishAuthState = {
3
+ token: string | null;
4
+ apiBaseUrl?: string;
5
+ };
6
+ declare class PlatformAuthStateService {
7
+ readCurrentAuthState: () => PlatformPublishAuthState;
8
+ private resolveConfigPath;
9
+ }
10
+ //#endregion
11
+ export { PlatformAuthStateService, PlatformPublishAuthState };
@@ -0,0 +1,26 @@
1
+ import { resolve } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ //#region src/publish/platform-auth-state.service.ts
5
+ var PlatformAuthStateService = class {
6
+ readCurrentAuthState = () => {
7
+ const configPath = this.resolveConfigPath();
8
+ if (!existsSync(configPath)) return { token: null };
9
+ try {
10
+ const raw = readFileSync(configPath, "utf-8");
11
+ const provider = JSON.parse(raw).providers?.nextclaw;
12
+ return {
13
+ token: typeof provider?.apiKey === "string" && provider.apiKey.trim().length > 0 ? provider.apiKey.trim() : null,
14
+ apiBaseUrl: typeof provider?.apiBase === "string" && provider.apiBase.trim().length > 0 ? provider.apiBase.trim() : void 0
15
+ };
16
+ } catch {
17
+ return { token: null };
18
+ }
19
+ };
20
+ resolveConfigPath = () => {
21
+ const nextclawHome = process.env.NEXTCLAW_HOME?.trim();
22
+ return resolve(nextclawHome && nextclawHome.length > 0 ? resolve(nextclawHome) : resolve(homedir(), ".nextclaw"), "config.json");
23
+ };
24
+ };
25
+ //#endregion
26
+ export { PlatformAuthStateService };
@@ -1,7 +1,7 @@
1
1
  import { AppPermissions } from "../manifest/app-manifest.types.js";
2
2
 
3
3
  //#region src/registry/app-remote-registry.types.d.ts
4
- declare const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.com/";
4
+ declare const DEFAULT_APP_REGISTRY_URL = "https://apps-registry.nextclaw.io/api/v1/apps/registry/";
5
5
  type AppPublisher = {
6
6
  id: string;
7
7
  name: string;
@@ -1,4 +1,4 @@
1
1
  //#region src/registry/app-remote-registry.types.ts
2
- const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.com/";
2
+ const DEFAULT_APP_REGISTRY_URL = "https://apps-registry.nextclaw.io/api/v1/apps/registry/";
3
3
  //#endregion
4
4
  export { DEFAULT_APP_REGISTRY_URL };
@@ -12,6 +12,8 @@ declare class AppScaffoldService {
12
12
  private buildManifest;
13
13
  private buildWatSource;
14
14
  private buildUiHtml;
15
+ private buildMarketplaceMetadata;
16
+ private buildReadme;
15
17
  private buildUiController;
16
18
  private buildIconSvg;
17
19
  }
@@ -13,6 +13,8 @@ var AppScaffoldService = class {
13
13
  const manifestPath = path.join(appDirectory, "manifest.json");
14
14
  await Promise.all([
15
15
  writeFile(manifestPath, `${JSON.stringify(this.buildManifest(appId, appName), null, 2)}\n`, "utf-8"),
16
+ writeFile(path.join(appDirectory, "marketplace.json"), `${JSON.stringify(this.buildMarketplaceMetadata(appName), null, 2)}\n`, "utf-8"),
17
+ writeFile(path.join(appDirectory, "README.md"), this.buildReadme(appName, appId), "utf-8"),
16
18
  writeFile(path.join(appDirectory, "main", "app.wasm"), Buffer.from(APP_WASM_BASE64, "base64")),
17
19
  writeFile(path.join(appDirectory, "main", "app.wat"), this.buildWatSource(), "utf-8"),
18
20
  writeFile(path.join(appDirectory, "ui", "index.html"), this.buildUiHtml(appName), "utf-8"),
@@ -191,6 +193,53 @@ var AppScaffoldService = class {
191
193
  <script type="module" src="./app.controller.js"><\/script>
192
194
  </body>
193
195
  </html>
196
+ `;
197
+ };
198
+ buildMarketplaceMetadata = (appName) => {
199
+ return {
200
+ slug: this.normalizeSlug(appName),
201
+ summary: `A minimal ${appName} app scaffold created by napp.`,
202
+ summaryI18n: {
203
+ en: `A minimal ${appName} app scaffold created by napp.`,
204
+ zh: `一个由 napp 创建的最小 ${appName} 应用骨架。`
205
+ },
206
+ description: `A minimal NextClaw app scaffold for ${appName}, ready for local run and marketplace publish.`,
207
+ descriptionI18n: {
208
+ en: `A minimal NextClaw app scaffold for ${appName}, ready for local run and marketplace publish.`,
209
+ zh: `一个面向 ${appName} 的最小 NextClaw 应用骨架,可直接本地运行并发布到 marketplace。`
210
+ },
211
+ author: "NextClaw",
212
+ tags: [
213
+ "starter",
214
+ "official",
215
+ "local"
216
+ ],
217
+ sourceRepo: "https://github.com/Peiiii/nextclaw",
218
+ homepage: "https://nextclaw.io",
219
+ featured: false
220
+ };
221
+ };
222
+ buildReadme = (appName, appId) => {
223
+ return `# ${appName}
224
+
225
+ This starter app was created by \`napp create\`.
226
+
227
+ ## Local workflow
228
+
229
+ \`\`\`bash
230
+ napp inspect .
231
+ napp run .
232
+ \`\`\`
233
+
234
+ ## Publish workflow
235
+
236
+ \`\`\`bash
237
+ napp publish .
238
+ \`\`\`
239
+
240
+ ## App ID
241
+
242
+ \`${appId}\`
194
243
  `;
195
244
  };
196
245
  buildUiController = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",