minara 0.1.4 → 0.2.0
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/LICENSE +21 -0
- package/README.md +172 -65
- package/dist/api/auth.d.ts +5 -1
- package/dist/api/auth.js +11 -0
- package/dist/api/payment.d.ts +17 -0
- package/dist/api/payment.js +39 -0
- package/dist/api/perps.d.ts +2 -0
- package/dist/api/perps.js +4 -0
- package/dist/api/tokens.d.ts +4 -0
- package/dist/api/tokens.js +8 -0
- package/dist/commands/assets.js +89 -8
- package/dist/commands/chat.js +153 -62
- package/dist/commands/config.js +82 -5
- package/dist/commands/copy-trade.js +10 -4
- package/dist/commands/deposit.js +5 -1
- package/dist/commands/discover.js +31 -4
- package/dist/commands/limit-order.js +16 -8
- package/dist/commands/login.js +88 -5
- package/dist/commands/perps.js +48 -13
- package/dist/commands/premium.d.ts +2 -0
- package/dist/commands/premium.js +398 -0
- package/dist/commands/swap.js +29 -13
- package/dist/commands/transfer.js +17 -11
- package/dist/commands/withdraw.js +17 -11
- package/dist/config.d.ts +2 -0
- package/dist/config.js +1 -0
- package/dist/formatters.d.ts +56 -0
- package/dist/formatters.js +376 -0
- package/dist/index.js +11 -1
- package/dist/touchid.d.ts +18 -0
- package/dist/touchid.js +181 -0
- package/dist/types.d.ts +67 -6
- package/dist/utils.d.ts +34 -0
- package/dist/utils.js +107 -1
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -22,25 +22,51 @@ async function* parseSSE(response) {
|
|
|
22
22
|
const lines = buffer.split('\n');
|
|
23
23
|
buffer = lines.pop() ?? '';
|
|
24
24
|
for (const line of lines) {
|
|
25
|
-
if (!line
|
|
25
|
+
if (!line)
|
|
26
|
+
continue;
|
|
27
|
+
// Handle AI SDK v5 streaming format: "type:value"
|
|
28
|
+
const colonIndex = line.indexOf(':');
|
|
29
|
+
if (colonIndex !== -1) {
|
|
30
|
+
const type = line.slice(0, colonIndex);
|
|
31
|
+
const data = line.slice(colonIndex + 1);
|
|
32
|
+
if (type === '0' && data) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(data);
|
|
35
|
+
if (typeof parsed === 'string') {
|
|
36
|
+
yield parsed;
|
|
37
|
+
}
|
|
38
|
+
else if (parsed.text) {
|
|
39
|
+
yield parsed.text;
|
|
40
|
+
}
|
|
41
|
+
else if (parsed.content) {
|
|
42
|
+
yield parsed.content;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
yield data;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
26
49
|
continue;
|
|
27
|
-
const data = line.slice(5).trim();
|
|
28
|
-
if (data === '[DONE]')
|
|
29
|
-
return;
|
|
30
|
-
try {
|
|
31
|
-
const parsed = JSON.parse(data);
|
|
32
|
-
const text = parsed?.choices?.[0]?.delta?.content
|
|
33
|
-
?? parsed?.content
|
|
34
|
-
?? parsed?.text
|
|
35
|
-
?? parsed?.data?.text
|
|
36
|
-
?? (typeof parsed === 'string' ? parsed : null);
|
|
37
|
-
if (text)
|
|
38
|
-
yield text;
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
// Handle standard SSE format: "data:json"
|
|
52
|
+
if (line.startsWith('data:')) {
|
|
53
|
+
const data = line.slice(5).trim();
|
|
54
|
+
if (data === '[DONE]')
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(data);
|
|
58
|
+
const text = parsed?.choices?.[0]?.delta?.content
|
|
59
|
+
?? parsed?.content
|
|
60
|
+
?? parsed?.text
|
|
61
|
+
?? parsed?.data?.text
|
|
62
|
+
?? (typeof parsed === 'string' ? parsed : null);
|
|
63
|
+
if (text)
|
|
64
|
+
yield text;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
if (data)
|
|
68
|
+
yield data;
|
|
69
|
+
}
|
|
44
70
|
}
|
|
45
71
|
}
|
|
46
72
|
}
|
|
@@ -50,13 +76,13 @@ async function* parseSSE(response) {
|
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
78
|
export const chatCommand = new Command('chat')
|
|
53
|
-
.description('Chat with Minara AI assistant')
|
|
54
|
-
.argument('[message]', 'Send a single message
|
|
79
|
+
.description('Chat with Minara AI assistant (interactive REPL when no message given)')
|
|
80
|
+
.argument('[message]', 'Send a single message and exit')
|
|
55
81
|
.option('-c, --chat-id <id>', 'Continue existing chat')
|
|
56
82
|
.option('--list', 'List past chats')
|
|
57
83
|
.option('--history <chatId>', 'Show chat history')
|
|
58
84
|
.option('--thinking', 'Enable thinking/degen mode')
|
|
59
|
-
.option('--
|
|
85
|
+
.option('--quality', 'Use quality mode instead of the default fast mode')
|
|
60
86
|
.action(wrapAction(async (messageArg, opts) => {
|
|
61
87
|
const creds = requireAuth();
|
|
62
88
|
// ── List chats ───────────────────────────────────────────────────────
|
|
@@ -89,43 +115,16 @@ export const chatCommand = new Command('chat')
|
|
|
89
115
|
}
|
|
90
116
|
// ── Chat context ─────────────────────────────────────────────────────
|
|
91
117
|
let chatId = opts?.chatId;
|
|
92
|
-
if (!chatId && !messageArg) {
|
|
93
|
-
const mode = await select({
|
|
94
|
-
message: 'Chat mode:',
|
|
95
|
-
choices: [
|
|
96
|
-
{ name: 'Start a new conversation', value: 'new' },
|
|
97
|
-
{ name: 'Continue existing conversation', value: 'continue' },
|
|
98
|
-
],
|
|
99
|
-
});
|
|
100
|
-
if (mode === 'continue') {
|
|
101
|
-
const spin = spinner('Fetching chats…');
|
|
102
|
-
const res = await listChats(creds.accessToken);
|
|
103
|
-
spin.stop();
|
|
104
|
-
const chats = res.data;
|
|
105
|
-
if (chats && chats.length > 0) {
|
|
106
|
-
chatId = await select({
|
|
107
|
-
message: 'Select chat:',
|
|
108
|
-
choices: chats.map((c) => ({
|
|
109
|
-
name: `${(c.chatId).slice(0, 12)}… ${c.name ?? '(untitled)'}`,
|
|
110
|
-
value: c.chatId,
|
|
111
|
-
})),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
info('No existing chats. Starting new.');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
118
|
if (!chatId)
|
|
120
119
|
chatId = randomUUID();
|
|
121
|
-
// ──
|
|
120
|
+
// ── Stream a response and print to stdout ────────────────────────────
|
|
122
121
|
async function sendAndPrint(msg) {
|
|
123
|
-
process.stdout.write(
|
|
122
|
+
process.stdout.write(chalk.green.bold('Minara') + chalk.dim(': '));
|
|
124
123
|
const response = await sendChatStream(creds.accessToken, {
|
|
125
124
|
chatId,
|
|
126
125
|
message: { role: 'user', content: msg },
|
|
127
126
|
thinking: opts?.thinking,
|
|
128
|
-
|
|
127
|
+
workMode: opts?.quality ? 'quality' : 'fast',
|
|
129
128
|
chartOptions: { chartsCountRecommendedLimit: 0 },
|
|
130
129
|
});
|
|
131
130
|
if (!response.ok) {
|
|
@@ -134,29 +133,73 @@ export const chatCommand = new Command('chat')
|
|
|
134
133
|
error(`API error ${response.status}: ${body}`);
|
|
135
134
|
return;
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
// Debug: Check if response body exists
|
|
137
|
+
if (!response.body) {
|
|
138
|
+
console.log(chalk.dim('(No response body)'));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let hasContent = false;
|
|
142
|
+
try {
|
|
143
|
+
for await (const chunk of parseSSE(response)) {
|
|
144
|
+
if (chunk) {
|
|
145
|
+
process.stdout.write(chunk);
|
|
146
|
+
hasContent = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
// Ignore cancellation errors
|
|
152
|
+
if (err && typeof err === 'object' && 'name' in err && err.name === 'AbortError') {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (process.env.DEBUG) {
|
|
156
|
+
console.log(chalk.dim(`\n[Stream error: ${err}]`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!hasContent) {
|
|
160
|
+
console.log(chalk.dim('(No response content)'));
|
|
139
161
|
}
|
|
140
162
|
console.log('\n');
|
|
141
163
|
}
|
|
164
|
+
// ── Single-shot mode: minara chat "message" ──────────────────────────
|
|
142
165
|
if (messageArg) {
|
|
143
166
|
await sendAndPrint(messageArg);
|
|
144
167
|
return;
|
|
145
168
|
}
|
|
146
|
-
// ── REPL
|
|
169
|
+
// ── Interactive REPL mode ────────────────────────────────────────────
|
|
170
|
+
const modeFlags = [
|
|
171
|
+
opts?.quality ? chalk.cyan('quality') : chalk.green('fast'),
|
|
172
|
+
opts?.thinking && chalk.yellow('thinking'),
|
|
173
|
+
].filter(Boolean);
|
|
174
|
+
const modeStr = modeFlags.length ? ` ${chalk.dim('[')}${modeFlags.join(chalk.dim(', '))}${chalk.dim(']')}` : '';
|
|
147
175
|
console.log('');
|
|
148
|
-
console.log(chalk.bold('Minara AI Chat')
|
|
149
|
-
|
|
176
|
+
console.log(chalk.green.bold('Minara AI Chat') +
|
|
177
|
+
chalk.dim(` session:${chatId.slice(0, 8)}`) +
|
|
178
|
+
modeStr);
|
|
179
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
180
|
+
console.log(chalk.dim('Type a message to chat. /help for commands, Ctrl+C to exit.'));
|
|
150
181
|
console.log('');
|
|
151
182
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
152
|
-
|
|
153
|
-
rl.
|
|
183
|
+
async function sendAndPrintWithPause(msg) {
|
|
184
|
+
rl.pause();
|
|
185
|
+
try {
|
|
186
|
+
await sendAndPrint(msg);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
rl.resume();
|
|
190
|
+
process.stdout.write('\n');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const ask = () => new Promise((resolve) => rl.question(chalk.blue.bold('>>> '), resolve));
|
|
194
|
+
rl.on('close', () => {
|
|
195
|
+
console.log(chalk.dim('\nGoodbye!'));
|
|
196
|
+
process.exit(0);
|
|
154
197
|
});
|
|
155
|
-
rl.on('close', () => { console.log(chalk.dim('\nGoodbye!')); process.exit(0); });
|
|
156
198
|
while (true) {
|
|
157
|
-
const userMsg = (await
|
|
199
|
+
const userMsg = (await ask()).trim();
|
|
158
200
|
if (!userMsg)
|
|
159
201
|
continue;
|
|
202
|
+
// ── REPL commands ──────────────────────────────────────────────────
|
|
160
203
|
if (userMsg.toLowerCase() === 'exit' || userMsg.toLowerCase() === 'quit') {
|
|
161
204
|
console.log(chalk.dim('Goodbye!'));
|
|
162
205
|
rl.close();
|
|
@@ -164,17 +207,65 @@ export const chatCommand = new Command('chat')
|
|
|
164
207
|
}
|
|
165
208
|
if (userMsg === '/new') {
|
|
166
209
|
chatId = randomUUID();
|
|
167
|
-
info(
|
|
210
|
+
info(`New conversation started ${chalk.dim(`(session:${chatId.slice(0, 8)})`)}`);
|
|
168
211
|
continue;
|
|
169
212
|
}
|
|
170
213
|
if (userMsg === '/id') {
|
|
171
214
|
console.log(chalk.dim(`Chat ID: ${chatId}`));
|
|
172
215
|
continue;
|
|
173
216
|
}
|
|
217
|
+
if (userMsg === '/continue') {
|
|
218
|
+
const spin = spinner('Fetching chats…');
|
|
219
|
+
const res = await listChats(creds.accessToken);
|
|
220
|
+
spin.stop();
|
|
221
|
+
const chats = res.data;
|
|
222
|
+
if (chats && chats.length > 0) {
|
|
223
|
+
const selected = await select({
|
|
224
|
+
message: 'Select a chat to continue:',
|
|
225
|
+
choices: chats.map((c) => ({
|
|
226
|
+
name: `${(c.chatId).slice(0, 12)}… ${c.name ?? '(untitled)'}`,
|
|
227
|
+
value: c.chatId,
|
|
228
|
+
})),
|
|
229
|
+
});
|
|
230
|
+
chatId = selected;
|
|
231
|
+
info(`Continuing chat ${chalk.dim(`(session:${chatId.slice(0, 8)})`)}`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
info('No existing chats found.');
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (userMsg === '/list') {
|
|
239
|
+
const spin = spinner('Fetching chats…');
|
|
240
|
+
const res = await listChats(creds.accessToken);
|
|
241
|
+
spin.stop();
|
|
242
|
+
const chats = res.data;
|
|
243
|
+
if (chats && chats.length > 0) {
|
|
244
|
+
console.log('');
|
|
245
|
+
for (const c of chats) {
|
|
246
|
+
const id = chalk.dim(c.chatId.slice(0, 8));
|
|
247
|
+
const name = c.name ?? chalk.dim('(untitled)');
|
|
248
|
+
const time = c.updatedAt ? chalk.dim(` ${c.updatedAt}`) : '';
|
|
249
|
+
console.log(` ${id} ${name}${time}`);
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
info('No chats yet.');
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
174
258
|
if (userMsg === '/help') {
|
|
175
|
-
console.log(
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log(chalk.bold(' Commands:'));
|
|
261
|
+
console.log(chalk.dim(' /new ') + 'Start a new conversation');
|
|
262
|
+
console.log(chalk.dim(' /continue ') + 'Continue an existing conversation');
|
|
263
|
+
console.log(chalk.dim(' /list ') + 'List all historical chats');
|
|
264
|
+
console.log(chalk.dim(' /id ') + 'Show current chat ID');
|
|
265
|
+
console.log(chalk.dim(' exit ') + 'Quit the chat');
|
|
266
|
+
console.log('');
|
|
176
267
|
continue;
|
|
177
268
|
}
|
|
178
|
-
await
|
|
269
|
+
await sendAndPrintWithPause(userMsg);
|
|
179
270
|
}
|
|
180
271
|
}));
|
package/dist/commands/config.js
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
import { input, select, confirm } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { loadConfig, saveConfig, getMinaraDir } from '../config.js';
|
|
5
|
-
import { success, info, wrapAction } from '../utils.js';
|
|
5
|
+
import { success, info, warn, wrapAction } from '../utils.js';
|
|
6
|
+
import { isTouchIdAvailable } from '../touchid.js';
|
|
6
7
|
export const configCommand = new Command('config')
|
|
7
8
|
.description('View or update CLI configuration')
|
|
8
9
|
.action(wrapAction(async () => {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const confirmTx = config.confirmBeforeTransaction !== false;
|
|
9
12
|
const action = await select({
|
|
10
13
|
message: 'Configuration:',
|
|
11
14
|
choices: [
|
|
12
15
|
{ name: 'Show current config', value: 'show' },
|
|
13
16
|
{ name: 'Set base URL', value: 'baseUrl' },
|
|
17
|
+
{
|
|
18
|
+
name: `Touch ID ${config.touchId ? chalk.green('[ON]') : chalk.dim('[OFF]')}`,
|
|
19
|
+
value: 'touchId',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: `Transaction Confirmation ${confirmTx ? chalk.green('[ON]') : chalk.dim('[OFF]')}`,
|
|
23
|
+
value: 'confirmTx',
|
|
24
|
+
},
|
|
14
25
|
{ name: 'Show config directory path', value: 'path' },
|
|
15
26
|
],
|
|
16
27
|
});
|
|
17
|
-
const config = loadConfig();
|
|
18
28
|
switch (action) {
|
|
19
29
|
case 'show':
|
|
20
30
|
console.log('');
|
|
21
31
|
console.log(chalk.bold('Current Configuration:'));
|
|
22
|
-
console.log(` Base URL
|
|
23
|
-
console.log(`
|
|
32
|
+
console.log(` Base URL : ${chalk.cyan(config.baseUrl)}`);
|
|
33
|
+
console.log(` Touch ID : ${config.touchId ? chalk.green('Enabled') : chalk.dim('Disabled')}`);
|
|
34
|
+
console.log(` Confirm Tx : ${confirmTx ? chalk.green('Enabled') : chalk.dim('Disabled')}`);
|
|
35
|
+
console.log(` Config Dir : ${chalk.dim(getMinaraDir())}`);
|
|
24
36
|
console.log('');
|
|
25
37
|
break;
|
|
26
38
|
case 'baseUrl': {
|
|
@@ -41,6 +53,71 @@ export const configCommand = new Command('config')
|
|
|
41
53
|
success(`Base URL set to ${url}`);
|
|
42
54
|
break;
|
|
43
55
|
}
|
|
56
|
+
case 'touchId': {
|
|
57
|
+
if (config.touchId) {
|
|
58
|
+
// Currently enabled — offer to disable
|
|
59
|
+
const disable = await confirm({
|
|
60
|
+
message: 'Touch ID is currently enabled. Disable it?',
|
|
61
|
+
default: false,
|
|
62
|
+
});
|
|
63
|
+
if (disable) {
|
|
64
|
+
saveConfig({ touchId: false });
|
|
65
|
+
success('Touch ID protection disabled.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Currently disabled — check availability then enable
|
|
70
|
+
console.log('');
|
|
71
|
+
info('Checking Touch ID availability…');
|
|
72
|
+
if (!isTouchIdAvailable()) {
|
|
73
|
+
console.log('');
|
|
74
|
+
warn('Touch ID is not available on this device.');
|
|
75
|
+
console.log(chalk.dim(' Make sure you are on a Mac with Touch ID and have enrolled at least one fingerprint.'));
|
|
76
|
+
console.log('');
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
console.log(chalk.green('✔'), 'Touch ID hardware detected.');
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(chalk.dim(' When enabled, all fund-related operations (transfer, withdraw, swap, order, etc.)'));
|
|
82
|
+
console.log(chalk.dim(' will require Touch ID verification before execution.'));
|
|
83
|
+
console.log('');
|
|
84
|
+
const enable = await confirm({
|
|
85
|
+
message: 'Enable Touch ID protection?',
|
|
86
|
+
default: true,
|
|
87
|
+
});
|
|
88
|
+
if (enable) {
|
|
89
|
+
saveConfig({ touchId: true });
|
|
90
|
+
success('Touch ID protection enabled!');
|
|
91
|
+
console.log(chalk.dim(' All fund-related operations now require fingerprint verification.'));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'confirmTx': {
|
|
97
|
+
if (confirmTx) {
|
|
98
|
+
const disable = await confirm({
|
|
99
|
+
message: 'Transaction confirmation is currently enabled. Disable it?',
|
|
100
|
+
default: false,
|
|
101
|
+
});
|
|
102
|
+
if (disable) {
|
|
103
|
+
saveConfig({ confirmBeforeTransaction: false });
|
|
104
|
+
success('Transaction confirmation disabled.');
|
|
105
|
+
warn('Fund-related operations will no longer require a second confirmation.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const enable = await confirm({
|
|
110
|
+
message: 'Enable transaction confirmation for fund-related operations?',
|
|
111
|
+
default: true,
|
|
112
|
+
});
|
|
113
|
+
if (enable) {
|
|
114
|
+
saveConfig({ confirmBeforeTransaction: true });
|
|
115
|
+
success('Transaction confirmation enabled!');
|
|
116
|
+
console.log(chalk.dim(' All fund-related operations now require a second confirmation before execution.'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
44
121
|
case 'path':
|
|
45
122
|
info(`Config directory: ${getMinaraDir()}`);
|
|
46
123
|
break;
|
|
@@ -3,7 +3,9 @@ import { input, select, confirm, number as numberPrompt } from '@inquirer/prompt
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as ctApi from '../api/copytrade.js';
|
|
5
5
|
import { requireAuth } from '../config.js';
|
|
6
|
-
import { success, info, spinner, assertApiOk, selectChain, wrapAction } from '../utils.js';
|
|
6
|
+
import { success, info, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation } from '../utils.js';
|
|
7
|
+
import { requireTouchId } from '../touchid.js';
|
|
8
|
+
import { printTxResult, printTable, COPY_TRADE_COLUMNS } from '../formatters.js';
|
|
7
9
|
// ─── create ──────────────────────────────────────────────────────────────
|
|
8
10
|
const createCmd = new Command('create')
|
|
9
11
|
.description('Create a copy trade bot')
|
|
@@ -48,6 +50,8 @@ const createCmd = new Command('create')
|
|
|
48
50
|
if (!ok)
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
53
|
+
await requireTransactionConfirmation(`Copy trade · $${fixedAmount}/trade · target ${targetAddress} · ${chain}`);
|
|
54
|
+
await requireTouchId();
|
|
51
55
|
const spin = spinner('Creating copy trade…');
|
|
52
56
|
const res = await ctApi.createCopyTrade(creds.accessToken, {
|
|
53
57
|
chain, targetAddress, name,
|
|
@@ -60,8 +64,7 @@ const createCmd = new Command('create')
|
|
|
60
64
|
spin.stop();
|
|
61
65
|
assertApiOk(res, 'Failed to create copy trade');
|
|
62
66
|
success('Copy trade created!');
|
|
63
|
-
|
|
64
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
67
|
+
printTxResult(res.data);
|
|
65
68
|
}));
|
|
66
69
|
// ─── list ────────────────────────────────────────────────────────────────
|
|
67
70
|
const listCmd = new Command('list')
|
|
@@ -78,7 +81,10 @@ const listCmd = new Command('list')
|
|
|
78
81
|
console.log(chalk.dim('No copy trades.'));
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
|
-
console.log(
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(chalk.bold('Copy Trades:'));
|
|
86
|
+
printTable(data, COPY_TRADE_COLUMNS);
|
|
87
|
+
console.log('');
|
|
82
88
|
}));
|
|
83
89
|
// ─── start / stop ────────────────────────────────────────────────────────
|
|
84
90
|
async function pickCopyTrade(token) {
|
package/dist/commands/deposit.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getCurrentUser } from '../api/auth.js';
|
|
|
6
6
|
import { getAccount } from '../api/crosschain.js';
|
|
7
7
|
import { requireAuth } from '../config.js';
|
|
8
8
|
import { info, spinner, unwrapApi, wrapAction } from '../utils.js';
|
|
9
|
+
import { printKV } from '../formatters.js';
|
|
9
10
|
/**
|
|
10
11
|
* Map wallet type keys from /auth/me → human-readable chain info.
|
|
11
12
|
*/
|
|
@@ -73,7 +74,10 @@ export const depositCommand = new Command('deposit')
|
|
|
73
74
|
default: false,
|
|
74
75
|
});
|
|
75
76
|
if (wantDetails) {
|
|
76
|
-
console.log(
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(chalk.bold('Account Details:'));
|
|
79
|
+
printKV(accountRes.data);
|
|
80
|
+
console.log('');
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
}));
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { input, select } from '@inquirer/prompts';
|
|
3
|
+
import chalk from 'chalk';
|
|
3
4
|
import { searchTokens, getTrendingTokens, searchStocks, getFearGreedIndex, getBitcoinMetrics } from '../api/tokens.js';
|
|
4
5
|
import { spinner, assertApiOk, wrapAction } from '../utils.js';
|
|
6
|
+
import { printKV, printTable, printFearGreed, printCryptoMetrics, TOKEN_COLUMNS } from '../formatters.js';
|
|
5
7
|
// ─── trending ────────────────────────────────────────────────────────────
|
|
6
8
|
const trendingCmd = new Command('trending')
|
|
7
9
|
.description('View trending tokens')
|
|
@@ -10,7 +12,15 @@ const trendingCmd = new Command('trending')
|
|
|
10
12
|
const res = await getTrendingTokens();
|
|
11
13
|
spin.stop();
|
|
12
14
|
assertApiOk(res, 'Failed to fetch trending tokens');
|
|
13
|
-
console.log(
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(chalk.bold('Trending Tokens:'));
|
|
17
|
+
if (Array.isArray(res.data) && res.data.length > 0) {
|
|
18
|
+
printTable(res.data, TOKEN_COLUMNS);
|
|
19
|
+
}
|
|
20
|
+
else if (res.data && typeof res.data === 'object') {
|
|
21
|
+
printKV(res.data);
|
|
22
|
+
}
|
|
23
|
+
console.log('');
|
|
14
24
|
}));
|
|
15
25
|
// ─── search ──────────────────────────────────────────────────────────────
|
|
16
26
|
const searchCmd = new Command('search')
|
|
@@ -31,7 +41,18 @@ const searchCmd = new Command('search')
|
|
|
31
41
|
: await searchStocks(keyword);
|
|
32
42
|
spin.stop();
|
|
33
43
|
assertApiOk(res, `Search for "${keyword}" failed`);
|
|
34
|
-
console.log(
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(chalk.bold(`Search Results for "${keyword}":`));
|
|
46
|
+
if (Array.isArray(res.data) && res.data.length > 0) {
|
|
47
|
+
printTable(res.data, category === 'tokens' ? TOKEN_COLUMNS : undefined);
|
|
48
|
+
}
|
|
49
|
+
else if (Array.isArray(res.data)) {
|
|
50
|
+
console.log(chalk.dim(' No results found.'));
|
|
51
|
+
}
|
|
52
|
+
else if (res.data && typeof res.data === 'object') {
|
|
53
|
+
printKV(res.data);
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
35
56
|
}));
|
|
36
57
|
// ─── fear-greed ──────────────────────────────────────────────────────────
|
|
37
58
|
const fearGreedCmd = new Command('fear-greed')
|
|
@@ -41,7 +62,10 @@ const fearGreedCmd = new Command('fear-greed')
|
|
|
41
62
|
const res = await getFearGreedIndex();
|
|
42
63
|
spin.stop();
|
|
43
64
|
assertApiOk(res, 'Failed to fetch Fear & Greed Index');
|
|
44
|
-
console.log(
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(chalk.bold('Fear & Greed Index:'));
|
|
67
|
+
printFearGreed(res.data);
|
|
68
|
+
console.log('');
|
|
45
69
|
}));
|
|
46
70
|
// ─── btc metrics ─────────────────────────────────────────────────────────
|
|
47
71
|
const btcCmd = new Command('btc-metrics')
|
|
@@ -51,7 +75,10 @@ const btcCmd = new Command('btc-metrics')
|
|
|
51
75
|
const res = await getBitcoinMetrics();
|
|
52
76
|
spin.stop();
|
|
53
77
|
assertApiOk(res, 'Failed to fetch Bitcoin metrics');
|
|
54
|
-
console.log(
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(chalk.bold('Bitcoin Metrics:'));
|
|
80
|
+
printCryptoMetrics(res.data);
|
|
81
|
+
console.log('');
|
|
55
82
|
}));
|
|
56
83
|
// ─── parent ──────────────────────────────────────────────────────────────
|
|
57
84
|
export const discoverCommand = new Command('discover')
|
|
@@ -3,7 +3,9 @@ import { input, select, confirm, number as numberPrompt } from '@inquirer/prompt
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as loApi from '../api/limitorder.js';
|
|
5
5
|
import { requireAuth } from '../config.js';
|
|
6
|
-
import { success, info, spinner, assertApiOk, selectChain, wrapAction } from '../utils.js';
|
|
6
|
+
import { success, info, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken, formatTokenLabel } from '../utils.js';
|
|
7
|
+
import { requireTouchId } from '../touchid.js';
|
|
8
|
+
import { printTxResult, printTable, LIMIT_ORDER_COLUMNS } from '../formatters.js';
|
|
7
9
|
// ─── create ──────────────────────────────────────────────────────────────
|
|
8
10
|
const createCmd = new Command('create')
|
|
9
11
|
.description('Create a limit order')
|
|
@@ -18,10 +20,11 @@ const createCmd = new Command('create')
|
|
|
18
20
|
{ name: 'Sell', value: 'sell' },
|
|
19
21
|
],
|
|
20
22
|
});
|
|
21
|
-
const
|
|
22
|
-
message: 'Target token contract address:',
|
|
23
|
+
const tokenInput = await input({
|
|
24
|
+
message: 'Target token (contract address or ticker):',
|
|
23
25
|
validate: (v) => (v.length > 0 ? true : 'Required'),
|
|
24
26
|
});
|
|
27
|
+
const tokenInfo = await lookupToken(tokenInput);
|
|
25
28
|
const priceCondition = await select({
|
|
26
29
|
message: 'Trigger when price is:',
|
|
27
30
|
choices: [
|
|
@@ -40,7 +43,8 @@ const createCmd = new Command('create')
|
|
|
40
43
|
console.log(chalk.bold('Limit Order:'));
|
|
41
44
|
console.log(` Chain : ${chalk.cyan(chain)}`);
|
|
42
45
|
console.log(` Side : ${side}`);
|
|
43
|
-
console.log(` Token : ${
|
|
46
|
+
console.log(` Token : ${formatTokenLabel(tokenInfo)}`);
|
|
47
|
+
console.log(` Address : ${chalk.yellow(tokenInfo.address)}`);
|
|
44
48
|
console.log(` Condition : price ${priceCondition} $${targetPrice}`);
|
|
45
49
|
console.log(` Amount : $${amount}`);
|
|
46
50
|
console.log(` Expires : ${new Date(expiredAt * 1000).toLocaleString()}`);
|
|
@@ -50,16 +54,17 @@ const createCmd = new Command('create')
|
|
|
50
54
|
if (!ok)
|
|
51
55
|
return;
|
|
52
56
|
}
|
|
57
|
+
await requireTransactionConfirmation(`Limit ${side} · $${amount} · price ${priceCondition} $${targetPrice} · ${chain}`, tokenInfo);
|
|
58
|
+
await requireTouchId();
|
|
53
59
|
const spin = spinner('Creating limit order…');
|
|
54
60
|
const res = await loApi.createLimitOrder(creds.accessToken, {
|
|
55
|
-
chain, side, amount, targetTokenCA,
|
|
61
|
+
chain, side, amount, targetTokenCA: tokenInfo.address,
|
|
56
62
|
priceCondition, targetPrice: targetPrice, expiredAt,
|
|
57
63
|
});
|
|
58
64
|
spin.stop();
|
|
59
65
|
assertApiOk(res, 'Failed to create limit order');
|
|
60
66
|
success('Limit order created!');
|
|
61
|
-
|
|
62
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
67
|
+
printTxResult(res.data);
|
|
63
68
|
}));
|
|
64
69
|
// ─── list ────────────────────────────────────────────────────────────────
|
|
65
70
|
const listCmd = new Command('list')
|
|
@@ -76,7 +81,10 @@ const listCmd = new Command('list')
|
|
|
76
81
|
console.log(chalk.dim('No limit orders.'));
|
|
77
82
|
return;
|
|
78
83
|
}
|
|
79
|
-
console.log(
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(chalk.bold('Limit Orders:'));
|
|
86
|
+
printTable(orders, LIMIT_ORDER_COLUMNS);
|
|
87
|
+
console.log('');
|
|
80
88
|
}));
|
|
81
89
|
// ─── cancel ──────────────────────────────────────────────────────────────
|
|
82
90
|
const cancelCmd = new Command('cancel')
|