naracli 1.0.17 → 1.0.22
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 +101 -114
- package/bin/nara-cli.ts +0 -20
- package/dist/nara-cli.mjs +49930 -2222
- package/index.ts +10 -58
- package/package.json +7 -6
- package/src/cli/commands/quest.ts +8 -7
- package/src/cli/commands/skills.ts +491 -0
- package/src/cli/commands/skillsInstall.ts +793 -0
- package/src/cli/commands/wallet.ts +13 -114
- package/src/cli/commands/zkid.ts +410 -0
- package/src/cli/index.ts +215 -9
- package/src/cli/prompts/searchMultiselect.ts +297 -0
- package/src/cli/types.ts +0 -138
- package/src/cli/utils/transaction.ts +1 -1
- package/src/cli/utils/validation.ts +0 -40
- package/src/cli/utils/wallet.ts +3 -1
- package/src/tests/helpers.ts +78 -0
- package/src/tests/skills.e2e.test.ts +126 -0
- package/src/tests/skills.test.ts +192 -0
- package/src/tests/test_skill.md +18 -0
- package/src/tests/zkid.e2e.test.ts +128 -0
- package/src/tests/zkid.test.ts +153 -0
- package/src/types/snarkjs.d.ts +4 -1
- package/dist/quest/nara_quest.json +0 -534
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/src/cli/commands/config.ts +0 -125
- package/src/cli/commands/migrate.ts +0 -270
- package/src/cli/commands/pool.ts +0 -364
- package/src/cli/commands/swap.ts +0 -349
- package/src/cli/quest/nara_quest.json +0 -534
- package/src/cli/quest/nara_quest_types.ts +0 -540
- package/src/cli/zk/answer_proof.wasm +0 -0
- package/src/cli/zk/answer_proof_final.zkey +0 -0
- package/src/client.ts +0 -96
- package/src/config.ts +0 -132
- package/src/constants.ts +0 -35
- package/src/migrate.ts +0 -222
- package/src/pool.ts +0 -259
- package/src/quest.ts +0 -387
- package/src/swap.ts +0 -608
package/index.ts
CHANGED
|
@@ -1,62 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nara CLI & SDK - CLI and SDK for the Nara chain (Solana-compatible)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* All transaction-related functions return unsigned transactions that
|
|
6
|
-
* must be signed and sent by the caller.
|
|
4
|
+
* Re-exports from nara-sdk for backward compatibility.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
// Export main client
|
|
10
|
-
export { NaraSDK, type NaraSDKConfig } from "./src/client";
|
|
11
|
-
|
|
12
|
-
// Export constants
|
|
13
|
-
export { DEFAULT_RPC_URL, DEFAULT_QUEST_PROGRAM_ID } from "./src/constants";
|
|
14
|
-
|
|
15
|
-
// Export config functions and types
|
|
16
|
-
export {
|
|
17
|
-
createConfig,
|
|
18
|
-
type CreateConfigOptions,
|
|
19
|
-
type CreateConfigResult,
|
|
20
|
-
} from "./src/config";
|
|
21
|
-
|
|
22
|
-
// Export pool functions and types
|
|
23
|
-
export {
|
|
24
|
-
createPool,
|
|
25
|
-
createPoolWithFirstBuy,
|
|
26
|
-
getPoolInfo,
|
|
27
|
-
getPoolProgress,
|
|
28
|
-
type CreatePoolParams,
|
|
29
|
-
type CreatePoolResult,
|
|
30
|
-
type CreatePoolWithFirstBuyParams,
|
|
31
|
-
type CreatePoolWithFirstBuyResult,
|
|
32
|
-
} from "./src/pool";
|
|
33
|
-
|
|
34
|
-
// Export swap functions and types
|
|
35
|
-
export {
|
|
36
|
-
buyToken,
|
|
37
|
-
sellToken,
|
|
38
|
-
getSwapQuote,
|
|
39
|
-
SwapMode,
|
|
40
|
-
type BuyTokenParams,
|
|
41
|
-
type BuyTokenResult,
|
|
42
|
-
type SellTokenParams,
|
|
43
|
-
type SellTokenResult,
|
|
44
|
-
type SwapQuoteResponse,
|
|
45
|
-
} from "./src/swap";
|
|
46
|
-
|
|
47
|
-
// Export migrate functions and types
|
|
48
|
-
export {
|
|
49
|
-
migrateToDAMMV2,
|
|
50
|
-
createLocker,
|
|
51
|
-
canMigrate,
|
|
52
|
-
type MigrateToDAMMV2Params,
|
|
53
|
-
type MigrateToDAMMV2Result,
|
|
54
|
-
type CreateLockerParams,
|
|
55
|
-
type CreateLockerResult,
|
|
56
|
-
} from "./src/migrate";
|
|
57
|
-
|
|
58
|
-
// Export quest functions and types
|
|
59
7
|
export {
|
|
8
|
+
NaraSDK,
|
|
9
|
+
type NaraSDKConfig,
|
|
10
|
+
DEFAULT_RPC_URL,
|
|
11
|
+
DEFAULT_QUEST_PROGRAM_ID,
|
|
60
12
|
getQuestInfo,
|
|
61
13
|
hasAnswered,
|
|
62
14
|
generateProof,
|
|
@@ -69,8 +21,8 @@ export {
|
|
|
69
21
|
type SubmitAnswerResult,
|
|
70
22
|
type SubmitRelayResult,
|
|
71
23
|
type QuestOptions,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
24
|
+
PublicKey,
|
|
25
|
+
Keypair,
|
|
26
|
+
Transaction,
|
|
27
|
+
BN,
|
|
28
|
+
} from "nara-sdk";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naracli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.22",
|
|
4
4
|
"description": "CLI for the Nara chain (Solana-compatible)",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"cli": "node --require ./bin/env-loader.cjs --import tsx bin/nara-cli.ts",
|
|
14
|
-
"build": "npx esbuild bin/nara-cli.ts --bundle --platform=node --format=esm --outfile=dist/nara-cli.mjs --
|
|
14
|
+
"build": "npx esbuild bin/nara-cli.ts --bundle --platform=node --format=esm --outfile=dist/nara-cli.mjs --external:@solana/web3.js --external:@solana/spl-token --external:@coral-xyz/anchor --external:bip39 --external:bs58 --external:bn.js --external:commander --external:ed25519-hd-key --external:snarkjs && printf '#!/usr/bin/env node\\nconst _w=console.warn;console.warn=(...a)=>{if(String(a[0]).includes(\"bigint\"))return;_w(...a)};\\ntry{process.loadEnvFile()}catch{}\\nimport(\"./nara-cli.mjs\");\\n' > dist/naracli.cjs",
|
|
15
|
+
"test": "node --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/skills.test.ts src/tests/zkid.test.ts",
|
|
16
|
+
"test:e2e": "node --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/*.e2e.test.ts",
|
|
15
17
|
"prepublishOnly": "npm run build"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
@@ -39,9 +41,7 @@
|
|
|
39
41
|
"typescript": "^5"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@meteora-ag/cp-amm-sdk": "^1.3.3",
|
|
44
|
-
"@meteora-ag/dynamic-bonding-curve-sdk": "^1.5.2",
|
|
44
|
+
"@clack/prompts": "^1.0.1",
|
|
45
45
|
"@solana/spl-token": "^0.4.14",
|
|
46
46
|
"@solana/web3.js": "^1.98.4",
|
|
47
47
|
"bip39": "^3.1.0",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"bs58": "^6.0.0",
|
|
50
50
|
"commander": "^12.1.0",
|
|
51
51
|
"ed25519-hd-key": "^1.3.0",
|
|
52
|
-
"
|
|
52
|
+
"nara-sdk": "^1.0.22",
|
|
53
|
+
"picocolors": "^1.1.1"
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
printWarning,
|
|
14
14
|
} from "../utils/output";
|
|
15
15
|
import type { GlobalOptions } from "../types";
|
|
16
|
-
import { DEFAULT_QUEST_RELAY_URL } from "../../constants";
|
|
17
16
|
import {
|
|
18
17
|
getQuestInfo,
|
|
19
18
|
hasAnswered,
|
|
@@ -21,7 +20,9 @@ import {
|
|
|
21
20
|
submitAnswer,
|
|
22
21
|
submitAnswerViaRelay,
|
|
23
22
|
parseQuestReward,
|
|
24
|
-
} from "
|
|
23
|
+
} from "nara-sdk";
|
|
24
|
+
|
|
25
|
+
const DEFAULT_QUEST_RELAY_URL = process.env.QUEST_RELAY_URL || "https://quest-api.nara.build/";
|
|
25
26
|
|
|
26
27
|
// ─── Anchor error parsing ────────────────────────────────────────
|
|
27
28
|
const QUEST_ERRORS: Record<number, string> = {
|
|
@@ -86,8 +87,8 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
86
87
|
round: quest.round,
|
|
87
88
|
questionId: quest.questionId,
|
|
88
89
|
question: quest.question,
|
|
89
|
-
rewardPerWinner: `${quest.rewardPerWinner}
|
|
90
|
-
totalReward: `${quest.totalReward}
|
|
90
|
+
rewardPerWinner: `${quest.rewardPerWinner} NARA`,
|
|
91
|
+
totalReward: `${quest.totalReward} NARA`,
|
|
91
92
|
rewardSlots: `${quest.winnerCount}/${quest.rewardCount}`,
|
|
92
93
|
remainingRewardSlots: quest.remainingSlots,
|
|
93
94
|
deadline: new Date(quest.deadline * 1000).toLocaleString(),
|
|
@@ -101,8 +102,8 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
101
102
|
console.log("");
|
|
102
103
|
console.log(` Question: ${quest.question}`);
|
|
103
104
|
console.log(` Round: #${quest.round}`);
|
|
104
|
-
console.log(` Reward per winner: ${quest.rewardPerWinner}
|
|
105
|
-
console.log(` Total reward: ${quest.totalReward}
|
|
105
|
+
console.log(` Reward per winner: ${quest.rewardPerWinner} NARA`);
|
|
106
|
+
console.log(` Total reward: ${quest.totalReward} NARA`);
|
|
106
107
|
console.log(
|
|
107
108
|
` Reward slots: ${quest.winnerCount}/${quest.rewardCount} (${quest.remainingSlots} remaining)`
|
|
108
109
|
);
|
|
@@ -224,7 +225,7 @@ async function handleReward(
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
if (reward.rewarded) {
|
|
227
|
-
printSuccess(`Congratulations! Reward received: ${reward.rewardNso}
|
|
228
|
+
printSuccess(`Congratulations! Reward received: ${reward.rewardNso} NARA (winner ${reward.winner})`);
|
|
228
229
|
if (options.json) {
|
|
229
230
|
formatOutput(
|
|
230
231
|
{
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills commands - interact with nara-skills-hub on-chain skill registry
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
import pc from "picocolors";
|
|
10
|
+
import { loadWallet, getRpcUrl } from "../utils/wallet";
|
|
11
|
+
import {
|
|
12
|
+
printError,
|
|
13
|
+
printInfo,
|
|
14
|
+
printSuccess,
|
|
15
|
+
formatOutput,
|
|
16
|
+
} from "../utils/output";
|
|
17
|
+
import type { GlobalOptions } from "../types";
|
|
18
|
+
import {
|
|
19
|
+
registerSkill,
|
|
20
|
+
getSkillInfo,
|
|
21
|
+
getSkillContent,
|
|
22
|
+
setDescription,
|
|
23
|
+
updateMetadata,
|
|
24
|
+
uploadSkillContent,
|
|
25
|
+
transferAuthority,
|
|
26
|
+
deleteSkill,
|
|
27
|
+
closeBuffer,
|
|
28
|
+
} from "nara-sdk";
|
|
29
|
+
import {
|
|
30
|
+
handleSkillsAdd,
|
|
31
|
+
handleSkillsRemove,
|
|
32
|
+
handleSkillsList,
|
|
33
|
+
handleSkillsCheck,
|
|
34
|
+
handleSkillsUpdate,
|
|
35
|
+
} from "./skillsInstall";
|
|
36
|
+
|
|
37
|
+
// ─── Command handlers ────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
async function handleSkillsRegister(
|
|
40
|
+
name: string,
|
|
41
|
+
author: string,
|
|
42
|
+
options: GlobalOptions
|
|
43
|
+
) {
|
|
44
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
45
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
46
|
+
const wallet = await loadWallet(options.wallet);
|
|
47
|
+
|
|
48
|
+
printInfo(`Registering skill "${name}" by "${author}"...`);
|
|
49
|
+
const { signature, skillPubkey } = await registerSkill(connection, wallet, name, author);
|
|
50
|
+
printSuccess("Skill registered!");
|
|
51
|
+
|
|
52
|
+
if (options.json) {
|
|
53
|
+
formatOutput({ name, author, skillPubkey: skillPubkey.toBase58(), signature }, true);
|
|
54
|
+
} else {
|
|
55
|
+
console.log(` Skill: ${skillPubkey.toBase58()}`);
|
|
56
|
+
console.log(` Transaction: ${signature}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function handleSkillsGet(name: string, options: GlobalOptions) {
|
|
61
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
62
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
63
|
+
|
|
64
|
+
const info = await getSkillInfo(connection, name);
|
|
65
|
+
|
|
66
|
+
const data = {
|
|
67
|
+
name: info.record.name,
|
|
68
|
+
author: info.record.author,
|
|
69
|
+
authority: info.record.authority.toBase58(),
|
|
70
|
+
version: info.record.version,
|
|
71
|
+
createdAt: new Date(info.record.createdAt * 1000).toISOString(),
|
|
72
|
+
updatedAt: info.record.updatedAt
|
|
73
|
+
? new Date(info.record.updatedAt * 1000).toISOString()
|
|
74
|
+
: null,
|
|
75
|
+
description: info.description ?? null,
|
|
76
|
+
metadata: info.metadata ?? null,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (options.json) {
|
|
80
|
+
formatOutput(data, true);
|
|
81
|
+
} else {
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(` Name: ${data.name}`);
|
|
84
|
+
console.log(` Author: ${data.author}`);
|
|
85
|
+
console.log(` Authority: ${data.authority}`);
|
|
86
|
+
console.log(` Version: ${data.version}`);
|
|
87
|
+
console.log(` Created: ${data.createdAt}`);
|
|
88
|
+
if (data.updatedAt) console.log(` Updated: ${data.updatedAt}`);
|
|
89
|
+
console.log(` Description: ${data.description ?? "(none)"}`);
|
|
90
|
+
console.log(` Metadata: ${data.metadata ?? "(none)"}`);
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function handleSkillsContent(
|
|
96
|
+
name: string,
|
|
97
|
+
options: GlobalOptions & { hex?: boolean }
|
|
98
|
+
) {
|
|
99
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
100
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
101
|
+
|
|
102
|
+
const content = await getSkillContent(connection, name);
|
|
103
|
+
if (!content) {
|
|
104
|
+
if (options.json) {
|
|
105
|
+
formatOutput({ name, content: null }, true);
|
|
106
|
+
} else {
|
|
107
|
+
printInfo("No content uploaded yet");
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (options.json) {
|
|
113
|
+
formatOutput(
|
|
114
|
+
{ name, size: content.length, content: options.hex ? content.toString("hex") : content.toString("utf8") },
|
|
115
|
+
true
|
|
116
|
+
);
|
|
117
|
+
} else if (options.hex) {
|
|
118
|
+
console.log(content.toString("hex"));
|
|
119
|
+
} else {
|
|
120
|
+
console.log(content.toString("utf8"));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function handleSkillsSetDescription(
|
|
125
|
+
name: string,
|
|
126
|
+
description: string,
|
|
127
|
+
options: GlobalOptions
|
|
128
|
+
) {
|
|
129
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
130
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
131
|
+
const wallet = await loadWallet(options.wallet);
|
|
132
|
+
|
|
133
|
+
printInfo(`Setting description for skill "${name}"...`);
|
|
134
|
+
const signature = await setDescription(connection, wallet, name, description);
|
|
135
|
+
printSuccess("Description updated!");
|
|
136
|
+
|
|
137
|
+
if (options.json) {
|
|
138
|
+
formatOutput({ name, description, signature }, true);
|
|
139
|
+
} else {
|
|
140
|
+
console.log(` Transaction: ${signature}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function handleSkillsSetMetadata(
|
|
145
|
+
name: string,
|
|
146
|
+
json: string,
|
|
147
|
+
options: GlobalOptions
|
|
148
|
+
) {
|
|
149
|
+
// Validate JSON
|
|
150
|
+
try {
|
|
151
|
+
JSON.parse(json);
|
|
152
|
+
} catch {
|
|
153
|
+
throw new Error("Invalid JSON provided for metadata");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
157
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
158
|
+
const wallet = await loadWallet(options.wallet);
|
|
159
|
+
|
|
160
|
+
printInfo(`Setting metadata for skill "${name}"...`);
|
|
161
|
+
const signature = await updateMetadata(connection, wallet, name, json);
|
|
162
|
+
printSuccess("Metadata updated!");
|
|
163
|
+
|
|
164
|
+
if (options.json) {
|
|
165
|
+
formatOutput({ name, signature }, true);
|
|
166
|
+
} else {
|
|
167
|
+
console.log(` Transaction: ${signature}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function handleSkillsUpload(
|
|
172
|
+
name: string,
|
|
173
|
+
filePath: string,
|
|
174
|
+
options: GlobalOptions
|
|
175
|
+
) {
|
|
176
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
177
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
178
|
+
const wallet = await loadWallet(options.wallet);
|
|
179
|
+
|
|
180
|
+
const content = await readFile(filePath);
|
|
181
|
+
printInfo(`Uploading ${content.length} bytes to skill "${name}"...`);
|
|
182
|
+
|
|
183
|
+
const signature = await uploadSkillContent(connection, wallet, name, content, {
|
|
184
|
+
onProgress(chunkIndex, totalChunks, sig) {
|
|
185
|
+
console.log(` [${chunkIndex}/${totalChunks}] tx: ${sig}`);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
printSuccess("Content uploaded!");
|
|
190
|
+
|
|
191
|
+
if (options.json) {
|
|
192
|
+
formatOutput({ name, size: content.length, signature }, true);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(` Finalize tx: ${signature}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function handleSkillsTransfer(
|
|
199
|
+
name: string,
|
|
200
|
+
newAuthority: string,
|
|
201
|
+
options: GlobalOptions
|
|
202
|
+
) {
|
|
203
|
+
let newPubkey: PublicKey;
|
|
204
|
+
try {
|
|
205
|
+
newPubkey = new PublicKey(newAuthority);
|
|
206
|
+
} catch {
|
|
207
|
+
throw new Error(`Invalid public key: ${newAuthority}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
211
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
212
|
+
const wallet = await loadWallet(options.wallet);
|
|
213
|
+
|
|
214
|
+
printInfo(`Transferring authority of skill "${name}" to ${newPubkey.toBase58()}...`);
|
|
215
|
+
const signature = await transferAuthority(connection, wallet, name, newPubkey);
|
|
216
|
+
printSuccess("Authority transferred!");
|
|
217
|
+
|
|
218
|
+
if (options.json) {
|
|
219
|
+
formatOutput({ name, newAuthority: newPubkey.toBase58(), signature }, true);
|
|
220
|
+
} else {
|
|
221
|
+
console.log(` Transaction: ${signature}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function handleSkillsCloseBuffer(name: string, options: GlobalOptions) {
|
|
226
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
227
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
228
|
+
const wallet = await loadWallet(options.wallet);
|
|
229
|
+
|
|
230
|
+
printInfo(`Closing pending buffer for skill "${name}"...`);
|
|
231
|
+
const signature = await closeBuffer(connection, wallet, name);
|
|
232
|
+
printSuccess("Pending buffer closed and rent reclaimed!");
|
|
233
|
+
|
|
234
|
+
if (options.json) {
|
|
235
|
+
formatOutput({ name, signature }, true);
|
|
236
|
+
} else {
|
|
237
|
+
console.log(` Transaction: ${signature}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function handleSkillsDelete(name: string, options: GlobalOptions & { yes?: boolean }) {
|
|
242
|
+
if (!options.json && !options.yes && process.stdin.isTTY) {
|
|
243
|
+
console.log();
|
|
244
|
+
p.intro(pc.bgRed(pc.white(" skills delete ")));
|
|
245
|
+
p.log.warn(
|
|
246
|
+
pc.yellow("This will permanently delete the skill from the blockchain.\n") +
|
|
247
|
+
pc.dim(" · On-chain data and content will be erased\n") +
|
|
248
|
+
pc.dim(" · This action cannot be undone")
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const confirmed = await p.confirm({
|
|
252
|
+
message: `Delete skill ${pc.cyan(name)} from the blockchain?`,
|
|
253
|
+
initialValue: false,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
257
|
+
p.cancel("Deletion cancelled");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
263
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
264
|
+
const wallet = await loadWallet(options.wallet);
|
|
265
|
+
|
|
266
|
+
if (!options.json) printInfo(`Deleting skill "${name}"...`);
|
|
267
|
+
const signature = await deleteSkill(connection, wallet, name);
|
|
268
|
+
|
|
269
|
+
if (options.json) {
|
|
270
|
+
formatOutput({ name, signature }, true);
|
|
271
|
+
} else {
|
|
272
|
+
printSuccess("Skill deleted and rent reclaimed!");
|
|
273
|
+
console.log(` Transaction: ${signature}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─── Register commands ───────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
export function registerSkillsCommands(program: Command): void {
|
|
280
|
+
const skills = program
|
|
281
|
+
.command("skills")
|
|
282
|
+
.description("Skills hub commands");
|
|
283
|
+
|
|
284
|
+
// skills register
|
|
285
|
+
skills
|
|
286
|
+
.command("register <name> <author>")
|
|
287
|
+
.description("Register a new skill on-chain")
|
|
288
|
+
.action(async (name: string, author: string, _opts: any, cmd: Command) => {
|
|
289
|
+
try {
|
|
290
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
291
|
+
await handleSkillsRegister(name, author, globalOpts);
|
|
292
|
+
} catch (error: any) {
|
|
293
|
+
printError(error.message);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// skills get
|
|
299
|
+
skills
|
|
300
|
+
.command("get <name>")
|
|
301
|
+
.description("Get skill info (record, description, metadata)")
|
|
302
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
303
|
+
try {
|
|
304
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
305
|
+
await handleSkillsGet(name, globalOpts);
|
|
306
|
+
} catch (error: any) {
|
|
307
|
+
printError(error.message);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// skills content
|
|
313
|
+
skills
|
|
314
|
+
.command("content <name>")
|
|
315
|
+
.description("Read skill content")
|
|
316
|
+
.option("--hex", "Output as hex instead of text")
|
|
317
|
+
.action(async (name: string, opts: { hex?: boolean }, cmd: Command) => {
|
|
318
|
+
try {
|
|
319
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
320
|
+
await handleSkillsContent(name, { ...globalOpts, ...opts });
|
|
321
|
+
} catch (error: any) {
|
|
322
|
+
printError(error.message);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// skills set-description
|
|
328
|
+
skills
|
|
329
|
+
.command("set-description <name> <description>")
|
|
330
|
+
.description("Set or update the skill description (max 512 bytes)")
|
|
331
|
+
.action(async (name: string, description: string, _opts: any, cmd: Command) => {
|
|
332
|
+
try {
|
|
333
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
334
|
+
await handleSkillsSetDescription(name, description, globalOpts);
|
|
335
|
+
} catch (error: any) {
|
|
336
|
+
printError(error.message);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// skills set-metadata
|
|
342
|
+
skills
|
|
343
|
+
.command("set-metadata <name> <json>")
|
|
344
|
+
.description("Set or update the skill JSON metadata (max 800 bytes)")
|
|
345
|
+
.action(async (name: string, json: string, _opts: any, cmd: Command) => {
|
|
346
|
+
try {
|
|
347
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
348
|
+
await handleSkillsSetMetadata(name, json, globalOpts);
|
|
349
|
+
} catch (error: any) {
|
|
350
|
+
printError(error.message);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// skills upload
|
|
356
|
+
skills
|
|
357
|
+
.command("upload <name> <file>")
|
|
358
|
+
.description("Upload skill content from a local file (chunked)")
|
|
359
|
+
.action(async (name: string, file: string, _opts: any, cmd: Command) => {
|
|
360
|
+
try {
|
|
361
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
362
|
+
await handleSkillsUpload(name, file, globalOpts);
|
|
363
|
+
} catch (error: any) {
|
|
364
|
+
printError(error.message);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// skills transfer
|
|
370
|
+
skills
|
|
371
|
+
.command("transfer <name> <new-authority>")
|
|
372
|
+
.description("Transfer skill authority to a new address")
|
|
373
|
+
.action(async (name: string, newAuthority: string, _opts: any, cmd: Command) => {
|
|
374
|
+
try {
|
|
375
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
376
|
+
await handleSkillsTransfer(name, newAuthority, globalOpts);
|
|
377
|
+
} catch (error: any) {
|
|
378
|
+
printError(error.message);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// skills close-buffer
|
|
384
|
+
skills
|
|
385
|
+
.command("close-buffer <name>")
|
|
386
|
+
.description("Close a pending upload buffer and reclaim rent")
|
|
387
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
388
|
+
try {
|
|
389
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
390
|
+
await handleSkillsCloseBuffer(name, globalOpts);
|
|
391
|
+
} catch (error: any) {
|
|
392
|
+
printError(error.message);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// skills delete
|
|
398
|
+
skills
|
|
399
|
+
.command("delete <name>")
|
|
400
|
+
.description("Delete a skill and reclaim all rent")
|
|
401
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
402
|
+
.action(async (name: string, opts: { yes?: boolean }, cmd: Command) => {
|
|
403
|
+
try {
|
|
404
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
405
|
+
await handleSkillsDelete(name, { ...globalOpts, ...opts });
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
printError(error.message);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// ─── Local install management ─────────────────────────────────
|
|
413
|
+
|
|
414
|
+
// skills add
|
|
415
|
+
skills
|
|
416
|
+
.command("add <name>")
|
|
417
|
+
.description("Install a skill from the chain into local agent directories")
|
|
418
|
+
.option("-g, --global", "Install globally (~/<agent>/skills/) instead of project-local")
|
|
419
|
+
.option("-a, --agent <agents...>", "Target specific agents (e.g. claude-code cursor)")
|
|
420
|
+
.action(async (name: string, opts: { global?: boolean; agent?: string[] }, cmd: Command) => {
|
|
421
|
+
try {
|
|
422
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
423
|
+
await handleSkillsAdd(name, { ...globalOpts, ...opts });
|
|
424
|
+
} catch (error: any) {
|
|
425
|
+
printError(error.message);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// skills remove
|
|
431
|
+
skills
|
|
432
|
+
.command("remove <name>")
|
|
433
|
+
.description("Remove a locally installed skill")
|
|
434
|
+
.option("-g, --global", "Remove from global scope")
|
|
435
|
+
.option("-a, --agent <agents...>", "Remove from specific agents only")
|
|
436
|
+
.action(async (name: string, opts: { global?: boolean; agent?: string[] }, cmd: Command) => {
|
|
437
|
+
try {
|
|
438
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
439
|
+
await handleSkillsRemove(name, { ...globalOpts, ...opts });
|
|
440
|
+
} catch (error: any) {
|
|
441
|
+
printError(error.message);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// skills list
|
|
447
|
+
skills
|
|
448
|
+
.command("list")
|
|
449
|
+
.description("List skills installed via naracli")
|
|
450
|
+
.option("-g, --global", "List global skills instead of project-local")
|
|
451
|
+
.action(async (opts: { global?: boolean }, cmd: Command) => {
|
|
452
|
+
try {
|
|
453
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
454
|
+
await handleSkillsList({ ...globalOpts, ...opts });
|
|
455
|
+
} catch (error: any) {
|
|
456
|
+
printError(error.message);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// skills check
|
|
462
|
+
skills
|
|
463
|
+
.command("check")
|
|
464
|
+
.description("Check installed skills for available chain updates")
|
|
465
|
+
.option("-g, --global", "Check global skills")
|
|
466
|
+
.action(async (opts: { global?: boolean }, cmd: Command) => {
|
|
467
|
+
try {
|
|
468
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
469
|
+
await handleSkillsCheck({ ...globalOpts, ...opts });
|
|
470
|
+
} catch (error: any) {
|
|
471
|
+
printError(error.message);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// skills update
|
|
477
|
+
skills
|
|
478
|
+
.command("update [names...]")
|
|
479
|
+
.description("Update installed skills to the latest chain version")
|
|
480
|
+
.option("-g, --global", "Update global skills")
|
|
481
|
+
.option("-a, --agent <agents...>", "Target specific agents")
|
|
482
|
+
.action(async (names: string[], opts: { global?: boolean; agent?: string[] }, cmd: Command) => {
|
|
483
|
+
try {
|
|
484
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
485
|
+
await handleSkillsUpdate(names, { ...globalOpts, ...opts });
|
|
486
|
+
} catch (error: any) {
|
|
487
|
+
printError(error.message);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|