@a2hmarket/a2hmarket 0.7.4 → 0.7.6
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/openclaw.plugin.json +4 -2
- package/package.json +1 -1
- package/scripts/install.mjs +117 -117
- package/src/tools/discussion.ts +4 -18
- package/src/tools/works.ts +4 -17
package/openclaw.plugin.json
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
"id": "a2hmarket",
|
|
3
3
|
"name": "A2H Market",
|
|
4
4
|
"description": "A2H Market — AI agent marketplace with self-managed A2A messaging via MQTT.",
|
|
5
|
-
"version": "0.
|
|
6
|
-
"skills": [
|
|
5
|
+
"version": "0.7.6",
|
|
6
|
+
"skills": [
|
|
7
|
+
"./skills"
|
|
8
|
+
],
|
|
7
9
|
"configSchema": {
|
|
8
10
|
"type": "object",
|
|
9
11
|
"properties": {},
|
package/package.json
CHANGED
package/scripts/install.mjs
CHANGED
|
@@ -167,12 +167,12 @@ async function pollForAuth(code) {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
170
|
-
process.stdout.write(`\r
|
|
170
|
+
process.stdout.write(`\r Waiting for authorization... ${DIM}(${remaining}s remaining)${RESET} `);
|
|
171
171
|
|
|
172
172
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
return { error: "
|
|
175
|
+
return { error: "Authorization timed out (5 minutes). Please run install again." };
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// ── Feishu Card Helper ───────────────────────────────────────────────────
|
|
@@ -189,13 +189,13 @@ async function getFeishuToken(appId, appSecret) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
const ONBOARDING_TEXT =
|
|
192
|
-
"🎉 A2H Market ·
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"🏪
|
|
196
|
-
"🛍️
|
|
197
|
-
"👀
|
|
198
|
-
"
|
|
192
|
+
"🎉 A2H Market · Setup Complete\n\n" +
|
|
193
|
+
"I'm now connected to A2H Market! The marketplace is buzzing with activity.\n\n" +
|
|
194
|
+
"I can help you with three things:\n\n" +
|
|
195
|
+
"🏪 **Sell** — List your products or services on the marketplace, or take on bounty tasks\n\n" +
|
|
196
|
+
"🛍️ **Shop** — Find and buy what you need — I'll search, compare, and negotiate for you\n\n" +
|
|
197
|
+
"👀 **Explore** — Not sure yet? I'll browse the market and discover opportunities for you\n\n" +
|
|
198
|
+
"Would you like to **sell something**, **buy something**, or just **explore** the market?";
|
|
199
199
|
|
|
200
200
|
async function sendOnboardingFeishu(appId, appSecret, target, agentId) {
|
|
201
201
|
const token = await getFeishuToken(appId, appSecret);
|
|
@@ -205,19 +205,19 @@ async function sendOnboardingFeishu(appId, appSecret, target, agentId) {
|
|
|
205
205
|
schema: "2.0",
|
|
206
206
|
config: { wide_screen_mode: true },
|
|
207
207
|
header: {
|
|
208
|
-
title: { tag: "plain_text", content: "🎉 A2H Market ·
|
|
208
|
+
title: { tag: "plain_text", content: "🎉 A2H Market · Setup Complete" },
|
|
209
209
|
template: "green",
|
|
210
210
|
},
|
|
211
211
|
body: {
|
|
212
212
|
elements: [
|
|
213
|
-
{ tag: "markdown", content: "
|
|
214
|
-
{ tag: "markdown", content: "
|
|
213
|
+
{ tag: "markdown", content: "I'm now connected to A2H Market! The marketplace is buzzing with activity." },
|
|
214
|
+
{ tag: "markdown", content: "I can help you with three things:" },
|
|
215
215
|
{ tag: "markdown", content:
|
|
216
|
-
"🏪
|
|
217
|
-
"🛍️
|
|
218
|
-
"👀
|
|
219
|
-
{ tag: "markdown", content: "
|
|
220
|
-
{ tag: "markdown", content: `---\n
|
|
216
|
+
"🏪 **Sell** — List your products or services on the marketplace, or take on bounty tasks\n\n" +
|
|
217
|
+
"🛍️ **Shop** — Find and buy what you need — I'll search, compare, and negotiate for you\n\n" +
|
|
218
|
+
"👀 **Explore** — Not sure yet? I'll browse the market and discover opportunities for you" },
|
|
219
|
+
{ tag: "markdown", content: "Would you like to **sell something**, **buy something**, or just **explore** the market?" },
|
|
220
|
+
{ tag: "markdown", content: `---\n*My A2H Agent: ${agentId}*` },
|
|
221
221
|
],
|
|
222
222
|
},
|
|
223
223
|
};
|
|
@@ -232,7 +232,7 @@ async function sendOnboardingFeishu(appId, appSecret, target, agentId) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
async function sendOnboardingDiscord(botToken, channelId, agentId) {
|
|
235
|
-
const text = ONBOARDING_TEXT + `\n\n---\
|
|
235
|
+
const text = ONBOARDING_TEXT + `\n\n---\n_My A2H Agent: ${agentId}_`;
|
|
236
236
|
const resp = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
|
|
237
237
|
method: "POST",
|
|
238
238
|
headers: { "Content-Type": "application/json", Authorization: `Bot ${botToken}` },
|
|
@@ -264,10 +264,10 @@ async function runUpdate() {
|
|
|
264
264
|
log(`\n${BOLD}🏪 A2H Market — Update${RESET}\n`);
|
|
265
265
|
|
|
266
266
|
// 1. Check OpenClaw
|
|
267
|
-
logStep(1, "
|
|
267
|
+
logStep(1, "Check Environment");
|
|
268
268
|
const version = checkOpenclaw();
|
|
269
269
|
if (!version) {
|
|
270
|
-
log(` ${CROSS} OpenClaw
|
|
270
|
+
log(` ${CROSS} OpenClaw not installed`);
|
|
271
271
|
process.exit(1);
|
|
272
272
|
}
|
|
273
273
|
log(` ${CHECK} ${version}`);
|
|
@@ -275,14 +275,14 @@ async function runUpdate() {
|
|
|
275
275
|
try {
|
|
276
276
|
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
277
277
|
if (!info.includes("a2hmarket")) throw new Error("not found");
|
|
278
|
-
log(` ${CHECK}
|
|
278
|
+
log(` ${CHECK} Plugin installed`);
|
|
279
279
|
} catch {
|
|
280
|
-
log(` ${CROSS}
|
|
280
|
+
log(` ${CROSS} Plugin not installed. Run: npx -y ${NPM_SPEC} install`);
|
|
281
281
|
process.exit(1);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// 2. Check versions
|
|
285
|
-
logStep(2, "
|
|
285
|
+
logStep(2, "Check Version");
|
|
286
286
|
let currentVersion = "unknown";
|
|
287
287
|
try {
|
|
288
288
|
const pluginPkg = join(OPENCLAW_DIR, "extensions", "a2hmarket", "package.json");
|
|
@@ -296,39 +296,39 @@ async function runUpdate() {
|
|
|
296
296
|
try {
|
|
297
297
|
latestVersion = execSync(`npm view ${NPM_SPEC} version 2>/dev/null`, { encoding: "utf-8" }).trim();
|
|
298
298
|
} catch {
|
|
299
|
-
log(` ${CROSS}
|
|
299
|
+
log(` ${CROSS} Cannot fetch latest version`);
|
|
300
300
|
process.exit(1);
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
log(`
|
|
304
|
-
log(`
|
|
303
|
+
log(` Current: ${CYAN}${currentVersion}${RESET}`);
|
|
304
|
+
log(` Latest: ${CYAN}${latestVersion}${RESET}`);
|
|
305
305
|
|
|
306
306
|
if (currentVersion === latestVersion) {
|
|
307
|
-
log(`\n ${CHECK}
|
|
307
|
+
log(`\n ${CHECK} Already up to date`);
|
|
308
308
|
process.exit(0);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
// 3. Backup credentials before uninstall
|
|
312
|
-
logStep(3, "
|
|
312
|
+
logStep(3, "Update Plugin");
|
|
313
313
|
let savedCreds = null;
|
|
314
314
|
try {
|
|
315
315
|
if (existsSync(CREDS_FILE)) {
|
|
316
316
|
savedCreds = JSON.parse(readFileSync(CREDS_FILE, "utf-8"));
|
|
317
|
-
log(` ${CHECK}
|
|
317
|
+
log(` ${CHECK} Credentials backed up`);
|
|
318
318
|
}
|
|
319
319
|
} catch {}
|
|
320
320
|
if (!savedCreds) {
|
|
321
|
-
log(` ${WARN}
|
|
321
|
+
log(` ${WARN} No credentials found — may need to reinstall after update`);
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
try {
|
|
325
|
-
log(`
|
|
325
|
+
log(` Uninstalling old version...`);
|
|
326
326
|
execSync('echo y | openclaw plugins uninstall a2hmarket 2>&1', { encoding: "utf-8", stdio: "pipe" });
|
|
327
|
-
log(`
|
|
327
|
+
log(` Installing new version...`);
|
|
328
328
|
execSync(`echo y | openclaw plugins install ${NPM_SPEC} 2>&1`, { encoding: "utf-8", stdio: "pipe" });
|
|
329
|
-
log(` ${CHECK}
|
|
329
|
+
log(` ${CHECK} Update complete`);
|
|
330
330
|
} catch (err) {
|
|
331
|
-
log(` ${CROSS}
|
|
331
|
+
log(` ${CROSS} Update failed: ${err.message}`);
|
|
332
332
|
process.exit(1);
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -343,11 +343,11 @@ async function runUpdate() {
|
|
|
343
343
|
};
|
|
344
344
|
if (savedCreds.notify) fileData.notify = savedCreds.notify;
|
|
345
345
|
writeFileSync(CREDS_FILE, JSON.stringify(fileData, null, 2) + "\n");
|
|
346
|
-
log(` ${CHECK}
|
|
346
|
+
log(` ${CHECK} Credentials restored`);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
// 4. Re-link openclaw module
|
|
350
|
-
log(`
|
|
350
|
+
log(` Linking module dependencies...`);
|
|
351
351
|
try {
|
|
352
352
|
const pluginDir = join(OPENCLAW_DIR, "extensions", "a2hmarket");
|
|
353
353
|
if (existsSync(pluginDir)) {
|
|
@@ -384,37 +384,37 @@ async function runUpdate() {
|
|
|
384
384
|
mkdirSync(symlinkDir, { recursive: true });
|
|
385
385
|
try { execSync(`rm -f "${symlinkTarget}"`, { stdio: "pipe" }); } catch {}
|
|
386
386
|
execSync(`ln -sf "${openclawPkg}" "${symlinkTarget}"`, { stdio: "pipe" });
|
|
387
|
-
log(` ${CHECK} openclaw/plugin-sdk
|
|
387
|
+
log(` ${CHECK} openclaw/plugin-sdk linked`);
|
|
388
388
|
} else {
|
|
389
|
-
log(` ${WARN}
|
|
389
|
+
log(` ${WARN} openclaw package not found`);
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
} catch (err) {
|
|
393
|
-
log(` ${WARN}
|
|
393
|
+
log(` ${WARN} Module linking failed: ${err.message}`);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// 5. Restart gateway
|
|
397
|
-
logStep(4, "
|
|
397
|
+
logStep(4, "Restart Gateway");
|
|
398
398
|
try {
|
|
399
399
|
execSync("openclaw gateway restart 2>&1", { encoding: "utf-8", stdio: "pipe" });
|
|
400
|
-
log(` ${CHECK} Gateway
|
|
400
|
+
log(` ${CHECK} Gateway restarted`);
|
|
401
401
|
} catch {
|
|
402
|
-
log(` ${WARN}
|
|
402
|
+
log(` ${WARN} Please run manually: openclaw gateway restart`);
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
// 6. Verify
|
|
406
|
-
logStep(5, "
|
|
406
|
+
logStep(5, "Verify");
|
|
407
407
|
await new Promise((r) => setTimeout(r, 5000));
|
|
408
408
|
try {
|
|
409
409
|
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
410
410
|
if (info.includes("Status: loaded")) {
|
|
411
|
-
log(` ${CHECK}
|
|
411
|
+
log(` ${CHECK} Plugin running`);
|
|
412
412
|
}
|
|
413
413
|
} catch {
|
|
414
|
-
log(` ${WARN}
|
|
414
|
+
log(` ${WARN} Please check: openclaw plugins info a2hmarket`);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
log(`\n${GREEN}${BOLD}🎉
|
|
417
|
+
log(`\n${GREEN}${BOLD}🎉 Update complete!${RESET} ${currentVersion} → ${CYAN}${latestVersion}${RESET}\n`);
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
// ── Uninstall ────────────────────────────────────────────────────────────
|
|
@@ -423,29 +423,29 @@ async function runUninstall() {
|
|
|
423
423
|
log(`\n${BOLD}🏪 A2H Market — Uninstall${RESET}\n`);
|
|
424
424
|
|
|
425
425
|
const prompt = createPrompt();
|
|
426
|
-
const confirm = await prompt.ask("
|
|
426
|
+
const confirm = await prompt.ask("Uninstall a2hmarket plugin and all data? (y/N)", "N");
|
|
427
427
|
prompt.close();
|
|
428
428
|
if (confirm.toLowerCase() !== "y") {
|
|
429
|
-
log(`
|
|
429
|
+
log(` Cancelled.`);
|
|
430
430
|
process.exit(0);
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
// 1. Uninstall plugin
|
|
434
|
-
log(`
|
|
434
|
+
log(` Uninstalling plugin...`);
|
|
435
435
|
try {
|
|
436
436
|
execSync('echo y | openclaw plugins uninstall a2hmarket 2>&1', { encoding: "utf-8", stdio: "pipe" });
|
|
437
|
-
log(` ${CHECK}
|
|
437
|
+
log(` ${CHECK} Plugin uninstalled`);
|
|
438
438
|
} catch {
|
|
439
|
-
log(` ${WARN}
|
|
439
|
+
log(` ${WARN} Plugin uninstall failed (may already be uninstalled)`);
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
// 2. Remove runtime data
|
|
443
443
|
if (existsSync(DATA_DIR)) {
|
|
444
444
|
try {
|
|
445
445
|
execSync(`rm -rf "${DATA_DIR}"`, { stdio: "pipe" });
|
|
446
|
-
log(` ${CHECK}
|
|
446
|
+
log(` ${CHECK} Data directory removed: ${DATA_DIR}`);
|
|
447
447
|
} catch {
|
|
448
|
-
log(` ${WARN}
|
|
448
|
+
log(` ${WARN} Failed to remove data directory: ${DATA_DIR}`);
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
|
|
@@ -458,21 +458,21 @@ async function runUninstall() {
|
|
|
458
458
|
// Reset to minimal — keep enabled:false so openclaw knows it was uninstalled
|
|
459
459
|
cfg.plugins.entries.a2hmarket = { enabled: false };
|
|
460
460
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
461
|
-
log(` ${CHECK} openclaw.json
|
|
461
|
+
log(` ${CHECK} openclaw.json cleaned`);
|
|
462
462
|
}
|
|
463
463
|
} catch {
|
|
464
|
-
log(` ${WARN} openclaw.json
|
|
464
|
+
log(` ${WARN} openclaw.json cleanup skipped`);
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
// 4. Restart gateway
|
|
468
468
|
try {
|
|
469
469
|
execSync("openclaw gateway restart 2>&1", { encoding: "utf-8", stdio: "pipe" });
|
|
470
|
-
log(` ${CHECK} Gateway
|
|
470
|
+
log(` ${CHECK} Gateway restarted`);
|
|
471
471
|
} catch {
|
|
472
|
-
log(` ${WARN}
|
|
472
|
+
log(` ${WARN} Please run manually: openclaw gateway restart`);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
log(`\n${GREEN}${BOLD}
|
|
475
|
+
log(`\n${GREEN}${BOLD}Uninstall complete${RESET}\n`);
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
// ── Main ─────────────────────────────────────────────────────────────────
|
|
@@ -491,11 +491,11 @@ async function main() {
|
|
|
491
491
|
|
|
492
492
|
if (cmd !== "install") {
|
|
493
493
|
log(`\n${BOLD}A2H Market — OpenClaw Plugin${RESET}\n`);
|
|
494
|
-
log(`
|
|
495
|
-
log(`
|
|
496
|
-
log(`
|
|
497
|
-
log(`
|
|
498
|
-
log(`
|
|
494
|
+
log(` Install: npx -y ${NPM_SPEC} install`);
|
|
495
|
+
log(` Quick: npx -y ${NPM_SPEC} install --agent ag_xxx:key`);
|
|
496
|
+
log(` Silent: npx -y ${NPM_SPEC} install --agent ag_xxx:key --notify feishu:ou_xxx --yes`);
|
|
497
|
+
log(` Update: npx -y ${NPM_SPEC} update`);
|
|
498
|
+
log(` Remove: npx -y ${NPM_SPEC} uninstall\n`);
|
|
499
499
|
process.exit(0);
|
|
500
500
|
}
|
|
501
501
|
|
|
@@ -518,16 +518,16 @@ async function main() {
|
|
|
518
518
|
log(`\n${BOLD}🏪 A2H Market — OpenClaw Plugin Installer${RESET}\n`);
|
|
519
519
|
|
|
520
520
|
// ── Step 1: Check OpenClaw ─────────────────────────────────────
|
|
521
|
-
logStep(1, "
|
|
521
|
+
logStep(1, "Check OpenClaw");
|
|
522
522
|
const version = checkOpenclaw();
|
|
523
523
|
if (!version) {
|
|
524
|
-
log(` ${CROSS} OpenClaw
|
|
524
|
+
log(` ${CROSS} OpenClaw not installed. Get it at: https://docs.openclaw.ai`);
|
|
525
525
|
process.exit(1);
|
|
526
526
|
}
|
|
527
527
|
log(` ${CHECK} ${version}`);
|
|
528
528
|
|
|
529
529
|
// ── Step 2: Authorize ──────────────────────────────────────────
|
|
530
|
-
logStep(2, "
|
|
530
|
+
logStep(2, "Authorization");
|
|
531
531
|
|
|
532
532
|
let agentId, agentKey, apiUrl, mqttUrl;
|
|
533
533
|
|
|
@@ -541,18 +541,18 @@ async function main() {
|
|
|
541
541
|
[agentId, agentKey] = agentValue.split(":", 2);
|
|
542
542
|
apiUrl = API_DEFAULT;
|
|
543
543
|
mqttUrl = MQTT_DEFAULT;
|
|
544
|
-
log(`
|
|
544
|
+
log(` Using CLI credentials: ${agentId}`);
|
|
545
545
|
} else if (existsSync(CREDS_FILE)) {
|
|
546
546
|
// Check existing
|
|
547
547
|
try {
|
|
548
548
|
const existing = JSON.parse(readFileSync(CREDS_FILE, "utf-8"));
|
|
549
549
|
const existingId = existing.agent_id ?? existing.agentId ?? "";
|
|
550
550
|
if (existingId) {
|
|
551
|
-
log(`
|
|
551
|
+
log(` Existing credentials: ${CYAN}${existingId}${RESET}`);
|
|
552
552
|
let reuse = "Y";
|
|
553
553
|
if (!autoYes) {
|
|
554
554
|
const prompt = createPrompt();
|
|
555
|
-
reuse = await prompt.ask("
|
|
555
|
+
reuse = await prompt.ask("Use existing credentials? (Y/n)", "Y");
|
|
556
556
|
prompt.close();
|
|
557
557
|
}
|
|
558
558
|
if (reuse.toLowerCase() !== "n") {
|
|
@@ -567,12 +567,12 @@ async function main() {
|
|
|
567
567
|
|
|
568
568
|
if (!agentId) {
|
|
569
569
|
// Authorization code flow
|
|
570
|
-
log(`
|
|
570
|
+
log(` Generating authorization link...`);
|
|
571
571
|
const { code, url } = await generateAuthCode();
|
|
572
572
|
|
|
573
|
-
log(`\n ${BOLD}
|
|
573
|
+
log(`\n ${BOLD}Open this link in your browser to authorize:${RESET}\n`);
|
|
574
574
|
log(` ${CYAN}${url}${RESET}\n`);
|
|
575
|
-
log(` ${DIM}
|
|
575
|
+
log(` ${DIM}Auth code: ${code}${RESET}\n`);
|
|
576
576
|
|
|
577
577
|
const result = await pollForAuth(code);
|
|
578
578
|
process.stdout.write("\r" + " ".repeat(60) + "\r"); // Clear progress line
|
|
@@ -586,16 +586,16 @@ async function main() {
|
|
|
586
586
|
agentKey = result.agentKey;
|
|
587
587
|
apiUrl = result.apiUrl;
|
|
588
588
|
mqttUrl = result.mqttUrl;
|
|
589
|
-
log(` ${CHECK}
|
|
589
|
+
log(` ${CHECK} Authorized! Agent ID: ${CYAN}${agentId}${RESET}`);
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
if (!agentId || !agentKey) {
|
|
593
|
-
log(` ${CROSS}
|
|
593
|
+
log(` ${CROSS} Invalid credentials`);
|
|
594
594
|
process.exit(1);
|
|
595
595
|
}
|
|
596
596
|
|
|
597
597
|
// Verify credentials
|
|
598
|
-
log(`
|
|
598
|
+
log(` Verifying credentials...`);
|
|
599
599
|
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
600
600
|
const path = "/findu-user/api/v1/user/profile/public";
|
|
601
601
|
const sig = createHmac("sha256", agentKey)
|
|
@@ -611,33 +611,33 @@ async function main() {
|
|
|
611
611
|
});
|
|
612
612
|
const data = await resp.json();
|
|
613
613
|
if (resp.ok && data.code === "200") {
|
|
614
|
-
log(` ${CHECK}
|
|
614
|
+
log(` ${CHECK} Nickname: ${data.data?.nickname ?? agentId}`);
|
|
615
615
|
} else {
|
|
616
|
-
log(` ${WARN}
|
|
616
|
+
log(` ${WARN} Credential verification failed, continuing (${data.message ?? resp.status})`);
|
|
617
617
|
}
|
|
618
618
|
} catch {
|
|
619
|
-
log(` ${WARN}
|
|
619
|
+
log(` ${WARN} Cannot verify credentials (network issue), continuing`);
|
|
620
620
|
}
|
|
621
621
|
|
|
622
622
|
// ── Step 3: Install plugin ─────────────────────────────────────
|
|
623
|
-
logStep(3, "
|
|
623
|
+
logStep(3, "Install Plugin");
|
|
624
624
|
try {
|
|
625
625
|
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
626
626
|
if (info.includes("Status: loaded")) {
|
|
627
|
-
log(` ${CHECK}
|
|
627
|
+
log(` ${CHECK} Plugin already installed`);
|
|
628
628
|
} else {
|
|
629
629
|
throw new Error("not loaded");
|
|
630
630
|
}
|
|
631
631
|
} catch {
|
|
632
632
|
try {
|
|
633
|
-
log(`
|
|
633
|
+
log(` Installing...`);
|
|
634
634
|
execSync(`echo y | openclaw plugins install ${NPM_SPEC} 2>&1`, {
|
|
635
635
|
encoding: "utf-8",
|
|
636
636
|
stdio: "pipe",
|
|
637
637
|
});
|
|
638
|
-
log(` ${CHECK}
|
|
638
|
+
log(` ${CHECK} Installed`);
|
|
639
639
|
} catch (err) {
|
|
640
|
-
log(` ${CROSS}
|
|
640
|
+
log(` ${CROSS} Install failed: ${err.message}`);
|
|
641
641
|
process.exit(1);
|
|
642
642
|
}
|
|
643
643
|
}
|
|
@@ -645,7 +645,7 @@ async function main() {
|
|
|
645
645
|
// ── Step 3.5: Create openclaw symlink ────────────────────────────
|
|
646
646
|
// Plugin needs to resolve "openclaw/plugin-sdk" at runtime.
|
|
647
647
|
// When installed via npm, the module isn't in the plugin's node_modules.
|
|
648
|
-
log(`
|
|
648
|
+
log(` Linking module dependencies...`);
|
|
649
649
|
try {
|
|
650
650
|
const pluginDir = join(OPENCLAW_DIR, "extensions", "a2hmarket");
|
|
651
651
|
if (existsSync(pluginDir)) {
|
|
@@ -687,18 +687,18 @@ async function main() {
|
|
|
687
687
|
mkdirSync(symlinkDir, { recursive: true });
|
|
688
688
|
try { execSync(`rm -f "${symlinkTarget}"`, { stdio: "pipe" }); } catch {}
|
|
689
689
|
execSync(`ln -sf "${openclawPkg}" "${symlinkTarget}"`, { stdio: "pipe" });
|
|
690
|
-
log(` ${CHECK} openclaw/plugin-sdk
|
|
690
|
+
log(` ${CHECK} openclaw/plugin-sdk linked`);
|
|
691
691
|
} else {
|
|
692
|
-
log(` ${WARN}
|
|
693
|
-
log(` ${DIM}
|
|
692
|
+
log(` ${WARN} openclaw package not found, service may not start`);
|
|
693
|
+
log(` ${DIM} Tried: ${candidates.slice(0, 3).join(", ")}${RESET}`);
|
|
694
694
|
}
|
|
695
695
|
}
|
|
696
696
|
} catch (err) {
|
|
697
|
-
log(` ${WARN}
|
|
697
|
+
log(` ${WARN} Module linking failed: ${err.message}`);
|
|
698
698
|
}
|
|
699
699
|
|
|
700
700
|
// ── Step 4: Save credentials & configure openclaw.json ───────
|
|
701
|
-
logStep(4, "
|
|
701
|
+
logStep(4, "Save Configuration");
|
|
702
702
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
703
703
|
|
|
704
704
|
const credsData = {
|
|
@@ -711,18 +711,18 @@ async function main() {
|
|
|
711
711
|
// Configure notification channel
|
|
712
712
|
if (presetNotify) {
|
|
713
713
|
credsData.notify = presetNotify;
|
|
714
|
-
log(` ${CHECK}
|
|
714
|
+
log(` ${CHECK} Notification: ${presetNotify.channel} → ${presetNotify.target}`);
|
|
715
715
|
} else if (!autoYes) {
|
|
716
716
|
const channels = detectChannels();
|
|
717
717
|
if (channels.length > 0) {
|
|
718
|
-
log(`
|
|
718
|
+
log(` Found ${channels.length} channel(s):`);
|
|
719
719
|
channels.forEach((ch, i) => {
|
|
720
720
|
log(` ${CYAN}${i + 1}${RESET}. ${ch.name}`);
|
|
721
721
|
});
|
|
722
722
|
|
|
723
723
|
const prompt2 = createPrompt();
|
|
724
724
|
const choice = await prompt2.ask(
|
|
725
|
-
|
|
725
|
+
`Select notification channel (1-${channels.length}, enter for default 1)`,
|
|
726
726
|
"1",
|
|
727
727
|
);
|
|
728
728
|
|
|
@@ -735,42 +735,42 @@ async function main() {
|
|
|
735
735
|
if (chosen.name === "feishu") {
|
|
736
736
|
target = detectFeishuTarget() || "";
|
|
737
737
|
if (target) {
|
|
738
|
-
log(`
|
|
738
|
+
log(` Detected Feishu user: ${CYAN}${target}${RESET}`);
|
|
739
739
|
} else {
|
|
740
|
-
target = await prompt2.ask("
|
|
740
|
+
target = await prompt2.ask("Enter Feishu open_id (ou_xxx) or chat_id (oc_xxx)", "");
|
|
741
741
|
}
|
|
742
742
|
} else if (chosen.name === "discord") {
|
|
743
|
-
target = await prompt2.ask("
|
|
743
|
+
target = await prompt2.ask("Enter Discord channel ID", "");
|
|
744
744
|
} else {
|
|
745
|
-
target = await prompt2.ask(
|
|
745
|
+
target = await prompt2.ask(`Enter ${chosen.name} target ID`, "");
|
|
746
746
|
}
|
|
747
747
|
|
|
748
748
|
if (target) {
|
|
749
749
|
credsData.notify = { channel: chosen.name, target };
|
|
750
|
-
log(` ${CHECK}
|
|
750
|
+
log(` ${CHECK} Notification configured: ${chosen.name} → ${target}`);
|
|
751
751
|
} else {
|
|
752
|
-
log(` ${WARN}
|
|
752
|
+
log(` ${WARN} No target ID entered, skipping notification`);
|
|
753
753
|
}
|
|
754
754
|
}
|
|
755
755
|
}
|
|
756
756
|
prompt2.close();
|
|
757
757
|
} else {
|
|
758
|
-
log(` ${DIM}
|
|
758
|
+
log(` ${DIM}No channels detected, skipping notification${RESET}`);
|
|
759
759
|
}
|
|
760
760
|
} else {
|
|
761
761
|
// --yes without --notify: try auto-detect feishu
|
|
762
762
|
const feishuTarget = detectFeishuTarget();
|
|
763
763
|
if (feishuTarget) {
|
|
764
764
|
credsData.notify = { channel: "feishu", target: feishuTarget };
|
|
765
|
-
log(` ${CHECK}
|
|
765
|
+
log(` ${CHECK} Auto-detected: feishu → ${feishuTarget}`);
|
|
766
766
|
} else {
|
|
767
|
-
log(` ${DIM}
|
|
767
|
+
log(` ${DIM}Skipping notification (use --notify to specify)${RESET}`);
|
|
768
768
|
}
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
// Save credentials to ~/.openclaw/a2hmarket/credentials.json
|
|
772
772
|
writeFileSync(CREDS_FILE, JSON.stringify(credsData, null, 2) + "\n");
|
|
773
|
-
log(` ${CHECK}
|
|
773
|
+
log(` ${CHECK} Credentials saved`);
|
|
774
774
|
|
|
775
775
|
// Ensure a2h tools in alsoAllow (if whitelist mode is active)
|
|
776
776
|
try {
|
|
@@ -797,7 +797,7 @@ async function main() {
|
|
|
797
797
|
}
|
|
798
798
|
if (added > 0) {
|
|
799
799
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
800
|
-
log(` ${CHECK} ${added}
|
|
800
|
+
log(` ${CHECK} ${added} tools added to allowlist`);
|
|
801
801
|
}
|
|
802
802
|
}
|
|
803
803
|
} catch {}
|
|
@@ -810,52 +810,52 @@ async function main() {
|
|
|
810
810
|
if (entry && (entry.agentId || entry.agentKey)) {
|
|
811
811
|
cfg.plugins.entries.a2hmarket = { enabled: true };
|
|
812
812
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
813
|
-
log(` ${CHECK}
|
|
813
|
+
log(` ${CHECK} Cleaned stale credentials from openclaw.json`);
|
|
814
814
|
}
|
|
815
815
|
} catch {}
|
|
816
816
|
|
|
817
817
|
// ── Step 5: Restart gateway ────────────────────────────────────
|
|
818
|
-
logStep(5, "
|
|
818
|
+
logStep(5, "Start Service");
|
|
819
819
|
try {
|
|
820
820
|
execSync("openclaw gateway restart 2>&1", { encoding: "utf-8", stdio: "pipe" });
|
|
821
|
-
log(` ${CHECK} Gateway
|
|
821
|
+
log(` ${CHECK} Gateway restarted`);
|
|
822
822
|
} catch {
|
|
823
|
-
log(` ${WARN}
|
|
823
|
+
log(` ${WARN} Please run manually: openclaw gateway restart`);
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
// ── Step 6: Verify ─────────────────────────────────────────────
|
|
827
|
-
logStep(6, "
|
|
827
|
+
logStep(6, "Verify");
|
|
828
828
|
await new Promise((r) => setTimeout(r, 5000));
|
|
829
829
|
try {
|
|
830
830
|
const info = execSync("openclaw plugins info a2hmarket 2>&1", { encoding: "utf-8" });
|
|
831
831
|
if (info.includes("Status: loaded")) {
|
|
832
|
-
log(` ${CHECK}
|
|
832
|
+
log(` ${CHECK} Plugin running`);
|
|
833
833
|
const toolMatch = info.match(/Tools:\n([\s\S]*?)(?:\n\n|\nServices:)/);
|
|
834
834
|
if (toolMatch) {
|
|
835
|
-
log(` ${CHECK} ${toolMatch[1].trim().split("\n").length}
|
|
835
|
+
log(` ${CHECK} ${toolMatch[1].trim().split("\n").length} tools registered`);
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
838
|
} catch {
|
|
839
|
-
log(` ${WARN}
|
|
839
|
+
log(` ${WARN} Please check: openclaw plugins info a2hmarket`);
|
|
840
840
|
}
|
|
841
841
|
|
|
842
842
|
// ── Step 7: Send onboarding message ──────────────────────────
|
|
843
843
|
if (credsData.notify?.channel && credsData.notify?.target) {
|
|
844
|
-
logStep(7, "
|
|
844
|
+
logStep(7, "Send Welcome Message");
|
|
845
845
|
try {
|
|
846
846
|
await sendOnboarding(credsData.notify.channel, credsData.notify.target, agentId);
|
|
847
|
-
log(` ${CHECK}
|
|
847
|
+
log(` ${CHECK} Welcome message sent to ${credsData.notify.channel}`);
|
|
848
848
|
} catch (err) {
|
|
849
|
-
log(` ${WARN}
|
|
849
|
+
log(` ${WARN} Welcome message failed: ${err.message}`);
|
|
850
850
|
}
|
|
851
851
|
}
|
|
852
852
|
|
|
853
853
|
// ── Done ───────────────────────────────────────────────────────
|
|
854
|
-
log(`\n${GREEN}${BOLD}🎉
|
|
854
|
+
log(`\n${GREEN}${BOLD}🎉 Setup complete!${RESET}\n`);
|
|
855
855
|
log(` Agent ID: ${CYAN}${agentId}${RESET}`);
|
|
856
|
-
log(`
|
|
856
|
+
log(` Data dir: ${DIM}${DATA_DIR}${RESET}`);
|
|
857
857
|
if (credsData.notify) {
|
|
858
|
-
log(`
|
|
858
|
+
log(` Notify: ${CYAN}${credsData.notify.channel}:${credsData.notify.target}${RESET}`);
|
|
859
859
|
}
|
|
860
860
|
log("");
|
|
861
861
|
}
|
package/src/tools/discussion.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function registerDiscussionTools(api: OpenClawPluginApi, client: A2HApiCl
|
|
|
8
8
|
api.registerTool({
|
|
9
9
|
name: "a2h_discussion_publish",
|
|
10
10
|
description:
|
|
11
|
-
"Publish a new discussion post.
|
|
11
|
+
"Publish a new discussion post. Confirm content with the human in conversation before calling.",
|
|
12
12
|
parameters: {
|
|
13
13
|
type: "object",
|
|
14
14
|
properties: {
|
|
@@ -19,17 +19,10 @@ export function registerDiscussionTools(api: OpenClawPluginApi, client: A2HApiCl
|
|
|
19
19
|
items: { type: "string" },
|
|
20
20
|
description: "Optional array of picture URLs",
|
|
21
21
|
},
|
|
22
|
-
confirm_human_reviewed: {
|
|
23
|
-
type: "boolean",
|
|
24
|
-
description: "Must be true — confirms a human has reviewed the content",
|
|
25
|
-
},
|
|
26
22
|
},
|
|
27
|
-
required: ["title", "content"
|
|
23
|
+
required: ["title", "content"],
|
|
28
24
|
},
|
|
29
25
|
execute: async (params: Record<string, unknown>) => {
|
|
30
|
-
if (params.confirm_human_reviewed !== true) {
|
|
31
|
-
throw new Error("confirm_human_reviewed must be true. A human must review discussion content before publishing.");
|
|
32
|
-
}
|
|
33
26
|
const body: Record<string, unknown> = {
|
|
34
27
|
type: 4,
|
|
35
28
|
title: params.title,
|
|
@@ -45,24 +38,17 @@ export function registerDiscussionTools(api: OpenClawPluginApi, client: A2HApiCl
|
|
|
45
38
|
api.registerTool({
|
|
46
39
|
name: "a2h_discussion_reply",
|
|
47
40
|
description:
|
|
48
|
-
"Reply to an existing discussion post.
|
|
41
|
+
"Reply to an existing discussion post. Confirm content with the human in conversation before calling.",
|
|
49
42
|
parameters: {
|
|
50
43
|
type: "object",
|
|
51
44
|
properties: {
|
|
52
45
|
parent_works_id: { type: "string", description: "ID of the discussion to reply to" },
|
|
53
46
|
title: { type: "string", description: "Reply title" },
|
|
54
47
|
content: { type: "string", description: "Reply content" },
|
|
55
|
-
confirm_human_reviewed: {
|
|
56
|
-
type: "boolean",
|
|
57
|
-
description: "Must be true — confirms a human has reviewed the content",
|
|
58
|
-
},
|
|
59
48
|
},
|
|
60
|
-
required: ["parent_works_id", "title", "content"
|
|
49
|
+
required: ["parent_works_id", "title", "content"],
|
|
61
50
|
},
|
|
62
51
|
execute: async (params: Record<string, unknown>) => {
|
|
63
|
-
if (params.confirm_human_reviewed !== true) {
|
|
64
|
-
throw new Error("confirm_human_reviewed must be true. A human must review discussion content before publishing.");
|
|
65
|
-
}
|
|
66
52
|
const body = {
|
|
67
53
|
type: 4,
|
|
68
54
|
title: params.title,
|
package/src/tools/works.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function registerWorksTools(api: OpenClawPluginApi, client: A2HApiClient)
|
|
|
62
62
|
api.registerTool({
|
|
63
63
|
name: "a2h_works_publish",
|
|
64
64
|
description:
|
|
65
|
-
"Publish a new works post (service or demand).
|
|
65
|
+
"Publish a new works post (service or demand). Confirm content with the human in conversation before calling.",
|
|
66
66
|
parameters: {
|
|
67
67
|
type: "object",
|
|
68
68
|
properties: {
|
|
@@ -73,18 +73,10 @@ export function registerWorksTools(api: OpenClawPluginApi, client: A2HApiClient)
|
|
|
73
73
|
service_method: { type: "string", description: "online or offline" },
|
|
74
74
|
service_location: { type: "string", description: "Location (for offline)" },
|
|
75
75
|
picture: { type: "string", description: "Cover image URL" },
|
|
76
|
-
confirm_human_reviewed: {
|
|
77
|
-
type: "boolean",
|
|
78
|
-
description: "Must be true — confirms human has reviewed this content",
|
|
79
|
-
},
|
|
80
76
|
},
|
|
81
|
-
required: ["type", "title", "content"
|
|
77
|
+
required: ["type", "title", "content"],
|
|
82
78
|
},
|
|
83
79
|
execute: async (params: Record<string, unknown>) => {
|
|
84
|
-
if (!params.confirm_human_reviewed) {
|
|
85
|
-
throw new Error("confirm_human_reviewed must be true. Human must review content before publishing.");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
80
|
const extendInfo: Record<string, unknown> = { pois: [] };
|
|
89
81
|
if (params.expected_price) extendInfo.expectedPrice = params.expected_price;
|
|
90
82
|
if (params.service_method) extendInfo.serviceMethod = params.service_method;
|
|
@@ -105,7 +97,7 @@ export function registerWorksTools(api: OpenClawPluginApi, client: A2HApiClient)
|
|
|
105
97
|
|
|
106
98
|
api.registerTool({
|
|
107
99
|
name: "a2h_works_update",
|
|
108
|
-
description: "Update an existing works post.
|
|
100
|
+
description: "Update an existing works post. Confirm changes with the human in conversation before calling.",
|
|
109
101
|
parameters: {
|
|
110
102
|
type: "object",
|
|
111
103
|
properties: {
|
|
@@ -117,15 +109,10 @@ export function registerWorksTools(api: OpenClawPluginApi, client: A2HApiClient)
|
|
|
117
109
|
service_method: { type: "string", description: "online or offline" },
|
|
118
110
|
service_location: { type: "string", description: "Location" },
|
|
119
111
|
picture: { type: "string", description: "Cover image URL" },
|
|
120
|
-
confirm_human_reviewed: { type: "boolean", description: "Must be true" },
|
|
121
112
|
},
|
|
122
|
-
required: ["works_id", "type", "title"
|
|
113
|
+
required: ["works_id", "type", "title"],
|
|
123
114
|
},
|
|
124
115
|
execute: async (params: Record<string, unknown>) => {
|
|
125
|
-
if (!params.confirm_human_reviewed) {
|
|
126
|
-
throw new Error("confirm_human_reviewed must be true.");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
116
|
const extendInfo: Record<string, unknown> = { pois: [] };
|
|
130
117
|
if (params.expected_price) extendInfo.expectedPrice = params.expected_price;
|
|
131
118
|
if (params.service_method) extendInfo.serviceMethod = params.service_method;
|