minara 0.2.2 → 0.2.3
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 +4 -5
- package/dist/commands/login.js +5 -86
- package/package.json +1 -1
- package/dist/api/copytrade.d.ts +0 -19
- package/dist/api/copytrade.js +0 -37
- package/dist/commands/copy-trade.d.ts +0 -2
- package/dist/commands/copy-trade.js +0 -176
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ npx minara --help
|
|
|
40
40
|
## Quick Start
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
# Login (interactive —
|
|
43
|
+
# Login (interactive — device code or email)
|
|
44
44
|
minara login
|
|
45
45
|
|
|
46
46
|
# Check your account
|
|
@@ -68,15 +68,14 @@ minara discover trending
|
|
|
68
68
|
|
|
69
69
|
| Command | Description |
|
|
70
70
|
| ---------------- | ------------------------------------------- |
|
|
71
|
-
| `minara login` | Login via
|
|
71
|
+
| `minara login` | Login via device code or email |
|
|
72
72
|
| `minara logout` | Logout and clear local credentials |
|
|
73
73
|
| `minara account` | View your account info and wallet addresses |
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
minara login # Interactive
|
|
76
|
+
minara login # Interactive: device code (default) or email
|
|
77
|
+
minara login --device # Device code (opens browser to verify)
|
|
77
78
|
minara login -e user@mail.com # Email verification code
|
|
78
|
-
minara login --google # Google OAuth (opens browser)
|
|
79
|
-
minara login --apple # Apple ID (opens browser)
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
### Wallet & Funds
|
package/dist/commands/login.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { sendEmailCode, verifyEmailCode,
|
|
4
|
+
import { sendEmailCode, verifyEmailCode, startDeviceAuth, getDeviceAuthStatus, } from '../api/auth.js';
|
|
5
5
|
import { saveCredentials, loadCredentials, loadConfig, saveConfig } from '../config.js';
|
|
6
|
-
import { success, error, info,
|
|
7
|
-
import { startOAuthServer } from '../oauth-server.js';
|
|
8
|
-
import { OAUTH_PROVIDERS } from '../types.js';
|
|
6
|
+
import { success, error, info, spinner, openBrowser, unwrapApi, wrapAction } from '../utils.js';
|
|
9
7
|
import { isTouchIdAvailable } from '../touchid.js';
|
|
10
8
|
// ─── Email login flow ─────────────────────────────────────────────────────
|
|
11
9
|
async function loginWithEmail(emailOpt) {
|
|
@@ -42,72 +40,6 @@ async function loginWithEmail(emailOpt) {
|
|
|
42
40
|
});
|
|
43
41
|
success(`Welcome${user.displayName ? `, ${user.displayName}` : ''}! Credentials saved to ~/.minara/`);
|
|
44
42
|
}
|
|
45
|
-
// ─── OAuth login flow ──────────────────────────────────────────────────────
|
|
46
|
-
async function loginWithOAuth(provider) {
|
|
47
|
-
const providerName = OAUTH_PROVIDERS.find((p) => p.value === provider)?.name ?? provider;
|
|
48
|
-
info(`Starting ${providerName} login…`);
|
|
49
|
-
const server = await startOAuthServer();
|
|
50
|
-
const spin = spinner(`Requesting ${providerName} authorization URL…`);
|
|
51
|
-
const urlRes = await getOAuthUrl(provider, server.callbackUrl);
|
|
52
|
-
spin.stop();
|
|
53
|
-
if (!urlRes.success || !urlRes.data?.url) {
|
|
54
|
-
server.close();
|
|
55
|
-
error(urlRes.error?.message
|
|
56
|
-
? `${providerName} login is not available: ${urlRes.error.message}`
|
|
57
|
-
: `Failed to get ${providerName} authorization URL`);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
const authUrl = urlRes.data.url;
|
|
61
|
-
console.log('');
|
|
62
|
-
console.log(chalk.bold(`Opening ${providerName} login in your browser…`));
|
|
63
|
-
console.log(chalk.dim(`If the browser doesn't open automatically, visit:`));
|
|
64
|
-
console.log(chalk.cyan(authUrl));
|
|
65
|
-
console.log('');
|
|
66
|
-
info('Waiting for you to complete authentication in the browser…');
|
|
67
|
-
info(chalk.dim('(Press Ctrl+C to cancel)'));
|
|
68
|
-
console.log('');
|
|
69
|
-
openBrowser(authUrl);
|
|
70
|
-
const result = await server.waitForCallback();
|
|
71
|
-
if (result.error) {
|
|
72
|
-
error(`Login failed: ${result.error}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
if (!result.accessToken) {
|
|
76
|
-
warn('No access token found in the callback response.');
|
|
77
|
-
console.log(chalk.dim('Raw callback parameters:'));
|
|
78
|
-
for (const [k, v] of Object.entries(result.rawParams)) {
|
|
79
|
-
console.log(chalk.dim(` ${k}: ${v}`));
|
|
80
|
-
}
|
|
81
|
-
error('Please check if the API returned the token in a different format.');
|
|
82
|
-
info('You can try logging in with email instead: minara login --email');
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
saveCredentials({
|
|
86
|
-
accessToken: result.accessToken,
|
|
87
|
-
userId: result.userId,
|
|
88
|
-
email: result.email,
|
|
89
|
-
displayName: result.displayName,
|
|
90
|
-
});
|
|
91
|
-
// Fetch full user info if only token was returned
|
|
92
|
-
if (!result.email && !result.displayName) {
|
|
93
|
-
const spin2 = spinner('Fetching account info…');
|
|
94
|
-
const meRes = await getCurrentUser(result.accessToken);
|
|
95
|
-
spin2.stop();
|
|
96
|
-
if (meRes.success && meRes.data) {
|
|
97
|
-
saveCredentials({
|
|
98
|
-
accessToken: result.accessToken,
|
|
99
|
-
userId: meRes.data.id,
|
|
100
|
-
email: meRes.data.email,
|
|
101
|
-
displayName: meRes.data.displayName,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
success(`${providerName} login successful! Credentials saved to ~/.minara/`);
|
|
106
|
-
if (result.displayName)
|
|
107
|
-
console.log(chalk.dim(` Welcome, ${result.displayName}`));
|
|
108
|
-
if (result.email)
|
|
109
|
-
console.log(chalk.dim(` ${result.email}`));
|
|
110
|
-
}
|
|
111
43
|
// ─── Device login flow (RFC 8628) ─────────────────────────────────────────
|
|
112
44
|
async function loginWithDevice() {
|
|
113
45
|
info('Starting device login...');
|
|
@@ -171,9 +103,7 @@ async function loginWithDevice() {
|
|
|
171
103
|
export const loginCommand = new Command('login')
|
|
172
104
|
.description('Login to your Minara account')
|
|
173
105
|
.option('-e, --email <email>', 'Login with email verification code')
|
|
174
|
-
.option('--
|
|
175
|
-
.option('--apple', 'Login with Apple ID')
|
|
176
|
-
.option('--device', 'Login with device code (for headless environments)')
|
|
106
|
+
.option('--device', 'Login with device code (opens browser)')
|
|
177
107
|
.action(wrapAction(async (opts) => {
|
|
178
108
|
// Warn if already logged in
|
|
179
109
|
const existing = loadCredentials();
|
|
@@ -190,12 +120,6 @@ export const loginCommand = new Command('login')
|
|
|
190
120
|
if (opts.email) {
|
|
191
121
|
method = 'email';
|
|
192
122
|
}
|
|
193
|
-
else if (opts.google) {
|
|
194
|
-
method = 'google';
|
|
195
|
-
}
|
|
196
|
-
else if (opts.apple) {
|
|
197
|
-
method = 'apple';
|
|
198
|
-
}
|
|
199
123
|
else if (opts.device) {
|
|
200
124
|
method = 'device';
|
|
201
125
|
}
|
|
@@ -203,10 +127,8 @@ export const loginCommand = new Command('login')
|
|
|
203
127
|
method = await select({
|
|
204
128
|
message: 'How would you like to login?',
|
|
205
129
|
choices: [
|
|
130
|
+
{ name: 'Device code (opens browser to verify)', value: 'device' },
|
|
206
131
|
{ name: 'Email verification code', value: 'email' },
|
|
207
|
-
{ name: 'Google', value: 'google' },
|
|
208
|
-
{ name: 'Apple ID', value: 'apple' },
|
|
209
|
-
{ name: 'Device code (for headless environments)', value: 'device' },
|
|
210
132
|
],
|
|
211
133
|
});
|
|
212
134
|
}
|
|
@@ -214,11 +136,8 @@ export const loginCommand = new Command('login')
|
|
|
214
136
|
if (method === 'email') {
|
|
215
137
|
await loginWithEmail(opts.email);
|
|
216
138
|
}
|
|
217
|
-
else if (method === 'device') {
|
|
218
|
-
await loginWithDevice();
|
|
219
|
-
}
|
|
220
139
|
else {
|
|
221
|
-
await
|
|
140
|
+
await loginWithDevice();
|
|
222
141
|
}
|
|
223
142
|
// ── Offer Touch ID setup (macOS only, if not already enabled) ────
|
|
224
143
|
const config = loadConfig();
|
package/package.json
CHANGED
package/dist/api/copytrade.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { CreateCopyTradeDto, UpdateCopyTradeDto, CopyTradeInfo } from '../types.js';
|
|
2
|
-
/** Create copy trade */
|
|
3
|
-
export declare function createCopyTrade(token: string, dto: CreateCopyTradeDto): Promise<import("../types.js").ApiResponse<CopyTradeInfo>>;
|
|
4
|
-
/** List user's copy trades */
|
|
5
|
-
export declare function listCopyTrades(token: string): Promise<import("../types.js").ApiResponse<CopyTradeInfo[]>>;
|
|
6
|
-
/** Get copy trade by ID */
|
|
7
|
-
export declare function getCopyTrade(token: string, id: string): Promise<import("../types.js").ApiResponse<CopyTradeInfo>>;
|
|
8
|
-
/** Update copy trade */
|
|
9
|
-
export declare function updateCopyTrade(token: string, id: string, dto: UpdateCopyTradeDto): Promise<import("../types.js").ApiResponse<CopyTradeInfo>>;
|
|
10
|
-
/** Delete copy trade */
|
|
11
|
-
export declare function deleteCopyTrade(token: string, id: string): Promise<import("../types.js").ApiResponse<void>>;
|
|
12
|
-
/** Start copy trade */
|
|
13
|
-
export declare function startCopyTrade(token: string, id: string): Promise<import("../types.js").ApiResponse<void>>;
|
|
14
|
-
/** Stop copy trade */
|
|
15
|
-
export declare function stopCopyTrade(token: string, id: string): Promise<import("../types.js").ApiResponse<void>>;
|
|
16
|
-
/** Get copy trade activity */
|
|
17
|
-
export declare function getCopyTradeActivity(token: string, id: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>[]>>;
|
|
18
|
-
/** Get copy trade PnL chart */
|
|
19
|
-
export declare function getCopyTradePnl(token: string, id: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>>>;
|
package/dist/api/copytrade.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { get, post, patch, del } from './client.js';
|
|
2
|
-
/** Create copy trade */
|
|
3
|
-
export function createCopyTrade(token, dto) {
|
|
4
|
-
return post('/copy-trade', { token, body: dto });
|
|
5
|
-
}
|
|
6
|
-
/** List user's copy trades */
|
|
7
|
-
export function listCopyTrades(token) {
|
|
8
|
-
return get('/copy-trade', { token });
|
|
9
|
-
}
|
|
10
|
-
/** Get copy trade by ID */
|
|
11
|
-
export function getCopyTrade(token, id) {
|
|
12
|
-
return get(`/copy-trade/${encodeURIComponent(id)}`, { token });
|
|
13
|
-
}
|
|
14
|
-
/** Update copy trade */
|
|
15
|
-
export function updateCopyTrade(token, id, dto) {
|
|
16
|
-
return patch(`/copy-trade/${encodeURIComponent(id)}`, { token, body: dto });
|
|
17
|
-
}
|
|
18
|
-
/** Delete copy trade */
|
|
19
|
-
export function deleteCopyTrade(token, id) {
|
|
20
|
-
return del(`/copy-trade/${encodeURIComponent(id)}`, { token });
|
|
21
|
-
}
|
|
22
|
-
/** Start copy trade */
|
|
23
|
-
export function startCopyTrade(token, id) {
|
|
24
|
-
return patch(`/copy-trade/${encodeURIComponent(id)}/start`, { token });
|
|
25
|
-
}
|
|
26
|
-
/** Stop copy trade */
|
|
27
|
-
export function stopCopyTrade(token, id) {
|
|
28
|
-
return patch(`/copy-trade/${encodeURIComponent(id)}/stop`, { token });
|
|
29
|
-
}
|
|
30
|
-
/** Get copy trade activity */
|
|
31
|
-
export function getCopyTradeActivity(token, id) {
|
|
32
|
-
return get(`/copy-trade/${encodeURIComponent(id)}/activity`, { token });
|
|
33
|
-
}
|
|
34
|
-
/** Get copy trade PnL chart */
|
|
35
|
-
export function getCopyTradePnl(token, id) {
|
|
36
|
-
return get(`/copy-trade/${encodeURIComponent(id)}/pnl/chart`, { token });
|
|
37
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { input, select, confirm, number as numberPrompt } from '@inquirer/prompts';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import * as ctApi from '../api/copytrade.js';
|
|
5
|
-
import { requireAuth } from '../config.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';
|
|
9
|
-
// ─── create ──────────────────────────────────────────────────────────────
|
|
10
|
-
const createCmd = new Command('create')
|
|
11
|
-
.description('Create a copy trade bot')
|
|
12
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
13
|
-
.action(wrapAction(async (opts) => {
|
|
14
|
-
const creds = requireAuth();
|
|
15
|
-
const chain = await selectChain('Chain:', true);
|
|
16
|
-
const targetAddress = await input({
|
|
17
|
-
message: 'Target wallet address to copy:',
|
|
18
|
-
validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
|
|
19
|
-
});
|
|
20
|
-
const name = await input({ message: 'Name for this copy trade (optional):' }) || undefined;
|
|
21
|
-
const fixedAmount = await numberPrompt({ message: 'Fixed buy amount (USD) per copy:', min: 1, required: true });
|
|
22
|
-
const copySell = await confirm({ message: 'Also copy sell actions?', default: true });
|
|
23
|
-
let copySellSamePercentage = false;
|
|
24
|
-
let copySellQuitPercentage;
|
|
25
|
-
if (copySell) {
|
|
26
|
-
copySellSamePercentage = await confirm({
|
|
27
|
-
message: 'Copy sell with same percentage as target?',
|
|
28
|
-
default: true,
|
|
29
|
-
});
|
|
30
|
-
copySellQuitPercentage = (await numberPrompt({
|
|
31
|
-
message: 'Clear position alert % (when target sells >= this %, clear your position; 0 to skip):',
|
|
32
|
-
default: 0,
|
|
33
|
-
})) || undefined;
|
|
34
|
-
}
|
|
35
|
-
console.log('');
|
|
36
|
-
console.log(chalk.bold('Copy Trade Bot:'));
|
|
37
|
-
console.log(` Chain : ${chalk.cyan(chain)}`);
|
|
38
|
-
console.log(` Target : ${chalk.yellow(targetAddress)}`);
|
|
39
|
-
if (name)
|
|
40
|
-
console.log(` Name : ${name}`);
|
|
41
|
-
console.log(` Buy Amount : $${fixedAmount}`);
|
|
42
|
-
console.log(` Copy Sell : ${copySell ? 'Yes' : 'No'}`);
|
|
43
|
-
if (copySellSamePercentage)
|
|
44
|
-
console.log(` Same % : Yes`);
|
|
45
|
-
if (copySellQuitPercentage)
|
|
46
|
-
console.log(` Quit Threshold : ${copySellQuitPercentage}%`);
|
|
47
|
-
console.log('');
|
|
48
|
-
if (!opts.yes) {
|
|
49
|
-
const ok = await confirm({ message: 'Create this copy trade?', default: false });
|
|
50
|
-
if (!ok)
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
await requireTransactionConfirmation(`Copy trade · $${fixedAmount}/trade · target ${targetAddress} · ${chain}`);
|
|
54
|
-
await requireTouchId();
|
|
55
|
-
const spin = spinner('Creating copy trade…');
|
|
56
|
-
const res = await ctApi.createCopyTrade(creds.accessToken, {
|
|
57
|
-
chain, targetAddress, name,
|
|
58
|
-
mode: 'fixedAmount',
|
|
59
|
-
fixedAmount: fixedAmount,
|
|
60
|
-
copySell,
|
|
61
|
-
copySellSamePercentage,
|
|
62
|
-
copySellQuitPercentage,
|
|
63
|
-
});
|
|
64
|
-
spin.stop();
|
|
65
|
-
assertApiOk(res, 'Failed to create copy trade');
|
|
66
|
-
success('Copy trade created!');
|
|
67
|
-
printTxResult(res.data);
|
|
68
|
-
}));
|
|
69
|
-
// ─── list ────────────────────────────────────────────────────────────────
|
|
70
|
-
const listCmd = new Command('list')
|
|
71
|
-
.alias('ls')
|
|
72
|
-
.description('List your copy trades')
|
|
73
|
-
.action(wrapAction(async () => {
|
|
74
|
-
const creds = requireAuth();
|
|
75
|
-
const spin = spinner('Fetching copy trades…');
|
|
76
|
-
const res = await ctApi.listCopyTrades(creds.accessToken);
|
|
77
|
-
spin.stop();
|
|
78
|
-
assertApiOk(res, 'Failed to fetch copy trades');
|
|
79
|
-
const data = res.data;
|
|
80
|
-
if (!data || data.length === 0) {
|
|
81
|
-
console.log(chalk.dim('No copy trades.'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
console.log('');
|
|
85
|
-
console.log(chalk.bold('Copy Trades:'));
|
|
86
|
-
printTable(data, COPY_TRADE_COLUMNS);
|
|
87
|
-
console.log('');
|
|
88
|
-
}));
|
|
89
|
-
// ─── start / stop ────────────────────────────────────────────────────────
|
|
90
|
-
async function pickCopyTrade(token) {
|
|
91
|
-
const spin = spinner('Fetching copy trades…');
|
|
92
|
-
const res = await ctApi.listCopyTrades(token);
|
|
93
|
-
spin.stop();
|
|
94
|
-
const trades = res.data;
|
|
95
|
-
if (!trades || trades.length === 0) {
|
|
96
|
-
info('No copy trades found.');
|
|
97
|
-
process.exit(0);
|
|
98
|
-
}
|
|
99
|
-
return select({
|
|
100
|
-
message: 'Select copy trade:',
|
|
101
|
-
choices: trades.map((t) => ({
|
|
102
|
-
name: `[${t.id.slice(0, 12)}…] ${t.name ?? t.targetAddress} status=${t.status ?? '?'}`,
|
|
103
|
-
value: t.id,
|
|
104
|
-
})),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
const startCmd = new Command('start')
|
|
108
|
-
.description('Start (resume) a copy trade')
|
|
109
|
-
.argument('[id]', 'Copy trade ID')
|
|
110
|
-
.action(wrapAction(async (idArg) => {
|
|
111
|
-
const creds = requireAuth();
|
|
112
|
-
const id = idArg ?? await pickCopyTrade(creds.accessToken);
|
|
113
|
-
const spin = spinner('Starting…');
|
|
114
|
-
const res = await ctApi.startCopyTrade(creds.accessToken, id);
|
|
115
|
-
spin.stop();
|
|
116
|
-
assertApiOk(res, 'Failed to start copy trade');
|
|
117
|
-
success('Copy trade started.');
|
|
118
|
-
}));
|
|
119
|
-
const stopCmd = new Command('stop')
|
|
120
|
-
.description('Stop (pause) a copy trade')
|
|
121
|
-
.argument('[id]', 'Copy trade ID')
|
|
122
|
-
.action(wrapAction(async (idArg) => {
|
|
123
|
-
const creds = requireAuth();
|
|
124
|
-
const id = idArg ?? await pickCopyTrade(creds.accessToken);
|
|
125
|
-
const ok = await confirm({ message: `Stop copy trade ${id.slice(0, 12)}…?`, default: false });
|
|
126
|
-
if (!ok)
|
|
127
|
-
return;
|
|
128
|
-
const spin = spinner('Stopping…');
|
|
129
|
-
const res = await ctApi.stopCopyTrade(creds.accessToken, id);
|
|
130
|
-
spin.stop();
|
|
131
|
-
assertApiOk(res, 'Failed to stop copy trade');
|
|
132
|
-
success('Copy trade stopped.');
|
|
133
|
-
}));
|
|
134
|
-
// ─── delete ──────────────────────────────────────────────────────────────
|
|
135
|
-
const deleteCmd = new Command('delete')
|
|
136
|
-
.description('Delete a copy trade')
|
|
137
|
-
.argument('[id]', 'Copy trade ID')
|
|
138
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
139
|
-
.action(wrapAction(async (idArg, opts) => {
|
|
140
|
-
const creds = requireAuth();
|
|
141
|
-
const id = idArg ?? await pickCopyTrade(creds.accessToken);
|
|
142
|
-
if (!opts?.yes) {
|
|
143
|
-
const ok = await confirm({ message: `Delete copy trade ${id.slice(0, 12)}…? This cannot be undone.`, default: false });
|
|
144
|
-
if (!ok)
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const spin = spinner('Deleting…');
|
|
148
|
-
const res = await ctApi.deleteCopyTrade(creds.accessToken, id);
|
|
149
|
-
spin.stop();
|
|
150
|
-
assertApiOk(res, 'Failed to delete copy trade');
|
|
151
|
-
success('Copy trade deleted.');
|
|
152
|
-
}));
|
|
153
|
-
// ─── parent ──────────────────────────────────────────────────────────────
|
|
154
|
-
export const copyTradeCommand = new Command('copy-trade')
|
|
155
|
-
.alias('ct')
|
|
156
|
-
.description('Copy trading — follow wallet addresses')
|
|
157
|
-
.addCommand(createCmd)
|
|
158
|
-
.addCommand(listCmd)
|
|
159
|
-
.addCommand(startCmd)
|
|
160
|
-
.addCommand(stopCmd)
|
|
161
|
-
.addCommand(deleteCmd)
|
|
162
|
-
.action(wrapAction(async () => {
|
|
163
|
-
const action = await select({
|
|
164
|
-
message: 'Copy Trade:',
|
|
165
|
-
choices: [
|
|
166
|
-
{ name: 'Create a new copy trade', value: 'create' },
|
|
167
|
-
{ name: 'List copy trades', value: 'list' },
|
|
168
|
-
{ name: 'Start a copy trade', value: 'start' },
|
|
169
|
-
{ name: 'Stop a copy trade', value: 'stop' },
|
|
170
|
-
{ name: 'Delete a copy trade', value: 'delete' },
|
|
171
|
-
],
|
|
172
|
-
});
|
|
173
|
-
const sub = copyTradeCommand.commands.find((c) => c.name() === action);
|
|
174
|
-
if (sub)
|
|
175
|
-
await sub.parseAsync([], { from: 'user' });
|
|
176
|
-
}));
|