minara 0.2.2 → 0.2.4

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 CHANGED
@@ -40,7 +40,7 @@ npx minara --help
40
40
  ## Quick Start
41
41
 
42
42
  ```bash
43
- # Login (interactive — choose email, Google, or Apple)
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 email, Google, or Apple ID |
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 method selection
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
@@ -1,26 +1,68 @@
1
1
  import { Command } from 'commander';
2
2
  import { input, select } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
- import { searchTokens, getTrendingTokens, searchStocks, getFearGreedIndex, getBitcoinMetrics } from '../api/tokens.js';
4
+ import { searchTokens, getTrendingTokens, getTrendingStocks, searchStocks, getFearGreedIndex, getBitcoinMetrics } from '../api/tokens.js';
5
5
  import { spinner, assertApiOk, wrapAction } from '../utils.js';
6
- import { printKV, printTable, printFearGreed, printCryptoMetrics, TOKEN_COLUMNS } from '../formatters.js';
6
+ import { printKV, printTable, printFearGreed, printCryptoMetrics, TOKEN_COLUMNS, STOCK_COLUMNS } from '../formatters.js';
7
+ function flattenStock(item) {
8
+ const td = (item.tradeData ?? {});
9
+ return {
10
+ symbol: item.symbol ?? td.symbol,
11
+ name: item.name ?? td.name,
12
+ price: td.price,
13
+ priceChange24H: td.price_change_24h_percent != null
14
+ ? Number(td.price_change_24h_percent) * 100
15
+ : undefined,
16
+ volume24H: td.volume_24h_usd,
17
+ marketCap: td.market,
18
+ };
19
+ }
7
20
  // ─── trending ────────────────────────────────────────────────────────────
8
21
  const trendingCmd = new Command('trending')
9
- .description('View trending tokens')
10
- .action(wrapAction(async () => {
11
- const spin = spinner('Fetching trending tokens…');
12
- const res = await getTrendingTokens();
13
- spin.stop();
14
- assertApiOk(res, 'Failed to fetch trending tokens');
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);
22
+ .description('View trending tokens or stocks')
23
+ .argument('[category]', 'tokens or stocks (default: interactive)')
24
+ .action(wrapAction(async (categoryArg) => {
25
+ let category = categoryArg?.toLowerCase();
26
+ if (!category || (category !== 'tokens' && category !== 'stocks')) {
27
+ category = await select({
28
+ message: 'Trending:',
29
+ choices: [
30
+ { name: 'Tokens (crypto)', value: 'tokens' },
31
+ { name: 'Stocks (tokenized)', value: 'stocks' },
32
+ ],
33
+ });
19
34
  }
20
- else if (res.data && typeof res.data === 'object') {
21
- printKV(res.data);
35
+ if (category === 'stocks') {
36
+ const spin = spinner('Fetching trending stocks…');
37
+ const res = await getTrendingStocks();
38
+ spin.stop();
39
+ assertApiOk(res, 'Failed to fetch trending stocks');
40
+ console.log('');
41
+ console.log(chalk.bold('Trending Stocks:'));
42
+ if (Array.isArray(res.data) && res.data.length > 0) {
43
+ const rows = res.data.map((s) => flattenStock(s));
44
+ printTable(rows, STOCK_COLUMNS);
45
+ }
46
+ else {
47
+ console.log(chalk.dim(' No trending stocks found.'));
48
+ }
49
+ console.log('');
50
+ }
51
+ else {
52
+ const spin = spinner('Fetching trending tokens…');
53
+ const res = await getTrendingTokens();
54
+ spin.stop();
55
+ assertApiOk(res, 'Failed to fetch trending tokens');
56
+ console.log('');
57
+ console.log(chalk.bold('Trending Tokens:'));
58
+ if (Array.isArray(res.data) && res.data.length > 0) {
59
+ printTable(res.data, TOKEN_COLUMNS);
60
+ }
61
+ else if (res.data && typeof res.data === 'object') {
62
+ printKV(res.data);
63
+ }
64
+ console.log('');
22
65
  }
23
- console.log('');
24
66
  }));
25
67
  // ─── search ──────────────────────────────────────────────────────────────
26
68
  const searchCmd = new Command('search')
@@ -91,7 +133,7 @@ export const discoverCommand = new Command('discover')
91
133
  const action = await select({
92
134
  message: 'Discover:',
93
135
  choices: [
94
- { name: 'Trending tokens', value: 'trending' },
136
+ { name: 'Trending tokens / stocks', value: 'trending' },
95
137
  { name: 'Search tokens / stocks', value: 'search' },
96
138
  { name: 'Fear & Greed Index', value: 'fear-greed' },
97
139
  { name: 'Bitcoin metrics', value: 'btc-metrics' },
@@ -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, getOAuthUrl, getCurrentUser, startDeviceAuth, getDeviceAuthStatus, } from '../api/auth.js';
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, warn, spinner, openBrowser, unwrapApi, wrapAction } from '../utils.js';
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('--google', 'Login with Google')
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 loginWithOAuth(method);
140
+ await loginWithDevice();
222
141
  }
223
142
  // ── Offer Touch ID setup (macOS only, if not already enabled) ────
224
143
  const config = loadConfig();
@@ -44,6 +44,7 @@ export declare const POSITION_COLUMNS: ColumnDef[];
44
44
  export declare const LIMIT_ORDER_COLUMNS: ColumnDef[];
45
45
  /** Trending / search tokens (TokenInfo[]) */
46
46
  export declare const TOKEN_COLUMNS: ColumnDef[];
47
+ export declare const STOCK_COLUMNS: ColumnDef[];
47
48
  /**
48
49
  * Pretty-print Fear & Greed Index — hides redundant `timestamp` and `price`.
49
50
  */
@@ -309,6 +309,14 @@ export const TOKEN_COLUMNS = [
309
309
  { key: 'volume24H', label: 'Volume 24h', format: compactUsd },
310
310
  { key: 'marketCap', label: 'Market Cap', format: compactUsd },
311
311
  ];
312
+ export const STOCK_COLUMNS = [
313
+ { key: 'symbol', label: 'Symbol', format: (v) => chalk.bold(String(v ?? '—')) },
314
+ { key: 'name', label: 'Name' },
315
+ { key: 'price', label: 'Price', format: (v) => formatValue(v, 'price') },
316
+ { key: 'priceChange24H', label: '24h %', format: (v) => formatValue(v, 'change') },
317
+ { key: 'volume24H', label: 'Volume 24h', format: compactUsd },
318
+ { key: 'marketCap', label: 'Market Cap', format: compactUsd },
319
+ ];
312
320
  // ═══════════════════════════════════════════════════════════════════════════
313
321
  // Specialised display helpers for discover commands
314
322
  // ═══════════════════════════════════════════════════════════════════════════
package/dist/index.js CHANGED
@@ -2,24 +2,28 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
+ import { setRawJson } from './formatters.js';
6
+ // Auth & Account
5
7
  import { loginCommand } from './commands/login.js';
6
- const require = createRequire(import.meta.url);
7
- const { version } = require('../package.json');
8
8
  import { logoutCommand } from './commands/logout.js';
9
9
  import { accountCommand } from './commands/account.js';
10
- import { assetsCommand } from './commands/assets.js';
10
+ // Wallet & Funds
11
11
  import { balanceCommand } from './commands/balance.js';
12
+ import { assetsCommand } from './commands/assets.js';
12
13
  import { depositCommand } from './commands/deposit.js';
13
14
  import { withdrawCommand } from './commands/withdraw.js';
15
+ // Trading
14
16
  import { swapCommand } from './commands/swap.js';
15
17
  import { transferCommand } from './commands/transfer.js';
16
18
  import { perpsCommand } from './commands/perps.js';
17
19
  import { limitOrderCommand } from './commands/limit-order.js';
20
+ // AI, Market, Premium, Config
18
21
  import { chatCommand } from './commands/chat.js';
19
22
  import { discoverCommand } from './commands/discover.js';
20
- import { configCommand } from './commands/config.js';
21
23
  import { premiumCommand } from './commands/premium.js';
22
- import { setRawJson } from './formatters.js';
24
+ import { configCommand } from './commands/config.js';
25
+ const require = createRequire(import.meta.url);
26
+ const { version } = require('../package.json');
23
27
  const program = new Command();
24
28
  program
25
29
  .name('minara')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minara",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI client for Minara.ai — login, trade, deposit/withdraw, chat and more from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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>>>;
@@ -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,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const copyTradeCommand: Command;
@@ -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
- }));