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.
Files changed (41) hide show
  1. package/README.md +101 -114
  2. package/bin/nara-cli.ts +0 -20
  3. package/dist/nara-cli.mjs +49930 -2222
  4. package/index.ts +10 -58
  5. package/package.json +7 -6
  6. package/src/cli/commands/quest.ts +8 -7
  7. package/src/cli/commands/skills.ts +491 -0
  8. package/src/cli/commands/skillsInstall.ts +793 -0
  9. package/src/cli/commands/wallet.ts +13 -114
  10. package/src/cli/commands/zkid.ts +410 -0
  11. package/src/cli/index.ts +215 -9
  12. package/src/cli/prompts/searchMultiselect.ts +297 -0
  13. package/src/cli/types.ts +0 -138
  14. package/src/cli/utils/transaction.ts +1 -1
  15. package/src/cli/utils/validation.ts +0 -40
  16. package/src/cli/utils/wallet.ts +3 -1
  17. package/src/tests/helpers.ts +78 -0
  18. package/src/tests/skills.e2e.test.ts +126 -0
  19. package/src/tests/skills.test.ts +192 -0
  20. package/src/tests/test_skill.md +18 -0
  21. package/src/tests/zkid.e2e.test.ts +128 -0
  22. package/src/tests/zkid.test.ts +153 -0
  23. package/src/types/snarkjs.d.ts +4 -1
  24. package/dist/quest/nara_quest.json +0 -534
  25. package/dist/zk/answer_proof.wasm +0 -0
  26. package/dist/zk/answer_proof_final.zkey +0 -0
  27. package/src/cli/commands/config.ts +0 -125
  28. package/src/cli/commands/migrate.ts +0 -270
  29. package/src/cli/commands/pool.ts +0 -364
  30. package/src/cli/commands/swap.ts +0 -349
  31. package/src/cli/quest/nara_quest.json +0 -534
  32. package/src/cli/quest/nara_quest_types.ts +0 -540
  33. package/src/cli/zk/answer_proof.wasm +0 -0
  34. package/src/cli/zk/answer_proof_final.zkey +0 -0
  35. package/src/client.ts +0 -96
  36. package/src/config.ts +0 -132
  37. package/src/constants.ts +0 -35
  38. package/src/migrate.ts +0 -222
  39. package/src/pool.ts +0 -259
  40. package/src/quest.ts +0 -387
  41. 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
- * This SDK provides functions to interact with the Nara chain.
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
- } from "./src/quest";
73
-
74
- // Re-export commonly used types from dependencies
75
- export { PublicKey, Keypair, Transaction } from "@solana/web3.js";
76
- export { default as BN } from "bn.js";
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.17",
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 --packages=external && mkdir -p dist/zk dist/quest && cp src/cli/zk/answer_proof.wasm src/cli/zk/answer_proof_final.zkey dist/zk/ && cp src/cli/quest/nara_quest.json dist/quest/ && 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",
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
- "@coral-xyz/anchor": "^0.32.1",
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
- "snarkjs": "^0.7.6"
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 "../../quest";
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} NSO`,
90
- totalReward: `${quest.totalReward} NSO`,
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} NSO`);
105
- console.log(` Total reward: ${quest.totalReward} NSO`);
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} NSO (winner ${reward.winner})`);
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
+ }