@clanker-chain/clanker-cli 2026.9.8-2 → 2026.9.10
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/bin/clanker.mjs +104 -4
- package/lib/identity-query.mjs +1 -1
- package/lib/openclaw-wire.mjs +2 -2
- package/lib/pair.mjs +252 -0
- package/package.json +1 -1
package/bin/clanker.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync, mkdirSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import os from "node:os";
|
|
7
8
|
import process from "node:process";
|
|
8
9
|
import { getAddress } from "viem";
|
|
@@ -97,8 +98,15 @@ function runScript(scriptPath, args = []) {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
function findRepoRoot() {
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
102
|
+
for (let i = 0; i < 8; i++) {
|
|
103
|
+
if (existsSync(join(dir, "chain", "foundry.toml"))) return dir;
|
|
104
|
+
const parent = dirname(dir);
|
|
105
|
+
if (parent === dir) break;
|
|
106
|
+
dir = parent;
|
|
107
|
+
}
|
|
108
|
+
// npm install: package root (no monorepo checkout)
|
|
109
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
/** chain up/deploy and check * need the monorepo; npm installs only ship bin/ + lib/. */
|
|
@@ -169,6 +177,10 @@ Usage:
|
|
|
169
177
|
clanker bot status <label> [--json]
|
|
170
178
|
clanker bot revoke <label> [--json] [--yes]
|
|
171
179
|
clanker bot rotate <label> <newKeyAddress> [--json] [--yes]
|
|
180
|
+
clanker pair add <operator-label> [--yes] [--auth-url URL] [--openclaw-home DIR]
|
|
181
|
+
clanker pair remove <operator-label> [--auth-url URL] [--openclaw-home DIR]
|
|
182
|
+
clanker pair list [--auth-url URL]
|
|
183
|
+
clanker pair status <operator-label> [--auth-url URL]
|
|
172
184
|
clanker init-openclaw
|
|
173
185
|
clanker chain up|deploy|mint-operator|mint-bot|rotate-bot-key|revoke-bot ...
|
|
174
186
|
clanker check mqtt <bot_id> <operator_id>
|
|
@@ -180,10 +192,11 @@ Profile:
|
|
|
180
192
|
~/.clanker/keys/ bot keys (also written to ~/.openclaw/keys/)
|
|
181
193
|
|
|
182
194
|
Humans: \`clanker setup\` then \`clanker doctor\` / \`whoami\`.
|
|
195
|
+
Pairing (Policy): both operators run \`clanker pair add\` before DMs deliver on the hub.
|
|
183
196
|
Mutates print a plan and confirm unless --yes or --json.
|
|
184
197
|
whoami is fast by default; pass --with-bots to enrich child bots (or use \`clanker bots\`).
|
|
185
198
|
|
|
186
|
-
See docs/operator-cli.md.
|
|
199
|
+
See docs/operator-cli.md and docs/trust-model.md.
|
|
187
200
|
`);
|
|
188
201
|
}
|
|
189
202
|
|
|
@@ -441,6 +454,93 @@ async function main() {
|
|
|
441
454
|
return;
|
|
442
455
|
}
|
|
443
456
|
|
|
457
|
+
if (cmd === "pair") {
|
|
458
|
+
const [sub, ...pairRest] = rest;
|
|
459
|
+
if (!sub || !["add", "remove", "list", "status"].includes(sub)) {
|
|
460
|
+
console.error("Usage: clanker pair add|remove|list|status [operator-label]");
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let peerLabel = null;
|
|
465
|
+
for (let i = 0; i < pairRest.length; i += 1) {
|
|
466
|
+
const a = pairRest[i];
|
|
467
|
+
if (a.startsWith("--")) {
|
|
468
|
+
if (a !== "--json" && a !== "--yes" && pairRest[i + 1] && !pairRest[i + 1].startsWith("--")) {
|
|
469
|
+
i += 1; // skip flag value
|
|
470
|
+
}
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
peerLabel = a;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const { runPairAction } = await import("../lib/pair.mjs");
|
|
478
|
+
try {
|
|
479
|
+
if (sub === "add") {
|
|
480
|
+
const planRows = [
|
|
481
|
+
["action", "pair add (Policy)"],
|
|
482
|
+
["peer", peerLabel ?? "(missing)"],
|
|
483
|
+
["note", "One-way allow; peer must pair you for bidirectional DMs"],
|
|
484
|
+
];
|
|
485
|
+
const ok = await confirmPlan(pairRest, planRows, `Allow operator ${peerLabel}?`);
|
|
486
|
+
if (!ok) process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
const out = await runPairAction(pairRest, sub, peerLabel);
|
|
489
|
+
if (hasFlag(pairRest, "--json")) {
|
|
490
|
+
console.log(JSON.stringify(out, null, 2));
|
|
491
|
+
} else if (sub === "list") {
|
|
492
|
+
console.log(`Operator: ${out.operatorId}`);
|
|
493
|
+
console.log(`Auth: ${out.authUrl}`);
|
|
494
|
+
const allows = out.listed?.allows ?? [];
|
|
495
|
+
if (!allows.length) {
|
|
496
|
+
console.log("(no allows)");
|
|
497
|
+
} else {
|
|
498
|
+
for (const a of allows) {
|
|
499
|
+
console.log(
|
|
500
|
+
` ${a.peer_operator_id} ${a.mutual ? "mutual" : "pending (one-way)"}`,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} else if (sub === "status") {
|
|
505
|
+
console.log(`peer: ${out.peerLabel}`);
|
|
506
|
+
console.log(`allowed: ${out.allowed}`);
|
|
507
|
+
console.log(`mutual: ${out.mutual}`);
|
|
508
|
+
if (out.allowed && !out.mutual) {
|
|
509
|
+
console.log("Waiting for peer to run: clanker pair add <your-operator>");
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
console.log(
|
|
513
|
+
`${sub} ${out.peerLabel}: ${out.result?.mutual ? "mutual" : "one-way / pending"}`,
|
|
514
|
+
);
|
|
515
|
+
if (out.openclawSync?.synced) {
|
|
516
|
+
console.log(`Synced allowOperators → ${out.openclawSync.path}`);
|
|
517
|
+
console.log(
|
|
518
|
+
"Restart (or reload) the OpenClaw gateway so channels.mqtt.allowOperators is picked up.",
|
|
519
|
+
);
|
|
520
|
+
} else if (out.openclawSync?.reason === "missing_openclaw_json") {
|
|
521
|
+
console.log(
|
|
522
|
+
`Hub Policy updated, but no openclaw.json at ${out.openclawSync.path} — ` +
|
|
523
|
+
"client allowOperators was not synced. Create OpenClaw config or set allowOperators manually.",
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
if (sub === "add" && !out.result?.mutual) {
|
|
527
|
+
nextHint(`Ask ${out.peerLabel} to run: clanker pair add ${out.operatorId}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} catch (err) {
|
|
531
|
+
exitCliError({
|
|
532
|
+
error: err.message,
|
|
533
|
+
because: "pairing needs operator key + mqtt-auth /pair endpoints",
|
|
534
|
+
try: [
|
|
535
|
+
"clanker doctor",
|
|
536
|
+
"clanker pair add org.peer --auth-url http://127.0.0.1:9090 --yes",
|
|
537
|
+
"See docs/trust-model.md",
|
|
538
|
+
],
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
444
544
|
if (cmd === "operator") {
|
|
445
545
|
const [sub, ...opArgv] = rest;
|
|
446
546
|
const {
|
|
@@ -935,7 +1035,7 @@ async function main() {
|
|
|
935
1035
|
nextHint([
|
|
936
1036
|
"clanker bot mint <label>",
|
|
937
1037
|
"Then channels.mqtt.botId / operatorId / privateKeyFile update automatically",
|
|
938
|
-
"See docs/operator-cli.md and docs/
|
|
1038
|
+
"See docs/operator-cli.md and docs/public-testnet-hub.md",
|
|
939
1039
|
]);
|
|
940
1040
|
process.exit(0);
|
|
941
1041
|
}
|
package/lib/identity-query.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "viem";
|
|
14
14
|
import { clankerIdentityAbi } from "./clanker-identity-abi.mjs";
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/** Public RPC eth_getLogs chunk size — wide ranges are often rejected. */
|
|
17
17
|
export const LOG_CHUNK_BLOCKS = 2000n;
|
|
18
18
|
|
|
19
19
|
export function labelToId(label) {
|
package/lib/openclaw-wire.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { join } from "node:path";
|
|
|
8
8
|
import { harnessSnippet } from "./profile.mjs";
|
|
9
9
|
|
|
10
10
|
/** Current published OpenClaw plugin pins for closed-beta invite. */
|
|
11
|
-
export const OPENCLAW_PLUGIN_PIN = "2026.
|
|
11
|
+
export const OPENCLAW_PLUGIN_PIN = "2026.9.10";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @param {string} [openclawHome]
|
|
@@ -99,7 +99,7 @@ export function hubConnectChecklist(opts) {
|
|
|
99
99
|
`openclaw plugins install @clanker-chain/mqtt-tools@${pin}`,
|
|
100
100
|
"Enable plugin ids mqtt + mqtt-tools (already set in openclaw.json if we wired it)",
|
|
101
101
|
`CONNECT broker: ${opts.channelsMqtt?.brokerUrl ?? "mqtts://mqtt.clanker-chain.com:8883"}`,
|
|
102
|
-
|
|
102
|
+
"Both operators: clanker pair add <peer-operator> (Policy — required before DMs deliver)",
|
|
103
103
|
"DM openclaw.france.prod-1 to smoke the mesh",
|
|
104
104
|
];
|
|
105
105
|
}
|
package/lib/pair.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator pairing client (Policy layer — docs/trust-model.md).
|
|
3
|
+
* Signs with the operator owner key; never the bot key.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
10
|
+
import { loadOperator } from "./profile.mjs";
|
|
11
|
+
import { openclawConfigPath } from "./openclaw-wire.mjs";
|
|
12
|
+
import { resolveOperatorKey } from "./resolve.mjs";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} baseUrl
|
|
16
|
+
* @param {{ operatorId: string, action: 'add'|'remove'|'list', peerLabel?: string }} opts
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchPairNonce(baseUrl, opts) {
|
|
19
|
+
const url = new URL("/pair-nonce", baseUrl.replace(/\/$/, ""));
|
|
20
|
+
url.searchParams.set("operator_id", opts.operatorId);
|
|
21
|
+
url.searchParams.set("action", opts.action);
|
|
22
|
+
if (opts.peerLabel) url.searchParams.set("peer_label", opts.peerLabel);
|
|
23
|
+
const res = await fetch(url.toString());
|
|
24
|
+
const text = await res.text();
|
|
25
|
+
let body;
|
|
26
|
+
try {
|
|
27
|
+
body = JSON.parse(text);
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`pair-nonce: unexpected response: ${text}`);
|
|
30
|
+
}
|
|
31
|
+
if (!res.ok || !body.nonce || !body.message) {
|
|
32
|
+
throw new Error(body.error || `pair-nonce failed: HTTP ${res.status}`);
|
|
33
|
+
}
|
|
34
|
+
return /** @type {{ nonce: string, message: string, expires_at?: string }} */ (body);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} baseUrl
|
|
39
|
+
* @param {{
|
|
40
|
+
* operatorId: string,
|
|
41
|
+
* peerLabel: string,
|
|
42
|
+
* action: 'add'|'remove',
|
|
43
|
+
* nonce: string,
|
|
44
|
+
* signature: `0x${string}`,
|
|
45
|
+
* }} opts
|
|
46
|
+
*/
|
|
47
|
+
export async function postPair(baseUrl, opts) {
|
|
48
|
+
const res = await fetch(new URL("/pair", baseUrl.replace(/\/$/, "")).toString(), {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "content-type": "application/json" },
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
operator_id: opts.operatorId,
|
|
53
|
+
peer_label: opts.peerLabel,
|
|
54
|
+
action: opts.action,
|
|
55
|
+
nonce: opts.nonce,
|
|
56
|
+
signature: opts.signature,
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
const text = await res.text();
|
|
60
|
+
let body;
|
|
61
|
+
try {
|
|
62
|
+
body = JSON.parse(text);
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error(`pair: unexpected response: ${text}`);
|
|
65
|
+
}
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
throw new Error(body.error || `pair failed: HTTP ${res.status}`);
|
|
68
|
+
}
|
|
69
|
+
return body;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} baseUrl
|
|
74
|
+
* @param {{ operatorId: string, nonce: string, signature: `0x${string}` }} opts
|
|
75
|
+
*/
|
|
76
|
+
export async function getPairList(baseUrl, opts) {
|
|
77
|
+
const url = new URL("/pair", baseUrl.replace(/\/$/, ""));
|
|
78
|
+
url.searchParams.set("operator_id", opts.operatorId);
|
|
79
|
+
url.searchParams.set("nonce", opts.nonce);
|
|
80
|
+
url.searchParams.set("signature", opts.signature);
|
|
81
|
+
const res = await fetch(url.toString());
|
|
82
|
+
const text = await res.text();
|
|
83
|
+
let body;
|
|
84
|
+
try {
|
|
85
|
+
body = JSON.parse(text);
|
|
86
|
+
} catch {
|
|
87
|
+
throw new Error(`pair list: unexpected response: ${text}`);
|
|
88
|
+
}
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
throw new Error(body.error || `pair list failed: HTTP ${res.status}`);
|
|
91
|
+
}
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Merge allowOperators + dmPolicy into ~/.openclaw/openclaw.json channels.mqtt.
|
|
97
|
+
* @param {{ peerLabel: string, action: 'add'|'remove', openclawHome?: string }} opts
|
|
98
|
+
*/
|
|
99
|
+
export function syncAllowOperators(opts) {
|
|
100
|
+
const openclawHome = opts.openclawHome ?? join(homedir(), ".openclaw");
|
|
101
|
+
const cfgPath = openclawConfigPath(openclawHome);
|
|
102
|
+
if (!existsSync(cfgPath)) {
|
|
103
|
+
return { synced: false, path: cfgPath, reason: "missing_openclaw_json" };
|
|
104
|
+
}
|
|
105
|
+
const cfg = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
106
|
+
const channels = { ...(cfg.channels ?? {}) };
|
|
107
|
+
const mqtt = { ...(channels.mqtt && typeof channels.mqtt === "object" ? channels.mqtt : {}) };
|
|
108
|
+
const prev = Array.isArray(mqtt.allowOperators)
|
|
109
|
+
? mqtt.allowOperators.filter((x) => typeof x === "string")
|
|
110
|
+
: [];
|
|
111
|
+
let next;
|
|
112
|
+
if (opts.action === "add") {
|
|
113
|
+
next = prev.includes(opts.peerLabel) ? prev : [...prev, opts.peerLabel];
|
|
114
|
+
} else {
|
|
115
|
+
next = prev.filter((x) => x !== opts.peerLabel);
|
|
116
|
+
}
|
|
117
|
+
mqtt.allowOperators = next;
|
|
118
|
+
if (!mqtt.dmPolicy) mqtt.dmPolicy = "pairing";
|
|
119
|
+
channels.mqtt = mqtt;
|
|
120
|
+
cfg.channels = channels;
|
|
121
|
+
mkdirSync(openclawHome, { recursive: true });
|
|
122
|
+
writeFileSync(cfgPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
|
|
123
|
+
return { synced: true, path: cfgPath, allowOperators: next };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolve mqtt-auth base URL from network / env / flag.
|
|
128
|
+
* @param {string[]} argv
|
|
129
|
+
* @param {{ mqttAuthServiceUrl?: string|null }} network
|
|
130
|
+
*/
|
|
131
|
+
export function resolvePairAuthUrl(argv, network) {
|
|
132
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
133
|
+
if (argv[i] === "--auth-url" && argv[i + 1]) return argv[i + 1];
|
|
134
|
+
}
|
|
135
|
+
if (process.env.MQTT_AUTH_SERVICE_URL) return process.env.MQTT_AUTH_SERVICE_URL;
|
|
136
|
+
if (network.mqttAuthServiceUrl) return network.mqttAuthServiceUrl;
|
|
137
|
+
throw new Error(
|
|
138
|
+
"mqttAuthServiceUrl required for pairing. Set profile via clanker setup, " +
|
|
139
|
+
"pass --auth-url, or set MQTT_AUTH_SERVICE_URL.",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Optional OpenClaw home for allowOperators sync (directory containing openclaw.json).
|
|
145
|
+
* @param {string[]} argv
|
|
146
|
+
* @returns {string|undefined}
|
|
147
|
+
*/
|
|
148
|
+
export function resolveOpenclawHome(argv) {
|
|
149
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
150
|
+
if (argv[i] === "--openclaw-home" && argv[i + 1]) return argv[i + 1];
|
|
151
|
+
}
|
|
152
|
+
if (process.env.OPENCLAW_HOME) return process.env.OPENCLAW_HOME;
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {string[]} argv
|
|
158
|
+
* @param {'add'|'remove'|'list'|'status'} action
|
|
159
|
+
* @param {string|null} peerLabel
|
|
160
|
+
*/
|
|
161
|
+
export async function runPairAction(argv, action, peerLabel) {
|
|
162
|
+
const resolved = resolveOperatorKey(argv, { requireRegistry: true });
|
|
163
|
+
const operator =
|
|
164
|
+
loadOperator(resolved.network.home) ??
|
|
165
|
+
(resolved.network.operatorLabel
|
|
166
|
+
? { label: resolved.network.operatorLabel }
|
|
167
|
+
: null);
|
|
168
|
+
const operatorId =
|
|
169
|
+
argv.includes("--operator") && argv[argv.indexOf("--operator") + 1]
|
|
170
|
+
? argv[argv.indexOf("--operator") + 1]
|
|
171
|
+
: operator?.label;
|
|
172
|
+
if (!operatorId) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"Operator label required. Run clanker setup / write operator.json, or pass --operator org.you.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const authUrl = resolvePairAuthUrl(argv, resolved.network);
|
|
179
|
+
const account = privateKeyToAccount(resolved.key);
|
|
180
|
+
|
|
181
|
+
if (action === "list" || action === "status") {
|
|
182
|
+
const { nonce, message } = await fetchPairNonce(authUrl, {
|
|
183
|
+
operatorId,
|
|
184
|
+
action: "list",
|
|
185
|
+
});
|
|
186
|
+
const signature = await account.signMessage({ message });
|
|
187
|
+
const listed = await getPairList(authUrl, {
|
|
188
|
+
operatorId,
|
|
189
|
+
nonce,
|
|
190
|
+
signature,
|
|
191
|
+
});
|
|
192
|
+
if (action === "status") {
|
|
193
|
+
if (!peerLabel) throw new Error("Usage: clanker pair status <operator-label>");
|
|
194
|
+
// Resolve peer by scanning allows — mutual flag is per peer_operator_id;
|
|
195
|
+
// we match by requesting add-style peer label via a second pair-nonce is heavy.
|
|
196
|
+
// Instead: check if any allow entry is mutual when peer label hashes match… we
|
|
197
|
+
// only have peer_operator_id hex. Re-fetch is unnecessary: call add-path status
|
|
198
|
+
// by computing whether peer is in allow list via a dedicated pair status using
|
|
199
|
+
// the peer label through POST-less approach — GET list doesn't include labels.
|
|
200
|
+
// Best effort: show mutual if we can add then remove? No.
|
|
201
|
+
// Call /pair-nonce + post with action that doesn't mutate? Use list + ask user.
|
|
202
|
+
// Simpler: after list, for status we POST nothing — check if peer's keccak is in list.
|
|
203
|
+
// We need peer id: keccak of peerLabel.
|
|
204
|
+
const { keccak256, toBytes } = await import("viem");
|
|
205
|
+
const peerId = keccak256(toBytes(peerLabel)).toLowerCase();
|
|
206
|
+
const hit = (listed.allows ?? []).find(
|
|
207
|
+
(a) => String(a.peer_operator_id).toLowerCase() === peerId,
|
|
208
|
+
);
|
|
209
|
+
return {
|
|
210
|
+
action: "status",
|
|
211
|
+
operatorId,
|
|
212
|
+
peerLabel,
|
|
213
|
+
allowed: Boolean(hit),
|
|
214
|
+
mutual: hit?.mutual === true,
|
|
215
|
+
authUrl,
|
|
216
|
+
listed,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return { action: "list", operatorId, authUrl, listed };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!peerLabel) {
|
|
223
|
+
throw new Error(`Usage: clanker pair ${action} <operator-label>`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { nonce, message } = await fetchPairNonce(authUrl, {
|
|
227
|
+
operatorId,
|
|
228
|
+
action,
|
|
229
|
+
peerLabel,
|
|
230
|
+
});
|
|
231
|
+
const signature = await account.signMessage({ message });
|
|
232
|
+
const result = await postPair(authUrl, {
|
|
233
|
+
operatorId,
|
|
234
|
+
peerLabel,
|
|
235
|
+
action,
|
|
236
|
+
nonce,
|
|
237
|
+
signature,
|
|
238
|
+
});
|
|
239
|
+
const sync = syncAllowOperators({
|
|
240
|
+
peerLabel,
|
|
241
|
+
action,
|
|
242
|
+
openclawHome: resolveOpenclawHome(argv),
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
action,
|
|
246
|
+
operatorId,
|
|
247
|
+
peerLabel,
|
|
248
|
+
authUrl,
|
|
249
|
+
result,
|
|
250
|
+
openclawSync: sync,
|
|
251
|
+
};
|
|
252
|
+
}
|