nervepay 1.3.5 → 1.3.7
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/nervepay-cli.js +45 -13
- package/package.json +1 -1
package/bin/nervepay-cli.js
CHANGED
|
@@ -380,7 +380,7 @@ program
|
|
|
380
380
|
.option('--code <code>', 'Pairing code from dashboard (legacy flow)')
|
|
381
381
|
.option('--api-url <url>', 'NervePay API URL', DEFAULT_API_URL)
|
|
382
382
|
.option('--gateway-url <url>', 'Gateway URL')
|
|
383
|
-
.option('--gateway-token <token>', 'Gateway token
|
|
383
|
+
.option('--gateway-token <token>', 'Gateway auth token')
|
|
384
384
|
.option('--name <name>', 'Gateway display name', 'OpenClaw Gateway')
|
|
385
385
|
.option('--timeout <seconds>', 'Approval timeout in seconds', '300')
|
|
386
386
|
.action(async (options) => {
|
|
@@ -412,13 +412,17 @@ async function deviceNodePairing(options) {
|
|
|
412
412
|
logOk('Agent loaded');
|
|
413
413
|
field(' DID', agentDid);
|
|
414
414
|
|
|
415
|
-
// Resolve gateway URL
|
|
415
|
+
// Resolve gateway URL and token
|
|
416
416
|
let gatewayUrl = options.gatewayUrl;
|
|
417
|
-
|
|
417
|
+
let gatewayToken = options.gatewayToken;
|
|
418
|
+
if (!gatewayUrl || !gatewayToken) {
|
|
418
419
|
const oc = await readOpenClawConfig();
|
|
419
|
-
|
|
420
|
+
const gwConf = extractGatewayConfig(oc);
|
|
421
|
+
gatewayUrl = gatewayUrl || gwConf.url;
|
|
422
|
+
gatewayToken = gatewayToken || gwConf.token;
|
|
420
423
|
}
|
|
421
424
|
if (!gatewayUrl) die('No gateway URL found', 'Use: nervepay pair --gateway-url <url>');
|
|
425
|
+
if (!gatewayToken) die('No gateway token found', 'Use: nervepay pair --gateway-token <token>');
|
|
422
426
|
|
|
423
427
|
const wsUrl = httpToWs(gatewayUrl);
|
|
424
428
|
field(' Gateway', wsUrl);
|
|
@@ -437,7 +441,9 @@ async function deviceNodePairing(options) {
|
|
|
437
441
|
|
|
438
442
|
// Pre-compute crypto outside WS handler to avoid async race conditions
|
|
439
443
|
const pubKeyBytes = decodeBase58(publicKeyBase58);
|
|
440
|
-
|
|
444
|
+
// OpenClaw expects base64url encoding (no padding) for public key
|
|
445
|
+
const pubKeyB64Url = Buffer.from(pubKeyBytes).toString('base64')
|
|
446
|
+
.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
|
441
447
|
|
|
442
448
|
const result = await new Promise((resolve, reject) => {
|
|
443
449
|
let settled = false;
|
|
@@ -472,13 +478,36 @@ async function deviceNodePairing(options) {
|
|
|
472
478
|
const frame = JSON.parse(data.toString());
|
|
473
479
|
logDebug('<<', JSON.stringify(frame).slice(0, 300));
|
|
474
480
|
|
|
475
|
-
// Challenge → sign and connect
|
|
481
|
+
// Challenge → build auth payload, sign, and connect
|
|
476
482
|
if (frame.event === 'connect.challenge') {
|
|
477
483
|
const nonce = frame.payload?.nonce || '';
|
|
478
484
|
logOk('Challenge received');
|
|
479
485
|
|
|
480
|
-
const
|
|
481
|
-
const
|
|
486
|
+
const signedAtMs = Date.now();
|
|
487
|
+
const role = 'operator';
|
|
488
|
+
const scopes = ['operator.read', 'operator.write'];
|
|
489
|
+
|
|
490
|
+
// Build structured payload per OpenClaw device-auth protocol (v2)
|
|
491
|
+
// Format: v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce
|
|
492
|
+
const payload = [
|
|
493
|
+
'v2',
|
|
494
|
+
deviceId,
|
|
495
|
+
'node-host',
|
|
496
|
+
'node',
|
|
497
|
+
role,
|
|
498
|
+
scopes.join(','),
|
|
499
|
+
String(signedAtMs),
|
|
500
|
+
gatewayToken,
|
|
501
|
+
nonce,
|
|
502
|
+
].join('|');
|
|
503
|
+
|
|
504
|
+
logDebug('Auth payload:', payload);
|
|
505
|
+
|
|
506
|
+
const payloadBytes = new TextEncoder().encode(payload);
|
|
507
|
+
const signatureB64 = await signRawBytes(privateKey, payloadBytes);
|
|
508
|
+
// Convert to base64url (no padding)
|
|
509
|
+
const signatureB64Url = signatureB64
|
|
510
|
+
.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
|
482
511
|
|
|
483
512
|
connectId = crypto.randomUUID();
|
|
484
513
|
ws.send(JSON.stringify({
|
|
@@ -486,10 +515,10 @@ async function deviceNodePairing(options) {
|
|
|
486
515
|
id: connectId,
|
|
487
516
|
method: 'connect',
|
|
488
517
|
params: {
|
|
489
|
-
role
|
|
518
|
+
role,
|
|
490
519
|
minProtocol: 3,
|
|
491
520
|
maxProtocol: 3,
|
|
492
|
-
scopes
|
|
521
|
+
scopes,
|
|
493
522
|
client: {
|
|
494
523
|
id: 'node-host',
|
|
495
524
|
version: VERSION,
|
|
@@ -498,11 +527,14 @@ async function deviceNodePairing(options) {
|
|
|
498
527
|
},
|
|
499
528
|
device: {
|
|
500
529
|
id: deviceId,
|
|
501
|
-
publicKey:
|
|
502
|
-
signature:
|
|
503
|
-
signedAt:
|
|
530
|
+
publicKey: pubKeyB64Url,
|
|
531
|
+
signature: signatureB64Url,
|
|
532
|
+
signedAt: signedAtMs,
|
|
504
533
|
nonce,
|
|
505
534
|
},
|
|
535
|
+
auth: {
|
|
536
|
+
token: gatewayToken,
|
|
537
|
+
},
|
|
506
538
|
},
|
|
507
539
|
}));
|
|
508
540
|
return;
|