naracli 1.0.63 → 1.0.64

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naracli",
3
- "version": "1.0.63",
3
+ "version": "1.0.64",
4
4
  "description": "CLI for the Nara chain (Solana-compatible)",
5
5
  "homepage": "https://nara.build",
6
6
  "repository": {
@@ -63,7 +63,7 @@
63
63
  "bs58": "^6.0.0",
64
64
  "commander": "^12.1.0",
65
65
  "ed25519-hd-key": "^1.3.0",
66
- "nara-sdk": "^1.0.57",
66
+ "nara-sdk": "^1.0.64",
67
67
  "picocolors": "^1.1.1"
68
68
  },
69
69
  "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
@@ -32,34 +32,61 @@ import {
32
32
  submitTweet,
33
33
  unbindTwitter,
34
34
  getTweetVerify,
35
+ getTweetRecord,
35
36
  getAgentRegistryConfig,
36
37
  } from "nara-sdk";
37
38
  import { readFileSync } from "node:fs";
38
39
  import { loadNetworkConfig, setAgentId, clearAgentId } from "../utils/agent-config";
39
40
  import { validateName } from "../utils/validation";
40
41
 
42
+ // ─── Helpers ─────────────────────────────────────────────────────
43
+
44
+ /** Try to get wallet pubkey without failing. */
45
+ async function tryGetWalletPubkey(walletPath?: string): Promise<string | undefined> {
46
+ try {
47
+ const wallet = await loadWallet(walletPath);
48
+ return wallet.publicKey.toBase58();
49
+ } catch {
50
+ return undefined;
51
+ }
52
+ }
53
+
54
+ /** Resolve agent ID: use explicit --agent-id option, or fall back to saved myid. */
55
+ async function resolveAgentId(options: GlobalOptions & { agentId?: string }): Promise<string> {
56
+ if (options.agentId) return options.agentId;
57
+ const rpcUrl = getRpcUrl(options.rpcUrl);
58
+ const pubkey = await tryGetWalletPubkey(options.wallet);
59
+ const networkConfig = loadNetworkConfig(rpcUrl, pubkey);
60
+ if (!networkConfig.agent_id) {
61
+ printError('No agent ID specified. Use --agent-id or run "agent register <id>" first.');
62
+ process.exit(1);
63
+ }
64
+ return networkConfig.agent_id;
65
+ }
66
+
41
67
  // ─── Command handlers ────────────────────────────────────────────
42
68
 
43
69
  async function handleAgentRegister(agentId: string, options: GlobalOptions & { referral?: string }) {
44
70
  validateName(agentId, "Agent ID");
45
71
  const rpcUrl = getRpcUrl(options.rpcUrl);
72
+ const wallet = await loadWallet(options.wallet);
73
+ const pubkey = wallet.publicKey.toBase58();
46
74
 
47
- // Check if an agent ID is already configured for this network
48
- const networkConfig = loadNetworkConfig(rpcUrl);
75
+ // Check if an agent ID is already configured for this wallet
76
+ const networkConfig = loadNetworkConfig(rpcUrl, pubkey);
49
77
  if (networkConfig.agent_id) {
50
- printError(`Agent ID "${networkConfig.agent_id}" is already configured for this network. Run "agent clear" first to unlink it.`);
78
+ printError(`Agent ID "${networkConfig.agent_id}" is already configured for this wallet. Run "agent clear" first to unlink it.`);
51
79
  process.exit(1);
52
80
  }
53
81
 
54
82
  const connection = new Connection(rpcUrl, "confirmed");
55
- const wallet = await loadWallet(options.wallet);
56
83
 
57
84
  if (!options.json) printInfo(`Registering agent "${agentId}"...`);
58
85
  const result = options.referral
59
86
  ? await registerAgentWithReferral(connection, wallet, agentId, options.referral)
60
87
  : await registerAgent(connection, wallet, agentId);
61
88
  if (!options.json) printSuccess(`Agent "${agentId}" registered!`);
62
- setAgentId(agentId, rpcUrl);
89
+ setAgentId(agentId, rpcUrl, pubkey);
63
90
 
64
91
  if (options.json) {
65
92
  formatOutput({ agentId, referral: options.referral ?? null, signature: result.signature, agentPubkey: result.agentPubkey.toBase58() }, true);
@@ -76,7 +103,39 @@ async function handleAgentGet(agentId: string, options: GlobalOptions) {
76
103
 
77
104
  const info = await getAgentInfo(connection, agentId);
78
105
 
79
- const data = {
106
+ // Fetch twitter binding info
107
+ let twitterData: { username: string; tweetUrl: string; status: string; verifiedAt: string | null } | null = null;
108
+ try {
109
+ const tw = await getAgentTwitter(connection, agentId);
110
+ if (tw) {
111
+ twitterData = {
112
+ username: tw.username,
113
+ tweetUrl: tw.tweetUrl,
114
+ status: TWITTER_STATUS[tw.status] ?? `unknown(${tw.status})`,
115
+ verifiedAt: tw.verifiedAt ? new Date(tw.verifiedAt * 1000).toISOString() : null,
116
+ };
117
+ }
118
+ } catch {
119
+ // Ignore — twitter binding may not exist
120
+ }
121
+
122
+ // Fetch tweet verification info
123
+ let tweetVerifyData: { tweetId: string; status: string; submittedAt: string | null; lastRewardedAt: string | null } | null = null;
124
+ try {
125
+ const tv = await getTweetVerify(connection, agentId);
126
+ if (tv) {
127
+ tweetVerifyData = {
128
+ tweetId: tv.tweetId.toString(),
129
+ status: TWITTER_STATUS[tv.status] ?? `unknown(${tv.status})`,
130
+ submittedAt: tv.submittedAt ? new Date(tv.submittedAt * 1000).toISOString() : null,
131
+ lastRewardedAt: tv.lastRewardedAt ? new Date(tv.lastRewardedAt * 1000).toISOString() : null,
132
+ };
133
+ }
134
+ } catch {
135
+ // Ignore
136
+ }
137
+
138
+ const data: Record<string, any> = {
80
139
  agentId: info.record.agentId,
81
140
  authority: info.record.authority.toBase58(),
82
141
  version: info.record.version,
@@ -84,6 +143,8 @@ async function handleAgentGet(agentId: string, options: GlobalOptions) {
84
143
  metadata: info.metadata,
85
144
  createdAt: new Date(info.record.createdAt * 1000).toISOString(),
86
145
  updatedAt: info.record.updatedAt ? new Date(info.record.updatedAt * 1000).toISOString() : null,
146
+ twitter: twitterData,
147
+ tweetVerify: tweetVerifyData,
87
148
  };
88
149
 
89
150
  if (options.json) {
@@ -97,7 +158,46 @@ async function handleAgentGet(agentId: string, options: GlobalOptions) {
97
158
  console.log(` Metadata: ${data.metadata ?? "(none)"}`);
98
159
  console.log(` Created: ${data.createdAt}`);
99
160
  if (data.updatedAt) console.log(` Updated: ${data.updatedAt}`);
161
+ // Twitter binding
162
+ if (twitterData) {
163
+ console.log(` Twitter: @${twitterData.username} (${twitterData.status})`);
164
+ if (twitterData.verifiedAt) console.log(` Twitter verified: ${twitterData.verifiedAt}`);
165
+ } else {
166
+ console.log(` Twitter: (none)`);
167
+ }
168
+ // Tweet verification
169
+ if (tweetVerifyData) {
170
+ console.log(` Tweet verify: ${tweetVerifyData.tweetId} (${tweetVerifyData.status})`);
171
+ if (tweetVerifyData.lastRewardedAt) console.log(` Tweet last rewarded: ${tweetVerifyData.lastRewardedAt}`);
172
+ }
100
173
  console.log("");
174
+ if (twitterData) {
175
+ // Bound — show tweet submit tip
176
+ if (tweetVerifyData?.lastRewardedAt) {
177
+ const lastRewarded = new Date(tweetVerifyData.lastRewardedAt).getTime();
178
+ const hoursAgo = (Date.now() - lastRewarded) / (1000 * 60 * 60);
179
+ if (hoursAgo >= 24) {
180
+ console.log(` Tip: You can verify your tweet again to earn more stake-free credits.`);
181
+ console.log(` npx naracli agent submit-tweet <tweet-url>`);
182
+ } else {
183
+ const hoursLeft = Math.ceil(24 - hoursAgo);
184
+ console.log(` Tip: Next tweet verification available in ~${hoursLeft}h.`);
185
+ }
186
+ } else {
187
+ console.log(` Tip: Submit a tweet to earn stake-free PoMI mining credits!`);
188
+ console.log(` npx naracli agent submit-tweet <tweet-url>`);
189
+ }
190
+ console.log(` Stake-free credits are based on tweet likes, bookmarks, retweets, and quotes.`);
191
+ console.log("");
192
+ } else {
193
+ // Not bound — show bind tip
194
+ console.log(` Tip: Bind your Twitter to get stake-free PoMI mining credits!`);
195
+ console.log(` 1. Post a tweet with this content:`);
196
+ console.log(` I'm claiming my AI agent "${agentId}" on NaraChain @NaraBuildAI`);
197
+ console.log(` 2. Then run:`);
198
+ console.log(` npx naracli agent bind-twitter <tweet-url>`);
199
+ console.log("");
200
+ }
101
201
  }
102
202
  }
103
203
 
@@ -287,17 +387,24 @@ async function handleAgentLog(
287
387
 
288
388
  async function handleAgentClear(options: GlobalOptions) {
289
389
  const rpcUrl = getRpcUrl(options.rpcUrl);
290
- const networkConfig = loadNetworkConfig(rpcUrl);
390
+ let pubkey: string | undefined;
391
+ try {
392
+ const wallet = await loadWallet(options.wallet);
393
+ pubkey = wallet.publicKey.toBase58();
394
+ } catch {
395
+ // No wallet — clear legacy format
396
+ }
397
+ const networkConfig = loadNetworkConfig(rpcUrl, pubkey);
291
398
  if (!networkConfig.agent_id) {
292
399
  if (options.json) {
293
400
  formatOutput({ cleared: false, message: "No agent ID configured" }, true);
294
401
  } else {
295
- printWarning("No agent ID configured for this network");
402
+ printWarning("No agent ID configured for this wallet");
296
403
  }
297
404
  return;
298
405
  }
299
406
  const oldId = networkConfig.agent_id;
300
- clearAgentId(rpcUrl);
407
+ clearAgentId(rpcUrl, pubkey);
301
408
  if (options.json) {
302
409
  formatOutput({ cleared: true, agentId: oldId }, true);
303
410
  } else {
@@ -309,39 +416,14 @@ async function handleAgentClear(options: GlobalOptions) {
309
416
 
310
417
  const TWITTER_STATUS: Record<number, string> = { 0: "none", 1: "pending", 2: "verified", 3: "rejected" };
311
418
 
312
- async function handleAgentTwitterGet(agentId: string, options: GlobalOptions) {
313
- const rpcUrl = getRpcUrl(options.rpcUrl);
314
- const connection = new Connection(rpcUrl, "confirmed");
315
-
316
- const info = await getAgentTwitter(connection, agentId);
317
- if (!info) {
318
- if (options.json) {
319
- formatOutput({ agentId, twitter: null }, true);
320
- } else {
321
- printWarning(`Agent "${agentId}" has no twitter binding`);
322
- }
323
- return;
324
- }
325
-
326
- const data = {
327
- agentId,
328
- username: info.username,
329
- tweetUrl: info.tweetUrl,
330
- status: TWITTER_STATUS[info.status] ?? `unknown(${info.status})`,
331
- verifiedAt: info.verifiedAt ? new Date(info.verifiedAt * 1000).toISOString() : null,
332
- };
333
-
334
- if (options.json) {
335
- formatOutput(data, true);
336
- } else {
337
- console.log("");
338
- console.log(` Agent ID: ${data.agentId}`);
339
- console.log(` Twitter: @${data.username}`);
340
- console.log(` Tweet: ${data.tweetUrl}`);
341
- console.log(` Status: ${data.status}`);
342
- if (data.verifiedAt) console.log(` Verified: ${data.verifiedAt}`);
343
- console.log("");
419
+ /** Parse tweet URL and extract username + tweetId. Accepts https://x.com/<username>/status/<id> or https://twitter.com/<username>/status/<id>. */
420
+ function parseTweetUrl(url: string): { username: string; tweetId: bigint; tweetUrl: string } {
421
+ const m = url.match(/^https?:\/\/(?:x\.com|twitter\.com)\/([A-Za-z0-9_]+)\/status\/(\d+)/);
422
+ if (!m) {
423
+ printError(`Invalid tweet URL. Expected format: https://x.com/<username>/status/<id>`);
424
+ process.exit(1);
344
425
  }
426
+ return { username: m[1], tweetId: BigInt(m[2]), tweetUrl: url };
345
427
  }
346
428
 
347
429
  async function handleAgentTwitterSet(agentId: string, username: string, tweetUrl: string, options: GlobalOptions) {
@@ -376,57 +458,29 @@ async function handleAgentTwitterUnbind(agentId: string, username: string, optio
376
458
  }
377
459
  }
378
460
 
379
- async function handleAgentSubmitTweet(agentId: string, username: string, tweetUrl: string, options: GlobalOptions) {
461
+ async function handleAgentSubmitTweet(agentId: string, tweetId: bigint, tweetUrl: string, options: GlobalOptions) {
380
462
  const rpcUrl = getRpcUrl(options.rpcUrl);
381
463
  const connection = new Connection(rpcUrl, "confirmed");
382
464
  const wallet = await loadWallet(options.wallet);
383
465
 
466
+ // Check if tweet has already been used
467
+ const existing = await getTweetRecord(connection, tweetId);
468
+ if (existing) {
469
+ printError(`This tweet has already been submitted and approved. Please use a different tweet.`);
470
+ process.exit(1);
471
+ }
472
+
384
473
  if (!options.json) printInfo(`Submitting tweet for verification...`);
385
- const signature = await submitTweet(connection, wallet, agentId, username, tweetUrl);
474
+ const signature = await submitTweet(connection, wallet, agentId, tweetId);
386
475
  if (!options.json) printSuccess(`Tweet submitted for verification!`);
387
476
 
388
477
  if (options.json) {
389
- formatOutput({ agentId, username, tweetUrl, signature }, true);
478
+ formatOutput({ agentId, tweetId: tweetId.toString(), tweetUrl, signature }, true);
390
479
  } else {
391
480
  console.log(` Transaction: ${signature}`);
392
481
  }
393
482
  }
394
483
 
395
- async function handleAgentTweetStatus(agentId: string, options: GlobalOptions) {
396
- const rpcUrl = getRpcUrl(options.rpcUrl);
397
- const connection = new Connection(rpcUrl, "confirmed");
398
-
399
- const info = await getTweetVerify(connection, agentId);
400
- if (!info) {
401
- if (options.json) {
402
- formatOutput({ agentId, tweetVerify: null }, true);
403
- } else {
404
- printWarning(`Agent "${agentId}" has no tweet verification record`);
405
- }
406
- return;
407
- }
408
-
409
- const data = {
410
- agentId,
411
- tweetUrl: info.tweetUrl,
412
- status: TWITTER_STATUS[info.status] ?? `unknown(${info.status})`,
413
- submittedAt: info.submittedAt ? new Date(info.submittedAt * 1000).toISOString() : null,
414
- lastRewardedAt: info.lastRewardedAt ? new Date(info.lastRewardedAt * 1000).toISOString() : null,
415
- };
416
-
417
- if (options.json) {
418
- formatOutput(data, true);
419
- } else {
420
- console.log("");
421
- console.log(` Agent ID: ${data.agentId}`);
422
- console.log(` Tweet: ${data.tweetUrl}`);
423
- console.log(` Status: ${data.status}`);
424
- if (data.submittedAt) console.log(` Submitted: ${data.submittedAt}`);
425
- if (data.lastRewardedAt) console.log(` Last rewarded: ${data.lastRewardedAt}`);
426
- console.log("");
427
- }
428
- }
429
-
430
484
  async function handleAgentConfig(options: GlobalOptions) {
431
485
  const rpcUrl = getRpcUrl(options.rpcUrl);
432
486
  const connection = new Connection(rpcUrl, "confirmed");
@@ -473,7 +527,8 @@ async function handleAgentConfig(options: GlobalOptions) {
473
527
 
474
528
  async function handleAgentMyId(options: GlobalOptions) {
475
529
  const rpcUrl = getRpcUrl(options.rpcUrl);
476
- const networkConfig = loadNetworkConfig(rpcUrl);
530
+ const pubkey = await tryGetWalletPubkey(options.wallet);
531
+ const networkConfig = loadNetworkConfig(rpcUrl, pubkey);
477
532
  if (!networkConfig.agent_id) {
478
533
  if (options.json) {
479
534
  formatOutput({ agentId: null }, true);
@@ -513,11 +568,13 @@ export function registerAgentCommands(program: Command): void {
513
568
 
514
569
  // agent get
515
570
  agent
516
- .command("get <agent-id>")
517
- .description("Get agent info (bio, metadata, version)")
518
- .action(async (agentId: string, _opts: any, cmd: Command) => {
571
+ .command("get")
572
+ .description("Get agent info (bio, metadata, twitter binding, tweet verification)")
573
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
574
+ .action(async (opts: any, cmd: Command) => {
519
575
  try {
520
576
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
577
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
521
578
  await handleAgentGet(agentId, globalOpts);
522
579
  } catch (error: any) {
523
580
  printError(error.message);
@@ -527,11 +584,13 @@ export function registerAgentCommands(program: Command): void {
527
584
 
528
585
  // agent set-bio
529
586
  agent
530
- .command("set-bio <agent-id> <bio>")
587
+ .command("set-bio <bio>")
531
588
  .description("Set agent bio (max 512 bytes)")
532
- .action(async (agentId: string, bio: string, _opts: any, cmd: Command) => {
589
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
590
+ .action(async (bio: string, opts: any, cmd: Command) => {
533
591
  try {
534
592
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
593
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
535
594
  await handleAgentSetBio(agentId, bio, globalOpts);
536
595
  } catch (error: any) {
537
596
  printError(error.message);
@@ -541,11 +600,13 @@ export function registerAgentCommands(program: Command): void {
541
600
 
542
601
  // agent set-metadata
543
602
  agent
544
- .command("set-metadata <agent-id> <json>")
603
+ .command("set-metadata <json>")
545
604
  .description("Set agent JSON metadata (max 800 bytes)")
546
- .action(async (agentId: string, jsonStr: string, _opts: any, cmd: Command) => {
605
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
606
+ .action(async (jsonStr: string, opts: any, cmd: Command) => {
547
607
  try {
548
608
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
609
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
549
610
  await handleAgentSetMetadata(agentId, jsonStr, globalOpts);
550
611
  } catch (error: any) {
551
612
  printError(error.message);
@@ -555,11 +616,13 @@ export function registerAgentCommands(program: Command): void {
555
616
 
556
617
  // agent upload-memory
557
618
  agent
558
- .command("upload-memory <agent-id> <file>")
619
+ .command("upload-memory <file>")
559
620
  .description("Upload memory data from file")
560
- .action(async (agentId: string, filePath: string, _opts: any, cmd: Command) => {
621
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
622
+ .action(async (filePath: string, opts: any, cmd: Command) => {
561
623
  try {
562
624
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
625
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
563
626
  await handleAgentUploadMemory(agentId, filePath, globalOpts);
564
627
  } catch (error: any) {
565
628
  printError(error.message);
@@ -569,11 +632,13 @@ export function registerAgentCommands(program: Command): void {
569
632
 
570
633
  // agent memory
571
634
  agent
572
- .command("memory <agent-id>")
635
+ .command("memory")
573
636
  .description("Read agent memory content")
574
- .action(async (agentId: string, _opts: any, cmd: Command) => {
637
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
638
+ .action(async (opts: any, cmd: Command) => {
575
639
  try {
576
640
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
641
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
577
642
  await handleAgentMemory(agentId, globalOpts);
578
643
  } catch (error: any) {
579
644
  printError(error.message);
@@ -583,11 +648,13 @@ export function registerAgentCommands(program: Command): void {
583
648
 
584
649
  // agent transfer
585
650
  agent
586
- .command("transfer <agent-id> <new-authority>")
651
+ .command("transfer <new-authority>")
587
652
  .description("Transfer agent authority to another wallet")
588
- .action(async (agentId: string, newAuthority: string, _opts: any, cmd: Command) => {
653
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
654
+ .action(async (newAuthority: string, opts: any, cmd: Command) => {
589
655
  try {
590
656
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
657
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
591
658
  await handleAgentTransfer(agentId, newAuthority, globalOpts);
592
659
  } catch (error: any) {
593
660
  printError(error.message);
@@ -597,11 +664,13 @@ export function registerAgentCommands(program: Command): void {
597
664
 
598
665
  // agent close-buffer
599
666
  agent
600
- .command("close-buffer <agent-id>")
667
+ .command("close-buffer")
601
668
  .description("Close upload buffer, reclaim rent")
602
- .action(async (agentId: string, _opts: any, cmd: Command) => {
669
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
670
+ .action(async (opts: any, cmd: Command) => {
603
671
  try {
604
672
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
673
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
605
674
  await handleAgentCloseBuffer(agentId, globalOpts);
606
675
  } catch (error: any) {
607
676
  printError(error.message);
@@ -631,7 +700,8 @@ export function registerAgentCommands(program: Command): void {
631
700
  try {
632
701
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
633
702
  const rpcUrl = getRpcUrl(globalOpts.rpcUrl);
634
- const networkConfig = loadNetworkConfig(rpcUrl);
703
+ const pubkey = await tryGetWalletPubkey(globalOpts.wallet);
704
+ const networkConfig = loadNetworkConfig(rpcUrl, pubkey);
635
705
  if (globalOpts.json) {
636
706
  formatOutput({ agentId: networkConfig.agent_id || null }, true);
637
707
  } else if (networkConfig.agent_id) {
@@ -675,11 +745,13 @@ export function registerAgentCommands(program: Command): void {
675
745
 
676
746
  // agent set-referral
677
747
  agent
678
- .command("set-referral <agent-id> <referral-agent-id>")
748
+ .command("set-referral <referral-agent-id>")
679
749
  .description("Set referral agent on-chain")
680
- .action(async (agentId: string, referralAgentId: string, _opts: any, cmd: Command) => {
750
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
751
+ .action(async (referralAgentId: string, opts: any, cmd: Command) => {
681
752
  try {
682
753
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
754
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
683
755
  await handleAgentSetReferral(agentId, referralAgentId, globalOpts);
684
756
  } catch (error: any) {
685
757
  printError(error.message);
@@ -689,14 +761,16 @@ export function registerAgentCommands(program: Command): void {
689
761
 
690
762
  // agent log
691
763
  agent
692
- .command("log <agent-id> <activity> <log>")
764
+ .command("log <activity> <log>")
693
765
  .description("Log an activity event on-chain")
766
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
694
767
  .option("--model <name>", "Model identifier")
695
768
  .option("--referral <agent-id>", "Referral agent ID")
696
- .action(async (agentId: string, activity: string, log: string, opts: { model?: string; referral?: string }, cmd: Command) => {
769
+ .action(async (activity: string, log: string, opts: { agentId?: string; model?: string; referral?: string }, cmd: Command) => {
697
770
  try {
698
771
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
699
- await handleAgentLog(agentId, activity, log, { ...globalOpts, ...opts });
772
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
773
+ await handleAgentLog(agentId, activity, log, { ...globalOpts, model: opts.model, referral: opts.referral });
700
774
  } catch (error: any) {
701
775
  printError(error.message);
702
776
  process.exit(1);
@@ -705,27 +779,55 @@ export function registerAgentCommands(program: Command): void {
705
779
 
706
780
  // ─── Twitter commands ───────────────────────────────────────────
707
781
 
708
- // agent twitter <agent-id>
782
+ // agent bind-twitter [tweet-url]
709
783
  agent
710
- .command("twitter <agent-id>")
711
- .description("Get agent's twitter binding status")
712
- .action(async (agentId: string, _opts: any, cmd: Command) => {
784
+ .command("bind-twitter [tweet-url]")
785
+ .description("Bind twitter to your agent for stake-free PoMI credits")
786
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
787
+ .addHelpText("after", `
788
+ Tweet content (replace <agent-id> with yours):
789
+ I'm claiming my AI agent "<agent-id>" on NaraChain @NaraBuildAI
790
+
791
+ Tweet URL format:
792
+ https://x.com/<username>/status/<id>
793
+
794
+ Example:
795
+ npx naracli agent bind-twitter https://x.com/yourname/status/123456789`)
796
+ .action(async (tweetUrl: string | undefined, opts: any, cmd: Command) => {
713
797
  try {
714
798
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
715
- await handleAgentTwitterGet(agentId, globalOpts);
716
- } catch (error: any) {
717
- printError(error.message);
718
- process.exit(1);
719
- }
720
- });
799
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
800
+
801
+ if (!tweetUrl) {
802
+ // No URL provided — check status and show tips
803
+ const rpcUrl = getRpcUrl(globalOpts.rpcUrl);
804
+ const connection = new Connection(rpcUrl, "confirmed");
805
+ try {
806
+ const tw = await getAgentTwitter(connection, agentId);
807
+ if (tw) {
808
+ const status = TWITTER_STATUS[tw.status] ?? `unknown(${tw.status})`;
809
+ console.log("");
810
+ console.log(` Twitter already bound: @${tw.username} (${status})`);
811
+ console.log(` Tweet: ${tw.tweetUrl}`);
812
+ if (tw.verifiedAt) console.log(` Verified: ${new Date(tw.verifiedAt * 1000).toISOString()}`);
813
+ console.log("");
814
+ return;
815
+ }
816
+ } catch {
817
+ // No binding found
818
+ }
819
+ console.log("");
820
+ console.log(` Bind your Twitter to get stake-free PoMI mining credits!`);
821
+ console.log(` 1. Post a tweet with this content:`);
822
+ console.log(` I'm claiming my AI agent "${agentId}" on NaraChain @NaraBuildAI`);
823
+ console.log(` 2. Then run:`);
824
+ console.log(` npx naracli agent bind-twitter <tweet-url>`);
825
+ console.log(` (URL format: https://x.com/<username>/status/<id>)`);
826
+ console.log("");
827
+ return;
828
+ }
721
829
 
722
- // agent set-twitter <agent-id> <username> <tweet-url>
723
- agent
724
- .command("set-twitter <agent-id> <username> <tweet-url>")
725
- .description("Bind a twitter account to your agent (charges verification fee). Tweet must contain your agent ID.")
726
- .action(async (agentId: string, username: string, tweetUrl: string, _opts: any, cmd: Command) => {
727
- try {
728
- const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
830
+ const { username } = parseTweetUrl(tweetUrl);
729
831
  await handleAgentTwitterSet(agentId, username, tweetUrl, globalOpts);
730
832
  } catch (error: any) {
731
833
  printError(error.message);
@@ -733,13 +835,15 @@ export function registerAgentCommands(program: Command): void {
733
835
  }
734
836
  });
735
837
 
736
- // agent unbind-twitter <agent-id> <username>
838
+ // agent unbind-twitter <username>
737
839
  agent
738
- .command("unbind-twitter <agent-id> <username>")
840
+ .command("unbind-twitter <username>")
739
841
  .description("Unbind twitter from your agent")
740
- .action(async (agentId: string, username: string, _opts: any, cmd: Command) => {
842
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
843
+ .action(async (username: string, opts: any, cmd: Command) => {
741
844
  try {
742
845
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
846
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
743
847
  await handleAgentTwitterUnbind(agentId, username, globalOpts);
744
848
  } catch (error: any) {
745
849
  printError(error.message);
@@ -747,28 +851,17 @@ export function registerAgentCommands(program: Command): void {
747
851
  }
748
852
  });
749
853
 
750
- // agent submit-tweet <agent-id> <username> <tweet-url>
854
+ // agent submit-tweet <tweet-url>
751
855
  agent
752
- .command("submit-tweet <agent-id> <username> <tweet-url>")
856
+ .command("submit-tweet <tweet-url>")
753
857
  .description("Submit a tweet for verification and earn rewards (charges verification fee)")
754
- .action(async (agentId: string, username: string, tweetUrl: string, _opts: any, cmd: Command) => {
755
- try {
756
- const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
757
- await handleAgentSubmitTweet(agentId, username, tweetUrl, globalOpts);
758
- } catch (error: any) {
759
- printError(error.message);
760
- process.exit(1);
761
- }
762
- });
763
-
764
- // agent tweet-status <agent-id>
765
- agent
766
- .command("tweet-status <agent-id>")
767
- .description("Check tweet verification status")
768
- .action(async (agentId: string, _opts: any, cmd: Command) => {
858
+ .option("--agent-id <id>", "Agent ID (defaults to saved myid)")
859
+ .action(async (tweetUrl: string, opts: any, cmd: Command) => {
769
860
  try {
770
861
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
771
- await handleAgentTweetStatus(agentId, globalOpts);
862
+ const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
863
+ const { tweetId } = parseTweetUrl(tweetUrl);
864
+ await handleAgentSubmitTweet(agentId, tweetId, tweetUrl, globalOpts);
772
865
  } catch (error: any) {
773
866
  printError(error.message);
774
867
  process.exit(1);
@@ -233,7 +233,7 @@ async function handleQuestAnswer(
233
233
  const rpcUrl = getRpcUrl(options.rpcUrl);
234
234
  const connection = new Connection(rpcUrl, "confirmed");
235
235
  const wallet = await loadWallet(options.wallet);
236
- const networkConfig = loadNetworkConfig(rpcUrl);
236
+ const networkConfig = loadNetworkConfig(rpcUrl, wallet.publicKey.toBase58());
237
237
  const configAgentId = networkConfig.agent_id;
238
238
  const agent = options.agent ?? "naracli";
239
239
  const model = options.model ?? "";