@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.
- package/LICENSE +21 -21
- package/README.md +268 -268
- package/package.json +41 -41
- package/src/api-server.js +518 -244
- package/src/cli.js +714 -671
- package/src/commands/api.js +9 -9
- package/src/commands/doctor.js +335 -335
- package/src/commands/info.js +15 -15
- package/src/commands/install.js +56 -56
- package/src/commands/list.js +78 -78
- package/src/commands/pack.js +249 -156
- package/src/commands/publish-remote.js +9 -9
- package/src/commands/publish.js +7 -7
- package/src/commands/rollback.js +59 -59
- package/src/commands/search.js +14 -14
- package/src/commands/serve.js +9 -9
- package/src/commands/stats.js +105 -105
- package/src/commands/uninstall.js +76 -76
- package/src/commands/update.js +54 -54
- package/src/commands/verify.js +133 -133
- package/src/commands/versions.js +75 -75
- package/src/commands/web.js +9 -9
- package/src/index.js +18 -18
- package/src/lib/auth.js +301 -0
- package/src/lib/bundle-transfer.js +58 -58
- package/src/lib/colors.js +60 -60
- package/src/lib/database.js +450 -244
- package/src/lib/debug.js +135 -135
- package/src/lib/fs-utils.js +107 -50
- package/src/lib/html.js +2163 -1824
- package/src/lib/http.js +168 -168
- package/src/lib/install.js +60 -60
- package/src/lib/manifest.js +124 -124
- package/src/lib/openclaw-config.js +40 -40
- package/src/lib/permissions.js +105 -0
- package/src/lib/privacy-engine.js +220 -0
- package/src/lib/registry.js +130 -130
- package/src/lib/remote.js +11 -11
- package/src/lib/security-scanner.js +233 -233
- package/src/lib/signing.js +158 -0
- package/src/lib/version-manager.js +77 -77
- package/src/server.js +176 -176
- package/src/web-server.js +135 -135
package/src/lib/registry.js
CHANGED
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { copyDir, ensureDir, pathExists, readJson, writeJson } from "./fs-utils.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 解析 agentSpec,支持两种格式:
|
|
6
|
-
* - 短名格式:slug 或 slug:version
|
|
7
|
-
* - URI 格式:agenthub://owner/slug@version
|
|
8
|
-
*/
|
|
9
|
-
export function parseSpec(agentSpec) {
|
|
10
|
-
// URI 格式:agenthub://owner/slug@version
|
|
11
|
-
if (agentSpec.startsWith("agenthub://")) {
|
|
12
|
-
const uri = agentSpec.slice("agenthub://".length);
|
|
13
|
-
const parts = uri.split("/");
|
|
14
|
-
const lastPart = parts[parts.length - 1] || parts[parts.length - 2];
|
|
15
|
-
const [slug, version] = lastPart.split("@");
|
|
16
|
-
return { slug, version: version || undefined };
|
|
17
|
-
}
|
|
18
|
-
// 短名格式:slug 或 slug:version
|
|
19
|
-
const [slug, version] = agentSpec.split(":");
|
|
20
|
-
return { slug, version: version || undefined };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 版本比较函数(用于排序)
|
|
25
|
-
* 按 semver 降序排列
|
|
26
|
-
*/
|
|
27
|
-
export function compareVersionsDesc(a, b) {
|
|
28
|
-
return b.version.localeCompare(a.version, undefined, { numeric: true });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 从 agent 条目中获取最新版本
|
|
33
|
-
*/
|
|
34
|
-
export function getLatestVersion(entries) {
|
|
35
|
-
if (!entries || entries.length === 0) return null;
|
|
36
|
-
return [...entries].sort(compareVersionsDesc)[0];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 按下载量和更新时间排序 Agent 列表
|
|
41
|
-
* 下载量降序,下载量一致时按更新时间降序
|
|
42
|
-
*/
|
|
43
|
-
export function sortByDownloadsAndTime(agents) {
|
|
44
|
-
return agents.sort((a, b) => {
|
|
45
|
-
if (b.downloads !== a.downloads) {
|
|
46
|
-
return b.downloads - a.downloads;
|
|
47
|
-
}
|
|
48
|
-
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
49
|
-
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
50
|
-
return timeB - timeA;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function publishBundle(bundleDir, registryDir) {
|
|
55
|
-
const manifest = await readJson(path.join(bundleDir, "MANIFEST.json"));
|
|
56
|
-
if (manifest.runtime?.type !== "openclaw") {
|
|
57
|
-
throw new Error("Only OpenClaw bundles are supported.");
|
|
58
|
-
}
|
|
59
|
-
if ((manifest.includes?.memory?.private ?? 0) > 0) {
|
|
60
|
-
throw new Error("Public publish rejected: private memory detected.");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const targetDir = path.join(registryDir, "agents", manifest.slug, manifest.version);
|
|
64
|
-
await ensureDir(path.join(registryDir, "agents", manifest.slug));
|
|
65
|
-
await copyDir(bundleDir, targetDir);
|
|
66
|
-
|
|
67
|
-
const indexPath = path.join(registryDir, "index.json");
|
|
68
|
-
const index = (await pathExists(indexPath)) ? await readJson(indexPath) : { agents: [] };
|
|
69
|
-
index.agents = index.agents.filter(
|
|
70
|
-
(entry) => !(entry.slug === manifest.slug && entry.version === manifest.version),
|
|
71
|
-
);
|
|
72
|
-
index.agents.push({
|
|
73
|
-
slug: manifest.slug,
|
|
74
|
-
version: manifest.version,
|
|
75
|
-
name: manifest.name,
|
|
76
|
-
description: manifest.description,
|
|
77
|
-
runtime: manifest.runtime,
|
|
78
|
-
updatedAt: new Date().toISOString(),
|
|
79
|
-
tags: manifest.metadata?.tags || [],
|
|
80
|
-
category: manifest.metadata?.category || 'General',
|
|
81
|
-
});
|
|
82
|
-
index.agents.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
83
|
-
await writeJson(indexPath, index);
|
|
84
|
-
|
|
85
|
-
return manifest;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function searchRegistry(registryDir, query) {
|
|
89
|
-
const indexPath = path.join(registryDir, "index.json");
|
|
90
|
-
if (!(await pathExists(indexPath))) {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
const index = await readJson(indexPath);
|
|
94
|
-
const normalized = query.toLowerCase().trim();
|
|
95
|
-
|
|
96
|
-
// 空查询返回所有
|
|
97
|
-
if (!normalized) {
|
|
98
|
-
return index.agents;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return index.agents.filter((entry) => {
|
|
102
|
-
// 搜索 slug
|
|
103
|
-
if (entry.slug?.toLowerCase().includes(normalized)) return true;
|
|
104
|
-
// 搜索 name
|
|
105
|
-
if (entry.name?.toLowerCase().includes(normalized)) return true;
|
|
106
|
-
// 搜索 description
|
|
107
|
-
if (entry.description?.toLowerCase().includes(normalized)) return true;
|
|
108
|
-
// 搜索 tags
|
|
109
|
-
if (entry.tags?.some(tag => tag.toLowerCase().includes(normalized))) return true;
|
|
110
|
-
// 搜索 category
|
|
111
|
-
if (entry.category?.toLowerCase().includes(normalized)) return true;
|
|
112
|
-
return false;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function readAgentInfo(registryDir, agentSpec) {
|
|
117
|
-
const { slug, version } = parseSpec(agentSpec);
|
|
118
|
-
const baseDir = path.join(registryDir, "agents", slug);
|
|
119
|
-
if (!(await pathExists(baseDir))) {
|
|
120
|
-
throw new Error(`Agent not found: ${slug}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let selectedVersion = version;
|
|
124
|
-
if (!selectedVersion) {
|
|
125
|
-
const index = await readJson(path.join(registryDir, "index.json"));
|
|
126
|
-
const versions = index.agents.filter((entry) => entry.slug === slug).map((entry) => entry.version).sort();
|
|
127
|
-
selectedVersion = versions.at(-1);
|
|
128
|
-
}
|
|
129
|
-
return readJson(path.join(baseDir, selectedVersion, "MANIFEST.json"));
|
|
130
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { copyDir, ensureDir, pathExists, readJson, writeJson } from "./fs-utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 解析 agentSpec,支持两种格式:
|
|
6
|
+
* - 短名格式:slug 或 slug:version
|
|
7
|
+
* - URI 格式:agenthub://owner/slug@version
|
|
8
|
+
*/
|
|
9
|
+
export function parseSpec(agentSpec) {
|
|
10
|
+
// URI 格式:agenthub://owner/slug@version
|
|
11
|
+
if (agentSpec.startsWith("agenthub://")) {
|
|
12
|
+
const uri = agentSpec.slice("agenthub://".length);
|
|
13
|
+
const parts = uri.split("/");
|
|
14
|
+
const lastPart = parts[parts.length - 1] || parts[parts.length - 2];
|
|
15
|
+
const [slug, version] = lastPart.split("@");
|
|
16
|
+
return { slug, version: version || undefined };
|
|
17
|
+
}
|
|
18
|
+
// 短名格式:slug 或 slug:version
|
|
19
|
+
const [slug, version] = agentSpec.split(":");
|
|
20
|
+
return { slug, version: version || undefined };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 版本比较函数(用于排序)
|
|
25
|
+
* 按 semver 降序排列
|
|
26
|
+
*/
|
|
27
|
+
export function compareVersionsDesc(a, b) {
|
|
28
|
+
return b.version.localeCompare(a.version, undefined, { numeric: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 从 agent 条目中获取最新版本
|
|
33
|
+
*/
|
|
34
|
+
export function getLatestVersion(entries) {
|
|
35
|
+
if (!entries || entries.length === 0) return null;
|
|
36
|
+
return [...entries].sort(compareVersionsDesc)[0];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 按下载量和更新时间排序 Agent 列表
|
|
41
|
+
* 下载量降序,下载量一致时按更新时间降序
|
|
42
|
+
*/
|
|
43
|
+
export function sortByDownloadsAndTime(agents) {
|
|
44
|
+
return agents.sort((a, b) => {
|
|
45
|
+
if (b.downloads !== a.downloads) {
|
|
46
|
+
return b.downloads - a.downloads;
|
|
47
|
+
}
|
|
48
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
49
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
50
|
+
return timeB - timeA;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function publishBundle(bundleDir, registryDir) {
|
|
55
|
+
const manifest = await readJson(path.join(bundleDir, "MANIFEST.json"));
|
|
56
|
+
if (manifest.runtime?.type !== "openclaw") {
|
|
57
|
+
throw new Error("Only OpenClaw bundles are supported.");
|
|
58
|
+
}
|
|
59
|
+
if ((manifest.includes?.memory?.private ?? 0) > 0) {
|
|
60
|
+
throw new Error("Public publish rejected: private memory detected.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const targetDir = path.join(registryDir, "agents", manifest.slug, manifest.version);
|
|
64
|
+
await ensureDir(path.join(registryDir, "agents", manifest.slug));
|
|
65
|
+
await copyDir(bundleDir, targetDir);
|
|
66
|
+
|
|
67
|
+
const indexPath = path.join(registryDir, "index.json");
|
|
68
|
+
const index = (await pathExists(indexPath)) ? await readJson(indexPath) : { agents: [] };
|
|
69
|
+
index.agents = index.agents.filter(
|
|
70
|
+
(entry) => !(entry.slug === manifest.slug && entry.version === manifest.version),
|
|
71
|
+
);
|
|
72
|
+
index.agents.push({
|
|
73
|
+
slug: manifest.slug,
|
|
74
|
+
version: manifest.version,
|
|
75
|
+
name: manifest.name,
|
|
76
|
+
description: manifest.description,
|
|
77
|
+
runtime: manifest.runtime,
|
|
78
|
+
updatedAt: new Date().toISOString(),
|
|
79
|
+
tags: manifest.metadata?.tags || [],
|
|
80
|
+
category: manifest.metadata?.category || 'General',
|
|
81
|
+
});
|
|
82
|
+
index.agents.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
83
|
+
await writeJson(indexPath, index);
|
|
84
|
+
|
|
85
|
+
return manifest;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function searchRegistry(registryDir, query) {
|
|
89
|
+
const indexPath = path.join(registryDir, "index.json");
|
|
90
|
+
if (!(await pathExists(indexPath))) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const index = await readJson(indexPath);
|
|
94
|
+
const normalized = query.toLowerCase().trim();
|
|
95
|
+
|
|
96
|
+
// 空查询返回所有
|
|
97
|
+
if (!normalized) {
|
|
98
|
+
return index.agents;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return index.agents.filter((entry) => {
|
|
102
|
+
// 搜索 slug
|
|
103
|
+
if (entry.slug?.toLowerCase().includes(normalized)) return true;
|
|
104
|
+
// 搜索 name
|
|
105
|
+
if (entry.name?.toLowerCase().includes(normalized)) return true;
|
|
106
|
+
// 搜索 description
|
|
107
|
+
if (entry.description?.toLowerCase().includes(normalized)) return true;
|
|
108
|
+
// 搜索 tags
|
|
109
|
+
if (entry.tags?.some(tag => tag.toLowerCase().includes(normalized))) return true;
|
|
110
|
+
// 搜索 category
|
|
111
|
+
if (entry.category?.toLowerCase().includes(normalized)) return true;
|
|
112
|
+
return false;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function readAgentInfo(registryDir, agentSpec) {
|
|
117
|
+
const { slug, version } = parseSpec(agentSpec);
|
|
118
|
+
const baseDir = path.join(registryDir, "agents", slug);
|
|
119
|
+
if (!(await pathExists(baseDir))) {
|
|
120
|
+
throw new Error(`Agent not found: ${slug}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let selectedVersion = version;
|
|
124
|
+
if (!selectedVersion) {
|
|
125
|
+
const index = await readJson(path.join(registryDir, "index.json"));
|
|
126
|
+
const versions = index.agents.filter((entry) => entry.slug === slug).map((entry) => entry.version).sort();
|
|
127
|
+
selectedVersion = versions.at(-1);
|
|
128
|
+
}
|
|
129
|
+
return readJson(path.join(baseDir, selectedVersion, "MANIFEST.json"));
|
|
130
|
+
}
|
package/src/lib/remote.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { fetchJsonWithFallback } from "./http.js";
|
|
2
|
-
|
|
3
|
-
export function resolveServerUrl(options = {}) {
|
|
4
|
-
return options.server || "https://agenthub.cyou";
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function fetchRemoteJson(pathname, options = {}) {
|
|
8
|
-
const serverUrl = resolveServerUrl(options);
|
|
9
|
-
const url = new URL(pathname, serverUrl).toString();
|
|
10
|
-
return fetchJsonWithFallback(url);
|
|
11
|
-
}
|
|
1
|
+
import { fetchJsonWithFallback } from "./http.js";
|
|
2
|
+
|
|
3
|
+
export function resolveServerUrl(options = {}) {
|
|
4
|
+
return options.server || "https://agenthub.cyou";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function fetchRemoteJson(pathname, options = {}) {
|
|
8
|
+
const serverUrl = resolveServerUrl(options);
|
|
9
|
+
const url = new URL(pathname, serverUrl).toString();
|
|
10
|
+
return fetchJsonWithFallback(url);
|
|
11
|
+
}
|