@zshuangmu/agenthub 0.4.14 → 0.4.16

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 (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +268 -268
  3. package/package.json +41 -41
  4. package/src/api-server.js +518 -244
  5. package/src/cli.js +714 -671
  6. package/src/commands/api.js +9 -9
  7. package/src/commands/doctor.js +335 -335
  8. package/src/commands/info.js +15 -15
  9. package/src/commands/install.js +56 -56
  10. package/src/commands/list.js +78 -78
  11. package/src/commands/pack.js +249 -156
  12. package/src/commands/publish-remote.js +9 -9
  13. package/src/commands/publish.js +7 -7
  14. package/src/commands/rollback.js +59 -59
  15. package/src/commands/search.js +14 -14
  16. package/src/commands/serve.js +9 -9
  17. package/src/commands/stats.js +105 -105
  18. package/src/commands/uninstall.js +76 -76
  19. package/src/commands/update.js +54 -54
  20. package/src/commands/verify.js +133 -133
  21. package/src/commands/versions.js +75 -75
  22. package/src/commands/web.js +9 -9
  23. package/src/index.js +18 -18
  24. package/src/lib/auth.js +301 -0
  25. package/src/lib/bundle-transfer.js +58 -58
  26. package/src/lib/colors.js +60 -60
  27. package/src/lib/database.js +450 -244
  28. package/src/lib/debug.js +135 -135
  29. package/src/lib/fs-utils.js +107 -50
  30. package/src/lib/html.js +2163 -1824
  31. package/src/lib/http.js +168 -168
  32. package/src/lib/install.js +60 -60
  33. package/src/lib/manifest.js +124 -124
  34. package/src/lib/openclaw-config.js +40 -40
  35. package/src/lib/permissions.js +105 -0
  36. package/src/lib/privacy-engine.js +220 -0
  37. package/src/lib/registry.js +130 -130
  38. package/src/lib/remote.js +11 -11
  39. package/src/lib/security-scanner.js +233 -233
  40. package/src/lib/signing.js +158 -0
  41. package/src/lib/version-manager.js +77 -77
  42. package/src/server.js +176 -176
  43. package/src/web-server.js +135 -135
@@ -1,77 +1,77 @@
1
- /**
2
- * Version Manager
3
- * Agent 版本管理共享逻辑
4
- */
5
-
6
- import path from "node:path";
7
- import { pathExists, readJson, writeJson } from "./fs-utils.js";
8
- import { installBundle } from "./install.js";
9
-
10
- /**
11
- * 获取当前安装的版本
12
- */
13
- export async function getCurrentVersion(targetWorkspace) {
14
- if (!targetWorkspace) return null;
15
-
16
- const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
17
- if (await pathExists(installRecordPath)) {
18
- const record = await readJson(installRecordPath);
19
- return record.version;
20
- }
21
- return null;
22
- }
23
-
24
- /**
25
- * 构建安装选项
26
- */
27
- export function buildInstallOptions(slug, version, targetWorkspace, options) {
28
- const registryDir = options.registry ? path.resolve(options.registry) : null;
29
-
30
- const installOptions = {
31
- agentSpec: `${slug}:${version}`,
32
- targetWorkspace,
33
- };
34
-
35
- if (registryDir) {
36
- installOptions.registryDir = registryDir;
37
- } else {
38
- installOptions.serverUrl = options.server || "https://agenthub.cyou";
39
- }
40
-
41
- return installOptions;
42
- }
43
-
44
- /**
45
- * 更新安装记录
46
- */
47
- export async function updateInstallRecord(targetWorkspace, slug, version, metadata = {}) {
48
- if (!targetWorkspace) return;
49
-
50
- const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
51
- await writeJson(installRecordPath, {
52
- slug,
53
- version,
54
- ...metadata,
55
- });
56
- }
57
-
58
- /**
59
- * 执行版本变更(更新或回滚)
60
- */
61
- export async function performVersionChange(slug, targetVersion, previousVersion, targetWorkspace, options) {
62
- const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
63
- const result = await installBundle(installOptions);
64
-
65
- const metadata = {
66
- updatedAt: new Date().toISOString(),
67
- previousVersion,
68
- };
69
-
70
- await updateInstallRecord(targetWorkspace, slug, targetVersion, metadata);
71
-
72
- return {
73
- manifest: result.manifest,
74
- previousVersion,
75
- targetVersion,
76
- };
77
- }
1
+ /**
2
+ * Version Manager
3
+ * Agent 版本管理共享逻辑
4
+ */
5
+
6
+ import path from "node:path";
7
+ import { pathExists, readJson, writeJson } from "./fs-utils.js";
8
+ import { installBundle } from "./install.js";
9
+
10
+ /**
11
+ * 获取当前安装的版本
12
+ */
13
+ export async function getCurrentVersion(targetWorkspace) {
14
+ if (!targetWorkspace) return null;
15
+
16
+ const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
17
+ if (await pathExists(installRecordPath)) {
18
+ const record = await readJson(installRecordPath);
19
+ return record.version;
20
+ }
21
+ return null;
22
+ }
23
+
24
+ /**
25
+ * 构建安装选项
26
+ */
27
+ export function buildInstallOptions(slug, version, targetWorkspace, options) {
28
+ const registryDir = options.registry ? path.resolve(options.registry) : null;
29
+
30
+ const installOptions = {
31
+ agentSpec: `${slug}:${version}`,
32
+ targetWorkspace,
33
+ };
34
+
35
+ if (registryDir) {
36
+ installOptions.registryDir = registryDir;
37
+ } else {
38
+ installOptions.serverUrl = options.server || "https://agenthub.cyou";
39
+ }
40
+
41
+ return installOptions;
42
+ }
43
+
44
+ /**
45
+ * 更新安装记录
46
+ */
47
+ export async function updateInstallRecord(targetWorkspace, slug, version, metadata = {}) {
48
+ if (!targetWorkspace) return;
49
+
50
+ const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
51
+ await writeJson(installRecordPath, {
52
+ slug,
53
+ version,
54
+ ...metadata,
55
+ });
56
+ }
57
+
58
+ /**
59
+ * 执行版本变更(更新或回滚)
60
+ */
61
+ export async function performVersionChange(slug, targetVersion, previousVersion, targetWorkspace, options) {
62
+ const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
63
+ const result = await installBundle(installOptions);
64
+
65
+ const metadata = {
66
+ updatedAt: new Date().toISOString(),
67
+ previousVersion,
68
+ };
69
+
70
+ await updateInstallRecord(targetWorkspace, slug, targetVersion, metadata);
71
+
72
+ return {
73
+ manifest: result.manifest,
74
+ previousVersion,
75
+ targetVersion,
76
+ };
77
+ }
package/src/server.js CHANGED
@@ -1,176 +1,176 @@
1
- import http from "node:http";
2
- import path from "node:path";
3
- import { readFile } from "node:fs/promises";
4
- import { infoCommand, installCommand, publishCommand, searchCommand } from "./index.js";
5
- import { publishUploadedBundle, serializeBundleDir } from "./lib/bundle-transfer.js";
6
- import { notFound, readJsonBody, sendHtml, sendJson } from "./lib/http.js";
7
- import { renderAgentDetailPage, renderAgentListPage, renderStatsPage } from "./lib/html.js";
8
- import { sortByDownloadsAndTime } from "./lib/registry.js";
9
- import {
10
- initDatabase,
11
- incrementDownloads,
12
- getAgentDownloads,
13
- getAgentsDownloads,
14
- getTotalDownloads,
15
- getDownloadRanking,
16
- getRecentDownloads,
17
- getDatabaseStats
18
- } from "./lib/database.js";
19
-
20
- export async function createServer({ registryDir, port = 3000, host = "0.0.0.0" }) {
21
- // 初始化数据库
22
- await initDatabase(registryDir);
23
-
24
- const server = http.createServer(async (request, response) => {
25
- try {
26
- const url = new URL(request.url, "http://127.0.0.1");
27
-
28
- // API: 获取 AgentHub Discover Skill
29
- if (url.pathname === "/skills/agenthub-discover/SKILL.md") {
30
- try {
31
- const skillPath = path.join(process.cwd(), "skills", "agenthub-discover", "SKILL.md");
32
- const content = await readFile(skillPath, "utf8");
33
- response.writeHead(200, {
34
- "Content-Type": "text/markdown; charset=utf-8",
35
- "Access-Control-Allow-Origin": "*"
36
- });
37
- response.end(content);
38
- } catch {
39
- response.writeHead(404, { "Content-Type": "application/json" });
40
- response.end(JSON.stringify({ error: "Skill not found" }));
41
- }
42
- return;
43
- }
44
-
45
- if (url.pathname === "/api/agents") {
46
- const agents = await searchCommand(url.searchParams.get("q") ?? "", { registry: registryDir });
47
- // 添加下载数
48
- const slugs = agents.map(a => a.slug);
49
- const downloads = await getAgentsDownloads(registryDir, slugs);
50
- const agentsWithDownloads = agents.map(a => ({ ...a, downloads: downloads[a.slug] || 0 }));
51
- sortByDownloadsAndTime(agentsWithDownloads);
52
- sendJson(response, 200, { agents: agentsWithDownloads });
53
- return;
54
- }
55
-
56
- if (url.pathname === "/api/publish" && request.method === "POST") {
57
- const body = await readJsonBody(request);
58
- const manifest = await publishCommand(body.bundleDir, { registry: registryDir });
59
- sendJson(response, 200, manifest);
60
- return;
61
- }
62
-
63
- if (url.pathname === "/api/publish-upload" && request.method === "POST") {
64
- const body = await readJsonBody(request);
65
- const manifest = await publishUploadedBundle({ payload: body, registryDir });
66
- sendJson(response, 200, manifest);
67
- return;
68
- }
69
-
70
- if (url.pathname === "/api/install" && request.method === "POST") {
71
- const body = await readJsonBody(request);
72
- const result = await installCommand(body.agent, {
73
- registry: registryDir,
74
- targetWorkspace: body.targetWorkspace,
75
- });
76
- // 记录下载(包含元数据)
77
- const slug = body.agent.split(":")[0];
78
- await incrementDownloads(registryDir, slug, {
79
- targetWorkspace: body.targetWorkspace,
80
- ip: request.socket.remoteAddress,
81
- userAgent: request.headers['user-agent']
82
- });
83
- sendJson(response, 200, result);
84
- return;
85
- }
86
-
87
- // API: 获取下载统计
88
- if (url.pathname === "/api/stats") {
89
- const stats = await getDatabaseStats(registryDir);
90
- const ranking = await getDownloadRanking(registryDir, 10);
91
- const recent = await getRecentDownloads(registryDir, 20);
92
- sendJson(response, 200, { stats, ranking, recent });
93
- return;
94
- }
95
-
96
- // API: 获取下载排行
97
- if (url.pathname === "/api/stats/ranking") {
98
- const limit = parseInt(url.searchParams.get("limit") || "10", 10);
99
- const ranking = await getDownloadRanking(registryDir, limit);
100
- sendJson(response, 200, { ranking });
101
- return;
102
- }
103
-
104
- if (url.pathname.startsWith("/api/agents/") && url.pathname.endsWith("/download")) {
105
- const slug = url.pathname.slice("/api/agents/".length, -"/download".length);
106
- const version = url.searchParams.get("version") || undefined;
107
- const manifest = await infoCommand(version ? `${slug}:${version}` : slug, { registry: registryDir });
108
- const bundleDir = path.join(registryDir, "agents", manifest.slug, manifest.version);
109
- const payload = await serializeBundleDir(bundleDir);
110
- // 记录下载(包含元数据)
111
- await incrementDownloads(registryDir, manifest.slug, {
112
- ip: request.socket.remoteAddress,
113
- userAgent: request.headers['user-agent']
114
- });
115
- sendJson(response, 200, payload);
116
- return;
117
- }
118
-
119
- if (url.pathname.startsWith("/api/agents/")) {
120
- const slug = url.pathname.slice("/api/agents/".length);
121
- const manifest = await infoCommand(slug, { registry: registryDir });
122
- // 添加下载数
123
- const downloads = await getAgentDownloads(registryDir, slug);
124
- sendJson(response, 200, { ...manifest, downloads });
125
- return;
126
- }
127
-
128
- if (url.pathname === "/") {
129
- const query = url.searchParams.get("q") ?? "";
130
- const agents = await searchCommand(query, { registry: registryDir });
131
- // 添加下载数
132
- const slugs = agents.map(a => a.slug);
133
- const downloads = await getAgentsDownloads(registryDir, slugs);
134
- const totalDownloads = await getTotalDownloads(registryDir);
135
- const agentsWithDownloads = agents.map(a => ({ ...a, downloads: downloads[a.slug] || 0 }));
136
- sortByDownloadsAndTime(agentsWithDownloads);
137
- sendHtml(response, 200, renderAgentListPage({ query, agents: agentsWithDownloads, totalDownloads }));
138
- return;
139
- }
140
-
141
- if (url.pathname.startsWith("/agents/")) {
142
- const slug = url.pathname.slice("/agents/".length);
143
- const manifest = await infoCommand(slug, { registry: registryDir });
144
- // 添加下载数
145
- const downloads = await getAgentDownloads(registryDir, slug);
146
- sendHtml(response, 200, renderAgentDetailPage({ ...manifest, downloads }));
147
- return;
148
- }
149
-
150
- // 统计页面
151
- if (url.pathname === "/stats") {
152
- const stats = await getDatabaseStats(registryDir);
153
- const ranking = await getDownloadRanking(registryDir, 10);
154
- const recent = await getRecentDownloads(registryDir, 20);
155
- sendHtml(response, 200, renderStatsPage({ stats, ranking, recent }));
156
- return;
157
- }
158
-
159
- notFound(response);
160
- } catch (error) {
161
- sendJson(response, 500, { error: error.message });
162
- }
163
- });
164
-
165
- await new Promise((resolve) => server.listen(port, host, resolve));
166
- const address = server.address();
167
- const actualPort = typeof address === "object" && address ? address.port : port;
168
-
169
- return {
170
- server,
171
- port: actualPort,
172
- host,
173
- baseUrl: `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${actualPort}`,
174
- close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
175
- };
176
- }
1
+ import http from "node:http";
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
4
+ import { infoCommand, installCommand, publishCommand, searchCommand } from "./index.js";
5
+ import { publishUploadedBundle, serializeBundleDir } from "./lib/bundle-transfer.js";
6
+ import { notFound, readJsonBody, sendHtml, sendJson } from "./lib/http.js";
7
+ import { renderAgentDetailPage, renderAgentListPage, renderStatsPage } from "./lib/html.js";
8
+ import { sortByDownloadsAndTime } from "./lib/registry.js";
9
+ import {
10
+ initDatabase,
11
+ incrementDownloads,
12
+ getAgentDownloads,
13
+ getAgentsDownloads,
14
+ getTotalDownloads,
15
+ getDownloadRanking,
16
+ getRecentDownloads,
17
+ getDatabaseStats
18
+ } from "./lib/database.js";
19
+
20
+ export async function createServer({ registryDir, port = 3000, host = "0.0.0.0" }) {
21
+ // 初始化数据库
22
+ await initDatabase(registryDir);
23
+
24
+ const server = http.createServer(async (request, response) => {
25
+ try {
26
+ const url = new URL(request.url, "http://127.0.0.1");
27
+
28
+ // API: 获取 AgentHub Discover Skill
29
+ if (url.pathname === "/skills/agenthub-discover/SKILL.md") {
30
+ try {
31
+ const skillPath = path.join(process.cwd(), "skills", "agenthub-discover", "SKILL.md");
32
+ const content = await readFile(skillPath, "utf8");
33
+ response.writeHead(200, {
34
+ "Content-Type": "text/markdown; charset=utf-8",
35
+ "Access-Control-Allow-Origin": "*"
36
+ });
37
+ response.end(content);
38
+ } catch {
39
+ response.writeHead(404, { "Content-Type": "application/json" });
40
+ response.end(JSON.stringify({ error: "Skill not found" }));
41
+ }
42
+ return;
43
+ }
44
+
45
+ if (url.pathname === "/api/agents") {
46
+ const agents = await searchCommand(url.searchParams.get("q") ?? "", { registry: registryDir });
47
+ // 添加下载数
48
+ const slugs = agents.map(a => a.slug);
49
+ const downloads = await getAgentsDownloads(registryDir, slugs);
50
+ const agentsWithDownloads = agents.map(a => ({ ...a, downloads: downloads[a.slug] || 0 }));
51
+ sortByDownloadsAndTime(agentsWithDownloads);
52
+ sendJson(response, 200, { agents: agentsWithDownloads });
53
+ return;
54
+ }
55
+
56
+ if (url.pathname === "/api/publish" && request.method === "POST") {
57
+ const body = await readJsonBody(request);
58
+ const manifest = await publishCommand(body.bundleDir, { registry: registryDir });
59
+ sendJson(response, 200, manifest);
60
+ return;
61
+ }
62
+
63
+ if (url.pathname === "/api/publish-upload" && request.method === "POST") {
64
+ const body = await readJsonBody(request);
65
+ const manifest = await publishUploadedBundle({ payload: body, registryDir });
66
+ sendJson(response, 200, manifest);
67
+ return;
68
+ }
69
+
70
+ if (url.pathname === "/api/install" && request.method === "POST") {
71
+ const body = await readJsonBody(request);
72
+ const result = await installCommand(body.agent, {
73
+ registry: registryDir,
74
+ targetWorkspace: body.targetWorkspace,
75
+ });
76
+ // 记录下载(包含元数据)
77
+ const slug = body.agent.split(":")[0];
78
+ await incrementDownloads(registryDir, slug, {
79
+ targetWorkspace: body.targetWorkspace,
80
+ ip: request.socket.remoteAddress,
81
+ userAgent: request.headers['user-agent']
82
+ });
83
+ sendJson(response, 200, result);
84
+ return;
85
+ }
86
+
87
+ // API: 获取下载统计
88
+ if (url.pathname === "/api/stats") {
89
+ const stats = await getDatabaseStats(registryDir);
90
+ const ranking = await getDownloadRanking(registryDir, 10);
91
+ const recent = await getRecentDownloads(registryDir, 20);
92
+ sendJson(response, 200, { stats, ranking, recent });
93
+ return;
94
+ }
95
+
96
+ // API: 获取下载排行
97
+ if (url.pathname === "/api/stats/ranking") {
98
+ const limit = parseInt(url.searchParams.get("limit") || "10", 10);
99
+ const ranking = await getDownloadRanking(registryDir, limit);
100
+ sendJson(response, 200, { ranking });
101
+ return;
102
+ }
103
+
104
+ if (url.pathname.startsWith("/api/agents/") && url.pathname.endsWith("/download")) {
105
+ const slug = url.pathname.slice("/api/agents/".length, -"/download".length);
106
+ const version = url.searchParams.get("version") || undefined;
107
+ const manifest = await infoCommand(version ? `${slug}:${version}` : slug, { registry: registryDir });
108
+ const bundleDir = path.join(registryDir, "agents", manifest.slug, manifest.version);
109
+ const payload = await serializeBundleDir(bundleDir);
110
+ // 记录下载(包含元数据)
111
+ await incrementDownloads(registryDir, manifest.slug, {
112
+ ip: request.socket.remoteAddress,
113
+ userAgent: request.headers['user-agent']
114
+ });
115
+ sendJson(response, 200, payload);
116
+ return;
117
+ }
118
+
119
+ if (url.pathname.startsWith("/api/agents/")) {
120
+ const slug = url.pathname.slice("/api/agents/".length);
121
+ const manifest = await infoCommand(slug, { registry: registryDir });
122
+ // 添加下载数
123
+ const downloads = await getAgentDownloads(registryDir, slug);
124
+ sendJson(response, 200, { ...manifest, downloads });
125
+ return;
126
+ }
127
+
128
+ if (url.pathname === "/") {
129
+ const query = url.searchParams.get("q") ?? "";
130
+ const agents = await searchCommand(query, { registry: registryDir });
131
+ // 添加下载数
132
+ const slugs = agents.map(a => a.slug);
133
+ const downloads = await getAgentsDownloads(registryDir, slugs);
134
+ const totalDownloads = await getTotalDownloads(registryDir);
135
+ const agentsWithDownloads = agents.map(a => ({ ...a, downloads: downloads[a.slug] || 0 }));
136
+ sortByDownloadsAndTime(agentsWithDownloads);
137
+ sendHtml(response, 200, renderAgentListPage({ query, agents: agentsWithDownloads, totalDownloads }));
138
+ return;
139
+ }
140
+
141
+ if (url.pathname.startsWith("/agents/")) {
142
+ const slug = url.pathname.slice("/agents/".length);
143
+ const manifest = await infoCommand(slug, { registry: registryDir });
144
+ // 添加下载数
145
+ const downloads = await getAgentDownloads(registryDir, slug);
146
+ sendHtml(response, 200, renderAgentDetailPage({ ...manifest, downloads }));
147
+ return;
148
+ }
149
+
150
+ // 统计页面
151
+ if (url.pathname === "/stats") {
152
+ const stats = await getDatabaseStats(registryDir);
153
+ const ranking = await getDownloadRanking(registryDir, 10);
154
+ const recent = await getRecentDownloads(registryDir, 20);
155
+ sendHtml(response, 200, renderStatsPage({ stats, ranking, recent }));
156
+ return;
157
+ }
158
+
159
+ notFound(response);
160
+ } catch (error) {
161
+ sendJson(response, 500, { error: error.message });
162
+ }
163
+ });
164
+
165
+ await new Promise((resolve) => server.listen(port, host, resolve));
166
+ const address = server.address();
167
+ const actualPort = typeof address === "object" && address ? address.port : port;
168
+
169
+ return {
170
+ server,
171
+ port: actualPort,
172
+ host,
173
+ baseUrl: `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${actualPort}`,
174
+ close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
175
+ };
176
+ }