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/src/cli/index.ts
CHANGED
|
@@ -3,23 +3,229 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import {
|
|
7
|
+
Transaction,
|
|
8
|
+
VersionedTransaction,
|
|
9
|
+
} from "@solana/web3.js";
|
|
10
10
|
import { registerWalletCommands } from "./commands/wallet";
|
|
11
11
|
import { registerQuestCommands } from "./commands/quest";
|
|
12
|
+
import { registerSkillsCommands } from "./commands/skills";
|
|
13
|
+
import { registerZkIdCommands } from "./commands/zkid";
|
|
14
|
+
import {
|
|
15
|
+
handleWalletAddress,
|
|
16
|
+
handleWalletBalance,
|
|
17
|
+
handleTokenBalance,
|
|
18
|
+
handleTxStatus,
|
|
19
|
+
handleTransferSol,
|
|
20
|
+
handleTransferToken,
|
|
21
|
+
} from "./commands/wallet";
|
|
22
|
+
import { loadWallet, getRpcUrl } from "./utils/wallet";
|
|
23
|
+
import { NaraSDK } from "nara-sdk";
|
|
24
|
+
import { printError, printInfo, printSuccess } from "./utils/output";
|
|
25
|
+
import type {
|
|
26
|
+
GlobalOptions,
|
|
27
|
+
WalletBalanceOptions,
|
|
28
|
+
TokenBalanceOptions,
|
|
29
|
+
TxStatusOptions,
|
|
30
|
+
TransferSolOptions,
|
|
31
|
+
TransferTokenOptions,
|
|
32
|
+
} from "./types";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Poll for transaction confirmation via HTTP
|
|
36
|
+
*/
|
|
37
|
+
async function pollConfirmation(
|
|
38
|
+
connection: any,
|
|
39
|
+
signature: string,
|
|
40
|
+
timeoutMs = 15000,
|
|
41
|
+
intervalMs = 1000
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const start = Date.now();
|
|
44
|
+
while (Date.now() - start < timeoutMs) {
|
|
45
|
+
const { value } = await connection.getSignatureStatuses([signature]);
|
|
46
|
+
const status = value?.[0];
|
|
47
|
+
if (status) {
|
|
48
|
+
if (status.err) {
|
|
49
|
+
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
50
|
+
}
|
|
51
|
+
if (status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
56
|
+
}
|
|
57
|
+
throw new Error("Transaction confirmation timeout");
|
|
58
|
+
}
|
|
12
59
|
|
|
13
60
|
/**
|
|
14
61
|
* Register all CLI commands
|
|
15
62
|
* @param program Commander program
|
|
16
63
|
*/
|
|
17
64
|
export function registerCommands(program: Command): void {
|
|
18
|
-
//
|
|
19
|
-
registerConfigCommands(program);
|
|
20
|
-
registerPoolCommands(program);
|
|
21
|
-
registerSwapCommands(program);
|
|
22
|
-
registerMigrateCommands(program);
|
|
65
|
+
// wallet (create, import only)
|
|
23
66
|
registerWalletCommands(program);
|
|
67
|
+
|
|
68
|
+
// quest
|
|
24
69
|
registerQuestCommands(program);
|
|
70
|
+
|
|
71
|
+
// skills
|
|
72
|
+
registerSkillsCommands(program);
|
|
73
|
+
|
|
74
|
+
// zkid
|
|
75
|
+
registerZkIdCommands(program);
|
|
76
|
+
|
|
77
|
+
// Top-level: address
|
|
78
|
+
program
|
|
79
|
+
.command("address")
|
|
80
|
+
.description("Show wallet address")
|
|
81
|
+
.action(async () => {
|
|
82
|
+
const opts = program.opts() as GlobalOptions;
|
|
83
|
+
try {
|
|
84
|
+
await handleWalletAddress(opts);
|
|
85
|
+
} catch (error: any) {
|
|
86
|
+
printError(error.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Top-level: balance
|
|
92
|
+
program
|
|
93
|
+
.command("balance")
|
|
94
|
+
.description("Check NARA balance")
|
|
95
|
+
.argument("[address]", "Wallet address (optional, defaults to current wallet)")
|
|
96
|
+
.action(async (address: string | undefined) => {
|
|
97
|
+
const opts = program.opts() as WalletBalanceOptions;
|
|
98
|
+
try {
|
|
99
|
+
await handleWalletBalance(address, opts);
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
printError(error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Top-level: token-balance
|
|
107
|
+
program
|
|
108
|
+
.command("token-balance <token-address>")
|
|
109
|
+
.description("Check token balance")
|
|
110
|
+
.option("--owner <address>", "Owner address (optional, defaults to current wallet)")
|
|
111
|
+
.action(async (tokenAddress: string, options: { owner?: string }) => {
|
|
112
|
+
const opts = program.opts() as TokenBalanceOptions;
|
|
113
|
+
try {
|
|
114
|
+
await handleTokenBalance(tokenAddress, { ...opts, ...options });
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
printError(error.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Top-level: tx-status
|
|
122
|
+
program
|
|
123
|
+
.command("tx-status <signature>")
|
|
124
|
+
.description("Check transaction status")
|
|
125
|
+
.action(async (signature: string) => {
|
|
126
|
+
const opts = program.opts() as TxStatusOptions;
|
|
127
|
+
try {
|
|
128
|
+
await handleTxStatus(signature, opts);
|
|
129
|
+
} catch (error: any) {
|
|
130
|
+
printError(error.message);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Top-level: transfer
|
|
136
|
+
program
|
|
137
|
+
.command("transfer <to> <amount>")
|
|
138
|
+
.description("Transfer NARA to another wallet")
|
|
139
|
+
.option("-e, --export-tx", "Export unsigned transaction", false)
|
|
140
|
+
.action(async (to: string, amount: string, options: { exportTx?: boolean }) => {
|
|
141
|
+
const opts = program.opts() as TransferSolOptions;
|
|
142
|
+
try {
|
|
143
|
+
await handleTransferSol(to, amount, { ...opts, ...options });
|
|
144
|
+
} catch (error: any) {
|
|
145
|
+
printError(error.message);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Top-level: transfer-token
|
|
151
|
+
program
|
|
152
|
+
.command("transfer-token <token-address> <to> <amount>")
|
|
153
|
+
.description("Transfer tokens to another wallet")
|
|
154
|
+
.option("--decimals <number>", "Token decimals", "6")
|
|
155
|
+
.option("-e, --export-tx", "Export unsigned transaction", false)
|
|
156
|
+
.action(async (tokenAddress: string, to: string, amount: string, options: { decimals?: string; exportTx?: boolean }) => {
|
|
157
|
+
const opts = program.opts() as TransferTokenOptions;
|
|
158
|
+
try {
|
|
159
|
+
await handleTransferToken(tokenAddress, to, amount, { ...opts, decimals: options.decimals ? parseInt(options.decimals) : undefined, exportTx: options.exportTx });
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
printError(error.message);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Top-level: sign
|
|
167
|
+
program
|
|
168
|
+
.command("sign <base64-tx>")
|
|
169
|
+
.description("Sign a base64-encoded transaction")
|
|
170
|
+
.option("--send", "Send the signed transaction", false)
|
|
171
|
+
.action(async (base64Tx: string, options: { send?: boolean }) => {
|
|
172
|
+
const opts = program.opts() as GlobalOptions;
|
|
173
|
+
try {
|
|
174
|
+
const wallet = await loadWallet(opts.wallet);
|
|
175
|
+
const buf = Buffer.from(base64Tx, "base64");
|
|
176
|
+
|
|
177
|
+
// Try VersionedTransaction first, fall back to legacy
|
|
178
|
+
let tx: Transaction | VersionedTransaction;
|
|
179
|
+
try {
|
|
180
|
+
tx = VersionedTransaction.deserialize(new Uint8Array(buf));
|
|
181
|
+
} catch {
|
|
182
|
+
tx = Transaction.from(buf);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Sign
|
|
186
|
+
if (tx instanceof VersionedTransaction) {
|
|
187
|
+
tx.sign([wallet]);
|
|
188
|
+
} else {
|
|
189
|
+
tx.sign(wallet);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options.send) {
|
|
193
|
+
const rpcUrl = getRpcUrl(opts.rpcUrl);
|
|
194
|
+
const sdk = new NaraSDK({ rpcUrl, commitment: "confirmed" });
|
|
195
|
+
const connection = sdk.getConnection();
|
|
196
|
+
|
|
197
|
+
printInfo("Sending transaction...");
|
|
198
|
+
let signature: string;
|
|
199
|
+
if (tx instanceof VersionedTransaction) {
|
|
200
|
+
signature = await connection.sendTransaction(tx, { maxRetries: 3 });
|
|
201
|
+
} else {
|
|
202
|
+
signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
printInfo("Confirming transaction...");
|
|
206
|
+
await pollConfirmation(connection, signature);
|
|
207
|
+
|
|
208
|
+
if (opts.json) {
|
|
209
|
+
console.log(JSON.stringify({ signature }, null, 2));
|
|
210
|
+
} else {
|
|
211
|
+
printSuccess("Transaction sent!");
|
|
212
|
+
console.log(`Signature: ${signature}`);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// Output signed base64
|
|
216
|
+
const serialized = tx instanceof VersionedTransaction
|
|
217
|
+
? Buffer.from(tx.serialize()).toString("base64")
|
|
218
|
+
: Buffer.from(tx.serialize()).toString("base64");
|
|
219
|
+
|
|
220
|
+
if (opts.json) {
|
|
221
|
+
console.log(JSON.stringify({ transaction: serialized }, null, 2));
|
|
222
|
+
} else {
|
|
223
|
+
console.log(serialized);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error: any) {
|
|
227
|
+
printError(error.message);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
25
231
|
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import { Writable } from 'stream';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
|
|
5
|
+
// Silent writable stream to prevent readline from echoing input
|
|
6
|
+
const silentOutput = new Writable({
|
|
7
|
+
write(_chunk, _encoding, callback) {
|
|
8
|
+
callback();
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export interface SearchItem<T> {
|
|
13
|
+
value: T;
|
|
14
|
+
label: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LockedSection<T> {
|
|
19
|
+
title: string;
|
|
20
|
+
items: SearchItem<T>[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SearchMultiselectOptions<T> {
|
|
24
|
+
message: string;
|
|
25
|
+
items: SearchItem<T>[];
|
|
26
|
+
maxVisible?: number;
|
|
27
|
+
initialSelected?: T[];
|
|
28
|
+
/** If true, require at least one item to be selected before submitting */
|
|
29
|
+
required?: boolean;
|
|
30
|
+
/** Locked section shown above the searchable list - items are always selected and can't be toggled */
|
|
31
|
+
lockedSection?: LockedSection<T>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const S_STEP_ACTIVE = pc.green('◆');
|
|
35
|
+
const S_STEP_CANCEL = pc.red('■');
|
|
36
|
+
const S_STEP_SUBMIT = pc.green('◇');
|
|
37
|
+
const S_RADIO_ACTIVE = pc.green('●');
|
|
38
|
+
const S_RADIO_INACTIVE = pc.dim('○');
|
|
39
|
+
const S_CHECKBOX_LOCKED = pc.green('✓');
|
|
40
|
+
const S_BULLET = pc.green('•');
|
|
41
|
+
const S_BAR = pc.dim('│');
|
|
42
|
+
const S_BAR_H = pc.dim('─');
|
|
43
|
+
|
|
44
|
+
export const cancelSymbol = Symbol('cancel');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Interactive search multiselect prompt.
|
|
48
|
+
* Allows users to filter a long list by typing and select multiple items.
|
|
49
|
+
* Optionally supports a "locked" section that displays always-selected items.
|
|
50
|
+
*/
|
|
51
|
+
export async function searchMultiselect<T>(
|
|
52
|
+
options: SearchMultiselectOptions<T>
|
|
53
|
+
): Promise<T[] | symbol> {
|
|
54
|
+
const {
|
|
55
|
+
message,
|
|
56
|
+
items,
|
|
57
|
+
maxVisible = 8,
|
|
58
|
+
initialSelected = [],
|
|
59
|
+
required = false,
|
|
60
|
+
lockedSection,
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const rl = readline.createInterface({
|
|
65
|
+
input: process.stdin,
|
|
66
|
+
output: silentOutput,
|
|
67
|
+
terminal: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Enable raw mode for keypress detection
|
|
71
|
+
if (process.stdin.isTTY) {
|
|
72
|
+
process.stdin.setRawMode(true);
|
|
73
|
+
}
|
|
74
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
75
|
+
|
|
76
|
+
let query = '';
|
|
77
|
+
let cursor = 0;
|
|
78
|
+
const selected = new Set<T>(initialSelected);
|
|
79
|
+
let lastRenderHeight = 0;
|
|
80
|
+
|
|
81
|
+
// Locked items are always included in the result
|
|
82
|
+
const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
|
|
83
|
+
|
|
84
|
+
const filter = (item: SearchItem<T>, q: string): boolean => {
|
|
85
|
+
if (!q) return true;
|
|
86
|
+
const lowerQ = q.toLowerCase();
|
|
87
|
+
return (
|
|
88
|
+
item.label.toLowerCase().includes(lowerQ) ||
|
|
89
|
+
String(item.value).toLowerCase().includes(lowerQ)
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getFiltered = (): SearchItem<T>[] => {
|
|
94
|
+
return items.filter((item) => filter(item, query));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const clearRender = (): void => {
|
|
98
|
+
if (lastRenderHeight > 0) {
|
|
99
|
+
// Move up and clear each line
|
|
100
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
101
|
+
for (let i = 0; i < lastRenderHeight; i++) {
|
|
102
|
+
process.stdout.write('\x1b[2K\x1b[1B');
|
|
103
|
+
}
|
|
104
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const render = (state: 'active' | 'submit' | 'cancel' = 'active'): void => {
|
|
109
|
+
clearRender();
|
|
110
|
+
|
|
111
|
+
const lines: string[] = [];
|
|
112
|
+
const filtered = getFiltered();
|
|
113
|
+
|
|
114
|
+
// Header
|
|
115
|
+
const icon =
|
|
116
|
+
state === 'active' ? S_STEP_ACTIVE : state === 'cancel' ? S_STEP_CANCEL : S_STEP_SUBMIT;
|
|
117
|
+
lines.push(`${icon} ${pc.bold(message)}`);
|
|
118
|
+
|
|
119
|
+
if (state === 'active') {
|
|
120
|
+
// Locked section (universal agents)
|
|
121
|
+
if (lockedSection && lockedSection.items.length > 0) {
|
|
122
|
+
lines.push(`${S_BAR}`);
|
|
123
|
+
const lockedTitle = `${pc.bold(lockedSection.title)} ${pc.dim('── always included')}`;
|
|
124
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
|
|
125
|
+
for (const item of lockedSection.items) {
|
|
126
|
+
lines.push(`${S_BAR} ${S_BULLET} ${pc.bold(item.label)}`);
|
|
127
|
+
}
|
|
128
|
+
lines.push(`${S_BAR}`);
|
|
129
|
+
lines.push(
|
|
130
|
+
`${S_BAR} ${S_BAR_H}${S_BAR_H} ${pc.bold('Additional agents')} ${S_BAR_H.repeat(29)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Search input
|
|
135
|
+
const searchLine = `${S_BAR} ${pc.dim('Search:')} ${query}${pc.inverse(' ')}`;
|
|
136
|
+
lines.push(searchLine);
|
|
137
|
+
|
|
138
|
+
// Hint
|
|
139
|
+
lines.push(`${S_BAR} ${pc.dim('↑↓ move, space select, enter confirm')}`);
|
|
140
|
+
lines.push(`${S_BAR}`);
|
|
141
|
+
|
|
142
|
+
// Items
|
|
143
|
+
const visibleStart = Math.max(
|
|
144
|
+
0,
|
|
145
|
+
Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible)
|
|
146
|
+
);
|
|
147
|
+
const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
|
|
148
|
+
const visibleItems = filtered.slice(visibleStart, visibleEnd);
|
|
149
|
+
|
|
150
|
+
if (filtered.length === 0) {
|
|
151
|
+
lines.push(`${S_BAR} ${pc.dim('No matches found')}`);
|
|
152
|
+
} else {
|
|
153
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
154
|
+
const item = visibleItems[i]!;
|
|
155
|
+
const actualIndex = visibleStart + i;
|
|
156
|
+
const isSelected = selected.has(item.value);
|
|
157
|
+
const isCursor = actualIndex === cursor;
|
|
158
|
+
|
|
159
|
+
const radio = isSelected ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
|
|
160
|
+
const label = isCursor ? pc.underline(item.label) : item.label;
|
|
161
|
+
const hint = item.hint ? pc.dim(` (${item.hint})`) : '';
|
|
162
|
+
|
|
163
|
+
const prefix = isCursor ? pc.cyan('❯') : ' ';
|
|
164
|
+
lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Show count if more items
|
|
168
|
+
const hiddenBefore = visibleStart;
|
|
169
|
+
const hiddenAfter = filtered.length - visibleEnd;
|
|
170
|
+
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
171
|
+
const parts: string[] = [];
|
|
172
|
+
if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
|
|
173
|
+
if (hiddenAfter > 0) parts.push(`↓ ${hiddenAfter} more`);
|
|
174
|
+
lines.push(`${S_BAR} ${pc.dim(parts.join(' '))}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Selected summary (include locked items)
|
|
179
|
+
lines.push(`${S_BAR}`);
|
|
180
|
+
const allSelectedLabels = [
|
|
181
|
+
...(lockedSection ? lockedSection.items.map((i) => i.label) : []),
|
|
182
|
+
...items.filter((item) => selected.has(item.value)).map((item) => item.label),
|
|
183
|
+
];
|
|
184
|
+
if (allSelectedLabels.length === 0) {
|
|
185
|
+
lines.push(`${S_BAR} ${pc.dim('Selected: (none)')}`);
|
|
186
|
+
} else {
|
|
187
|
+
const summary =
|
|
188
|
+
allSelectedLabels.length <= 3
|
|
189
|
+
? allSelectedLabels.join(', ')
|
|
190
|
+
: `${allSelectedLabels.slice(0, 3).join(', ')} +${allSelectedLabels.length - 3} more`;
|
|
191
|
+
lines.push(`${S_BAR} ${pc.green('Selected:')} ${summary}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
lines.push(`${pc.dim('└')}`);
|
|
195
|
+
} else if (state === 'submit') {
|
|
196
|
+
// Final state - show what was selected (including locked)
|
|
197
|
+
const allSelectedLabels = [
|
|
198
|
+
...(lockedSection ? lockedSection.items.map((i) => i.label) : []),
|
|
199
|
+
...items.filter((item) => selected.has(item.value)).map((item) => item.label),
|
|
200
|
+
];
|
|
201
|
+
lines.push(`${S_BAR} ${pc.dim(allSelectedLabels.join(', '))}`);
|
|
202
|
+
} else if (state === 'cancel') {
|
|
203
|
+
lines.push(`${S_BAR} ${pc.strikethrough(pc.dim('Cancelled'))}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
207
|
+
lastRenderHeight = lines.length;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const cleanup = (): void => {
|
|
211
|
+
process.stdin.removeListener('keypress', keypressHandler);
|
|
212
|
+
if (process.stdin.isTTY) {
|
|
213
|
+
process.stdin.setRawMode(false);
|
|
214
|
+
}
|
|
215
|
+
rl.close();
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const submit = (): void => {
|
|
219
|
+
// If required and no locked items, don't allow submitting with no selection
|
|
220
|
+
if (required && selected.size === 0 && lockedValues.length === 0) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
render('submit');
|
|
224
|
+
cleanup();
|
|
225
|
+
// Include locked values in the result
|
|
226
|
+
resolve([...lockedValues, ...Array.from(selected)]);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const cancel = (): void => {
|
|
230
|
+
render('cancel');
|
|
231
|
+
cleanup();
|
|
232
|
+
resolve(cancelSymbol);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Handle keypresses
|
|
236
|
+
const keypressHandler = (_str: string, key: readline.Key): void => {
|
|
237
|
+
if (!key) return;
|
|
238
|
+
|
|
239
|
+
const filtered = getFiltered();
|
|
240
|
+
|
|
241
|
+
if (key.name === 'return') {
|
|
242
|
+
submit();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
|
247
|
+
cancel();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (key.name === 'up') {
|
|
252
|
+
cursor = Math.max(0, cursor - 1);
|
|
253
|
+
render();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (key.name === 'down') {
|
|
258
|
+
cursor = Math.min(filtered.length - 1, cursor + 1);
|
|
259
|
+
render();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (key.name === 'space') {
|
|
264
|
+
const item = filtered[cursor];
|
|
265
|
+
if (item) {
|
|
266
|
+
if (selected.has(item.value)) {
|
|
267
|
+
selected.delete(item.value);
|
|
268
|
+
} else {
|
|
269
|
+
selected.add(item.value);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
render();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (key.name === 'backspace') {
|
|
277
|
+
query = query.slice(0, -1);
|
|
278
|
+
cursor = 0;
|
|
279
|
+
render();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Regular character input
|
|
284
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
285
|
+
query += key.sequence;
|
|
286
|
+
cursor = 0;
|
|
287
|
+
render();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
process.stdin.on('keypress', keypressHandler);
|
|
293
|
+
|
|
294
|
+
// Initial render
|
|
295
|
+
render();
|
|
296
|
+
});
|
|
297
|
+
}
|
package/src/cli/types.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* CLI-specific types and interfaces
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { PublicKey } from "@solana/web3.js";
|
|
6
|
-
|
|
7
5
|
/**
|
|
8
6
|
* Global options available on all commands
|
|
9
7
|
*/
|
|
@@ -16,142 +14,6 @@ export interface GlobalOptions {
|
|
|
16
14
|
json?: boolean;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
/**
|
|
20
|
-
* Config create command options
|
|
21
|
-
*/
|
|
22
|
-
export interface ConfigCreateOptions extends GlobalOptions {
|
|
23
|
-
/** Fee claimer wallet address */
|
|
24
|
-
feeClaimer?: string;
|
|
25
|
-
/** Leftover token receiver wallet address */
|
|
26
|
-
leftoverReceiver?: string;
|
|
27
|
-
/** Total token supply */
|
|
28
|
-
totalSupply?: number;
|
|
29
|
-
/** Initial market cap */
|
|
30
|
-
initialMcap?: number;
|
|
31
|
-
/** Migration market cap */
|
|
32
|
-
migrationMcap?: number;
|
|
33
|
-
/** Export unsigned transaction */
|
|
34
|
-
exportTx?: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Pool create command options
|
|
39
|
-
*/
|
|
40
|
-
export interface PoolCreateOptions extends GlobalOptions {
|
|
41
|
-
/** Token name */
|
|
42
|
-
name: string;
|
|
43
|
-
/** Token symbol */
|
|
44
|
-
symbol: string;
|
|
45
|
-
/** Metadata URI */
|
|
46
|
-
uri: string;
|
|
47
|
-
/** DBC config address */
|
|
48
|
-
dbcConfig: string;
|
|
49
|
-
/** Pool creator address */
|
|
50
|
-
creator?: string;
|
|
51
|
-
/** Export unsigned transaction */
|
|
52
|
-
exportTx?: boolean;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Pool create with buy command options
|
|
57
|
-
*/
|
|
58
|
-
export interface PoolCreateWithBuyOptions extends PoolCreateOptions {
|
|
59
|
-
/** Initial buy amount in SOL */
|
|
60
|
-
amount: number;
|
|
61
|
-
/** Buyer address */
|
|
62
|
-
buyer?: string;
|
|
63
|
-
/** Token receiver address */
|
|
64
|
-
receiver?: string;
|
|
65
|
-
/** Slippage in basis points */
|
|
66
|
-
slippage?: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Pool info command options
|
|
71
|
-
*/
|
|
72
|
-
export interface PoolInfoOptions extends GlobalOptions {
|
|
73
|
-
/** Token address */
|
|
74
|
-
tokenAddress: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Swap buy command options
|
|
79
|
-
*/
|
|
80
|
-
export interface SwapBuyOptions extends GlobalOptions {
|
|
81
|
-
/** Token address */
|
|
82
|
-
tokenAddress: string;
|
|
83
|
-
/** Amount in SOL */
|
|
84
|
-
amount: number;
|
|
85
|
-
/** Slippage in basis points */
|
|
86
|
-
slippage?: number;
|
|
87
|
-
/** Swap mode */
|
|
88
|
-
mode?: string;
|
|
89
|
-
/** Export unsigned transaction */
|
|
90
|
-
exportTx?: boolean;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Swap sell command options
|
|
95
|
-
*/
|
|
96
|
-
export interface SwapSellOptions extends GlobalOptions {
|
|
97
|
-
/** Token address */
|
|
98
|
-
tokenAddress: string;
|
|
99
|
-
/** Amount in tokens */
|
|
100
|
-
amount: number;
|
|
101
|
-
/** Token decimals */
|
|
102
|
-
decimals?: number;
|
|
103
|
-
/** Slippage in basis points */
|
|
104
|
-
slippage?: number;
|
|
105
|
-
/** Swap mode */
|
|
106
|
-
mode?: string;
|
|
107
|
-
/** Export unsigned transaction */
|
|
108
|
-
exportTx?: boolean;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Swap quote command options
|
|
113
|
-
*/
|
|
114
|
-
export interface SwapQuoteOptions extends GlobalOptions {
|
|
115
|
-
/** Token address */
|
|
116
|
-
tokenAddress: string;
|
|
117
|
-
/** Amount */
|
|
118
|
-
amount: number;
|
|
119
|
-
/** Direction: buy or sell */
|
|
120
|
-
direction: string;
|
|
121
|
-
/** Token decimals (for sell only) */
|
|
122
|
-
decimals?: number;
|
|
123
|
-
/** Slippage in basis points */
|
|
124
|
-
slippage?: number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Migrate launch command options
|
|
129
|
-
*/
|
|
130
|
-
export interface MigrateLaunchOptions extends GlobalOptions {
|
|
131
|
-
/** Token address */
|
|
132
|
-
tokenAddress: string;
|
|
133
|
-
/** Export unsigned transaction */
|
|
134
|
-
exportTx?: boolean;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Migrate create locker command options
|
|
139
|
-
*/
|
|
140
|
-
export interface MigrateCreateLockerOptions extends GlobalOptions {
|
|
141
|
-
/** Token address */
|
|
142
|
-
tokenAddress: string;
|
|
143
|
-
/** Export unsigned transaction */
|
|
144
|
-
exportTx?: boolean;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Migrate check command options
|
|
149
|
-
*/
|
|
150
|
-
export interface MigrateCheckOptions extends GlobalOptions {
|
|
151
|
-
/** Token address */
|
|
152
|
-
tokenAddress: string;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
17
|
/**
|
|
156
18
|
* Wallet balance command options
|
|
157
19
|
*/
|