@nextclaw/app-runtime 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -3
- package/dist/bundle/app-bundle.service.d.ts +27 -0
- package/dist/bundle/app-bundle.service.js +133 -0
- package/dist/bundle/app-bundle.types.d.ts +24 -0
- package/dist/cli/app-runtime-cli.service.d.ts +27 -0
- package/dist/cli/app-runtime-cli.service.js +281 -0
- package/dist/cli/app-runtime-options.service.d.ts +60 -0
- package/dist/cli/app-runtime-options.service.js +215 -0
- package/dist/commands/create.controller.d.ts +14 -0
- package/dist/commands/create.controller.js +23 -0
- package/dist/commands/grant.controller.d.ts +16 -0
- package/dist/commands/grant.controller.js +25 -0
- package/dist/commands/info.controller.d.ts +14 -0
- package/dist/commands/info.controller.js +27 -0
- package/dist/commands/install.controller.d.ts +15 -0
- package/dist/commands/install.controller.js +24 -0
- package/dist/commands/list.controller.d.ts +13 -0
- package/dist/commands/list.controller.js +25 -0
- package/dist/commands/pack.controller.d.ts +15 -0
- package/dist/commands/pack.controller.js +25 -0
- package/dist/commands/permissions.controller.d.ts +14 -0
- package/dist/commands/permissions.controller.js +26 -0
- package/dist/commands/registry.controller.d.ts +16 -0
- package/dist/commands/registry.controller.js +27 -0
- package/dist/commands/revoke.controller.d.ts +15 -0
- package/dist/commands/revoke.controller.js +24 -0
- package/dist/commands/run.controller.js +14 -6
- package/dist/commands/uninstall.controller.d.ts +15 -0
- package/dist/commands/uninstall.controller.js +23 -0
- package/dist/commands/update.controller.d.ts +16 -0
- package/dist/commands/update.controller.js +29 -0
- package/dist/host/app-instance.service.d.ts +3 -1
- package/dist/host/app-instance.service.js +2 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +23 -2
- package/dist/install/app-installation.service.d.ts +37 -0
- package/dist/install/app-installation.service.js +255 -0
- package/dist/install/app-installation.types.d.ts +62 -0
- package/dist/main.js +5 -108
- package/dist/package.js +1 -1
- package/dist/paths/app-home.service.d.ts +16 -0
- package/dist/paths/app-home.service.js +42 -0
- package/dist/permissions/app-grant.service.d.ts +22 -0
- package/dist/permissions/app-grant.service.js +63 -0
- package/dist/permissions/app-permissions.service.d.ts +3 -1
- package/dist/permissions/app-permissions.service.js +12 -5
- package/dist/permissions/app-permissions.types.d.ts +28 -1
- package/dist/registry/app-registry-config.service.d.ts +16 -0
- package/dist/registry/app-registry-config.service.js +84 -0
- package/dist/registry/app-registry.service.d.ts +38 -0
- package/dist/registry/app-registry.service.js +115 -0
- package/dist/registry/app-registry.types.d.ts +33 -0
- package/dist/registry/app-remote-registry-client.service.d.ts +28 -0
- package/dist/registry/app-remote-registry-client.service.js +114 -0
- package/dist/registry/app-remote-registry.types.d.ts +52 -0
- package/dist/registry/app-remote-registry.types.js +4 -0
- package/dist/scaffold/app-scaffold.service.d.ts +19 -0
- package/dist/scaffold/app-scaffold.service.js +260 -0
- package/package.json +4 -2
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AppRegistryConfigService } from "./app-registry-config.service.js";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/registry/app-remote-registry-client.service.ts
|
|
6
|
+
var AppRemoteRegistryClientService = class {
|
|
7
|
+
constructor(configService = new AppRegistryConfigService()) {
|
|
8
|
+
this.configService = configService;
|
|
9
|
+
}
|
|
10
|
+
resolve = async (params) => {
|
|
11
|
+
const { appId, version: requestedVersion, registryUrl: overrideRegistryUrl } = params;
|
|
12
|
+
const registryUrl = overrideRegistryUrl ? this.normalizeRegistryUrl(overrideRegistryUrl) : (await this.configService.getSnapshot()).currentUrl;
|
|
13
|
+
const metadataUrl = new URL(encodeURIComponent(appId), registryUrl).toString();
|
|
14
|
+
const response = await fetch(metadataUrl);
|
|
15
|
+
if (!response.ok) throw new Error(`无法从 registry 拉取 ${appId} metadata:${response.status} ${response.statusText}`);
|
|
16
|
+
const document = this.parseDocument(await response.json(), appId);
|
|
17
|
+
const version = requestedVersion ?? document["dist-tags"].latest;
|
|
18
|
+
const versionRecord = document.versions[version];
|
|
19
|
+
if (!versionRecord) throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);
|
|
20
|
+
return {
|
|
21
|
+
registryUrl,
|
|
22
|
+
metadataUrl,
|
|
23
|
+
appId,
|
|
24
|
+
version,
|
|
25
|
+
description: versionRecord.description ?? document.description,
|
|
26
|
+
publisher: versionRecord.publisher,
|
|
27
|
+
permissions: versionRecord.permissions,
|
|
28
|
+
bundleUrl: new URL(versionRecord.dist.bundle, metadataUrl).toString(),
|
|
29
|
+
sha256: versionRecord.dist.sha256
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
downloadBundle = async (params) => {
|
|
33
|
+
const { resolution, targetDirectory } = params;
|
|
34
|
+
const response = await fetch(resolution.bundleUrl);
|
|
35
|
+
if (!response.ok) throw new Error(`无法下载 bundle:${resolution.bundleUrl} (${response.status} ${response.statusText})`);
|
|
36
|
+
const bundleBytes = Buffer.from(await response.arrayBuffer());
|
|
37
|
+
const actualSha256 = createHash("sha256").update(bundleBytes).digest("hex");
|
|
38
|
+
if (actualSha256 !== resolution.sha256) throw new Error(`bundle checksum 校验失败:期望 ${resolution.sha256},实际 ${actualSha256}`);
|
|
39
|
+
const bundlePath = path.join(targetDirectory, `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}.napp`);
|
|
40
|
+
await writeFile(bundlePath, bundleBytes);
|
|
41
|
+
return { bundlePath };
|
|
42
|
+
};
|
|
43
|
+
parseDocument = (rawDocument, appId) => {
|
|
44
|
+
if (!rawDocument || typeof rawDocument !== "object" || Array.isArray(rawDocument)) throw new Error(`registry ${appId} metadata 必须是对象。`);
|
|
45
|
+
const candidate = rawDocument;
|
|
46
|
+
const name = this.readRequiredString(candidate.name, "name");
|
|
47
|
+
if (name !== appId) throw new Error(`registry 返回的包名与请求不一致:请求 ${appId},收到 ${name}`);
|
|
48
|
+
const distTags = candidate["dist-tags"];
|
|
49
|
+
if (!distTags || typeof distTags !== "object" || Array.isArray(distTags)) throw new Error("registry metadata 缺少 dist-tags。");
|
|
50
|
+
const versions = candidate.versions;
|
|
51
|
+
if (!versions || typeof versions !== "object" || Array.isArray(versions)) throw new Error("registry metadata 缺少 versions。");
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
description: this.readOptionalString(candidate.description, "description"),
|
|
55
|
+
"dist-tags": { latest: this.readRequiredString(distTags.latest, "dist-tags.latest") },
|
|
56
|
+
versions: Object.fromEntries(Object.entries(versions).map(([version, rawVersion]) => [version, this.parseVersion(rawVersion, name, version)]))
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
parseVersion = (rawVersion, appId, version) => {
|
|
60
|
+
if (!rawVersion || typeof rawVersion !== "object" || Array.isArray(rawVersion)) throw new Error(`registry metadata 的 versions.${version} 必须是对象。`);
|
|
61
|
+
const candidate = rawVersion;
|
|
62
|
+
const name = this.readRequiredString(candidate.name, `versions.${version}.name`);
|
|
63
|
+
const parsedVersion = this.readRequiredString(candidate.version, `versions.${version}.version`);
|
|
64
|
+
if (name !== appId || parsedVersion !== version) throw new Error(`registry metadata 的 versions.${version} 与 app id/version 不一致。`);
|
|
65
|
+
const dist = candidate.dist;
|
|
66
|
+
if (!dist || typeof dist !== "object" || Array.isArray(dist)) throw new Error(`registry metadata 的 versions.${version}.dist 必须是对象。`);
|
|
67
|
+
const distCandidate = dist;
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
version: parsedVersion,
|
|
71
|
+
description: this.readOptionalString(candidate.description, `versions.${version}.description`),
|
|
72
|
+
publisher: this.parsePublisher(candidate.publisher, version),
|
|
73
|
+
permissions: candidate.permissions,
|
|
74
|
+
dist: {
|
|
75
|
+
bundle: this.readRequiredString(distCandidate.bundle, `versions.${version}.dist.bundle`),
|
|
76
|
+
sha256: this.readRequiredString(distCandidate.sha256, `versions.${version}.dist.sha256`)
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
parsePublisher = (rawPublisher, version) => {
|
|
81
|
+
if (rawPublisher === void 0) return;
|
|
82
|
+
if (!rawPublisher || typeof rawPublisher !== "object" || Array.isArray(rawPublisher)) throw new Error(`versions.${version}.publisher 必须是对象。`);
|
|
83
|
+
const candidate = rawPublisher;
|
|
84
|
+
return {
|
|
85
|
+
id: this.readRequiredString(candidate.id, `versions.${version}.publisher.id`),
|
|
86
|
+
name: this.readRequiredString(candidate.name, `versions.${version}.publisher.name`),
|
|
87
|
+
url: this.readOptionalString(candidate.url, `versions.${version}.publisher.url`)
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
readRequiredString = (value, fieldName) => {
|
|
91
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${fieldName} 必须是非空字符串。`);
|
|
92
|
+
return value.trim();
|
|
93
|
+
};
|
|
94
|
+
readOptionalString = (value, fieldName) => {
|
|
95
|
+
if (value === void 0) return;
|
|
96
|
+
return this.readRequiredString(value, fieldName);
|
|
97
|
+
};
|
|
98
|
+
normalizeBundleFileName = (appId) => {
|
|
99
|
+
return appId.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
100
|
+
};
|
|
101
|
+
normalizeRegistryUrl = (registryUrl) => {
|
|
102
|
+
let normalized;
|
|
103
|
+
try {
|
|
104
|
+
normalized = new URL(registryUrl);
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`非法 registry URL:${registryUrl}`);
|
|
107
|
+
}
|
|
108
|
+
if (normalized.protocol !== "http:" && normalized.protocol !== "https:") throw new Error(`registry URL 只支持 http/https:${registryUrl}`);
|
|
109
|
+
const text = normalized.toString();
|
|
110
|
+
return text.endsWith("/") ? text : `${text}/`;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
export { AppRemoteRegistryClientService };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AppPermissions } from "../manifest/app-manifest.types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/registry/app-remote-registry.types.d.ts
|
|
4
|
+
declare const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.com/";
|
|
5
|
+
type AppPublisher = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
};
|
|
10
|
+
type AppRegistryConfig = {
|
|
11
|
+
schemaVersion: 1;
|
|
12
|
+
registry: {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type AppRegistryConfigSnapshot = {
|
|
17
|
+
defaultUrl: string;
|
|
18
|
+
currentUrl: string;
|
|
19
|
+
source: "default" | "config" | "env";
|
|
20
|
+
};
|
|
21
|
+
type AppRemoteRegistryVersion = {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
publisher?: AppPublisher;
|
|
26
|
+
permissions?: AppPermissions;
|
|
27
|
+
dist: {
|
|
28
|
+
bundle: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
type AppRemoteRegistryDocument = {
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
"dist-tags": {
|
|
36
|
+
latest: string;
|
|
37
|
+
};
|
|
38
|
+
versions: Record<string, AppRemoteRegistryVersion>;
|
|
39
|
+
};
|
|
40
|
+
type AppRemoteRegistryResolution = {
|
|
41
|
+
registryUrl: string;
|
|
42
|
+
metadataUrl: string;
|
|
43
|
+
appId: string;
|
|
44
|
+
version: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
publisher?: AppPublisher;
|
|
47
|
+
permissions?: AppPermissions;
|
|
48
|
+
bundleUrl: string;
|
|
49
|
+
sha256: string;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/scaffold/app-scaffold.service.d.ts
|
|
2
|
+
type AppScaffoldResult = {
|
|
3
|
+
appDirectory: string;
|
|
4
|
+
manifestPath: string;
|
|
5
|
+
};
|
|
6
|
+
declare class AppScaffoldService {
|
|
7
|
+
scaffold: (targetDirectory: string) => Promise<AppScaffoldResult>;
|
|
8
|
+
private assertTargetDoesNotExist;
|
|
9
|
+
private buildAppId;
|
|
10
|
+
private buildAppName;
|
|
11
|
+
private normalizeSlug;
|
|
12
|
+
private buildManifest;
|
|
13
|
+
private buildWatSource;
|
|
14
|
+
private buildUiHtml;
|
|
15
|
+
private buildUiController;
|
|
16
|
+
private buildIconSvg;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppScaffoldService };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/scaffold/app-scaffold.service.ts
|
|
4
|
+
var AppScaffoldService = class {
|
|
5
|
+
scaffold = async (targetDirectory) => {
|
|
6
|
+
const appDirectory = path.resolve(targetDirectory);
|
|
7
|
+
await this.assertTargetDoesNotExist(appDirectory);
|
|
8
|
+
await mkdir(path.join(appDirectory, "main"), { recursive: true });
|
|
9
|
+
await mkdir(path.join(appDirectory, "ui"), { recursive: true });
|
|
10
|
+
await mkdir(path.join(appDirectory, "assets"), { recursive: true });
|
|
11
|
+
const appName = this.buildAppName(appDirectory);
|
|
12
|
+
const appId = this.buildAppId(appDirectory);
|
|
13
|
+
const manifestPath = path.join(appDirectory, "manifest.json");
|
|
14
|
+
await Promise.all([
|
|
15
|
+
writeFile(manifestPath, `${JSON.stringify(this.buildManifest(appId, appName), null, 2)}\n`, "utf-8"),
|
|
16
|
+
writeFile(path.join(appDirectory, "main", "app.wasm"), Buffer.from(APP_WASM_BASE64, "base64")),
|
|
17
|
+
writeFile(path.join(appDirectory, "main", "app.wat"), this.buildWatSource(), "utf-8"),
|
|
18
|
+
writeFile(path.join(appDirectory, "ui", "index.html"), this.buildUiHtml(appName), "utf-8"),
|
|
19
|
+
writeFile(path.join(appDirectory, "ui", "app.controller.js"), this.buildUiController(), "utf-8"),
|
|
20
|
+
writeFile(path.join(appDirectory, "assets", "icon.svg"), this.buildIconSvg(), "utf-8")
|
|
21
|
+
]);
|
|
22
|
+
return {
|
|
23
|
+
appDirectory,
|
|
24
|
+
manifestPath
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
assertTargetDoesNotExist = async (targetDirectory) => {
|
|
28
|
+
try {
|
|
29
|
+
await access(targetDirectory);
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`目标目录已存在:${targetDirectory}`);
|
|
34
|
+
};
|
|
35
|
+
buildAppId = (targetDirectory) => {
|
|
36
|
+
return `nextclaw.${this.normalizeSlug(path.basename(targetDirectory))}`;
|
|
37
|
+
};
|
|
38
|
+
buildAppName = (targetDirectory) => {
|
|
39
|
+
return this.normalizeSlug(path.basename(targetDirectory)).split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
40
|
+
};
|
|
41
|
+
normalizeSlug = (value) => {
|
|
42
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
43
|
+
if (!normalized) throw new Error("应用目录名无法生成合法的应用标识。");
|
|
44
|
+
return normalized;
|
|
45
|
+
};
|
|
46
|
+
buildManifest = (appId, appName) => {
|
|
47
|
+
return {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
id: appId,
|
|
50
|
+
name: appName,
|
|
51
|
+
version: "0.1.0",
|
|
52
|
+
description: "A minimal NextClaw micro app scaffold created by napp.",
|
|
53
|
+
icon: "assets/icon.svg",
|
|
54
|
+
main: {
|
|
55
|
+
kind: "wasm",
|
|
56
|
+
entry: "main/app.wasm",
|
|
57
|
+
export: "summarize_notes",
|
|
58
|
+
action: "runStarterDemo"
|
|
59
|
+
},
|
|
60
|
+
ui: { entry: "ui/index.html" },
|
|
61
|
+
permissions: {
|
|
62
|
+
storage: { namespace: appId.replace(/\./g, "-") },
|
|
63
|
+
capabilities: { hostBridge: true }
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
buildWatSource = () => {
|
|
68
|
+
return `(module
|
|
69
|
+
(func (export "summarize_notes") (param i32 i32) (result i32)
|
|
70
|
+
local.get 0
|
|
71
|
+
local.get 1
|
|
72
|
+
i32.add
|
|
73
|
+
i32.const 200
|
|
74
|
+
i32.add))\n`;
|
|
75
|
+
};
|
|
76
|
+
buildUiHtml = (appName) => {
|
|
77
|
+
return `<!doctype html>
|
|
78
|
+
<html lang="zh-CN">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="utf-8" />
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
82
|
+
<title>${appName}</title>
|
|
83
|
+
<style>
|
|
84
|
+
:root {
|
|
85
|
+
color-scheme: light;
|
|
86
|
+
font-family: "IBM Plex Sans", "Helvetica Neue", sans-serif;
|
|
87
|
+
background:
|
|
88
|
+
radial-gradient(circle at top left, rgba(255, 206, 134, 0.24), transparent 30%),
|
|
89
|
+
linear-gradient(165deg, #f7f1e7 0%, #fcfcfe 48%, #e1ebf5 100%);
|
|
90
|
+
color: #11233a;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
body {
|
|
94
|
+
margin: 0;
|
|
95
|
+
min-height: 100vh;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main {
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
max-width: 760px;
|
|
101
|
+
margin: 0 auto;
|
|
102
|
+
padding: 48px 20px 72px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.panel {
|
|
106
|
+
background: rgba(255, 255, 255, 0.78);
|
|
107
|
+
border: 1px solid rgba(17, 35, 58, 0.08);
|
|
108
|
+
border-radius: 24px;
|
|
109
|
+
box-shadow: 0 18px 42px rgba(17, 35, 58, 0.12);
|
|
110
|
+
padding: 28px;
|
|
111
|
+
backdrop-filter: blur(18px);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
h1 {
|
|
115
|
+
margin: 0 0 12px;
|
|
116
|
+
font-size: clamp(30px, 6vw, 54px);
|
|
117
|
+
line-height: 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
p {
|
|
121
|
+
line-height: 1.6;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
button {
|
|
125
|
+
border: 0;
|
|
126
|
+
border-radius: 999px;
|
|
127
|
+
background: #11233a;
|
|
128
|
+
color: #fff;
|
|
129
|
+
padding: 12px 18px;
|
|
130
|
+
font-size: 15px;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dl {
|
|
135
|
+
display: grid;
|
|
136
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
137
|
+
gap: 12px;
|
|
138
|
+
margin: 24px 0 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
dt {
|
|
142
|
+
font-size: 12px;
|
|
143
|
+
color: #556881;
|
|
144
|
+
text-transform: uppercase;
|
|
145
|
+
letter-spacing: 0.08em;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
dd {
|
|
149
|
+
margin: 6px 0 0;
|
|
150
|
+
font-size: 24px;
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pre {
|
|
155
|
+
margin-top: 20px;
|
|
156
|
+
padding: 14px;
|
|
157
|
+
border-radius: 16px;
|
|
158
|
+
background: rgba(17, 35, 58, 0.07);
|
|
159
|
+
overflow: auto;
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
162
|
+
</head>
|
|
163
|
+
<body>
|
|
164
|
+
<main>
|
|
165
|
+
<section class="panel">
|
|
166
|
+
<p>NextClaw Micro App</p>
|
|
167
|
+
<h1 id="title">Loading...</h1>
|
|
168
|
+
<p id="description">A starter scaffold created by napp create.</p>
|
|
169
|
+
<button id="run-button" type="button">Run Starter Demo</button>
|
|
170
|
+
<dl>
|
|
171
|
+
<div>
|
|
172
|
+
<dt>Action</dt>
|
|
173
|
+
<dd id="action">-</dd>
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<dt>Documents</dt>
|
|
177
|
+
<dd id="document-count">-</dd>
|
|
178
|
+
</div>
|
|
179
|
+
<div>
|
|
180
|
+
<dt>Text Bytes</dt>
|
|
181
|
+
<dd id="text-bytes">-</dd>
|
|
182
|
+
</div>
|
|
183
|
+
<div>
|
|
184
|
+
<dt>Wasm Score</dt>
|
|
185
|
+
<dd id="wasm-score">-</dd>
|
|
186
|
+
</div>
|
|
187
|
+
</dl>
|
|
188
|
+
<pre id="details"></pre>
|
|
189
|
+
</section>
|
|
190
|
+
</main>
|
|
191
|
+
<script type="module" src="./app.controller.js"><\/script>
|
|
192
|
+
</body>
|
|
193
|
+
</html>
|
|
194
|
+
`;
|
|
195
|
+
};
|
|
196
|
+
buildUiController = () => {
|
|
197
|
+
return `const titleNode = document.getElementById("title");
|
|
198
|
+
const descriptionNode = document.getElementById("description");
|
|
199
|
+
const actionNode = document.getElementById("action");
|
|
200
|
+
const documentCountNode = document.getElementById("document-count");
|
|
201
|
+
const textBytesNode = document.getElementById("text-bytes");
|
|
202
|
+
const wasmScoreNode = document.getElementById("wasm-score");
|
|
203
|
+
const detailsNode = document.getElementById("details");
|
|
204
|
+
const runButton = document.getElementById("run-button");
|
|
205
|
+
|
|
206
|
+
const setDetails = (value) => {
|
|
207
|
+
detailsNode.textContent = JSON.stringify(value, null, 2);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const loadManifest = async () => {
|
|
211
|
+
const response = await fetch("/__napp/manifest");
|
|
212
|
+
const payload = await response.json();
|
|
213
|
+
titleNode.textContent = payload.manifest.name;
|
|
214
|
+
descriptionNode.textContent = payload.manifest.description;
|
|
215
|
+
actionNode.textContent = payload.manifest.main.action;
|
|
216
|
+
setDetails(payload);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const runDemo = async () => {
|
|
220
|
+
runButton.disabled = true;
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch("/__napp/run", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
"content-type": "application/json",
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
action: actionNode.textContent,
|
|
229
|
+
}),
|
|
230
|
+
});
|
|
231
|
+
const payload = await response.json();
|
|
232
|
+
documentCountNode.textContent = String(payload.result.input.documentCount);
|
|
233
|
+
textBytesNode.textContent = String(payload.result.input.textBytes);
|
|
234
|
+
wasmScoreNode.textContent = String(payload.result.output.output);
|
|
235
|
+
setDetails(payload);
|
|
236
|
+
} finally {
|
|
237
|
+
runButton.disabled = false;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
runButton.addEventListener("click", () => {
|
|
242
|
+
void runDemo();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await loadManifest();
|
|
246
|
+
`;
|
|
247
|
+
};
|
|
248
|
+
buildIconSvg = () => {
|
|
249
|
+
return `<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
250
|
+
<rect width="160" height="160" rx="40" fill="#11233A" />
|
|
251
|
+
<path d="M42 45H118V58H74V115H58V58H42V45Z" fill="#F8C97E" />
|
|
252
|
+
<path d="M92 74H118V87H92V74Z" fill="#FFFFFF" fill-opacity="0.92" />
|
|
253
|
+
<path d="M92 95H118V108H92V95Z" fill="#FFFFFF" fill-opacity="0.72" />
|
|
254
|
+
</svg>
|
|
255
|
+
`;
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
const APP_WASM_BASE64 = "AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL";
|
|
259
|
+
//#endregion
|
|
260
|
+
export { AppScaffoldService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/app-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Standalone micro app runtime and CLI for NextClaw apps.",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"dist",
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
|
-
"dependencies": {
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"fflate": "^0.8.2"
|
|
43
|
+
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^20.17.6",
|
|
44
46
|
"prettier": "^3.3.3",
|