@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/commands/pack.js
CHANGED
|
@@ -1,156 +1,249 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pack Command
|
|
3
|
-
* 打包 OpenClaw 工作区为 Agent Bundle
|
|
4
|
-
*
|
|
5
|
-
* 根据 PRD v1.1 Bundle 结构规范实现
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Pack Command
|
|
3
|
+
* 打包 OpenClaw 工作区为 Agent Bundle
|
|
4
|
+
*
|
|
5
|
+
* 根据 PRD v1.1 Bundle 结构规范实现
|
|
6
|
+
* v0.5: 集成隐私防护引擎 + 安全扫描
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
11
|
+
import { countFiles, copyDirFiltered, ensureDir, readJson, writeJson, pathExists } from "../lib/fs-utils.js";
|
|
12
|
+
import { createManifest, validateManifest, generateBundleId } from "../lib/manifest.js";
|
|
13
|
+
import { extractOpenClawTemplate } from "../lib/openclaw-config.js";
|
|
14
|
+
import { scanPrivacyRisks, getPackFilterOptions, generatePrivacyReport } from "../lib/privacy-engine.js";
|
|
15
|
+
import { scanWorkspace as securityScanWorkspace } from "../lib/security-scanner.js";
|
|
16
|
+
|
|
17
|
+
const WORKSPACE_CORE_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md", "BOOTSTRAP.md"];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 扫描工作区文件
|
|
21
|
+
*/
|
|
22
|
+
async function scanWorkspace(workspacePath) {
|
|
23
|
+
const files = {
|
|
24
|
+
core: [],
|
|
25
|
+
skills: [],
|
|
26
|
+
prompts: [],
|
|
27
|
+
canvas: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const filename of WORKSPACE_CORE_FILES) {
|
|
31
|
+
const filePath = path.join(workspacePath, filename);
|
|
32
|
+
if (await pathExists(filePath)) {
|
|
33
|
+
files.core.push(filename);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 检查 skills 目录
|
|
38
|
+
const skillsPath = path.join(workspacePath, "skills");
|
|
39
|
+
if (await pathExists(skillsPath)) {
|
|
40
|
+
const entries = await readdir(skillsPath, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
files.skills.push(entry.name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 检查 PROMPTS 目录
|
|
49
|
+
const promptsPath = path.join(workspacePath, "PROMPTS");
|
|
50
|
+
if (await pathExists(promptsPath)) {
|
|
51
|
+
const entries = await readdir(promptsPath, { withFileTypes: true });
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (entry.isFile() && entry.name.endsWith(".txt")) {
|
|
54
|
+
files.prompts.push(entry.name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 检查 canvas 目录
|
|
60
|
+
const canvasPath = path.join(workspacePath, "canvas");
|
|
61
|
+
files.canvas = await pathExists(canvasPath);
|
|
62
|
+
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pack 命令主函数
|
|
68
|
+
*
|
|
69
|
+
* @param {object} options
|
|
70
|
+
* @param {string} [options.workspace] - 工作区路径
|
|
71
|
+
* @param {string} [options.output] - 输出目录
|
|
72
|
+
* @param {string} [options.config] - OpenClaw 配置文件路径
|
|
73
|
+
* @param {string} [options.version] - 版本号
|
|
74
|
+
* @param {string} [options.name] - 自定义名称
|
|
75
|
+
* @param {boolean} [options.strict] - 严格模式:发现高危敏感信息时中止打包
|
|
76
|
+
* @param {boolean} [options.stripPrivate] - 自动剥离私有数据(默认 true)
|
|
77
|
+
* @param {boolean} [options.skipScan] - 跳过安全扫描
|
|
78
|
+
*/
|
|
79
|
+
export async function packCommand(options) {
|
|
80
|
+
const workspace = path.resolve(options.workspace || process.cwd());
|
|
81
|
+
const output = path.resolve(options.output ?? "./bundles");
|
|
82
|
+
const configPath = options.config ? path.resolve(options.config) : null;
|
|
83
|
+
const version = options.version || "1.0.0";
|
|
84
|
+
const customName = options.name || null;
|
|
85
|
+
const strict = options.strict === true;
|
|
86
|
+
const stripPrivate = options.stripPrivate !== false; // 默认开启
|
|
87
|
+
const skipScan = options.skipScan === true;
|
|
88
|
+
|
|
89
|
+
// 从工作区名称或自定义名称生成 slug
|
|
90
|
+
const baseName = customName || path.basename(workspace);
|
|
91
|
+
const slug = baseName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
92
|
+
const bundleDir = path.join(output, `${slug}-${version}.agent`);
|
|
93
|
+
|
|
94
|
+
// ===== 隐私防护阶段 =====
|
|
95
|
+
|
|
96
|
+
// 1. 安全扫描(除非显式跳过)
|
|
97
|
+
let securityResult = null;
|
|
98
|
+
let privacyResult = null;
|
|
99
|
+
|
|
100
|
+
if (!skipScan) {
|
|
101
|
+
// 1.1 安全扫描:检测 API Key、密码、Token 等
|
|
102
|
+
securityResult = await securityScanWorkspace(workspace);
|
|
103
|
+
|
|
104
|
+
// 1.2 隐私风险扫描:检测私有记忆、敏感信息
|
|
105
|
+
privacyResult = await scanPrivacyRisks(workspace);
|
|
106
|
+
|
|
107
|
+
// 1.3 严格模式:发现高危问题时中止
|
|
108
|
+
if (strict) {
|
|
109
|
+
const criticalFindings = securityResult.findings.filter(
|
|
110
|
+
(f) => f.severity === "critical" || f.severity === "high"
|
|
111
|
+
);
|
|
112
|
+
if (criticalFindings.length > 0) {
|
|
113
|
+
const details = criticalFindings
|
|
114
|
+
.map((f) => ` ${f.file}: ${f.type} (${f.count} 处)`)
|
|
115
|
+
.join("\n");
|
|
116
|
+
throw new Error(
|
|
117
|
+
`[严格模式] 安全扫描发现 ${criticalFindings.length} 个高危问题,打包已中止:\n${details}\n\n提示: 移除敏感信息后重试,或使用 --no-strict 跳过严格检查`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (privacyResult.hasPrivateMemory) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`[严格模式] 检测到 memory/private/ 中有 ${privacyResult.privateMemoryCount} 个文件,打包已中止。\n\n提示: 私有记忆会被自动排除,使用 --no-strict 允许打包`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2. 扫描工作区文件结构
|
|
130
|
+
const workspaceFiles = await scanWorkspace(workspace);
|
|
131
|
+
|
|
132
|
+
// 3. 统计 Memory 分层(用于 Manifest 记录,不影响过滤)
|
|
133
|
+
const memoryCounts = {
|
|
134
|
+
public: await countFiles(path.join(workspace, "memory", "public")),
|
|
135
|
+
portable: await countFiles(path.join(workspace, "memory", "portable")),
|
|
136
|
+
private: await countFiles(path.join(workspace, "memory", "private")),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// 4. 读取并处理 OpenClaw 配置
|
|
140
|
+
let template = {};
|
|
141
|
+
if (configPath && (await pathExists(configPath))) {
|
|
142
|
+
const config = await readJson(configPath);
|
|
143
|
+
template = extractOpenClawTemplate(config);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 5. 生成 MANIFEST
|
|
147
|
+
const manifest = createManifest({
|
|
148
|
+
slug,
|
|
149
|
+
name: customName || path.basename(workspace),
|
|
150
|
+
memoryCounts,
|
|
151
|
+
openclawTemplate: template,
|
|
152
|
+
skills: workspaceFiles.skills,
|
|
153
|
+
prompts: workspaceFiles.prompts,
|
|
154
|
+
tags: options.tags && typeof options.tags === "string" && options.tags.trim() ? options.tags.split(",").map((t) => t.trim()) : [],
|
|
155
|
+
category: options.category,
|
|
156
|
+
version,
|
|
157
|
+
featured: options.featured === true,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// 6. 验证 MANIFEST
|
|
161
|
+
const validation = validateManifest(manifest);
|
|
162
|
+
if (!validation.valid) {
|
|
163
|
+
throw new Error(`Invalid manifest: ${validation.errors.join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ===== 构建阶段 =====
|
|
167
|
+
|
|
168
|
+
// 7. 创建 Bundle 目录结构
|
|
169
|
+
await ensureDir(bundleDir);
|
|
170
|
+
|
|
171
|
+
// 7.1 使用带过滤的复制替代全量复制
|
|
172
|
+
const filterOptions = stripPrivate
|
|
173
|
+
? getPackFilterOptions(manifest)
|
|
174
|
+
: { excludeDirs: [".git", "node_modules"], excludeFiles: [], maxFileSize: 50 * 1024 * 1024 };
|
|
175
|
+
|
|
176
|
+
const copyReport = await copyDirFiltered(
|
|
177
|
+
workspace,
|
|
178
|
+
path.join(bundleDir, "WORKSPACE"),
|
|
179
|
+
filterOptions,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// 7.2 写入 OPENCLAW.template.json
|
|
183
|
+
await writeJson(path.join(bundleDir, "OPENCLAW.template.json"), template);
|
|
184
|
+
|
|
185
|
+
// 7.3 写入 MANIFEST.json(更新排除信息)
|
|
186
|
+
if (stripPrivate) {
|
|
187
|
+
// 更新 memory.private 为 0 因为已被剥离
|
|
188
|
+
manifest.includes.memory.private = 0;
|
|
189
|
+
manifest.includes.memory.count = manifest.includes.memory.public + manifest.includes.memory.portable;
|
|
190
|
+
}
|
|
191
|
+
await writeJson(path.join(bundleDir, "MANIFEST.json"), manifest);
|
|
192
|
+
|
|
193
|
+
// 7.4 生成隐私合规报告
|
|
194
|
+
if (!skipScan && privacyResult) {
|
|
195
|
+
const privacyReport = generatePrivacyReport(privacyResult, copyReport);
|
|
196
|
+
await writeJson(path.join(bundleDir, "PRIVACY_REPORT.json"), privacyReport);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 7.5 生成 README.md
|
|
200
|
+
const privacyBadge = stripPrivate ? "🛡️ 已通过隐私审查" : "⚠️ 未启用隐私防护";
|
|
201
|
+
const readme = `# ${manifest.name}
|
|
202
|
+
|
|
203
|
+
${manifest.description}
|
|
204
|
+
|
|
205
|
+
${privacyBadge}
|
|
206
|
+
|
|
207
|
+
## 安装
|
|
208
|
+
|
|
209
|
+
\`\`\`bash
|
|
210
|
+
agenthub install ${manifest.slug}
|
|
211
|
+
\`\`\`
|
|
212
|
+
|
|
213
|
+
## 包含内容
|
|
214
|
+
|
|
215
|
+
- **记忆**: ${manifest.includes.memory.count} 条 (public: ${manifest.includes.memory.public}, portable: ${manifest.includes.memory.portable})
|
|
216
|
+
- **技能**: ${manifest.includes.skills.join(", ") || "无"}
|
|
217
|
+
- **提示词**: ${manifest.includes.prompts} 个
|
|
218
|
+
|
|
219
|
+
## 隐私说明
|
|
220
|
+
|
|
221
|
+
- 私有记忆 (memory/private): ${stripPrivate ? "已自动剥离" : "未处理"}
|
|
222
|
+
- 敏感信息扫描: ${skipScan ? "已跳过" : "已完成"}
|
|
223
|
+
- 文件过滤: 排除了 ${copyReport.skipped.length} 个文件/目录
|
|
224
|
+
|
|
225
|
+
## 要求
|
|
226
|
+
|
|
227
|
+
- 运行时: OpenClaw ${manifest.runtime.version}
|
|
228
|
+
`;
|
|
229
|
+
await writeFile(path.join(bundleDir, "README.md"), readme, "utf8");
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
bundleDir,
|
|
233
|
+
manifest,
|
|
234
|
+
validation,
|
|
235
|
+
bundleId: generateBundleId(manifest.slug, manifest.version),
|
|
236
|
+
// 新增:安全与隐私报告
|
|
237
|
+
security: securityResult ? {
|
|
238
|
+
warnings: securityResult.warnings,
|
|
239
|
+
findingsCount: securityResult.findings.length,
|
|
240
|
+
canPublish: securityResult.canPublish,
|
|
241
|
+
} : null,
|
|
242
|
+
privacy: {
|
|
243
|
+
stripped: stripPrivate,
|
|
244
|
+
skippedFiles: copyReport.skipped.length,
|
|
245
|
+
copiedFiles: copyReport.copied.length,
|
|
246
|
+
skippedList: copyReport.skipped,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { publishRemoteBundle } from "../lib/bundle-transfer.js";
|
|
3
|
-
|
|
4
|
-
export async function publishRemoteCommand(bundleDir, options) {
|
|
5
|
-
return publishRemoteBundle({
|
|
6
|
-
bundleDir: path.resolve(bundleDir),
|
|
7
|
-
serverUrl: options.server,
|
|
8
|
-
});
|
|
9
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { publishRemoteBundle } from "../lib/bundle-transfer.js";
|
|
3
|
+
|
|
4
|
+
export async function publishRemoteCommand(bundleDir, options) {
|
|
5
|
+
return publishRemoteBundle({
|
|
6
|
+
bundleDir: path.resolve(bundleDir),
|
|
7
|
+
serverUrl: options.server,
|
|
8
|
+
});
|
|
9
|
+
}
|
package/src/commands/publish.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { publishBundle } from "../lib/registry.js";
|
|
3
|
-
|
|
4
|
-
export async function publishCommand(bundleDir, options) {
|
|
5
|
-
const registry = path.resolve(options.registry);
|
|
6
|
-
return publishBundle(path.resolve(bundleDir), registry);
|
|
7
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { publishBundle } from "../lib/registry.js";
|
|
3
|
+
|
|
4
|
+
export async function publishCommand(bundleDir, options) {
|
|
5
|
+
const registry = path.resolve(options.registry);
|
|
6
|
+
return publishBundle(path.resolve(bundleDir), registry);
|
|
7
|
+
}
|
package/src/commands/rollback.js
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rollback Command
|
|
3
|
-
* 回滚 Agent 到指定版本
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { getCurrentVersion, updateInstallRecord, buildInstallOptions } from "../lib/version-manager.js";
|
|
8
|
-
import { installBundle } from "../lib/install.js";
|
|
9
|
-
import { parseSpec } from "../lib/registry.js";
|
|
10
|
-
import { versionsCommand } from "./versions.js";
|
|
11
|
-
import { success, warning, highlight, muted, symbols } from "../lib/colors.js";
|
|
12
|
-
|
|
13
|
-
export async function rollbackCommand(agentSpec, options = {}) {
|
|
14
|
-
const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
|
|
15
|
-
const targetVersion = options.to;
|
|
16
|
-
|
|
17
|
-
if (!targetVersion) {
|
|
18
|
-
throw new Error("请指定回滚版本: --to <version>");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const { slug } = parseSpec(agentSpec);
|
|
22
|
-
|
|
23
|
-
// 验证目标版本存在
|
|
24
|
-
const versions = await versionsCommand(slug, options);
|
|
25
|
-
const versionExists = versions.some((v) => v.version === targetVersion);
|
|
26
|
-
if (!versionExists) {
|
|
27
|
-
throw new Error(`版本 ${targetVersion} 不存在`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 获取当前版本
|
|
31
|
-
const currentVersion = await getCurrentVersion(targetWorkspace);
|
|
32
|
-
|
|
33
|
-
// 执行回滚
|
|
34
|
-
const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
|
|
35
|
-
const result = await installBundle(installOptions);
|
|
36
|
-
|
|
37
|
-
// 记录回滚
|
|
38
|
-
await updateInstallRecord(targetWorkspace, slug, targetVersion, {
|
|
39
|
-
rolledBackAt: new Date().toISOString(),
|
|
40
|
-
rolledBackFrom: currentVersion,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
rolledBack: true,
|
|
45
|
-
message: [
|
|
46
|
-
success(`${symbols.success} 回滚成功`),
|
|
47
|
-
"",
|
|
48
|
-
` ${highlight("Agent:")} ${slug}`,
|
|
49
|
-
` ${muted("回滚前:")} ${currentVersion || "未知"}`,
|
|
50
|
-
` ${highlight("当前版本:")} ${targetVersion}`,
|
|
51
|
-
"",
|
|
52
|
-
` ${muted("运行")} ${highlight("agenthub verify " + slug)} ${muted("校验安装状态")}`,
|
|
53
|
-
` ${muted("运行")} ${highlight("agenthub update " + slug)} ${muted("恢复到最新版本")}`,
|
|
54
|
-
].join("\n"),
|
|
55
|
-
currentVersion: targetVersion,
|
|
56
|
-
previousVersion: currentVersion,
|
|
57
|
-
manifest: result.manifest,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Command
|
|
3
|
+
* 回滚 Agent 到指定版本
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { getCurrentVersion, updateInstallRecord, buildInstallOptions } from "../lib/version-manager.js";
|
|
8
|
+
import { installBundle } from "../lib/install.js";
|
|
9
|
+
import { parseSpec } from "../lib/registry.js";
|
|
10
|
+
import { versionsCommand } from "./versions.js";
|
|
11
|
+
import { success, warning, highlight, muted, symbols } from "../lib/colors.js";
|
|
12
|
+
|
|
13
|
+
export async function rollbackCommand(agentSpec, options = {}) {
|
|
14
|
+
const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
|
|
15
|
+
const targetVersion = options.to;
|
|
16
|
+
|
|
17
|
+
if (!targetVersion) {
|
|
18
|
+
throw new Error("请指定回滚版本: --to <version>");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { slug } = parseSpec(agentSpec);
|
|
22
|
+
|
|
23
|
+
// 验证目标版本存在
|
|
24
|
+
const versions = await versionsCommand(slug, options);
|
|
25
|
+
const versionExists = versions.some((v) => v.version === targetVersion);
|
|
26
|
+
if (!versionExists) {
|
|
27
|
+
throw new Error(`版本 ${targetVersion} 不存在`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 获取当前版本
|
|
31
|
+
const currentVersion = await getCurrentVersion(targetWorkspace);
|
|
32
|
+
|
|
33
|
+
// 执行回滚
|
|
34
|
+
const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
|
|
35
|
+
const result = await installBundle(installOptions);
|
|
36
|
+
|
|
37
|
+
// 记录回滚
|
|
38
|
+
await updateInstallRecord(targetWorkspace, slug, targetVersion, {
|
|
39
|
+
rolledBackAt: new Date().toISOString(),
|
|
40
|
+
rolledBackFrom: currentVersion,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
rolledBack: true,
|
|
45
|
+
message: [
|
|
46
|
+
success(`${symbols.success} 回滚成功`),
|
|
47
|
+
"",
|
|
48
|
+
` ${highlight("Agent:")} ${slug}`,
|
|
49
|
+
` ${muted("回滚前:")} ${currentVersion || "未知"}`,
|
|
50
|
+
` ${highlight("当前版本:")} ${targetVersion}`,
|
|
51
|
+
"",
|
|
52
|
+
` ${muted("运行")} ${highlight("agenthub verify " + slug)} ${muted("校验安装状态")}`,
|
|
53
|
+
` ${muted("运行")} ${highlight("agenthub update " + slug)} ${muted("恢复到最新版本")}`,
|
|
54
|
+
].join("\n"),
|
|
55
|
+
currentVersion: targetVersion,
|
|
56
|
+
previousVersion: currentVersion,
|
|
57
|
+
manifest: result.manifest,
|
|
58
|
+
};
|
|
59
|
+
}
|
package/src/commands/search.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { searchRegistry } from "../lib/registry.js";
|
|
3
|
-
import { fetchRemoteJson } from "../lib/remote.js";
|
|
4
|
-
|
|
5
|
-
export async function searchCommand(query, options = {}) {
|
|
6
|
-
if (options.registry) {
|
|
7
|
-
return searchRegistry(path.resolve(options.registry), query);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const params = new URLSearchParams();
|
|
11
|
-
if (query) params.set("q", query);
|
|
12
|
-
const result = await fetchRemoteJson(`/api/agents?${params.toString()}`, options);
|
|
13
|
-
return result.agents || [];
|
|
14
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { searchRegistry } from "../lib/registry.js";
|
|
3
|
+
import { fetchRemoteJson } from "../lib/remote.js";
|
|
4
|
+
|
|
5
|
+
export async function searchCommand(query, options = {}) {
|
|
6
|
+
if (options.registry) {
|
|
7
|
+
return searchRegistry(path.resolve(options.registry), query);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (query) params.set("q", query);
|
|
12
|
+
const result = await fetchRemoteJson(`/api/agents?${params.toString()}`, options);
|
|
13
|
+
return result.agents || [];
|
|
14
|
+
}
|
package/src/commands/serve.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { createServer } from "../server.js";
|
|
3
|
-
|
|
4
|
-
export async function serveCommand(options) {
|
|
5
|
-
const registryDir = path.resolve(options.registry);
|
|
6
|
-
const port = options.port ? Number(options.port) : 3000;
|
|
7
|
-
const host = options.host || "0.0.0.0";
|
|
8
|
-
return createServer({ registryDir, port, host });
|
|
9
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createServer } from "../server.js";
|
|
3
|
+
|
|
4
|
+
export async function serveCommand(options) {
|
|
5
|
+
const registryDir = path.resolve(options.registry);
|
|
6
|
+
const port = options.port ? Number(options.port) : 3000;
|
|
7
|
+
const host = options.host || "0.0.0.0";
|
|
8
|
+
return createServer({ registryDir, port, host });
|
|
9
|
+
}
|