minara 0.2.9 → 0.3.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.
@@ -0,0 +1,19 @@
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>>>;
@@ -0,0 +1,37 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const copyTradeCommand: Command;
@@ -0,0 +1,170 @@
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 } from '../utils.js';
7
+ // ─── create ──────────────────────────────────────────────────────────────
8
+ const createCmd = new Command('create')
9
+ .description('Create a copy trade bot')
10
+ .option('-y, --yes', 'Skip confirmation')
11
+ .action(wrapAction(async (opts) => {
12
+ const creds = requireAuth();
13
+ const chain = await selectChain('Chain:', true);
14
+ const targetAddress = await input({
15
+ message: 'Target wallet address to copy:',
16
+ validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
17
+ });
18
+ const name = await input({ message: 'Name for this copy trade (optional):' }) || undefined;
19
+ const fixedAmount = await numberPrompt({ message: 'Fixed buy amount (USD) per copy:', min: 1, required: true });
20
+ const copySell = await confirm({ message: 'Also copy sell actions?', default: true });
21
+ let copySellSamePercentage = false;
22
+ let copySellQuitPercentage;
23
+ if (copySell) {
24
+ copySellSamePercentage = await confirm({
25
+ message: 'Copy sell with same percentage as target?',
26
+ default: true,
27
+ });
28
+ copySellQuitPercentage = (await numberPrompt({
29
+ message: 'Clear position alert % (when target sells >= this %, clear your position; 0 to skip):',
30
+ default: 0,
31
+ })) || undefined;
32
+ }
33
+ console.log('');
34
+ console.log(chalk.bold('Copy Trade Bot:'));
35
+ console.log(` Chain : ${chalk.cyan(chain)}`);
36
+ console.log(` Target : ${chalk.yellow(targetAddress)}`);
37
+ if (name)
38
+ console.log(` Name : ${name}`);
39
+ console.log(` Buy Amount : $${fixedAmount}`);
40
+ console.log(` Copy Sell : ${copySell ? 'Yes' : 'No'}`);
41
+ if (copySellSamePercentage)
42
+ console.log(` Same % : Yes`);
43
+ if (copySellQuitPercentage)
44
+ console.log(` Quit Threshold : ${copySellQuitPercentage}%`);
45
+ console.log('');
46
+ if (!opts.yes) {
47
+ const ok = await confirm({ message: 'Create this copy trade?', default: false });
48
+ if (!ok)
49
+ return;
50
+ }
51
+ const spin = spinner('Creating copy trade…');
52
+ const res = await ctApi.createCopyTrade(creds.accessToken, {
53
+ chain, targetAddress, name,
54
+ mode: 'fixedAmount',
55
+ fixedAmount: fixedAmount,
56
+ copySell,
57
+ copySellSamePercentage,
58
+ copySellQuitPercentage,
59
+ });
60
+ spin.stop();
61
+ assertApiOk(res, 'Failed to create copy trade');
62
+ success('Copy trade created!');
63
+ if (res.data)
64
+ console.log(JSON.stringify(res.data, null, 2));
65
+ }));
66
+ // ─── list ────────────────────────────────────────────────────────────────
67
+ const listCmd = new Command('list')
68
+ .alias('ls')
69
+ .description('List your copy trades')
70
+ .action(wrapAction(async () => {
71
+ const creds = requireAuth();
72
+ const spin = spinner('Fetching copy trades…');
73
+ const res = await ctApi.listCopyTrades(creds.accessToken);
74
+ spin.stop();
75
+ assertApiOk(res, 'Failed to fetch copy trades');
76
+ const data = res.data;
77
+ if (!data || data.length === 0) {
78
+ console.log(chalk.dim('No copy trades.'));
79
+ return;
80
+ }
81
+ console.log(JSON.stringify(data, null, 2));
82
+ }));
83
+ // ─── start / stop ────────────────────────────────────────────────────────
84
+ async function pickCopyTrade(token) {
85
+ const spin = spinner('Fetching copy trades…');
86
+ const res = await ctApi.listCopyTrades(token);
87
+ spin.stop();
88
+ const trades = res.data;
89
+ if (!trades || trades.length === 0) {
90
+ info('No copy trades found.');
91
+ process.exit(0);
92
+ }
93
+ return select({
94
+ message: 'Select copy trade:',
95
+ choices: trades.map((t) => ({
96
+ name: `[${t.id.slice(0, 12)}…] ${t.name ?? t.targetAddress} status=${t.status ?? '?'}`,
97
+ value: t.id,
98
+ })),
99
+ });
100
+ }
101
+ const startCmd = new Command('start')
102
+ .description('Start (resume) a copy trade')
103
+ .argument('[id]', 'Copy trade ID')
104
+ .action(wrapAction(async (idArg) => {
105
+ const creds = requireAuth();
106
+ const id = idArg ?? await pickCopyTrade(creds.accessToken);
107
+ const spin = spinner('Starting…');
108
+ const res = await ctApi.startCopyTrade(creds.accessToken, id);
109
+ spin.stop();
110
+ assertApiOk(res, 'Failed to start copy trade');
111
+ success('Copy trade started.');
112
+ }));
113
+ const stopCmd = new Command('stop')
114
+ .description('Stop (pause) a copy trade')
115
+ .argument('[id]', 'Copy trade ID')
116
+ .action(wrapAction(async (idArg) => {
117
+ const creds = requireAuth();
118
+ const id = idArg ?? await pickCopyTrade(creds.accessToken);
119
+ const ok = await confirm({ message: `Stop copy trade ${id.slice(0, 12)}…?`, default: false });
120
+ if (!ok)
121
+ return;
122
+ const spin = spinner('Stopping…');
123
+ const res = await ctApi.stopCopyTrade(creds.accessToken, id);
124
+ spin.stop();
125
+ assertApiOk(res, 'Failed to stop copy trade');
126
+ success('Copy trade stopped.');
127
+ }));
128
+ // ─── delete ──────────────────────────────────────────────────────────────
129
+ const deleteCmd = new Command('delete')
130
+ .description('Delete a copy trade')
131
+ .argument('[id]', 'Copy trade ID')
132
+ .option('-y, --yes', 'Skip confirmation')
133
+ .action(wrapAction(async (idArg, opts) => {
134
+ const creds = requireAuth();
135
+ const id = idArg ?? await pickCopyTrade(creds.accessToken);
136
+ if (!opts?.yes) {
137
+ const ok = await confirm({ message: `Delete copy trade ${id.slice(0, 12)}…? This cannot be undone.`, default: false });
138
+ if (!ok)
139
+ return;
140
+ }
141
+ const spin = spinner('Deleting…');
142
+ const res = await ctApi.deleteCopyTrade(creds.accessToken, id);
143
+ spin.stop();
144
+ assertApiOk(res, 'Failed to delete copy trade');
145
+ success('Copy trade deleted.');
146
+ }));
147
+ // ─── parent ──────────────────────────────────────────────────────────────
148
+ export const copyTradeCommand = new Command('copy-trade')
149
+ .alias('ct')
150
+ .description('Copy trading — follow wallet addresses')
151
+ .addCommand(createCmd)
152
+ .addCommand(listCmd)
153
+ .addCommand(startCmd)
154
+ .addCommand(stopCmd)
155
+ .addCommand(deleteCmd)
156
+ .action(wrapAction(async () => {
157
+ const action = await select({
158
+ message: 'Copy Trade:',
159
+ choices: [
160
+ { name: 'Create a new copy trade', value: 'create' },
161
+ { name: 'List copy trades', value: 'list' },
162
+ { name: 'Start a copy trade', value: 'start' },
163
+ { name: 'Stop a copy trade', value: 'stop' },
164
+ { name: 'Delete a copy trade', value: 'delete' },
165
+ ],
166
+ });
167
+ const sub = copyTradeCommand.commands.find((c) => c.name() === action);
168
+ if (sub)
169
+ await sub.parseAsync([], { from: 'user' });
170
+ }));
@@ -286,6 +286,8 @@ const cancelCmd = new Command('cancel')
286
286
  const closeCmd = new Command('close')
287
287
  .description('Close an open perps position at market price')
288
288
  .option('-y, --yes', 'Skip confirmation')
289
+ .option('-a, --all', 'Close all open positions (non-interactive)')
290
+ .option('-s, --symbol <symbol>', 'Close position by symbol (non-interactive, e.g. BTC, ETH)')
289
291
  .action(wrapAction(async (opts) => {
290
292
  const creds = requireAuth();
291
293
  const spin = spinner('Fetching positions…');
@@ -307,21 +309,119 @@ const closeCmd = new Command('close')
307
309
  const color = n >= 0 ? chalk.green : chalk.red;
308
310
  return color(`${n >= 0 ? '+' : ''}${fmt(n)}`);
309
311
  };
310
- const selected = await select({
311
- message: 'Select position to close:',
312
- choices: positions.map((p) => {
312
+ // Helper to close specific positions (used by --all and --symbol)
313
+ const closePositions = async (positionsToClose, title) => {
314
+ console.log('');
315
+ console.log(chalk.bold(title));
316
+ console.log(` Positions to close: ${positionsToClose.length}`);
317
+ positionsToClose.forEach((p) => {
313
318
  const symbol = String(p.symbol ?? '');
314
319
  const side = String(p.side ?? '').toLowerCase();
315
- const sideLabel = side === 'long' || side === 'buy' ? chalk.green('LONG') : chalk.red('SHORT');
320
+ const sideLabel = side === 'long' || side === 'buy' ? 'LONG' : 'SHORT';
316
321
  const sz = String(p.size ?? '');
317
- const entry = fmt(Number(p.entryPrice ?? 0));
318
- const pnl = pnlFmt(Number(p.unrealizedPnl ?? 0));
319
- return {
320
- name: `${chalk.bold(symbol.padEnd(6))} ${sideLabel} ${sz} @ ${chalk.yellow(entry)} PnL: ${pnl}`,
321
- value: p,
322
+ console.log(` - ${symbol} ${sideLabel} ${sz}`);
323
+ });
324
+ console.log('');
325
+ if (!opts.yes) {
326
+ await requireTransactionConfirmation(`Close ${positionsToClose.length} position(s) @ Market`);
327
+ }
328
+ await requireTouchId();
329
+ const orderSpin = spinner('Closing positions…');
330
+ const results = [];
331
+ for (const pos of positionsToClose) {
332
+ const symbol = String(pos.symbol ?? '');
333
+ const side = String(pos.side ?? '').toLowerCase();
334
+ const sz = String(pos.size ?? '');
335
+ const isLong = side === 'long' || side === 'buy';
336
+ const isBuy = !isLong;
337
+ const assetMeta = assets.find((a) => a.name.toUpperCase() === symbol.toUpperCase());
338
+ const marketPx = assetMeta?.markPx;
339
+ if (!marketPx || marketPx <= 0) {
340
+ results.push({ symbol, side, success: false, error: 'Could not fetch price' });
341
+ continue;
342
+ }
343
+ const slippagePx = isBuy ? marketPx * 1.01 : marketPx * 0.99;
344
+ const limitPx = slippagePx.toPrecision(5);
345
+ const order = {
346
+ a: symbol,
347
+ b: isBuy,
348
+ p: limitPx,
349
+ s: sz,
350
+ r: true,
351
+ t: { trigger: { triggerPx: String(marketPx), tpsl: 'tp', isMarket: true } },
322
352
  };
323
- }),
353
+ try {
354
+ const orderRes = await perpsApi.placeOrders(creds.accessToken, { orders: [order], grouping: 'na' });
355
+ if (orderRes.success) {
356
+ results.push({ symbol, side, success: true });
357
+ }
358
+ else {
359
+ const errMsg = orderRes.error ? `${orderRes.error.code}: ${orderRes.error.message}` : 'Unknown error';
360
+ results.push({ symbol, side, success: false, error: errMsg });
361
+ }
362
+ }
363
+ catch (e) {
364
+ results.push({ symbol, side, success: false, error: String(e) });
365
+ }
366
+ }
367
+ orderSpin.stop();
368
+ // Report results
369
+ const succeeded = results.filter((r) => r.success);
370
+ const failed = results.filter((r) => !r.success);
371
+ if (succeeded.length > 0) {
372
+ success(`Closed ${succeeded.length} position(s):`);
373
+ succeeded.forEach((r) => {
374
+ console.log(` ✓ ${r.symbol} ${r.side.toUpperCase()}`);
375
+ });
376
+ }
377
+ if (failed.length > 0) {
378
+ warn(`Failed to close ${failed.length} position(s):`);
379
+ failed.forEach((r) => {
380
+ console.log(` ✗ ${r.symbol} ${r.side.toUpperCase()}: ${r.error}`);
381
+ });
382
+ }
383
+ };
384
+ // If --all flag is set, close all positions directly (non-interactive)
385
+ if (opts.all) {
386
+ await closePositions(positions, 'Close ALL Positions:');
387
+ return;
388
+ }
389
+ // If --symbol flag is set, close positions matching the symbol (non-interactive)
390
+ if (opts.symbol) {
391
+ const symbolUpper = opts.symbol.toUpperCase();
392
+ const matchingPositions = positions.filter((p) => String(p.symbol ?? '').toUpperCase() === symbolUpper);
393
+ if (matchingPositions.length === 0) {
394
+ warn(`No open positions found for symbol: ${opts.symbol}`);
395
+ return;
396
+ }
397
+ await closePositions(matchingPositions, `Close ${opts.symbol.toUpperCase()} Positions:`);
398
+ return;
399
+ }
400
+ const positionChoices = positions.map((p) => {
401
+ const symbol = String(p.symbol ?? '');
402
+ const side = String(p.side ?? '').toLowerCase();
403
+ const sideLabel = side === 'long' || side === 'buy' ? chalk.green('LONG') : chalk.red('SHORT');
404
+ const sz = String(p.size ?? '');
405
+ const entry = fmt(Number(p.entryPrice ?? 0));
406
+ const pnl = pnlFmt(Number(p.unrealizedPnl ?? 0));
407
+ return {
408
+ name: `${chalk.bold(symbol.padEnd(6))} ${sideLabel} ${sz} @ ${chalk.yellow(entry)} PnL: ${pnl}`,
409
+ value: p,
410
+ };
324
411
  });
412
+ // Add "ALL POSITIONS" option at the beginning
413
+ const allOption = { name: chalk.bold.cyan('[ CLOSE ALL POSITIONS ]'), value: '__ALL__' };
414
+ const choices = [allOption, ...positionChoices];
415
+ const selected = await select({
416
+ message: 'Select position to close:',
417
+ choices,
418
+ });
419
+ // Handle "ALL POSITIONS" selection
420
+ if (selected === '__ALL__') {
421
+ await closePositions(positions, 'Close ALL Positions:');
422
+ return;
423
+ }
424
+ // Single position close (existing logic)
325
425
  const symbol = String(selected.symbol ?? '');
326
426
  const side = String(selected.side ?? '').toLowerCase();
327
427
  const sz = String(selected.size ?? '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minara",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
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",