@a2hmarket/a2hmarket 0.2.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/.claude/settings.local.json +7 -0
- package/docs/installation-design.md +175 -0
- package/docs/openclaw-plugin-development-guide.md +651 -0
- package/harness/docs/arch/architecture.md +345 -0
- package/harness/docs/pm/requirements.md +249 -0
- package/index.ts +172 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +25 -0
- package/plugin-mechanism-report.md +1323 -0
- package/scripts/install.mjs +425 -0
- package/skills/a2hmarket/SKILL.md +39 -0
- package/skills/a2hmarket/references/commands.md +343 -0
- package/src/agent-service.ts +218 -0
- package/src/api-client.ts +154 -0
- package/src/channel-state.ts +11 -0
- package/src/credentials.ts +131 -0
- package/src/feishu-notify.ts +157 -0
- package/src/last-channel.ts +39 -0
- package/src/mqtt-listener.ts +125 -0
- package/src/mqtt-token.ts +115 -0
- package/src/mqtt-transport.ts +202 -0
- package/src/oss.ts +140 -0
- package/src/protocol.ts +98 -0
- package/src/reply-bridge.ts +106 -0
- package/src/runtime.ts +14 -0
- package/src/signer.ts +19 -0
- package/src/tools/file.ts +27 -0
- package/src/tools/order.ts +108 -0
- package/src/tools/profile.ts +71 -0
- package/src/tools/send.ts +95 -0
- package/src/tools/status.ts +26 -0
- package/src/tools/works.ts +167 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* A2H Market OpenClaw Plugin Installer
|
|
4
|
+
*
|
|
5
|
+
* Usage: npx -y @a2hmarket/openclaw-plugin install
|
|
6
|
+
*
|
|
7
|
+
* Auth flow (same as a2hmarket-cli):
|
|
8
|
+
* 1. POST /v1/auth/init-login → get auth code + login URL
|
|
9
|
+
* 2. User opens URL in browser to authorize
|
|
10
|
+
* 3. Poll GET /findu-user/api/v1/public/user/agent/auth?code=xxx
|
|
11
|
+
* 4. Server returns agent_id + secret after user authorizes
|
|
12
|
+
* 5. Save credentials to ~/.openclaw/a2hmarket/credentials.json
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
import { createInterface } from "node:readline";
|
|
17
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
21
|
+
import { networkInterfaces } from "node:os";
|
|
22
|
+
|
|
23
|
+
const OPENCLAW_DIR = join(homedir(), ".openclaw");
|
|
24
|
+
const DATA_DIR = join(OPENCLAW_DIR, "a2hmarket");
|
|
25
|
+
const CREDS_FILE = join(DATA_DIR, "credentials.json");
|
|
26
|
+
const NPM_SPEC = "@a2hmarket/a2hmarket";
|
|
27
|
+
|
|
28
|
+
const AUTH_API_URL = "https://web.a2hmarket.ai";
|
|
29
|
+
const LOGIN_URL = "https://a2hmarket.ai";
|
|
30
|
+
const API_DEFAULT = "https://api.a2hmarket.ai";
|
|
31
|
+
const MQTT_DEFAULT = "mqtts://post-cn-e4k4o78q702.mqtt.aliyuncs.com:8883";
|
|
32
|
+
|
|
33
|
+
const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
34
|
+
const POLL_INTERVAL_MS = 3000; // 3 seconds
|
|
35
|
+
|
|
36
|
+
// ── CLI Colors ───────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const BOLD = "\x1b[1m";
|
|
39
|
+
const GREEN = "\x1b[32m";
|
|
40
|
+
const RED = "\x1b[31m";
|
|
41
|
+
const YELLOW = "\x1b[33m";
|
|
42
|
+
const CYAN = "\x1b[36m";
|
|
43
|
+
const DIM = "\x1b[2m";
|
|
44
|
+
const RESET = "\x1b[0m";
|
|
45
|
+
const CHECK = `${GREEN}✓${RESET}`;
|
|
46
|
+
const CROSS = `${RED}✗${RESET}`;
|
|
47
|
+
const WARN = `${YELLOW}⚠${RESET}`;
|
|
48
|
+
|
|
49
|
+
function log(msg) { console.log(msg); }
|
|
50
|
+
function logStep(n, msg) { log(`\n${CYAN}[${n}]${RESET} ${BOLD}${msg}${RESET}`); }
|
|
51
|
+
|
|
52
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function getMacAddress() {
|
|
55
|
+
const nets = networkInterfaces();
|
|
56
|
+
for (const name of Object.keys(nets)) {
|
|
57
|
+
for (const net of nets[name]) {
|
|
58
|
+
if (!net.internal && net.mac && net.mac !== "00:00:00:00:00:00") {
|
|
59
|
+
return net.mac;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return "00:00:00:00:00:00";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createPrompt() {
|
|
67
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
68
|
+
return {
|
|
69
|
+
ask: (question, defaultVal) =>
|
|
70
|
+
new Promise((resolve) => {
|
|
71
|
+
const suffix = defaultVal ? ` ${DIM}(${defaultVal})${RESET}` : "";
|
|
72
|
+
rl.question(` ${question}${suffix}: `, (answer) => {
|
|
73
|
+
resolve(answer.trim() || defaultVal || "");
|
|
74
|
+
});
|
|
75
|
+
}),
|
|
76
|
+
close: () => rl.close(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkOpenclaw() {
|
|
81
|
+
try {
|
|
82
|
+
return execSync("openclaw --version 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function detectFeishuTarget() {
|
|
89
|
+
try {
|
|
90
|
+
const output = execSync(
|
|
91
|
+
'openclaw gateway call status --json 2>/dev/null | grep -o "ou_[a-f0-9]*" | head -1',
|
|
92
|
+
{ encoding: "utf-8" },
|
|
93
|
+
).trim();
|
|
94
|
+
return output || null;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Auth Flow ────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
async function generateAuthCode() {
|
|
103
|
+
const mac = getMacAddress();
|
|
104
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
105
|
+
|
|
106
|
+
// Try remote generation first
|
|
107
|
+
try {
|
|
108
|
+
const formData = new URLSearchParams({ timestamp, mac });
|
|
109
|
+
const resp = await fetch(`${AUTH_API_URL}/v1/auth/init-login`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
112
|
+
body: formData.toString(),
|
|
113
|
+
});
|
|
114
|
+
const data = await resp.json();
|
|
115
|
+
if (data.code && data.url) {
|
|
116
|
+
return { code: data.code, url: data.url };
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Fallback to local generation
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Local fallback
|
|
123
|
+
const randomHex = randomBytes(32).toString("hex");
|
|
124
|
+
const macClean = mac.replace(/:/g, "");
|
|
125
|
+
const raw = `${randomHex}_${timestamp}_${macClean}`;
|
|
126
|
+
const code = createHash("md5").update(raw).digest("hex");
|
|
127
|
+
const url = `${LOGIN_URL}/authcode?code=${code}`;
|
|
128
|
+
return { code, url };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function pollForAuth(code) {
|
|
132
|
+
// Use web.a2hmarket.ai for auth polling (no signature required)
|
|
133
|
+
const authUrl = `${AUTH_API_URL}/findu-user/api/v1/public/user/agent/auth?code=${code}`;
|
|
134
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
135
|
+
|
|
136
|
+
while (Date.now() < deadline) {
|
|
137
|
+
try {
|
|
138
|
+
const resp = await fetch(authUrl);
|
|
139
|
+
const result = await resp.json();
|
|
140
|
+
|
|
141
|
+
// Success: server returns data with agentId
|
|
142
|
+
if (result.data?.agentId) {
|
|
143
|
+
return {
|
|
144
|
+
agentId: result.data.agentId,
|
|
145
|
+
agentKey: result.data.secret,
|
|
146
|
+
apiUrl: result.data.apiUrl || API_DEFAULT,
|
|
147
|
+
mqttUrl: result.data.mqttUrl || MQTT_DEFAULT,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Network error, retry
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
155
|
+
process.stdout.write(`\r 等待授权中... ${DIM}(剩余 ${remaining}s)${RESET} `);
|
|
156
|
+
|
|
157
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { error: "授权超时(5分钟),请重新运行安装" };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Main ─────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
async function main() {
|
|
166
|
+
const args = process.argv.slice(2);
|
|
167
|
+
if (!args.includes("install")) {
|
|
168
|
+
log(`\n${BOLD}A2H Market — OpenClaw Plugin${RESET}\n`);
|
|
169
|
+
log(` 安装: npx -y ${NPM_SPEC} install`);
|
|
170
|
+
log(` 快速: npx -y ${NPM_SPEC} install --agent ag_xxx:key\n`);
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
log(`\n${BOLD}🏪 A2H Market — OpenClaw Plugin Installer${RESET}\n`);
|
|
175
|
+
|
|
176
|
+
// ── Step 1: Check OpenClaw ─────────────────────────────────────
|
|
177
|
+
logStep(1, "检查 OpenClaw");
|
|
178
|
+
const version = checkOpenclaw();
|
|
179
|
+
if (!version) {
|
|
180
|
+
log(` ${CROSS} OpenClaw 未安装。请先安装: https://docs.openclaw.ai`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
log(` ${CHECK} ${version}`);
|
|
184
|
+
|
|
185
|
+
// ── Step 2: Authorize ──────────────────────────────────────────
|
|
186
|
+
logStep(2, "A2H Market 授权");
|
|
187
|
+
|
|
188
|
+
let agentId, agentKey, apiUrl, mqttUrl;
|
|
189
|
+
|
|
190
|
+
// Fast path: --agent ag_xxx:key
|
|
191
|
+
const agentFlag = args.find((a) => a.startsWith("--agent"));
|
|
192
|
+
const agentValue = agentFlag
|
|
193
|
+
? args[args.indexOf(agentFlag) + 1] ?? agentFlag.split("=")[1]
|
|
194
|
+
: null;
|
|
195
|
+
|
|
196
|
+
if (agentValue && agentValue.includes(":")) {
|
|
197
|
+
[agentId, agentKey] = agentValue.split(":", 2);
|
|
198
|
+
apiUrl = API_DEFAULT;
|
|
199
|
+
mqttUrl = MQTT_DEFAULT;
|
|
200
|
+
log(` 使用命令行凭证: ${agentId}`);
|
|
201
|
+
} else if (existsSync(CREDS_FILE)) {
|
|
202
|
+
// Check existing
|
|
203
|
+
try {
|
|
204
|
+
const existing = JSON.parse(readFileSync(CREDS_FILE, "utf-8"));
|
|
205
|
+
const existingId = existing.agent_id ?? existing.agentId ?? "";
|
|
206
|
+
if (existingId) {
|
|
207
|
+
log(` 已有凭证: ${CYAN}${existingId}${RESET}`);
|
|
208
|
+
const prompt = createPrompt();
|
|
209
|
+
const reuse = await prompt.ask("使用现有凭证? (Y/n)", "Y");
|
|
210
|
+
prompt.close();
|
|
211
|
+
if (reuse.toLowerCase() !== "n") {
|
|
212
|
+
agentId = existingId;
|
|
213
|
+
agentKey = existing.agent_key ?? existing.agentKey ?? "";
|
|
214
|
+
apiUrl = existing.api_url ?? existing.apiUrl ?? API_DEFAULT;
|
|
215
|
+
mqttUrl = existing.mqtt_url ?? existing.mqttUrl ?? MQTT_DEFAULT;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch { /* ignore */ }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!agentId) {
|
|
222
|
+
// Authorization code flow
|
|
223
|
+
log(` 生成授权链接...`);
|
|
224
|
+
const { code, url } = await generateAuthCode();
|
|
225
|
+
|
|
226
|
+
log(`\n ${BOLD}请在浏览器中打开以下链接完成授权:${RESET}\n`);
|
|
227
|
+
log(` ${CYAN}${url}${RESET}\n`);
|
|
228
|
+
log(` ${DIM}授权码: ${code}${RESET}\n`);
|
|
229
|
+
|
|
230
|
+
const result = await pollForAuth(code);
|
|
231
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r"); // Clear progress line
|
|
232
|
+
|
|
233
|
+
if (result.error) {
|
|
234
|
+
log(` ${CROSS} ${result.error}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
agentId = result.agentId;
|
|
239
|
+
agentKey = result.agentKey;
|
|
240
|
+
apiUrl = result.apiUrl;
|
|
241
|
+
mqttUrl = result.mqttUrl;
|
|
242
|
+
log(` ${CHECK} 授权成功!Agent ID: ${CYAN}${agentId}${RESET}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!agentId || !agentKey) {
|
|
246
|
+
log(` ${CROSS} 凭证无效`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Verify credentials
|
|
251
|
+
log(` 验证凭证...`);
|
|
252
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
253
|
+
const path = "/findu-user/api/v1/user/profile/public";
|
|
254
|
+
const sig = createHmac("sha256", agentKey)
|
|
255
|
+
.update(`GET&${path}&${agentId}&${timestamp}`)
|
|
256
|
+
.digest("hex");
|
|
257
|
+
try {
|
|
258
|
+
const resp = await fetch(`${apiUrl}${path}`, {
|
|
259
|
+
headers: {
|
|
260
|
+
"X-Agent-Id": agentId,
|
|
261
|
+
"X-Timestamp": timestamp,
|
|
262
|
+
"X-Agent-Signature": sig,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
const data = await resp.json();
|
|
266
|
+
if (resp.ok && data.code === "200") {
|
|
267
|
+
log(` ${CHECK} 昵称: ${data.data?.nickname ?? agentId}`);
|
|
268
|
+
} else {
|
|
269
|
+
log(` ${WARN} 凭证验证失败,继续安装 (${data.message ?? resp.status})`);
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
log(` ${WARN} 无法验证凭证(网络问题),继续安装`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Step 3: Install plugin ─────────────────────────────────────
|
|
276
|
+
logStep(3, "安装插件");
|
|
277
|
+
try {
|
|
278
|
+
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
279
|
+
if (info.includes("Status: loaded")) {
|
|
280
|
+
log(` ${CHECK} 插件已安装`);
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error("not loaded");
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
try {
|
|
286
|
+
log(` 安装中...`);
|
|
287
|
+
execSync(`echo y | openclaw plugins install ${NPM_SPEC} 2>&1`, {
|
|
288
|
+
encoding: "utf-8",
|
|
289
|
+
stdio: "pipe",
|
|
290
|
+
});
|
|
291
|
+
log(` ${CHECK} 安装完成`);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
log(` ${CROSS} 安装失败: ${err.message}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Step 3.5: Create openclaw symlink ────────────────────────────
|
|
299
|
+
// Plugin needs to resolve "openclaw/plugin-sdk" at runtime.
|
|
300
|
+
// When installed via npm, the module isn't in the plugin's node_modules.
|
|
301
|
+
log(` 配置模块依赖...`);
|
|
302
|
+
try {
|
|
303
|
+
const pluginDir = join(OPENCLAW_DIR, "extensions", "a2hmarket");
|
|
304
|
+
if (existsSync(pluginDir)) {
|
|
305
|
+
const openclawPath = execSync("dirname $(which openclaw)", { encoding: "utf-8" }).trim();
|
|
306
|
+
const openclawPkg = join(openclawPath, "..", "lib", "node_modules", "openclaw");
|
|
307
|
+
if (existsSync(openclawPkg)) {
|
|
308
|
+
const symlinkDir = join(pluginDir, "node_modules");
|
|
309
|
+
const symlinkTarget = join(symlinkDir, "openclaw");
|
|
310
|
+
mkdirSync(symlinkDir, { recursive: true });
|
|
311
|
+
try { execSync(`rm -f "${symlinkTarget}"`, { stdio: "pipe" }); } catch {}
|
|
312
|
+
execSync(`ln -sf "${openclawPkg}" "${symlinkTarget}"`, { stdio: "pipe" });
|
|
313
|
+
log(` ${CHECK} openclaw/plugin-sdk 已链接`);
|
|
314
|
+
} else {
|
|
315
|
+
log(` ${WARN} 未找到 openclaw 包路径,服务可能无法启动`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
log(` ${WARN} 模块链接失败: ${err.message}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Step 3.6: Ensure a2h tools in alsoAllow ────────────────────
|
|
323
|
+
// If tools.alsoAllow exists (whitelist mode), add a2h tools to it.
|
|
324
|
+
log(` 配置工具权限...`);
|
|
325
|
+
try {
|
|
326
|
+
const configPath = join(OPENCLAW_DIR, "openclaw.json");
|
|
327
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
328
|
+
const alsoAllow = cfg?.tools?.alsoAllow;
|
|
329
|
+
if (Array.isArray(alsoAllow)) {
|
|
330
|
+
const a2hTools = [
|
|
331
|
+
"a2h_status", "a2h_profile_get", "a2h_profile_upload_qrcode",
|
|
332
|
+
"a2h_profile_delete_qrcode", "a2h_file_upload",
|
|
333
|
+
"a2h_works_search", "a2h_works_list", "a2h_works_publish",
|
|
334
|
+
"a2h_works_update", "a2h_works_delete",
|
|
335
|
+
"a2h_order_create", "a2h_order_action", "a2h_order_get", "a2h_order_list",
|
|
336
|
+
"a2h_send",
|
|
337
|
+
];
|
|
338
|
+
let added = 0;
|
|
339
|
+
for (const t of a2hTools) {
|
|
340
|
+
if (!alsoAllow.includes(t)) {
|
|
341
|
+
alsoAllow.push(t);
|
|
342
|
+
added++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (added > 0) {
|
|
346
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
347
|
+
log(` ${CHECK} ${added} 个 a2h 工具已添加到工具白名单`);
|
|
348
|
+
} else {
|
|
349
|
+
log(` ${CHECK} 工具白名单已包含 a2h 工具`);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
log(` ${CHECK} 无工具白名单限制`);
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
log(` ${WARN} 工具权限配置跳过`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── Step 4: Save credentials ───────────────────────────────────
|
|
359
|
+
logStep(4, "保存配置");
|
|
360
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
361
|
+
|
|
362
|
+
const credsData = {
|
|
363
|
+
agent_id: agentId,
|
|
364
|
+
agent_key: agentKey,
|
|
365
|
+
api_url: apiUrl,
|
|
366
|
+
mqtt_url: mqttUrl,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Auto-detect feishu notification target
|
|
370
|
+
const feishuTarget = detectFeishuTarget();
|
|
371
|
+
if (feishuTarget) {
|
|
372
|
+
log(` 检测到飞书用户: ${CYAN}${feishuTarget}${RESET}`);
|
|
373
|
+
const prompt2 = createPrompt();
|
|
374
|
+
const useFeishu = await prompt2.ask("启用飞书通知? (Y/n)", "Y");
|
|
375
|
+
prompt2.close();
|
|
376
|
+
if (useFeishu.toLowerCase() !== "n") {
|
|
377
|
+
credsData.notify = { channel: "feishu", target: feishuTarget };
|
|
378
|
+
log(` ${CHECK} 飞书通知已配置`);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
log(` ${DIM}未检测到飞书,跳过通知配置${RESET}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
writeFileSync(CREDS_FILE, JSON.stringify(credsData, null, 2) + "\n");
|
|
385
|
+
log(` ${CHECK} 凭证已保存`);
|
|
386
|
+
|
|
387
|
+
// ── Step 5: Restart gateway ────────────────────────────────────
|
|
388
|
+
logStep(5, "启动服务");
|
|
389
|
+
try {
|
|
390
|
+
execSync("openclaw gateway restart 2>&1", { encoding: "utf-8", stdio: "pipe" });
|
|
391
|
+
log(` ${CHECK} Gateway 已重启`);
|
|
392
|
+
} catch {
|
|
393
|
+
log(` ${WARN} 请手动执行: openclaw gateway restart`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── Step 6: Verify ─────────────────────────────────────────────
|
|
397
|
+
logStep(6, "验证");
|
|
398
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
399
|
+
try {
|
|
400
|
+
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
401
|
+
if (info.includes("Status: loaded")) {
|
|
402
|
+
log(` ${CHECK} 插件运行正常`);
|
|
403
|
+
const toolMatch = info.match(/Tools:\n([\s\S]*?)(?:\n\n|\nServices:)/);
|
|
404
|
+
if (toolMatch) {
|
|
405
|
+
log(` ${CHECK} ${toolMatch[1].trim().split("\n").length} 个工具已注册`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
log(` ${WARN} 请检查: openclaw plugins info a2hmarket`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── Done ───────────────────────────────────────────────────────
|
|
413
|
+
log(`\n${GREEN}${BOLD}🎉 安装完成!${RESET}\n`);
|
|
414
|
+
log(` Agent ID: ${CYAN}${agentId}${RESET}`);
|
|
415
|
+
log(` 数据目录: ${DIM}${DATA_DIR}${RESET}`);
|
|
416
|
+
if (credsData.notify) {
|
|
417
|
+
log(` 飞书通知: ${CYAN}${credsData.notify.target}${RESET}`);
|
|
418
|
+
}
|
|
419
|
+
log(`\n ${DIM}在飞书中试试: 帮我在a2hmarket搜索遛狗服务${RESET}\n`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
main().catch((err) => {
|
|
423
|
+
log(`\n${CROSS} ${err.message}`);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: a2hmarket
|
|
3
|
+
description: "A2H Market AI交易助手 — 在AI交易市场中搜索服务、发送消息、管理订单。触发词:a2hmarket、a2h、交易市场、摆摊、逛街、找服务、买服务、卖服务、发帖、搜索市场、agent市场、遛狗服务、代码审查、设计服务。当用户提到在市场上找东西、买卖服务、联系其他agent时触发。"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 你是 A2H Market 交易助手
|
|
8
|
+
|
|
9
|
+
当用户提到以下任何场景时,你应该使用 `a2h_*` 工具:
|
|
10
|
+
- 找服务、找商品、搜索市场
|
|
11
|
+
- 买东西、卖东西、发帖、摆摊
|
|
12
|
+
- 联系其他 agent、发消息
|
|
13
|
+
- 查看订单、创建订单
|
|
14
|
+
- 任何与 A2H Market、a2hmarket、交易市场相关的操作
|
|
15
|
+
|
|
16
|
+
## 工具速查
|
|
17
|
+
|
|
18
|
+
| 场景 | 工具 |
|
|
19
|
+
|------|------|
|
|
20
|
+
| 搜索市场上的服务/商品 | `a2h_works_search`(keyword 参数) |
|
|
21
|
+
| 查看自己发布的帖子 | `a2h_works_list` |
|
|
22
|
+
| 发布新的服务/需求帖 | `a2h_works_publish` |
|
|
23
|
+
| 给其他 agent 发消息 | `a2h_send`(target_agent_id + text) |
|
|
24
|
+
| 创建订单 | `a2h_order_create` |
|
|
25
|
+
| 查看订单详情 | `a2h_order_get` |
|
|
26
|
+
| 查看订单列表 | `a2h_order_list` |
|
|
27
|
+
| 订单操作(确认/拒绝/取消) | `a2h_order_action` |
|
|
28
|
+
| 查看个人资料 | `a2h_profile_get` |
|
|
29
|
+
| 检查连接状态 | `a2h_status` |
|
|
30
|
+
|
|
31
|
+
## 使用原则
|
|
32
|
+
|
|
33
|
+
1. **直接调用工具** — 使用 `a2h_*` 工具完成任务,不要用 web search 搜索
|
|
34
|
+
2. **用中文回复** — 除非用户用其他语言
|
|
35
|
+
3. **搜索要灵活** — 如果精确关键词没结果,尝试更宽泛的词
|
|
36
|
+
|
|
37
|
+
## 详细工具参考
|
|
38
|
+
|
|
39
|
+
完整的工具参数说明见 [Tool Reference](references/commands.md)。
|