@gholl-studio/pier-connector 0.0.3 → 0.0.5

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.
@@ -25,6 +25,11 @@
25
25
  "default": "",
26
26
  "description": "Bot Secret Key (obtain from pier-connector.gholl.com)"
27
27
  },
28
+ "privateKey": {
29
+ "type": "string",
30
+ "default": "",
31
+ "description": "(Optional/Advanced) Wallet Private Key for AI Auto-Hosting"
32
+ },
28
33
  "natsUrl": {
29
34
  "type": "string",
30
35
  "default": "wss://pier.gholl.com/nexus",
@@ -45,6 +50,11 @@
45
50
  "default": "openclaw-workers",
46
51
  "description": "NATS Queue Group to join for load balancing tasks"
47
52
  },
53
+ "agentId": {
54
+ "type": "string",
55
+ "default": "",
56
+ "description": "(Optional) The Agent ID to bind this connector to for context handling"
57
+ },
48
58
  "walletAddress": {
49
59
  "type": "string",
50
60
  "default": "",
@@ -65,6 +75,10 @@
65
75
  "label": "Bot Secret Key (Secret)",
66
76
  "placeholder": "sk_..."
67
77
  },
78
+ "privateKey": {
79
+ "label": "Private Key (Optional Auto-Host)",
80
+ "placeholder": "0x..."
81
+ },
68
82
  "natsUrl": {
69
83
  "label": "NATS WebSocket URL (Override)",
70
84
  "placeholder": "wss://pier.gholl.com/nexus"
@@ -81,6 +95,10 @@
81
95
  "label": "Queue Group",
82
96
  "placeholder": "openclaw-workers"
83
97
  },
98
+ "agentId": {
99
+ "label": "Bound Agent ID",
100
+ "placeholder": "uuid-..."
101
+ },
84
102
  "walletAddress": {
85
103
  "label": "Wallet Address (Rewards)",
86
104
  "placeholder": "0x..."
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gholl-studio/pier-connector",
3
3
  "author": "gholl",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "description": "OpenClaw plugin that connects to the Pier job marketplace. Automatically fetches, executes, and reports distributed tasks for rewards.",
6
6
  "type": "module",
7
7
  "main": "src/index.js",
@@ -32,6 +32,7 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "@nats-io/transport-node": "^3.0.0",
35
+ "ethers": "^6.16.0",
35
36
  "inquirer": "^13.3.0"
36
37
  },
37
38
  "peerDependencies": {
package/src/config.js CHANGED
@@ -26,6 +26,12 @@ export const DEFAULTS = Object.freeze({
26
26
  /** Secret key for node authentication and heartbeats */
27
27
  SECRET_KEY: '',
28
28
 
29
+ /** Optional Private Key for auto-registration via Wallet Signature (Advanced) */
30
+ PRIVATE_KEY: '',
31
+
29
32
  /** Optional Wallet address to receive points/payments for completed tasks */
30
33
  WALLET_ADDRESS: '',
34
+
35
+ /** Optional Agent ID to bind tasks to, instead of creating new sessions */
36
+ AGENT_ID: '',
31
37
  });
package/src/index.js CHANGED
@@ -13,6 +13,9 @@ import { parseJob, safeRespond, truncate } from './job-handler.js';
13
13
  import { createRequestPayload, createResultPayload, createErrorPayload } from './protocol.js';
14
14
  import { DEFAULTS } from './config.js';
15
15
  import inquirer from 'inquirer';
16
+ import { ethers } from 'ethers';
17
+ import fs from 'fs';
18
+ import path from 'path';
16
19
 
17
20
  /**
18
21
  * OpenClaw plugin register function.
@@ -45,14 +48,24 @@ export default function register(api) {
45
48
  pierApiUrl: cfg.pierApiUrl || DEFAULTS.PIER_API_URL,
46
49
  nodeId: cfg.nodeId || DEFAULTS.NODE_ID,
47
50
  secretKey: cfg.secretKey || DEFAULTS.SECRET_KEY,
51
+ privateKey: cfg.privateKey || process.env.PIER_PRIVATE_KEY || DEFAULTS.PRIVATE_KEY,
48
52
  natsUrl: cfg.natsUrl || DEFAULTS.NATS_URL,
49
53
  subject: cfg.subject || DEFAULTS.PIER_SUBJECT,
50
54
  publishSubject: cfg.publishSubject || DEFAULTS.PUBLISH_SUBJECT,
51
55
  queueGroup: cfg.queueGroup || DEFAULTS.QUEUE_GROUP,
56
+ agentId: cfg.agentId || DEFAULTS.AGENT_ID,
52
57
  walletAddress: cfg.walletAddress || DEFAULTS.WALLET_ADDRESS,
53
58
  };
54
59
  }
55
60
 
61
+ /** Runtime config caching to remember newly generated auto-credentials */
62
+ let runtimeConfigCache = null;
63
+
64
+ function getActiveConfig() {
65
+ if (runtimeConfigCache) return runtimeConfigCache;
66
+ return resolveConfig();
67
+ }
68
+
56
69
  /** Heartbeat timer reference */
57
70
  let heartbeatTimer = null;
58
71
 
@@ -135,8 +148,8 @@ export default function register(api) {
135
148
  id: jobId,
136
149
  reply: text,
137
150
  latencyMs: elapsed ? Number(elapsed) : undefined,
138
- workerId: resolveConfig().nodeId,
139
- walletAddress: resolveConfig().walletAddress,
151
+ workerId: getActiveConfig().nodeId,
152
+ walletAddress: getActiveConfig().walletAddress,
140
153
  });
141
154
 
142
155
  safeRespond(msg, responsePayload);
@@ -162,15 +175,65 @@ export default function register(api) {
162
175
 
163
176
  start: async () => {
164
177
  const config = resolveConfig();
178
+ runtimeConfigCache = config;
165
179
  logger.info('[pier-connector] 🚀 Starting background service …');
166
180
 
167
181
  try {
168
- // 1. Mandatory credentials check
182
+ // 1. Mandatory credentials check or Auto Register
169
183
  if (!config.nodeId || !config.secretKey) {
170
- logger.warn('[pier-connector] ⚠️ Bot Node ID or Secret is missing.');
171
- logger.warn('[pier-connector] Please run "openclaw pier setup" or go to pier-connector.gholl.com to create a bot.');
172
- connectionStatus = 'error';
173
- return;
184
+ if (config.privateKey) {
185
+ logger.info('[pier-connector] 🛠 No Node ID found. Executing automatic registration via Private Key...');
186
+ try {
187
+ const wallet = new ethers.Wallet(config.privateKey);
188
+ const address = wallet.address;
189
+ logger.info(`[pier-connector] 🛠 Wallet Address: ${address}`);
190
+
191
+ const challengeRes = await fetch(`${config.pierApiUrl}/auth/challenge?wallet_address=${address}`);
192
+ if (!challengeRes.ok) throw new Error("Failed to get challenge");
193
+ const { challenge } = await challengeRes.json();
194
+
195
+ const signature = await wallet.signMessage(challenge);
196
+
197
+ const loginRes = await fetch(`${config.pierApiUrl}/auth/login`, {
198
+ method: 'POST',
199
+ headers: { 'Content-Type': 'application/json' },
200
+ body: JSON.stringify({ wallet_address: address, challenge, signature })
201
+ });
202
+ if (!loginRes.ok) throw new Error("Failed to login");
203
+ const { api_key } = await loginRes.json();
204
+
205
+ logger.info(`[pier-connector] 🛠 Authenticated. Registering node...`);
206
+
207
+ const hostName = api?.getRuntimeInfo?.()?.hostname ?? 'Auto-Node';
208
+ const regRes = await fetch(`${config.pierApiUrl}/nodes/register`, {
209
+ method: 'POST',
210
+ headers: {
211
+ 'Content-Type': 'application/json',
212
+ 'Authorization': `Bearer ${api_key}`
213
+ },
214
+ body: JSON.stringify({ wallet_address: address, name: hostName })
215
+ });
216
+
217
+ if (!regRes.ok) throw new Error("Failed to register node");
218
+ const { id, secret_key } = await regRes.json();
219
+
220
+ logger.info(`[pier-connector] ✔ Node registered automatically: ${id}`);
221
+ config.nodeId = id;
222
+ config.secretKey = secret_key;
223
+ if (!config.walletAddress) {
224
+ config.walletAddress = address;
225
+ }
226
+ } catch (err) {
227
+ logger.error(`[pier-connector] ✖ Auto-registration failed: ${err.message}`);
228
+ connectionStatus = 'error';
229
+ return;
230
+ }
231
+ } else {
232
+ logger.warn('[pier-connector] ⚠️ Bot Node ID or Secret is missing.');
233
+ logger.warn('[pier-connector] Please run "openclaw pier setup" or provide a privateKey for auto-hosting.');
234
+ connectionStatus = 'error';
235
+ return;
236
+ }
174
237
  }
175
238
 
176
239
  connectionStatus = 'connecting';
@@ -185,7 +248,7 @@ export default function register(api) {
185
248
 
186
249
  // 3. Start Heartbeat Timer
187
250
  const runHeartbeat = async () => {
188
- const currentConfig = resolveConfig();
251
+ const currentConfig = getActiveConfig();
189
252
  if (!currentConfig.nodeId) return;
190
253
  await heartbeatNode(currentConfig);
191
254
  };
@@ -256,6 +319,7 @@ export default function register(api) {
256
319
  accountId: 'default',
257
320
  senderId: `pier:${job.meta?.sender ?? 'anonymous'}`,
258
321
  text: job.task,
322
+ assignedAgentId: config.agentId || undefined,
259
323
  metadata: {
260
324
  pierJobId: job.id,
261
325
  pierNatsMsg: msg,
@@ -360,7 +424,7 @@ export default function register(api) {
360
424
  };
361
425
  }
362
426
 
363
- const config = resolveConfig();
427
+ const config = getActiveConfig();
364
428
  const timeout = params.timeoutMs || 60000;
365
429
 
366
430
  const taskPayload = createRequestPayload({
@@ -424,7 +488,7 @@ export default function register(api) {
424
488
  name: 'pier',
425
489
  description: 'Show Pier connector status',
426
490
  handler: () => {
427
- const config = resolveConfig();
491
+ const config = getActiveConfig();
428
492
  const uptime = connectedAt
429
493
  ? `${Math.round((Date.now() - connectedAt.getTime()) / 1000)}s`
430
494
  : 'N/A';
@@ -462,58 +526,175 @@ export default function register(api) {
462
526
  const currentConfig = resolveConfig();
463
527
 
464
528
  console.log('\n🚢 \x1b[1m\x1b[36mPier Connector Setup (V1.1)\x1b[0m');
465
- console.log('Please register your bot at \x1b[1m\x1b[33mhttps://pier-connector.gholl.com\x1b[0m first.\n');
466
-
467
- const answers = await inquirer.prompt([
468
- {
469
- type: 'input',
470
- name: 'pierApiUrl',
471
- message: 'Pier API URL:',
472
- default: currentConfig.pierApiUrl,
473
- },
474
- {
475
- type: 'input',
476
- name: 'nodeId',
477
- message: 'Bot Node ID (UUID):',
478
- default: currentConfig.nodeId,
479
- },
480
- {
481
- type: 'password',
482
- name: 'secretKey',
483
- message: 'Bot Secret Key:',
484
- default: currentConfig.secretKey,
485
- },
486
- {
487
- type: 'input',
488
- name: 'walletAddress',
489
- message: 'Your Wallet Address (for payout):',
490
- default: currentConfig.walletAddress,
491
- },
492
- ]);
529
+ console.log('You can register manually or let the Auto-Host (Advanced) feature do it for you using a Wallet Private Key.\n');
530
+
531
+ let setupMethod = 'manual';
532
+ try {
533
+ const answer = await inquirer.prompt([
534
+ {
535
+ type: 'list',
536
+ name: 'method',
537
+ message: 'How would you like to configure your node credentials?',
538
+ choices: [
539
+ { name: 'Manual Entry (Recommended) - I have a Node ID and Secret Key', value: 'manual' },
540
+ { name: 'Auto-Host (Advanced) - Register automatically using my Wallet Private Key', value: 'auto' }
541
+ ],
542
+ default: 'manual'
543
+ }
544
+ ]);
545
+ if (answer.method) {
546
+ setupMethod = answer.method;
547
+ }
548
+ } catch (err) {
549
+ console.log('\n\x1b[33mFalling back to Manual Entry mode due to terminal configuration.\x1b[0m');
550
+ setupMethod = 'manual';
551
+ }
552
+
553
+ let finalNodeId = currentConfig.nodeId;
554
+ let finalSecretKey = currentConfig.secretKey;
555
+ let finalPrivateKey = currentConfig.privateKey;
556
+ if (setupMethod === 'manual') {
557
+ const manualAnswers = await inquirer.prompt([
558
+ { type: 'input', name: 'pierApiUrl', message: 'Pier API URL:', default: currentConfig.pierApiUrl },
559
+ { type: 'input', name: 'nodeId', message: 'Bot Node ID (UUID):', default: finalNodeId },
560
+ { type: 'password', name: 'secretKey', message: 'Bot Secret Key:', default: finalSecretKey },
561
+ { type: 'input', name: 'walletAddress', message: 'Your Wallet Address (for payout):', default: finalWallet }
562
+ ]);
563
+ finalNodeId = manualAnswers.nodeId;
564
+ finalSecretKey = manualAnswers.secretKey;
565
+ finalWallet = manualAnswers.walletAddress;
566
+ currentConfig.pierApiUrl = manualAnswers.pierApiUrl;
567
+ } else if (setupMethod === 'auto') {
568
+ const autoAnswers = await inquirer.prompt([
569
+ { type: 'input', name: 'pierApiUrl', message: 'Pier API URL:', default: currentConfig.pierApiUrl },
570
+ { type: 'password', name: 'privateKey', message: 'Your Wallet Private Key (Hex, 0x...):', default: finalPrivateKey }
571
+ ]);
572
+ console.log('\n\x1b[36mRegistering your node...\x1b[0m');
573
+ try {
574
+ const wallet = new ethers.Wallet(autoAnswers.privateKey);
575
+ console.log(`\x1b[32m✔ Loaded wallet: ${wallet.address}\x1b[0m`);
576
+ finalWallet = finalWallet || wallet.address;
577
+ finalPrivateKey = autoAnswers.privateKey;
578
+ currentConfig.pierApiUrl = autoAnswers.pierApiUrl;
579
+
580
+ const challengeRes = await fetch(`${currentConfig.pierApiUrl}/auth/challenge?wallet_address=${wallet.address}`);
581
+ if (!challengeRes.ok) throw new Error("Failed to get challenge");
582
+ const { challenge } = await challengeRes.json();
583
+
584
+ const signature = await wallet.signMessage(challenge);
585
+
586
+ const loginRes = await fetch(`${currentConfig.pierApiUrl}/auth/login`, {
587
+ method: 'POST',
588
+ headers: { 'Content-Type': 'application/json' },
589
+ body: JSON.stringify({ wallet_address: wallet.address, challenge, signature })
590
+ });
591
+ if (!loginRes.ok) throw new Error("Failed to login");
592
+ const { api_key } = await loginRes.json();
593
+
594
+ const hostName = api?.getRuntimeInfo?.()?.hostname ?? 'Auto-Node';
595
+ const regRes = await fetch(`${currentConfig.pierApiUrl}/nodes/register`, {
596
+ method: 'POST',
597
+ headers: {
598
+ 'Content-Type': 'application/json',
599
+ 'Authorization': `Bearer ${api_key}`
600
+ },
601
+ body: JSON.stringify({ wallet_address: wallet.address, name: hostName })
602
+ });
603
+
604
+ if (!regRes.ok) throw new Error(`Failed to auto-register node (${regRes.status})`);
605
+ const { id, secret_key } = await regRes.json();
606
+
607
+ finalNodeId = id;
608
+ finalSecretKey = secret_key;
609
+ console.log(`\x1b[32m✔ Node registered automatically! Node ID: ${id}\x1b[0m`);
610
+ } catch (err) {
611
+ console.error(`\x1b[31m✖ Failed to auto-register: ${err.message}\x1b[0m`);
612
+ return; // Stop setup
613
+ }
614
+ }
615
+
616
+ // ── Select Agent Binding ──
617
+ let finalAgentId = currentConfig.agentId;
618
+ try {
619
+ let agentsInfo = [];
620
+ if (api.runtime && typeof api.runtime.getAgents === 'function') {
621
+ const agentsMap = await api.runtime.getAgents();
622
+ if (agentsMap) {
623
+ agentsInfo = Object.entries(agentsMap).map(([id, info]) => ({
624
+ name: `${info.name || 'Unnamed Agent'} (${id})`,
625
+ value: id
626
+ }));
627
+ }
628
+ }
629
+
630
+ if (agentsInfo.length > 0) {
631
+ agentsInfo.unshift({ name: 'None (Default Routing)', value: '' });
632
+ const agentAnswer = await inquirer.prompt([
633
+ {
634
+ type: 'list',
635
+ name: 'agentId',
636
+ message: 'Select an Agent to bind for processing these tasks:',
637
+ choices: agentsInfo,
638
+ default: finalAgentId || ''
639
+ }
640
+ ]);
641
+ finalAgentId = agentAnswer.agentId;
642
+ }
643
+ } catch (err) {
644
+ logger.warn(`Could not load agents list: ${err.message}`);
645
+ }
493
646
 
494
647
  // Determine how to save.
495
648
  console.log('\n✅ \x1b[32mConfiguration Captured!\x1b[0m\n');
496
649
 
497
650
  const newConfigBlock = {
498
- plugins: {
499
- entries: {
500
- "pier-connector": {
501
- enabled: true,
502
- config: {
503
- pierApiUrl: answers.pierApiUrl,
504
- nodeId: answers.nodeId,
505
- secretKey: answers.secretKey,
506
- walletAddress: answers.walletAddress
507
- }
508
- }
509
- }
510
- }
651
+ pierApiUrl: currentConfig.pierApiUrl,
652
+ nodeId: finalNodeId,
653
+ secretKey: finalSecretKey,
654
+ walletAddress: finalWallet,
655
+ agentId: finalAgentId
511
656
  };
657
+
658
+ if (setupMethod === 'auto') {
659
+ newConfigBlock.privateKey = finalPrivateKey;
660
+ console.log('\x1b[33mNote: Private Key is included for future automatic reconnects.\x1b[0m');
661
+ }
662
+
663
+ // Write to openclaw.json
664
+ try {
665
+ const cwd = process.cwd();
666
+ const configPath = path.join(cwd, 'openclaw.json');
667
+ let existingConfig = {};
668
+ if (fs.existsSync(configPath)) {
669
+ const raw = fs.readFileSync(configPath, 'utf8');
670
+ existingConfig = JSON.parse(raw);
671
+ }
672
+
673
+ if (!existingConfig.plugins) existingConfig.plugins = {};
674
+ if (!existingConfig.plugins.entries) existingConfig.plugins.entries = {};
675
+ if (!existingConfig.plugins.entries['pier-connector']) {
676
+ existingConfig.plugins.entries['pier-connector'] = { enabled: true };
677
+ }
678
+
679
+ existingConfig.plugins.entries['pier-connector'].config = {
680
+ ...(existingConfig.plugins.entries['pier-connector'].config || {}),
681
+ ...newConfigBlock
682
+ };
683
+
684
+ fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf8');
685
+ console.log(`\x1b[32m✔ Successfully wrote configuration to ${configPath}\x1b[0m`);
686
+ } catch (err) {
687
+ console.error(`\x1b[31m✖ Failed to write to openclaw.json automatically: ${err.message}\x1b[0m`);
688
+ console.log('Please ensure your \x1b[1m\x1b[33mopenclaw.json\x1b[0m includes the following block in plugins.entries:\n');
689
+ console.log(JSON.stringify({
690
+ "pier-connector": {
691
+ enabled: true,
692
+ config: newConfigBlock
693
+ }
694
+ }, null, 2));
695
+ }
512
696
 
513
- console.log('Currently, CLI automatic config writes depend on the framework host.');
514
- console.log('Please ensure your \x1b[1m\x1b[33mopenclaw.config.json\x1b[0m includes the following block:\n');
515
- console.log(JSON.stringify(newConfigBlock, null, 2));
516
- console.log('\nRestart OpenClaw to apply changes.');
697
+ console.log('\nRestart OpenClaw (or reload plugins) to apply changes.');
517
698
  });
518
699
  },
519
700
  { commands: ['pier'] }