naracli 1.0.32 → 1.0.37
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 +9 -6
- package/dist/nara-cli-bundle.cjs +94287 -55928
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/package.json +6 -3
- package/src/cli/commands/agent.ts +39 -7
- package/src/cli/commands/config.ts +31 -13
- package/src/cli/commands/quest.ts +8 -7
- package/src/cli/commands/zkid.ts +5 -5
- package/src/cli/utils/agent-config.ts +127 -22
- package/src/cli/utils/wallet.ts +9 -7
- package/src/tests/agent.test.ts +193 -0
- package/src/tests/config.test.ts +199 -0
- package/src/tests/validation.test.ts +80 -0
- package/src/tests/zkid.test.ts +6 -6
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naracli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.37",
|
|
4
4
|
"description": "CLI for the Nara chain (Solana-compatible)",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"cli": "node --require ./bin/env-loader.cjs --import tsx bin/nara-cli.ts",
|
|
14
14
|
"build": "node scripts/build.cjs",
|
|
15
|
-
"test": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/quest.test.ts src/tests/skills.test.ts src/tests/zkid.test.ts src/tests/agent-registry.test.ts",
|
|
15
|
+
"test": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/validation.test.ts src/tests/config.test.ts src/tests/agent.test.ts src/tests/quest.test.ts src/tests/skills.test.ts src/tests/zkid.test.ts src/tests/agent-registry.test.ts",
|
|
16
|
+
"test:validation": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/validation.test.ts",
|
|
17
|
+
"test:config": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/config.test.ts",
|
|
18
|
+
"test:agent-cli": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/agent.test.ts",
|
|
16
19
|
"test:quest": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/quest.test.ts",
|
|
17
20
|
"test:skills": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/skills.test.ts",
|
|
18
21
|
"test:zkid": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/zkid.test.ts",
|
|
@@ -53,7 +56,7 @@
|
|
|
53
56
|
"bs58": "^6.0.0",
|
|
54
57
|
"commander": "^12.1.0",
|
|
55
58
|
"ed25519-hd-key": "^1.3.0",
|
|
56
|
-
"nara-sdk": "^1.0.
|
|
59
|
+
"nara-sdk": "^1.0.37",
|
|
57
60
|
"picocolors": "^1.1.1"
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
import type { GlobalOptions } from "../types";
|
|
16
16
|
import {
|
|
17
17
|
registerAgent,
|
|
18
|
-
getAgentRecord,
|
|
19
18
|
getAgentInfo,
|
|
20
19
|
getAgentMemory,
|
|
21
20
|
setBio,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
transferAgentAuthority,
|
|
26
25
|
deleteAgent,
|
|
27
26
|
logActivity,
|
|
27
|
+
setReferral as setReferralOnChain,
|
|
28
28
|
} from "nara-sdk";
|
|
29
29
|
import { readFileSync } from "node:fs";
|
|
30
30
|
import { addAgentId } from "../utils/agent-config";
|
|
@@ -32,22 +32,23 @@ import { validateName } from "../utils/validation";
|
|
|
32
32
|
|
|
33
33
|
// ─── Command handlers ────────────────────────────────────────────
|
|
34
34
|
|
|
35
|
-
async function handleAgentRegister(agentId: string, options: GlobalOptions) {
|
|
35
|
+
async function handleAgentRegister(agentId: string, options: GlobalOptions & { referral?: string }) {
|
|
36
36
|
validateName(agentId, "Agent ID");
|
|
37
37
|
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
38
38
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
39
39
|
const wallet = await loadWallet(options.wallet);
|
|
40
40
|
|
|
41
41
|
if (!options.json) printInfo(`Registering agent "${agentId}"...`);
|
|
42
|
-
const result = await registerAgent(connection, wallet, agentId);
|
|
42
|
+
const result = await registerAgent(connection, wallet, agentId, undefined, options.referral);
|
|
43
43
|
if (!options.json) printSuccess(`Agent "${agentId}" registered!`);
|
|
44
|
-
addAgentId(agentId);
|
|
44
|
+
addAgentId(agentId, rpcUrl);
|
|
45
45
|
|
|
46
46
|
if (options.json) {
|
|
47
|
-
formatOutput({ agentId, signature: result.signature, agentPubkey: result.agentPubkey.toBase58() }, true);
|
|
47
|
+
formatOutput({ agentId, referral: options.referral ?? null, signature: result.signature, agentPubkey: result.agentPubkey.toBase58() }, true);
|
|
48
48
|
} else {
|
|
49
49
|
console.log(` Transaction: ${result.signature}`);
|
|
50
50
|
console.log(` Agent PDA: ${result.agentPubkey.toBase58()}`);
|
|
51
|
+
if (options.referral) console.log(` Referral: ${options.referral}`);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -225,6 +226,22 @@ async function handleAgentDelete(agentId: string, options: GlobalOptions) {
|
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
|
|
229
|
+
async function handleAgentSetReferral(agentId: string, referralAgentId: string, options: GlobalOptions) {
|
|
230
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
231
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
232
|
+
const wallet = await loadWallet(options.wallet);
|
|
233
|
+
|
|
234
|
+
if (!options.json) printInfo(`Setting referral for "${agentId}" to "${referralAgentId}"...`);
|
|
235
|
+
const signature = await setReferralOnChain(connection, wallet, agentId, referralAgentId);
|
|
236
|
+
if (!options.json) printSuccess(`Referral set on-chain!`);
|
|
237
|
+
|
|
238
|
+
if (options.json) {
|
|
239
|
+
formatOutput({ agentId, referral: referralAgentId, signature }, true);
|
|
240
|
+
} else {
|
|
241
|
+
console.log(` Transaction: ${signature}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
228
245
|
async function handleAgentLog(
|
|
229
246
|
agentId: string,
|
|
230
247
|
activity: string,
|
|
@@ -259,10 +276,11 @@ export function registerAgentCommands(program: Command): void {
|
|
|
259
276
|
agent
|
|
260
277
|
.command("register <agent-id>")
|
|
261
278
|
.description("Register a new agent on-chain")
|
|
262
|
-
.
|
|
279
|
+
.option("--referral <agent-id>", "Referral agent ID")
|
|
280
|
+
.action(async (agentId: string, opts: { referral?: string }, cmd: Command) => {
|
|
263
281
|
try {
|
|
264
282
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
265
|
-
await handleAgentRegister(agentId, globalOpts);
|
|
283
|
+
await handleAgentRegister(agentId, { ...globalOpts, ...opts });
|
|
266
284
|
} catch (error: any) {
|
|
267
285
|
printError(error.message);
|
|
268
286
|
process.exit(1);
|
|
@@ -381,6 +399,20 @@ export function registerAgentCommands(program: Command): void {
|
|
|
381
399
|
}
|
|
382
400
|
});
|
|
383
401
|
|
|
402
|
+
// agent set-referral
|
|
403
|
+
agent
|
|
404
|
+
.command("set-referral <agent-id> <referral-agent-id>")
|
|
405
|
+
.description("Set referral agent on-chain")
|
|
406
|
+
.action(async (agentId: string, referralAgentId: string, _opts: any, cmd: Command) => {
|
|
407
|
+
try {
|
|
408
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
409
|
+
await handleAgentSetReferral(agentId, referralAgentId, globalOpts);
|
|
410
|
+
} catch (error: any) {
|
|
411
|
+
printError(error.message);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
384
416
|
// agent log
|
|
385
417
|
agent
|
|
386
418
|
.command("log <agent-id> <activity> <log>")
|
|
@@ -3,32 +3,50 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
loadGlobalConfig,
|
|
8
|
+
saveGlobalConfig,
|
|
9
|
+
loadNetworkConfig,
|
|
10
|
+
rpcUrlToNetworkName,
|
|
11
|
+
} from "../utils/agent-config";
|
|
12
|
+
import { getRpcUrl } from "../utils/wallet";
|
|
7
13
|
import { printError, printSuccess, formatOutput } from "../utils/output";
|
|
8
14
|
import { DEFAULT_RPC_URL } from "nara-sdk";
|
|
9
15
|
import type { GlobalOptions } from "../types";
|
|
10
16
|
|
|
11
17
|
function handleConfigGet(options: GlobalOptions) {
|
|
12
|
-
const
|
|
18
|
+
const globalConfig = loadGlobalConfig();
|
|
19
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
20
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
21
|
+
const networkName = rpcUrlToNetworkName(rpcUrl);
|
|
22
|
+
|
|
13
23
|
const data = {
|
|
14
|
-
rpc_url:
|
|
15
|
-
wallet:
|
|
16
|
-
rpc_url_custom: !!
|
|
17
|
-
wallet_custom: !!
|
|
24
|
+
rpc_url: globalConfig.rpc_url ?? DEFAULT_RPC_URL,
|
|
25
|
+
wallet: globalConfig.wallet ?? "~/.config/nara/id.json",
|
|
26
|
+
rpc_url_custom: !!globalConfig.rpc_url,
|
|
27
|
+
wallet_custom: !!globalConfig.wallet,
|
|
28
|
+
network: networkName,
|
|
29
|
+
agent_ids: networkConfig.agent_ids,
|
|
30
|
+
zk_ids: networkConfig.zk_ids,
|
|
18
31
|
};
|
|
19
32
|
|
|
20
33
|
if (options.json) {
|
|
21
34
|
formatOutput(data, true);
|
|
22
35
|
} else {
|
|
23
36
|
console.log("");
|
|
24
|
-
console.log(` RPC URL:
|
|
25
|
-
console.log(` Wallet:
|
|
37
|
+
console.log(` RPC URL: ${data.rpc_url}${data.rpc_url_custom ? "" : " (default)"}`);
|
|
38
|
+
console.log(` Wallet: ${data.wallet}${data.wallet_custom ? "" : " (default)"}`);
|
|
39
|
+
console.log(` Network: ${networkName}`);
|
|
40
|
+
if (networkConfig.agent_ids.length > 0)
|
|
41
|
+
console.log(` Agents: ${networkConfig.agent_ids.join(", ")}`);
|
|
42
|
+
if (networkConfig.zk_ids.length > 0)
|
|
43
|
+
console.log(` ZK IDs: ${networkConfig.zk_ids.join(", ")}`);
|
|
26
44
|
console.log("");
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
function handleConfigSet(key: string, value: string, options: GlobalOptions) {
|
|
31
|
-
const config =
|
|
49
|
+
const config = loadGlobalConfig();
|
|
32
50
|
|
|
33
51
|
switch (key) {
|
|
34
52
|
case "rpc-url":
|
|
@@ -41,18 +59,18 @@ function handleConfigSet(key: string, value: string, options: GlobalOptions) {
|
|
|
41
59
|
throw new Error(`Unknown config key: "${key}". Valid keys: rpc-url, wallet`);
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
saveGlobalConfig(config);
|
|
45
63
|
if (!options.json) printSuccess(`Config "${key}" set to "${value}"`);
|
|
46
64
|
if (options.json) formatOutput({ key, value }, true);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
function handleConfigReset(key: string | undefined, options: GlobalOptions) {
|
|
50
|
-
const config =
|
|
68
|
+
const config = loadGlobalConfig();
|
|
51
69
|
|
|
52
70
|
if (!key) {
|
|
53
71
|
delete config.rpc_url;
|
|
54
72
|
delete config.wallet;
|
|
55
|
-
|
|
73
|
+
saveGlobalConfig(config);
|
|
56
74
|
if (!options.json) printSuccess("All config reset to defaults");
|
|
57
75
|
} else {
|
|
58
76
|
switch (key) {
|
|
@@ -65,7 +83,7 @@ function handleConfigReset(key: string | undefined, options: GlobalOptions) {
|
|
|
65
83
|
default:
|
|
66
84
|
throw new Error(`Unknown config key: "${key}". Valid keys: rpc-url, wallet`);
|
|
67
85
|
}
|
|
68
|
-
|
|
86
|
+
saveGlobalConfig(config);
|
|
69
87
|
if (!options.json) printSuccess(`Config "${key}" reset to default`);
|
|
70
88
|
}
|
|
71
89
|
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
parseQuestReward,
|
|
23
23
|
type ActivityLog,
|
|
24
24
|
} from "nara-sdk";
|
|
25
|
-
import {
|
|
25
|
+
import { loadNetworkConfig } from "../utils/agent-config";
|
|
26
26
|
|
|
27
27
|
const DEFAULT_QUEST_RELAY_URL = process.env.QUEST_RELAY_URL || "https://quest-api.nara.build/";
|
|
28
28
|
|
|
@@ -59,7 +59,7 @@ function formatTimeRemaining(seconds: number): string {
|
|
|
59
59
|
|
|
60
60
|
// ─── Command: quest get ──────────────────────────────────────────
|
|
61
61
|
async function handleQuestGet(options: GlobalOptions) {
|
|
62
|
-
const rpcUrl =
|
|
62
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
63
63
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
64
64
|
|
|
65
65
|
let wallet: Keypair;
|
|
@@ -126,13 +126,14 @@ async function handleQuestAnswer(
|
|
|
126
126
|
answer: string,
|
|
127
127
|
options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string }
|
|
128
128
|
) {
|
|
129
|
-
const rpcUrl =
|
|
129
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
130
130
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
131
131
|
const wallet = await loadWallet(options.wallet);
|
|
132
|
-
const
|
|
133
|
-
const configAgentId =
|
|
132
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
133
|
+
const configAgentId = networkConfig.agent_ids[0];
|
|
134
134
|
const agent = options.agent ?? "naracli";
|
|
135
135
|
const model = options.model ?? "";
|
|
136
|
+
const referral = options.referral;
|
|
136
137
|
|
|
137
138
|
// 1. Fetch quest info
|
|
138
139
|
let quest;
|
|
@@ -165,7 +166,7 @@ async function handleQuestAnswer(
|
|
|
165
166
|
|
|
166
167
|
let proof;
|
|
167
168
|
try {
|
|
168
|
-
proof = await generateProof(answer, quest.answerHash, wallet.publicKey);
|
|
169
|
+
proof = await generateProof(answer, quest.answerHash, wallet.publicKey, quest.round);
|
|
169
170
|
} catch (err: any) {
|
|
170
171
|
if (err.message?.includes("Assert Failed")) {
|
|
171
172
|
printError("Wrong answer");
|
|
@@ -207,7 +208,7 @@ async function handleQuestAnswer(
|
|
|
207
208
|
try {
|
|
208
209
|
let activityLog: ActivityLog | undefined;
|
|
209
210
|
if (configAgentId) {
|
|
210
|
-
activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId:
|
|
211
|
+
activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId: referral };
|
|
211
212
|
}
|
|
212
213
|
const result = await submitAnswer(connection, wallet, proof.solana, agent, model, undefined, activityLog);
|
|
213
214
|
printSuccess("Answer submitted!");
|
package/src/cli/commands/zkid.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
ZKID_DENOMINATIONS,
|
|
28
28
|
} from "nara-sdk";
|
|
29
29
|
import BN from "bn.js";
|
|
30
|
-
import { addZkId,
|
|
30
|
+
import { addZkId, loadNetworkConfig } from "../utils/agent-config";
|
|
31
31
|
|
|
32
32
|
// ─── Denomination helpers ────────────────────────────────────────
|
|
33
33
|
|
|
@@ -73,7 +73,7 @@ async function handleZkIdCreate(name: string, options: GlobalOptions) {
|
|
|
73
73
|
if (!options.json) printInfo(`Registering ZK ID "${name}"...`);
|
|
74
74
|
const signature = await createZkId(connection, wallet, name, idSecret);
|
|
75
75
|
if (!options.json) printSuccess(`ZK ID "${name}" registered!`);
|
|
76
|
-
addZkId(name);
|
|
76
|
+
addZkId(name, rpcUrl);
|
|
77
77
|
|
|
78
78
|
if (options.json) {
|
|
79
79
|
formatOutput({ name, signature }, true);
|
|
@@ -149,12 +149,12 @@ async function handleZkIdScan(
|
|
|
149
149
|
if (name) {
|
|
150
150
|
names = [name];
|
|
151
151
|
} else {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
152
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
153
|
+
if (networkConfig.zk_ids.length === 0) {
|
|
154
154
|
printError("No ZK IDs in config. Provide a name or create a ZK ID first.");
|
|
155
155
|
process.exit(1);
|
|
156
156
|
}
|
|
157
|
-
names =
|
|
157
|
+
names = networkConfig.zk_ids;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const allResults: Array<{ name: string; deposits: any[] }> = [];
|
|
@@ -1,50 +1,155 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent config utilities
|
|
2
|
+
* Agent config utilities
|
|
3
|
+
*
|
|
4
|
+
* Global config: ~/.config/nara/config.json — rpc_url, wallet
|
|
5
|
+
* Network config: ~/.config/nara/agent-{network}.json — agent_ids, zk_ids
|
|
6
|
+
*
|
|
7
|
+
* {network} is derived from the effective RPC URL:
|
|
8
|
+
* https://mainnet-api.nara.build/ → mainnet-api-nara-build
|
|
9
|
+
* https://devnet-api.nara.build/ → devnet-api-nara-build
|
|
10
|
+
* http://127.0.0.1:8899/ → 127-0-0-1-8899
|
|
3
11
|
*/
|
|
4
12
|
|
|
5
|
-
import { join
|
|
13
|
+
import { join } from "node:path";
|
|
6
14
|
import { homedir } from "node:os";
|
|
7
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
15
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
16
|
+
import { DEFAULT_RPC_URL } from "nara-sdk";
|
|
8
17
|
|
|
9
|
-
const
|
|
18
|
+
const CONFIG_DIR = join(homedir(), ".config", "nara");
|
|
19
|
+
const GLOBAL_CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
// ─── URL → filename ─────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export function rpcUrlToNetworkName(url: string): string {
|
|
24
|
+
let name = url.replace(/^https?:\/\//, "");
|
|
25
|
+
name = name.replace(/\/+$/, "");
|
|
26
|
+
name = name.replace(/[^a-zA-Z0-9-]/g, "-");
|
|
27
|
+
name = name.replace(/-+/g, "-");
|
|
28
|
+
name = name.replace(/^-|-$/g, "");
|
|
29
|
+
return name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function networkConfigPath(rpcUrl: string): string {
|
|
33
|
+
return join(CONFIG_DIR, `agent-${rpcUrlToNetworkName(rpcUrl)}.json`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Global config (rpc_url, wallet) ─────────────────────────────
|
|
37
|
+
|
|
38
|
+
export interface GlobalConfig {
|
|
14
39
|
rpc_url?: string;
|
|
15
40
|
wallet?: string;
|
|
16
41
|
}
|
|
17
42
|
|
|
18
|
-
|
|
43
|
+
export function loadGlobalConfig(): GlobalConfig {
|
|
44
|
+
try {
|
|
45
|
+
const raw = readFileSync(GLOBAL_CONFIG_PATH, "utf-8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
return {
|
|
48
|
+
rpc_url: parsed.rpc_url ?? undefined,
|
|
49
|
+
wallet: parsed.wallet ?? undefined,
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function saveGlobalConfig(config: GlobalConfig): void {
|
|
57
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
58
|
+
writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the effective RPC URL (without CLI flag context).
|
|
63
|
+
* For use when no CLI flag is available.
|
|
64
|
+
*/
|
|
65
|
+
export function getConfiguredRpcUrl(): string {
|
|
66
|
+
const global = loadGlobalConfig();
|
|
67
|
+
return global.rpc_url || DEFAULT_RPC_URL;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Network config (agent_ids, zk_ids) ──────────────────────────
|
|
71
|
+
|
|
72
|
+
export interface NetworkConfig {
|
|
73
|
+
agent_ids: string[];
|
|
74
|
+
zk_ids: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const DEFAULT_NETWORK_CONFIG: NetworkConfig = { agent_ids: [], zk_ids: [] };
|
|
19
78
|
|
|
20
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Load network-specific config.
|
|
81
|
+
* @param rpcUrl - effective RPC URL (determines which file to load)
|
|
82
|
+
*/
|
|
83
|
+
export function loadNetworkConfig(rpcUrl?: string): NetworkConfig {
|
|
84
|
+
const url = rpcUrl || getConfiguredRpcUrl();
|
|
85
|
+
const path = networkConfigPath(url);
|
|
21
86
|
try {
|
|
22
|
-
const raw = readFileSync(
|
|
87
|
+
const raw = readFileSync(path, "utf-8");
|
|
23
88
|
const parsed = JSON.parse(raw);
|
|
24
89
|
return {
|
|
25
90
|
agent_ids: Array.isArray(parsed.agent_ids) ? parsed.agent_ids : [],
|
|
26
91
|
zk_ids: Array.isArray(parsed.zk_ids) ? parsed.zk_ids : [],
|
|
27
|
-
rpc_url: parsed.rpc_url ?? undefined,
|
|
28
|
-
wallet: parsed.wallet ?? undefined,
|
|
29
92
|
};
|
|
30
93
|
} catch {
|
|
31
|
-
return { ...
|
|
94
|
+
return { ...DEFAULT_NETWORK_CONFIG };
|
|
32
95
|
}
|
|
33
96
|
}
|
|
34
97
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Save network-specific config.
|
|
100
|
+
*/
|
|
101
|
+
export function saveNetworkConfig(config: NetworkConfig, rpcUrl?: string): void {
|
|
102
|
+
const url = rpcUrl || getConfiguredRpcUrl();
|
|
103
|
+
const path = networkConfigPath(url);
|
|
104
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
105
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n");
|
|
38
106
|
}
|
|
39
107
|
|
|
40
|
-
|
|
41
|
-
|
|
108
|
+
// ─── Convenience helpers ─────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export function addAgentId(id: string, rpcUrl?: string): void {
|
|
111
|
+
const config = loadNetworkConfig(rpcUrl);
|
|
42
112
|
config.agent_ids = [id, ...config.agent_ids.filter((x) => x !== id)];
|
|
43
|
-
|
|
113
|
+
saveNetworkConfig(config, rpcUrl);
|
|
44
114
|
}
|
|
45
115
|
|
|
46
|
-
export function addZkId(name: string): void {
|
|
47
|
-
const config =
|
|
116
|
+
export function addZkId(name: string, rpcUrl?: string): void {
|
|
117
|
+
const config = loadNetworkConfig(rpcUrl);
|
|
48
118
|
config.zk_ids = [name, ...config.zk_ids.filter((x) => x !== name)];
|
|
49
|
-
|
|
119
|
+
saveNetworkConfig(config, rpcUrl);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Migration: import old agent.json if network config doesn't exist ──
|
|
123
|
+
|
|
124
|
+
const LEGACY_CONFIG_PATH = join(CONFIG_DIR, "agent.json");
|
|
125
|
+
|
|
126
|
+
export function migrateIfNeeded(rpcUrl?: string): void {
|
|
127
|
+
const url = rpcUrl || getConfiguredRpcUrl();
|
|
128
|
+
const path = networkConfigPath(url);
|
|
129
|
+
if (existsSync(path)) return;
|
|
130
|
+
if (!existsSync(LEGACY_CONFIG_PATH)) return;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const raw = readFileSync(LEGACY_CONFIG_PATH, "utf-8");
|
|
134
|
+
const parsed = JSON.parse(raw);
|
|
135
|
+
|
|
136
|
+
// Migrate network-specific fields
|
|
137
|
+
const networkConfig: NetworkConfig = {
|
|
138
|
+
agent_ids: Array.isArray(parsed.agent_ids) ? parsed.agent_ids : [],
|
|
139
|
+
zk_ids: Array.isArray(parsed.zk_ids) ? parsed.zk_ids : [],
|
|
140
|
+
};
|
|
141
|
+
saveNetworkConfig(networkConfig, url);
|
|
142
|
+
|
|
143
|
+
// Migrate global fields if config.json doesn't exist
|
|
144
|
+
if (!existsSync(GLOBAL_CONFIG_PATH)) {
|
|
145
|
+
const globalConfig: GlobalConfig = {};
|
|
146
|
+
if (parsed.rpc_url) globalConfig.rpc_url = parsed.rpc_url;
|
|
147
|
+
if (parsed.wallet) globalConfig.wallet = parsed.wallet;
|
|
148
|
+
if (Object.keys(globalConfig).length > 0) {
|
|
149
|
+
saveGlobalConfig(globalConfig);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Ignore migration errors
|
|
154
|
+
}
|
|
50
155
|
}
|
package/src/cli/utils/wallet.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Keypair } from "@solana/web3.js";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { DEFAULT_RPC_URL } from "nara-sdk";
|
|
9
|
-
import {
|
|
9
|
+
import { loadGlobalConfig, migrateIfNeeded } from "./agent-config";
|
|
10
10
|
|
|
11
11
|
const DEFAULT_WALLET_PATH = join(homedir(), ".config", "nara", "id.json");
|
|
12
12
|
|
|
@@ -22,13 +22,13 @@ function resolvePath(p: string): string {
|
|
|
22
22
|
*
|
|
23
23
|
* Priority:
|
|
24
24
|
* 1. CLI flag (walletPath parameter)
|
|
25
|
-
* 2.
|
|
25
|
+
* 2. Global config (~/.config/nara/config.json wallet field)
|
|
26
26
|
* 3. Default path (~/.config/nara/id.json)
|
|
27
27
|
*/
|
|
28
28
|
export async function loadWallet(walletPath?: string): Promise<Keypair> {
|
|
29
29
|
let path = walletPath;
|
|
30
30
|
if (!path) {
|
|
31
|
-
const config =
|
|
31
|
+
const config = loadGlobalConfig();
|
|
32
32
|
path = config.wallet ? resolvePath(config.wallet) : DEFAULT_WALLET_PATH;
|
|
33
33
|
} else {
|
|
34
34
|
path = resolvePath(path);
|
|
@@ -64,11 +64,13 @@ export async function loadWallet(walletPath?: string): Promise<Keypair> {
|
|
|
64
64
|
*
|
|
65
65
|
* Priority:
|
|
66
66
|
* 1. CLI flag (rpcUrl parameter)
|
|
67
|
-
* 2.
|
|
67
|
+
* 2. Global config (~/.config/nara/config.json rpc_url field)
|
|
68
68
|
* 3. Default (from SDK constants)
|
|
69
|
+
*
|
|
70
|
+
* Also triggers migration from legacy agent.json if needed.
|
|
69
71
|
*/
|
|
70
72
|
export function getRpcUrl(rpcUrl?: string): string {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
73
|
+
const effective = rpcUrl || loadGlobalConfig().rpc_url || DEFAULT_RPC_URL;
|
|
74
|
+
migrateIfNeeded(effective);
|
|
75
|
+
return effective;
|
|
74
76
|
}
|