minara 0.4.0 → 0.4.2

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.
@@ -455,11 +455,18 @@ const positionsCmd = new Command('positions')
455
455
  console.log(chalk.dim(` Total positions: ${totalPositions}`));
456
456
  console.log('');
457
457
  }));
458
- // ─── order ───────────────────────────────────────────────────────────────
459
458
  const orderCmd = new Command('order')
460
459
  .description('Place a perps order')
461
460
  .option(WALLET_OPT[0], WALLET_OPT[1])
462
461
  .option('-y, --yes', 'Skip confirmation')
462
+ .option('-S, --side <side>', 'Order side: long/buy or short/sell')
463
+ .option('-s, --symbol <symbol>', 'Asset symbol (e.g. BTC, ETH)')
464
+ .option('-T, --type <type>', 'Order type: market or limit', 'market')
465
+ .option('-p, --price <price>', 'Limit price (required for limit orders)')
466
+ .option('-z, --size <size>', 'Position size in contracts')
467
+ .option('-r, --reduce-only', 'Reduce-only order')
468
+ .option('-g, --grouping <grouping>', 'TP/SL grouping: na, normalTpsl, positionTpsl', 'na')
469
+ .option('--tpsl <type>', 'Trigger type for market orders: tp (take profit) or sl (stop loss)', 'tp')
463
470
  .action(wrapAction(async (opts) => {
464
471
  const creds = requireAuth();
465
472
  const resolved = await resolveWallet(creds.accessToken, opts.wallet, 'Place order on which wallet?');
@@ -481,6 +488,8 @@ const orderCmd = new Command('order')
481
488
  console.log('');
482
489
  return;
483
490
  }
491
+ // Determine if running in non-interactive mode
492
+ const nonInteractive = opts.side && opts.symbol && opts.size;
484
493
  info('Building a Hyperliquid perps order…');
485
494
  const dataSpin = spinner('Fetching market data…');
486
495
  const address = await perpsApi.getPerpsAddress(creds.accessToken);
@@ -493,15 +502,36 @@ const orderCmd = new Command('order')
493
502
  for (const l of leverages) {
494
503
  leverageMap.set(l.coin.toUpperCase(), { value: l.leverageValue, type: l.leverageType });
495
504
  }
496
- const isBuy = await select({
497
- message: 'Side:',
498
- choices: [
499
- { name: 'Long (buy)', value: true },
500
- { name: 'Short (sell)', value: false },
501
- ],
502
- });
505
+ // ── Side ─────────────────────────────────────────────────────────────
506
+ let isBuy;
507
+ if (opts.side) {
508
+ const sideLower = opts.side.toLowerCase();
509
+ if (sideLower === 'long' || sideLower === 'buy') {
510
+ isBuy = true;
511
+ }
512
+ else if (sideLower === 'short' || sideLower === 'sell') {
513
+ isBuy = false;
514
+ }
515
+ else {
516
+ console.error(chalk.red('✖'), `Invalid side: ${opts.side}. Use 'long', 'buy', 'short', or 'sell'.`);
517
+ process.exit(1);
518
+ }
519
+ }
520
+ else {
521
+ isBuy = await select({
522
+ message: 'Side:',
523
+ choices: [
524
+ { name: 'Long (buy)', value: true },
525
+ { name: 'Short (sell)', value: false },
526
+ ],
527
+ });
528
+ }
529
+ // ── Asset ────────────────────────────────────────────────────────────
503
530
  let asset;
504
- if (assets.length > 0) {
531
+ if (opts.symbol) {
532
+ asset = opts.symbol.toUpperCase();
533
+ }
534
+ else if (assets.length > 0) {
505
535
  asset = await select({
506
536
  message: 'Asset:',
507
537
  choices: assets.map((a) => {
@@ -525,42 +555,124 @@ const orderCmd = new Command('order')
525
555
  else {
526
556
  info(`No leverage set for ${asset} — use 'minara perps leverage' to configure`);
527
557
  }
528
- const orderType = await select({
529
- message: 'Order type:',
530
- choices: [
531
- { name: 'Market', value: 'market' },
532
- { name: 'Limit', value: 'limit' },
533
- ],
534
- });
558
+ // ── Order Type ───────────────────────────────────────────────────────
559
+ let orderType;
560
+ if (opts.type) {
561
+ const typeLower = opts.type.toLowerCase();
562
+ if (typeLower === 'market') {
563
+ orderType = 'market';
564
+ }
565
+ else if (typeLower === 'limit') {
566
+ orderType = 'limit';
567
+ }
568
+ else {
569
+ console.error(chalk.red('✖'), `Invalid order type: ${opts.type}. Use 'market' or 'limit'.`);
570
+ process.exit(1);
571
+ }
572
+ }
573
+ else {
574
+ orderType = await select({
575
+ message: 'Order type:',
576
+ choices: [
577
+ { name: 'Market', value: 'market' },
578
+ { name: 'Limit', value: 'limit' },
579
+ ],
580
+ });
581
+ }
582
+ // ── Price ────────────────────────────────────────────────────────────
535
583
  const assetMeta = assets.find((a) => a.name.toUpperCase() === asset.toUpperCase());
536
584
  let limitPx;
537
585
  let marketPx;
538
586
  if (orderType === 'limit') {
539
- limitPx = await input({ message: 'Limit price:' });
587
+ if (opts.price) {
588
+ limitPx = opts.price;
589
+ }
590
+ else if (nonInteractive) {
591
+ console.error(chalk.red('✖'), 'Limit orders require --price');
592
+ process.exit(1);
593
+ }
594
+ else {
595
+ limitPx = await input({ message: 'Limit price:' });
596
+ }
540
597
  }
541
598
  else {
599
+ // Market order
542
600
  marketPx = assetMeta?.markPx;
543
- if (marketPx && marketPx > 0) {
601
+ if (opts.price) {
602
+ // User specified price for market order (use as trigger)
603
+ limitPx = opts.price;
604
+ marketPx = Number(opts.price);
605
+ info(`Market order at ~$${marketPx}`);
606
+ }
607
+ else if (marketPx && marketPx > 0) {
544
608
  const slippagePx = isBuy ? marketPx * 1.01 : marketPx * 0.99;
545
609
  limitPx = slippagePx.toPrecision(5);
546
610
  info(`Market order at ~$${marketPx}`);
547
611
  }
612
+ else if (nonInteractive) {
613
+ console.error(chalk.red('✖'), `Could not fetch current price for ${asset}. Use --price to specify.`);
614
+ process.exit(1);
615
+ }
548
616
  else {
549
617
  warn(`Could not fetch current price for ${asset}. Enter the approximate market price.`);
550
618
  limitPx = await input({ message: 'Price:' });
551
619
  marketPx = Number(limitPx);
552
620
  }
553
621
  }
554
- const sz = await input({ message: 'Size (in contracts):' });
555
- const reduceOnly = await confirm({ message: 'Reduce only?', default: false });
556
- const grouping = await select({
557
- message: 'Grouping (TP/SL):',
558
- choices: [
559
- { name: 'None', value: 'na' },
560
- { name: 'Normal TP/SL', value: 'normalTpsl' },
561
- { name: 'Position TP/SL', value: 'positionTpsl' },
562
- ],
563
- });
622
+ // ── Size ─────────────────────────────────────────────────────────────
623
+ let sz;
624
+ if (opts.size) {
625
+ sz = opts.size;
626
+ }
627
+ else if (nonInteractive) {
628
+ console.error(chalk.red(''), 'Size is required. Use --size');
629
+ process.exit(1);
630
+ }
631
+ else {
632
+ sz = await input({ message: 'Size (in contracts):' });
633
+ }
634
+ // ── Reduce Only ──────────────────────────────────────────────────────
635
+ let reduceOnly;
636
+ if (opts.reduceOnly !== undefined) {
637
+ reduceOnly = opts.reduceOnly;
638
+ }
639
+ else if (nonInteractive) {
640
+ reduceOnly = false;
641
+ }
642
+ else {
643
+ reduceOnly = await confirm({ message: 'Reduce only?', default: false });
644
+ }
645
+ // ── Grouping ─────────────────────────────────────────────────────────
646
+ let grouping;
647
+ if (opts.grouping) {
648
+ const groupingLower = opts.grouping.toLowerCase();
649
+ if (groupingLower === 'na' || groupingLower === 'none') {
650
+ grouping = 'na';
651
+ }
652
+ else if (groupingLower === 'normaltpsl' || groupingLower === 'normal_tpsl') {
653
+ grouping = 'normalTpsl';
654
+ }
655
+ else if (groupingLower === 'positiontpsl' || groupingLower === 'position_tpsl') {
656
+ grouping = 'positionTpsl';
657
+ }
658
+ else {
659
+ console.error(chalk.red('✖'), `Invalid grouping: ${opts.grouping}. Use 'na', 'normalTpsl', or 'positionTpsl'.`);
660
+ process.exit(1);
661
+ }
662
+ }
663
+ else if (nonInteractive) {
664
+ grouping = 'na';
665
+ }
666
+ else {
667
+ grouping = await select({
668
+ message: 'Grouping (TP/SL):',
669
+ choices: [
670
+ { name: 'None', value: 'na' },
671
+ { name: 'Normal TP/SL', value: 'normalTpsl' },
672
+ { name: 'Position TP/SL', value: 'positionTpsl' },
673
+ ],
674
+ });
675
+ }
564
676
  const order = {
565
677
  a: asset,
566
678
  b: isBuy,
@@ -569,7 +681,7 @@ const orderCmd = new Command('order')
569
681
  r: reduceOnly,
570
682
  t: orderType === 'limit'
571
683
  ? { limit: { tif: 'Gtc' } }
572
- : { trigger: { triggerPx: String(marketPx ?? limitPx), tpsl: 'tp', isMarket: true } },
684
+ : { trigger: { triggerPx: String(marketPx ?? limitPx), tpsl: opts.tpsl ?? 'tp', isMarket: true } },
573
685
  };
574
686
  const priceLabel = orderType === 'market' ? `Market (~$${marketPx ?? limitPx})` : `$${limitPx}`;
575
687
  const levLabel = currentLev ? `${currentLev.value}x (${currentLev.type})` : '—';
@@ -578,7 +690,7 @@ const orderCmd = new Command('order')
578
690
  console.log(` Asset : ${chalk.bold(order.a)}`);
579
691
  console.log(` Side : ${formatOrderSide(order.b ? 'buy' : 'sell')}`);
580
692
  console.log(` Leverage : ${chalk.cyan(levLabel)}`);
581
- console.log(` Type : ${orderType === 'market' ? 'Market' : 'Limit (GTC)'}`);
693
+ console.log(` Type : ${orderType === 'market' ? `Market (${opts.tpsl === 'sl' ? 'Stop Loss' : 'Take Profit'})` : 'Limit (GTC)'}`);
582
694
  console.log(` Price : ${chalk.yellow(priceLabel)}`);
583
695
  console.log(` Size : ${chalk.bold(order.s)}`);
584
696
  console.log(` Reduce Only : ${order.r ? chalk.yellow('Yes') : 'No'}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minara",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",