naracli 1.0.94 → 1.0.95
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 +4 -3
- package/dist/nara-cli-bundle.cjs +324 -271
- package/package.json +2 -2
- package/src/cli/commands/agent.ts +51 -0
- package/src/cli/commands/quest.ts +39 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naracli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.95",
|
|
4
4
|
"description": "CLI for the Nara chain (Solana-compatible)",
|
|
5
5
|
"homepage": "https://nara.build",
|
|
6
6
|
"repository": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"bs58": "^6.0.0",
|
|
68
68
|
"commander": "^12.1.0",
|
|
69
69
|
"ed25519-hd-key": "^1.3.0",
|
|
70
|
-
"nara-sdk": "^1.0.
|
|
70
|
+
"nara-sdk": "^1.0.85",
|
|
71
71
|
"picocolors": "^1.1.1"
|
|
72
72
|
},
|
|
73
73
|
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
registerAgent,
|
|
19
19
|
registerAgentWithReferral,
|
|
20
20
|
getAgentInfo,
|
|
21
|
+
listAgentsByAuthority,
|
|
21
22
|
getAgentMemory,
|
|
22
23
|
setBio,
|
|
23
24
|
setMetadata,
|
|
@@ -517,6 +518,35 @@ async function handleAgentRecover(agentId: string, options: GlobalOptions) {
|
|
|
517
518
|
}
|
|
518
519
|
}
|
|
519
520
|
|
|
521
|
+
async function handleAgentList(options: GlobalOptions) {
|
|
522
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
523
|
+
const wallet = await loadWallet(options.wallet);
|
|
524
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
525
|
+
|
|
526
|
+
const agentIds = await listAgentsByAuthority(connection, wallet.publicKey);
|
|
527
|
+
|
|
528
|
+
if (options.json) {
|
|
529
|
+
formatOutput({ authority: wallet.publicKey.toBase58(), count: agentIds.length, agentIds }, true);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (agentIds.length === 0) {
|
|
534
|
+
printWarning(`No agents found for ${wallet.publicKey.toBase58()}`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const networkConfig = loadNetworkConfig(rpcUrl, wallet.publicKey.toBase58());
|
|
539
|
+
const savedId = networkConfig.agent_id;
|
|
540
|
+
|
|
541
|
+
console.log("");
|
|
542
|
+
console.log(` Authority: ${wallet.publicKey.toBase58()}`);
|
|
543
|
+
console.log(` Agents (${agentIds.length}):`);
|
|
544
|
+
for (const id of agentIds) {
|
|
545
|
+
console.log(` - ${id}${id === savedId ? " (saved locally)" : ""}`);
|
|
546
|
+
}
|
|
547
|
+
console.log("");
|
|
548
|
+
}
|
|
549
|
+
|
|
520
550
|
async function handleAgentClear(options: GlobalOptions) {
|
|
521
551
|
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
522
552
|
let pubkey: string | undefined;
|
|
@@ -610,6 +640,13 @@ async function handleAgentSubmitTweet(agentId: string, tweetId: bigint, tweetUrl
|
|
|
610
640
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
611
641
|
const wallet = await loadWallet(options.wallet);
|
|
612
642
|
|
|
643
|
+
// Require Twitter binding before submitting a tweet
|
|
644
|
+
const twitter = await getAgentTwitter(connection, agentId).catch(() => null);
|
|
645
|
+
if (!twitter) {
|
|
646
|
+
printError(`Agent "${agentId}" has not bound a Twitter account yet. Run "npx naracli agent bind-twitter" first.`);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
|
|
613
650
|
// Check if tweet has already been used
|
|
614
651
|
const existing = await getTweetRecord(connection, tweetId);
|
|
615
652
|
if (existing) {
|
|
@@ -907,6 +944,20 @@ export function registerAgentCommands(program: Command): void {
|
|
|
907
944
|
}
|
|
908
945
|
});
|
|
909
946
|
|
|
947
|
+
// agent list
|
|
948
|
+
agent
|
|
949
|
+
.command("list")
|
|
950
|
+
.description("List all agent IDs registered to this wallet's authority")
|
|
951
|
+
.action(async (_opts: any, cmd: Command) => {
|
|
952
|
+
try {
|
|
953
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
954
|
+
await handleAgentList(globalOpts);
|
|
955
|
+
} catch (error: any) {
|
|
956
|
+
printError(error.message);
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
|
|
910
961
|
// agent clear
|
|
911
962
|
agent
|
|
912
963
|
.command("clear")
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
unstake as questUnstake,
|
|
27
27
|
getStakeInfo,
|
|
28
28
|
type ActivityLog,
|
|
29
|
-
type StakeInfo,
|
|
30
29
|
type QuestOptions,
|
|
31
30
|
} from "nara-sdk";
|
|
32
31
|
import { loadNetworkConfig } from "../utils/agent-config";
|
|
@@ -59,6 +58,7 @@ const QUEST_ERRORS: Record<number, string> = {
|
|
|
59
58
|
6009: "invalidMaxRewardCount",
|
|
60
59
|
6010: "unstakeNotReady",
|
|
61
60
|
6011: "insufficientStakeBalance",
|
|
61
|
+
6024: "noCredits",
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
function anchorErrorCode(err: any): string {
|
|
@@ -70,6 +70,9 @@ function anchorErrorCode(err: any): string {
|
|
|
70
70
|
return "";
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const STAKE_DEPRECATION_NOTICE =
|
|
74
|
+
"PoMI has been upgraded to Boost PoMI — mining is now gated by boost credits, not stake. The stake channel is being phased out; please unstake your NARA soon.";
|
|
75
|
+
|
|
73
76
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
74
77
|
function formatTimeRemaining(seconds: number): string {
|
|
75
78
|
if (seconds <= 0) return "expired";
|
|
@@ -80,7 +83,7 @@ function formatTimeRemaining(seconds: number): string {
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
// ─── Command: quest get ──────────────────────────────────────────
|
|
83
|
-
async function handleQuestGet(options: GlobalOptions
|
|
86
|
+
async function handleQuestGet(options: GlobalOptions) {
|
|
84
87
|
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
85
88
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
86
89
|
|
|
@@ -107,15 +110,12 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
|
|
|
107
110
|
return;
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// Fetch free credits (stake-free answer quota)
|
|
114
|
-
let freeCredits = 0;
|
|
113
|
+
// Fetch Boost PoMI credits (required to submit an answer)
|
|
114
|
+
let boostCredits = 0;
|
|
115
115
|
try {
|
|
116
116
|
const stakeInfo = await getStakeInfo(connection, wallet.publicKey);
|
|
117
117
|
if (stakeInfo) {
|
|
118
|
-
|
|
118
|
+
boostCredits = stakeInfo.boostCredits;
|
|
119
119
|
}
|
|
120
120
|
} catch {
|
|
121
121
|
// Ignore — wallet may not have a stake record
|
|
@@ -126,21 +126,13 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
|
|
|
126
126
|
question: quest.question,
|
|
127
127
|
difficulty: quest.difficulty,
|
|
128
128
|
totalReward: `${quest.totalReward} NARA`,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
creditRewardPerWinner: `${quest.creditRewardPerWinner} NARA`,
|
|
133
|
-
creditRewardSlots: `${quest.creditWinnerCount}/${quest.creditRewardCount}`,
|
|
134
|
-
creditRemainingSlots: quest.creditRemainingSlots,
|
|
129
|
+
rewardPerWinner: `${quest.stakeRewardPerWinner} NARA`,
|
|
130
|
+
rewardSlots: `${quest.stakeWinnerCount}/${quest.stakeRewardCount}`,
|
|
131
|
+
remainingSlots: quest.stakeRemainingSlots,
|
|
135
132
|
deadline: new Date(quest.deadline * 1000).toLocaleString(),
|
|
136
133
|
timeRemaining: formatTimeRemaining(quest.timeRemaining),
|
|
137
134
|
expired: quest.expired,
|
|
138
|
-
|
|
139
|
-
stakeRequirement: stakeRequired ? `${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA` : "0 NARA",
|
|
140
|
-
stakeHigh: `${quest.stakeHigh} NARA`,
|
|
141
|
-
stakeLow: `${quest.stakeLow} NARA`,
|
|
142
|
-
avgParticipantStake: `${quest.avgParticipantStake} NARA`,
|
|
143
|
-
freeCredits,
|
|
135
|
+
boostCredits,
|
|
144
136
|
};
|
|
145
137
|
|
|
146
138
|
if (options.json) {
|
|
@@ -152,19 +144,9 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
|
|
|
152
144
|
console.log(` Difficulty: ${quest.difficulty}`);
|
|
153
145
|
console.log(` Total reward: ${quest.totalReward} NARA`);
|
|
154
146
|
console.log(
|
|
155
|
-
`
|
|
156
|
-
);
|
|
157
|
-
console.log(
|
|
158
|
-
` Boost reward: ${quest.creditRewardPerWinner} NARA/winner, ${quest.creditWinnerCount}/${quest.creditRewardCount} (${quest.creditRemainingSlots} remaining)`
|
|
147
|
+
` Boost PoMI reward: ${quest.stakeRewardPerWinner} NARA/winner, ${quest.stakeWinnerCount}/${quest.stakeRewardCount} (${quest.stakeRemainingSlots} remaining)`
|
|
159
148
|
);
|
|
160
|
-
|
|
161
|
-
console.log(` Stake requirement: ${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA (decays ${quest.stakeHigh} → ${quest.stakeLow})`);
|
|
162
|
-
} else if (options.verbose) {
|
|
163
|
-
console.log(` Stake requirement: none (${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA, decays ${quest.stakeHigh} → ${quest.stakeLow})`);
|
|
164
|
-
} else {
|
|
165
|
-
console.log(` Stake requirement: none`);
|
|
166
|
-
}
|
|
167
|
-
console.log(` Boost credits: ${freeCredits}`);
|
|
149
|
+
console.log(` Boost credits: ${boostCredits}${boostCredits === 0 ? " (required to answer — acquire credits first)" : ""}`);
|
|
168
150
|
console.log(` Deadline: ${new Date(quest.deadline * 1000).toLocaleString()}`);
|
|
169
151
|
if (quest.timeRemaining > 0) {
|
|
170
152
|
console.log(` Time remaining: ${formatTimeRemaining(quest.timeRemaining)}`);
|
|
@@ -206,7 +188,6 @@ async function handleQuestConfig(options: GlobalOptions) {
|
|
|
206
188
|
stakeBpsLow,
|
|
207
189
|
decayMs: config.decayMs,
|
|
208
190
|
minQuestInterval: config.minQuestInterval,
|
|
209
|
-
freeStakeMultiplier: config.freeStakeMultiplier,
|
|
210
191
|
};
|
|
211
192
|
|
|
212
193
|
if (options.json) {
|
|
@@ -221,7 +202,6 @@ async function handleQuestConfig(options: GlobalOptions) {
|
|
|
221
202
|
console.log(` Stake BPS Low: ${stakeBpsLow / 100}% (${stakeBpsLow} BPS)`);
|
|
222
203
|
console.log(` Decay (ms): ${data.decayMs}`);
|
|
223
204
|
console.log(` Min Quest Interval: ${data.minQuestInterval}s`);
|
|
224
|
-
console.log(` Free Stake Multiplier: ${data.freeStakeMultiplier}x`);
|
|
225
205
|
console.log("");
|
|
226
206
|
}
|
|
227
207
|
}
|
|
@@ -229,7 +209,7 @@ async function handleQuestConfig(options: GlobalOptions) {
|
|
|
229
209
|
// ─── Command: quest answer ───────────────────────────────────────
|
|
230
210
|
async function handleQuestAnswer(
|
|
231
211
|
answer: string,
|
|
232
|
-
options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string
|
|
212
|
+
options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string }
|
|
233
213
|
) {
|
|
234
214
|
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
235
215
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
@@ -315,8 +295,7 @@ async function handleQuestAnswer(
|
|
|
315
295
|
if (configAgentId) {
|
|
316
296
|
activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId: referral };
|
|
317
297
|
}
|
|
318
|
-
const
|
|
319
|
-
const result = await submitAnswer(connection, wallet, proof.solana, agent, model, stakeOpt !== undefined ? { stake: stakeOpt } : undefined, activityLog);
|
|
298
|
+
const result = await submitAnswer(connection, wallet, proof.solana, agent, model, undefined, activityLog);
|
|
320
299
|
printSuccess("Answer submitted!");
|
|
321
300
|
console.log(` Transaction: ${result.signature}`);
|
|
322
301
|
await handleReward(connection, result.signature, options);
|
|
@@ -338,12 +317,13 @@ async function handleQuestStake(amount: string, options: GlobalOptions) {
|
|
|
338
317
|
process.exit(1);
|
|
339
318
|
}
|
|
340
319
|
|
|
320
|
+
if (!options.json) printWarning(STAKE_DEPRECATION_NOTICE);
|
|
341
321
|
if (!options.json) printInfo(`Staking ${n} NARA...`);
|
|
342
322
|
const signature = await questStake(connection, wallet, n);
|
|
343
323
|
if (!options.json) printSuccess(`Staked ${n} NARA!`);
|
|
344
324
|
|
|
345
325
|
if (options.json) {
|
|
346
|
-
formatOutput({ amount: n, signature }, true);
|
|
326
|
+
formatOutput({ amount: n, signature, notice: STAKE_DEPRECATION_NOTICE }, true);
|
|
347
327
|
} else {
|
|
348
328
|
console.log(` Transaction: ${signature}`);
|
|
349
329
|
}
|
|
@@ -361,12 +341,13 @@ async function handleQuestUnstake(amount: string, options: GlobalOptions) {
|
|
|
361
341
|
process.exit(1);
|
|
362
342
|
}
|
|
363
343
|
|
|
344
|
+
if (!options.json) printWarning(STAKE_DEPRECATION_NOTICE);
|
|
364
345
|
if (!options.json) printInfo(`Unstaking ${n} NARA...`);
|
|
365
346
|
const signature = await questUnstake(connection, wallet, n);
|
|
366
347
|
if (!options.json) printSuccess(`Unstaked ${n} NARA!`);
|
|
367
348
|
|
|
368
349
|
if (options.json) {
|
|
369
|
-
formatOutput({ amount: n, signature }, true);
|
|
350
|
+
formatOutput({ amount: n, signature, notice: STAKE_DEPRECATION_NOTICE }, true);
|
|
370
351
|
} else {
|
|
371
352
|
console.log(` Transaction: ${signature}`);
|
|
372
353
|
}
|
|
@@ -389,12 +370,20 @@ async function handleQuestStakeInfo(options: GlobalOptions) {
|
|
|
389
370
|
}
|
|
390
371
|
|
|
391
372
|
if (options.json) {
|
|
392
|
-
formatOutput({
|
|
373
|
+
formatOutput({
|
|
374
|
+
staked: true,
|
|
375
|
+
amount: stakeInfo.amount,
|
|
376
|
+
stakeRound: stakeInfo.stakeRound,
|
|
377
|
+
boostCredits: stakeInfo.boostCredits,
|
|
378
|
+
notice: stakeInfo.amount > 0 ? STAKE_DEPRECATION_NOTICE : undefined,
|
|
379
|
+
}, true);
|
|
393
380
|
} else {
|
|
394
381
|
console.log("");
|
|
395
382
|
console.log(` Staked: ${stakeInfo.amount} NARA`);
|
|
396
383
|
console.log(` Stake round: ${stakeInfo.stakeRound}`);
|
|
384
|
+
console.log(` Boost credits: ${stakeInfo.boostCredits}`);
|
|
397
385
|
console.log("");
|
|
386
|
+
if (stakeInfo.amount > 0) printWarning(STAKE_DEPRECATION_NOTICE);
|
|
398
387
|
}
|
|
399
388
|
}
|
|
400
389
|
|
|
@@ -464,6 +453,9 @@ function handleSubmitError(err: any) {
|
|
|
464
453
|
case "insufficientStakeBalance":
|
|
465
454
|
printError("Unstake amount exceeds staked balance");
|
|
466
455
|
break;
|
|
456
|
+
case "noCredits":
|
|
457
|
+
printError("Boost PoMI requires boost credits. Acquire credits before submitting an answer.");
|
|
458
|
+
break;
|
|
467
459
|
default:
|
|
468
460
|
printError(`Failed to submit answer: ${err.message ?? String(err)}`);
|
|
469
461
|
if (err.logs) {
|
|
@@ -483,12 +475,11 @@ export function registerQuestCommands(program: Command): void {
|
|
|
483
475
|
// quest get
|
|
484
476
|
quest
|
|
485
477
|
.command("get")
|
|
486
|
-
.description("Get current quest info (question, deadline, difficulty,
|
|
487
|
-
.
|
|
488
|
-
.action(async (opts: any, cmd: Command) => {
|
|
478
|
+
.description("Get current quest info (question, deadline, difficulty, boost credits)")
|
|
479
|
+
.action(async (_opts: any, cmd: Command) => {
|
|
489
480
|
try {
|
|
490
481
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
491
|
-
await handleQuestGet(
|
|
482
|
+
await handleQuestGet(globalOpts);
|
|
492
483
|
} catch (error: any) {
|
|
493
484
|
printError(error.message);
|
|
494
485
|
process.exit(1);
|
|
@@ -498,18 +489,16 @@ export function registerQuestCommands(program: Command): void {
|
|
|
498
489
|
// quest answer
|
|
499
490
|
quest
|
|
500
491
|
.command("answer <answer>")
|
|
501
|
-
.description("Submit a quest answer with ZK proof.
|
|
492
|
+
.description("Submit a quest answer with ZK proof. Requires boost credits. Use --relay when balance is 0 (gasless). Always pass --agent and --model for reward tracking.")
|
|
502
493
|
.option("--relay [url]", `Submit via gasless relay (default: ${DEFAULT_QUEST_RELAY_URL}, backup: https://quest2-api.nara.build/)`)
|
|
503
494
|
.option("--agent <name>", "Agent/platform type: claude-code, cursor, chatgpt, openclaw, etc. (default: naracli)")
|
|
504
495
|
.option("--model <name>", "AI model used: claude-opus-4-6, claude-sonnet-4-6, gpt-4o, etc.")
|
|
505
496
|
.option("--referral <agent-id>", "Referral agent ID for earning referral points")
|
|
506
|
-
.option("--stake [amount]", 'Stake NARA in the same tx ("auto" to top-up to requirement, or an exact amount)')
|
|
507
497
|
.action(async (answer: string, opts: any, cmd: Command) => {
|
|
508
498
|
try {
|
|
509
499
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
510
500
|
const relayUrl = opts.relay === true ? DEFAULT_QUEST_RELAY_URL : opts.relay;
|
|
511
|
-
|
|
512
|
-
await handleQuestAnswer(answer, { ...globalOpts, relay: relayUrl, agent: opts.agent, model: opts.model, referral: opts.referral, stake: stakeVal });
|
|
501
|
+
await handleQuestAnswer(answer, { ...globalOpts, relay: relayUrl, agent: opts.agent, model: opts.model, referral: opts.referral });
|
|
513
502
|
} catch (error: any) {
|
|
514
503
|
printError(error.message);
|
|
515
504
|
process.exit(1);
|
|
@@ -519,7 +508,7 @@ export function registerQuestCommands(program: Command): void {
|
|
|
519
508
|
// quest config
|
|
520
509
|
quest
|
|
521
510
|
.command("config")
|
|
522
|
-
.description("Show quest program config (reward counts,
|
|
511
|
+
.description("Show quest program config (reward counts, decay, intervals)")
|
|
523
512
|
.action(async (_opts: any, cmd: Command) => {
|
|
524
513
|
try {
|
|
525
514
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
@@ -561,7 +550,7 @@ export function registerQuestCommands(program: Command): void {
|
|
|
561
550
|
// quest stake-info
|
|
562
551
|
quest
|
|
563
552
|
.command("stake-info")
|
|
564
|
-
.description("Get your current quest stake info")
|
|
553
|
+
.description("Get your current quest stake info (stake balance + boost credits)")
|
|
565
554
|
.action(async (_opts: any, cmd: Command) => {
|
|
566
555
|
try {
|
|
567
556
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|