naracli 1.0.63 → 1.0.65
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/README.md +29 -13
- package/bin/nara-cli.ts +3 -2
- package/dist/nara-cli-bundle.cjs +42276 -6043
- package/package.json +2 -2
- package/src/cli/commands/agent.ts +231 -139
- package/src/cli/commands/quest.ts +1 -1
- package/src/cli/utils/agent-config.ts +91 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naracli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.65",
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
const tweetText = `Claiming my AI agent ${agentId} on NaraChain @NaraBuildAI`;
|
|
195
|
+
const tweetIntent = `https://x.com/intent/tweet?text=${tweetText.replace(/ /g, "%20")}`;
|
|
196
|
+
console.log(` Tip: Bind your Twitter to get stake-free PoMI mining credits!`);
|
|
197
|
+
console.log(` 1. Post a tweet: ${tweetIntent}`);
|
|
198
|
+
console.log(` 2. Then run: 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
|
-
|
|
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
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
517
|
-
.description("Get agent info (bio, metadata,
|
|
518
|
-
.
|
|
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 <
|
|
587
|
+
.command("set-bio <bio>")
|
|
531
588
|
.description("Set agent bio (max 512 bytes)")
|
|
532
|
-
.
|
|
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 <
|
|
603
|
+
.command("set-metadata <json>")
|
|
545
604
|
.description("Set agent JSON metadata (max 800 bytes)")
|
|
546
|
-
.
|
|
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 <
|
|
619
|
+
.command("upload-memory <file>")
|
|
559
620
|
.description("Upload memory data from file")
|
|
560
|
-
.
|
|
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
|
|
635
|
+
.command("memory")
|
|
573
636
|
.description("Read agent memory content")
|
|
574
|
-
.
|
|
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 <
|
|
651
|
+
.command("transfer <new-authority>")
|
|
587
652
|
.description("Transfer agent authority to another wallet")
|
|
588
|
-
.
|
|
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
|
|
667
|
+
.command("close-buffer")
|
|
601
668
|
.description("Close upload buffer, reclaim rent")
|
|
602
|
-
.
|
|
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
|
|
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 <
|
|
748
|
+
.command("set-referral <referral-agent-id>")
|
|
679
749
|
.description("Set referral agent on-chain")
|
|
680
|
-
.
|
|
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 <
|
|
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 (
|
|
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
|
-
|
|
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,54 @@ export function registerAgentCommands(program: Command): void {
|
|
|
705
779
|
|
|
706
780
|
// ─── Twitter commands ───────────────────────────────────────────
|
|
707
781
|
|
|
708
|
-
// agent twitter
|
|
782
|
+
// agent bind-twitter [tweet-url]
|
|
709
783
|
agent
|
|
710
|
-
.command("twitter
|
|
711
|
-
.description("
|
|
712
|
-
.
|
|
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
|
+
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
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
+
const tweetText = `Claiming my AI agent ${agentId} on NaraChain @NaraBuildAI`;
|
|
820
|
+
const tweetIntent = `https://x.com/intent/tweet?text=${tweetText.replace(/ /g, "%20")}`;
|
|
821
|
+
console.log("");
|
|
822
|
+
console.log(` Bind your Twitter to get stake-free PoMI mining credits!`);
|
|
823
|
+
console.log(` 1. Post a tweet: ${tweetIntent}`);
|
|
824
|
+
console.log(` 2. Then run: npx naracli agent bind-twitter <tweet-url>`);
|
|
825
|
+
console.log("");
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
721
828
|
|
|
722
|
-
|
|
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;
|
|
829
|
+
const { username } = parseTweetUrl(tweetUrl);
|
|
729
830
|
await handleAgentTwitterSet(agentId, username, tweetUrl, globalOpts);
|
|
730
831
|
} catch (error: any) {
|
|
731
832
|
printError(error.message);
|
|
@@ -733,13 +834,15 @@ export function registerAgentCommands(program: Command): void {
|
|
|
733
834
|
}
|
|
734
835
|
});
|
|
735
836
|
|
|
736
|
-
// agent unbind-twitter <
|
|
837
|
+
// agent unbind-twitter <username>
|
|
737
838
|
agent
|
|
738
|
-
.command("unbind-twitter <
|
|
839
|
+
.command("unbind-twitter <username>")
|
|
739
840
|
.description("Unbind twitter from your agent")
|
|
740
|
-
.
|
|
841
|
+
.option("--agent-id <id>", "Agent ID (defaults to saved myid)")
|
|
842
|
+
.action(async (username: string, opts: any, cmd: Command) => {
|
|
741
843
|
try {
|
|
742
844
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
845
|
+
const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
|
|
743
846
|
await handleAgentTwitterUnbind(agentId, username, globalOpts);
|
|
744
847
|
} catch (error: any) {
|
|
745
848
|
printError(error.message);
|
|
@@ -747,28 +850,17 @@ export function registerAgentCommands(program: Command): void {
|
|
|
747
850
|
}
|
|
748
851
|
});
|
|
749
852
|
|
|
750
|
-
// agent submit-tweet <
|
|
853
|
+
// agent submit-tweet <tweet-url>
|
|
751
854
|
agent
|
|
752
|
-
.command("submit-tweet <
|
|
855
|
+
.command("submit-tweet <tweet-url>")
|
|
753
856
|
.description("Submit a tweet for verification and earn rewards (charges verification fee)")
|
|
754
|
-
.
|
|
755
|
-
|
|
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) => {
|
|
857
|
+
.option("--agent-id <id>", "Agent ID (defaults to saved myid)")
|
|
858
|
+
.action(async (tweetUrl: string, opts: any, cmd: Command) => {
|
|
769
859
|
try {
|
|
770
860
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
771
|
-
await
|
|
861
|
+
const agentId = await resolveAgentId({ ...globalOpts, agentId: opts.agentId });
|
|
862
|
+
const { tweetId } = parseTweetUrl(tweetUrl);
|
|
863
|
+
await handleAgentSubmitTweet(agentId, tweetId, tweetUrl, globalOpts);
|
|
772
864
|
} catch (error: any) {
|
|
773
865
|
printError(error.message);
|
|
774
866
|
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 ?? "";
|