minara 0.1.5 → 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/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 +77 -53
- 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 +17 -1
- 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 +55 -6
- package/dist/utils.d.ts +34 -0
- package/dist/utils.js +107 -1
- package/package.json +1 -1
|
@@ -3,11 +3,13 @@ import { input, confirm } from '@inquirer/prompts';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { transfer } from '../api/crosschain.js';
|
|
5
5
|
import { requireAuth } from '../config.js';
|
|
6
|
-
import { success, warn, spinner, assertApiOk, selectChain, wrapAction } from '../utils.js';
|
|
6
|
+
import { success, warn, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken, formatTokenLabel } from '../utils.js';
|
|
7
|
+
import { requireTouchId } from '../touchid.js';
|
|
8
|
+
import { printTxResult } from '../formatters.js';
|
|
7
9
|
export const transferCommand = new Command('transfer')
|
|
8
10
|
.description('Transfer tokens to another address')
|
|
9
11
|
.option('-c, --chain <chain>', 'Blockchain')
|
|
10
|
-
.option('-t, --token <address>', 'Token contract address')
|
|
12
|
+
.option('-t, --token <address|ticker>', 'Token contract address or ticker symbol')
|
|
11
13
|
.option('-a, --amount <amount>', 'Token amount to send')
|
|
12
14
|
.option('--to <address>', 'Recipient address')
|
|
13
15
|
.option('-y, --yes', 'Skip confirmation')
|
|
@@ -16,10 +18,11 @@ export const transferCommand = new Command('transfer')
|
|
|
16
18
|
// ── 1. Chain ─────────────────────────────────────────────────────────
|
|
17
19
|
const chain = opts.chain ?? await selectChain();
|
|
18
20
|
// ── 2. Token ─────────────────────────────────────────────────────────
|
|
19
|
-
const
|
|
20
|
-
message: 'Token
|
|
21
|
-
validate: (v) => (v.length > 0 ? true : '
|
|
21
|
+
const tokenInput = opts.token ?? await input({
|
|
22
|
+
message: 'Token (address or ticker, native = 0x0…0):',
|
|
23
|
+
validate: (v) => (v.length > 0 ? true : 'Token is required'),
|
|
22
24
|
});
|
|
25
|
+
const tokenInfo = await lookupToken(tokenInput);
|
|
23
26
|
// ── 3. Amount ────────────────────────────────────────────────────────
|
|
24
27
|
const amount = opts.amount ?? await input({
|
|
25
28
|
message: 'Amount to send:',
|
|
@@ -33,11 +36,12 @@ export const transferCommand = new Command('transfer')
|
|
|
33
36
|
message: 'Recipient address:',
|
|
34
37
|
validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
|
|
35
38
|
});
|
|
36
|
-
// ── 5. Summary
|
|
39
|
+
// ── 5. Summary ───────────────────────────────────────────────────────
|
|
37
40
|
console.log('');
|
|
38
41
|
console.log(chalk.bold.red('⚠ Transfer Summary:'));
|
|
39
42
|
console.log(` Chain : ${chalk.cyan(chain)}`);
|
|
40
|
-
console.log(` Token : ${
|
|
43
|
+
console.log(` Token : ${formatTokenLabel(tokenInfo)}`);
|
|
44
|
+
console.log(` Address : ${chalk.yellow(tokenInfo.address)}`);
|
|
41
45
|
console.log(` Amount : ${chalk.bold(amount)}`);
|
|
42
46
|
console.log(` To : ${chalk.yellow(recipient)}`);
|
|
43
47
|
console.log('');
|
|
@@ -49,12 +53,14 @@ export const transferCommand = new Command('transfer')
|
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
|
-
// ── 6.
|
|
56
|
+
// ── 6. Transaction confirmation & Touch ID ────────────────────────────
|
|
57
|
+
await requireTransactionConfirmation(`Transfer ${amount} tokens → ${recipient} · ${chain}`, tokenInfo);
|
|
58
|
+
await requireTouchId();
|
|
59
|
+
// ── 7. Execute ───────────────────────────────────────────────────────
|
|
53
60
|
const spin = spinner('Processing transfer…');
|
|
54
|
-
const res = await transfer(creds.accessToken, { chain, tokenAddress, tokenAmount: amount, recipient });
|
|
61
|
+
const res = await transfer(creds.accessToken, { chain, tokenAddress: tokenInfo.address, tokenAmount: amount, recipient });
|
|
55
62
|
spin.stop();
|
|
56
63
|
assertApiOk(res, 'Transfer failed');
|
|
57
64
|
success('Transfer submitted!');
|
|
58
|
-
|
|
59
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
65
|
+
printTxResult(res.data);
|
|
60
66
|
}));
|
|
@@ -3,11 +3,13 @@ import { input, confirm } from '@inquirer/prompts';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { transfer, getAssets } from '../api/crosschain.js';
|
|
5
5
|
import { requireAuth } from '../config.js';
|
|
6
|
-
import { success, warn, spinner, assertApiOk, selectChain, wrapAction } from '../utils.js';
|
|
6
|
+
import { success, warn, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken, formatTokenLabel } from '../utils.js';
|
|
7
|
+
import { requireTouchId } from '../touchid.js';
|
|
8
|
+
import { printTxResult } from '../formatters.js';
|
|
7
9
|
export const withdrawCommand = new Command('withdraw')
|
|
8
10
|
.description('Withdraw tokens from your Minara wallet to an external address')
|
|
9
11
|
.option('-c, --chain <chain>', 'Blockchain network')
|
|
10
|
-
.option('-t, --token <address>', 'Token contract address')
|
|
12
|
+
.option('-t, --token <address|ticker>', 'Token contract address or ticker symbol')
|
|
11
13
|
.option('-a, --amount <amount>', 'Amount to withdraw')
|
|
12
14
|
.option('--to <address>', 'Destination address')
|
|
13
15
|
.option('-y, --yes', 'Skip confirmation')
|
|
@@ -39,10 +41,11 @@ export const withdrawCommand = new Command('withdraw')
|
|
|
39
41
|
// ── 2. Chain ─────────────────────────────────────────────────────────
|
|
40
42
|
const chain = opts.chain ?? await selectChain('Withdraw on which blockchain?');
|
|
41
43
|
// ── 3. Token ─────────────────────────────────────────────────────────
|
|
42
|
-
const
|
|
43
|
-
message: `Token
|
|
44
|
-
validate: (v) => (v.length > 0 ? true : 'Token
|
|
44
|
+
const tokenInput = opts.token ?? await input({
|
|
45
|
+
message: `Token on ${chalk.cyan(chain)} (address or ticker, native = ${'0x' + '0'.repeat(40)}):`,
|
|
46
|
+
validate: (v) => (v.length > 0 ? true : 'Token is required'),
|
|
45
47
|
});
|
|
48
|
+
const tokenInfo = await lookupToken(tokenInput);
|
|
46
49
|
// ── 4. Amount ────────────────────────────────────────────────────────
|
|
47
50
|
const amount = opts.amount ?? await input({
|
|
48
51
|
message: 'Amount to withdraw:',
|
|
@@ -56,11 +59,12 @@ export const withdrawCommand = new Command('withdraw')
|
|
|
56
59
|
message: 'Destination address (your external wallet):',
|
|
57
60
|
validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
|
|
58
61
|
});
|
|
59
|
-
// ── 6. Summary
|
|
62
|
+
// ── 6. Summary ───────────────────────────────────────────────────────
|
|
60
63
|
console.log('');
|
|
61
64
|
console.log(chalk.bold.red('⚠ Withdrawal Summary'));
|
|
62
65
|
console.log(` Chain : ${chalk.cyan(chain)}`);
|
|
63
|
-
console.log(` Token : ${
|
|
66
|
+
console.log(` Token : ${formatTokenLabel(tokenInfo)}`);
|
|
67
|
+
console.log(` Address : ${chalk.yellow(tokenInfo.address)}`);
|
|
64
68
|
console.log(` Amount : ${chalk.bold(amount)}`);
|
|
65
69
|
console.log(` Destination : ${chalk.yellow(recipient)}`);
|
|
66
70
|
console.log('');
|
|
@@ -77,13 +81,15 @@ export const withdrawCommand = new Command('withdraw')
|
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
|
-
// ── 7.
|
|
84
|
+
// ── 7. Transaction confirmation & Touch ID ────────────────────────────
|
|
85
|
+
await requireTransactionConfirmation(`Withdraw ${amount} tokens → ${recipient} · ${chain}`, tokenInfo);
|
|
86
|
+
await requireTouchId();
|
|
87
|
+
// ── 8. Execute ───────────────────────────────────────────────────────
|
|
81
88
|
const spin = spinner('Processing withdrawal…');
|
|
82
|
-
const res = await transfer(creds.accessToken, { chain, tokenAddress, tokenAmount: amount, recipient });
|
|
89
|
+
const res = await transfer(creds.accessToken, { chain, tokenAddress: tokenInfo.address, tokenAmount: amount, recipient });
|
|
83
90
|
spin.stop();
|
|
84
91
|
assertApiOk(res, 'Withdrawal failed');
|
|
85
92
|
success('Withdrawal submitted!');
|
|
86
|
-
|
|
87
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
93
|
+
printTxResult(res.data);
|
|
88
94
|
console.log(chalk.dim('\nIt may take a few minutes for the transaction to be confirmed on-chain.'));
|
|
89
95
|
}));
|
package/dist/config.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare function clearCredentials(): void;
|
|
|
5
5
|
export declare function requireAuth(): Credentials;
|
|
6
6
|
export interface AppConfig {
|
|
7
7
|
baseUrl: string;
|
|
8
|
+
touchId?: boolean;
|
|
9
|
+
confirmBeforeTransaction?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export declare function loadConfig(): AppConfig;
|
|
10
12
|
export declare function saveConfig(config: Partial<AppConfig>): void;
|
package/dist/config.js
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Enable raw JSON output (all formatters fall back to JSON.stringify). */
|
|
2
|
+
export declare function setRawJson(enabled: boolean): void;
|
|
3
|
+
/** Check if raw JSON output mode is active. */
|
|
4
|
+
export declare function isRawJson(): boolean;
|
|
5
|
+
/** Convert camelCase / snake_case key to "Title Case" label. */
|
|
6
|
+
export declare function formatLabel(key: string): string;
|
|
7
|
+
/** Smart-format a single value for terminal display. */
|
|
8
|
+
export declare function formatValue(value: unknown, key?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Print an object as aligned key-value pairs.
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* Transaction Id : 0x1234…abcd
|
|
14
|
+
* Status : pending
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function printKV(data: Record<string, unknown> | object, indent?: number): void;
|
|
18
|
+
export interface ColumnDef {
|
|
19
|
+
/** Object key (supports nested: "a.b") */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Column header label (defaults to formatLabel(key)) */
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Custom formatter for cell value */
|
|
24
|
+
format?: (value: unknown, row: Record<string, unknown>) => string;
|
|
25
|
+
/** Max column width (content will be truncated) */
|
|
26
|
+
maxWidth?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Print an array of objects as a CLI table.
|
|
30
|
+
*
|
|
31
|
+
* If `columns` is omitted, columns are auto-detected from the data.
|
|
32
|
+
*/
|
|
33
|
+
export declare function printTable(data: Record<string, unknown>[] | object[], columns?: ColumnDef[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Pretty-print a transaction result (deposit, withdraw, swap, transfer, order, etc.)
|
|
36
|
+
* Falls back silently if data is empty/undefined.
|
|
37
|
+
*/
|
|
38
|
+
export declare function printTxResult(data: unknown): void;
|
|
39
|
+
/** Spot wallet assets (WalletAsset[]) */
|
|
40
|
+
export declare const ASSET_COLUMNS: ColumnDef[];
|
|
41
|
+
/** Perps positions (PerpsPosition[]) */
|
|
42
|
+
export declare const POSITION_COLUMNS: ColumnDef[];
|
|
43
|
+
/** Limit orders (LimitOrderInfo[]) */
|
|
44
|
+
export declare const LIMIT_ORDER_COLUMNS: ColumnDef[];
|
|
45
|
+
/** Copy trades (CopyTradeInfo[]) */
|
|
46
|
+
export declare const COPY_TRADE_COLUMNS: ColumnDef[];
|
|
47
|
+
/** Trending / search tokens (TokenInfo[]) */
|
|
48
|
+
export declare const TOKEN_COLUMNS: ColumnDef[];
|
|
49
|
+
/**
|
|
50
|
+
* Pretty-print Fear & Greed Index — hides redundant `timestamp` and `price`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function printFearGreed(data: Record<string, unknown>): void;
|
|
53
|
+
/**
|
|
54
|
+
* Pretty-print BTC/crypto metrics — flatten currentQuote, skip ohlcvQuotes.
|
|
55
|
+
*/
|
|
56
|
+
export declare function printCryptoMetrics(data: Record<string, unknown>): void;
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// CLI-friendly data formatters
|
|
3
|
+
//
|
|
4
|
+
// Replaces raw JSON.stringify output with tables and key-value displays.
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import Table from 'cli-table3';
|
|
8
|
+
// ─── Raw JSON mode ───────────────────────────────────────────────────────
|
|
9
|
+
let _rawJson = false;
|
|
10
|
+
/** Enable raw JSON output (all formatters fall back to JSON.stringify). */
|
|
11
|
+
export function setRawJson(enabled) { _rawJson = enabled; }
|
|
12
|
+
/** Check if raw JSON output mode is active. */
|
|
13
|
+
export function isRawJson() { return _rawJson; }
|
|
14
|
+
// ─── Hidden keys (internal IDs, metadata noise) ─────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Keys matching this pattern are hidden from printKV / auto-detected table
|
|
17
|
+
* columns. They are still included in --json raw output.
|
|
18
|
+
*/
|
|
19
|
+
const HIDDEN_KEYS = /^_?id$|^_id$|^__v$/i;
|
|
20
|
+
// ─── Per-context key exclusions (non-regex, for specific commands) ───────
|
|
21
|
+
/** Keys to skip from Fear & Greed display (redundant with pointDate). */
|
|
22
|
+
const FEAR_GREED_HIDDEN = new Set(['timestamp', 'price']);
|
|
23
|
+
// ─── Key / value helpers ─────────────────────────────────────────────────
|
|
24
|
+
/** Convert camelCase / snake_case key to "Title Case" label. */
|
|
25
|
+
export function formatLabel(key) {
|
|
26
|
+
return key
|
|
27
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // camelCase → camel Case
|
|
28
|
+
.replace(/[_-]/g, ' ') // snake_case / kebab → spaces
|
|
29
|
+
.replace(/\b\w/g, (c) => c.toUpperCase()); // Capitalise words
|
|
30
|
+
}
|
|
31
|
+
/** Smart-format a single value for terminal display. */
|
|
32
|
+
export function formatValue(value, key) {
|
|
33
|
+
if (value === null || value === undefined || value === '')
|
|
34
|
+
return chalk.dim('—');
|
|
35
|
+
if (typeof value === 'boolean')
|
|
36
|
+
return value ? chalk.green('Yes') : chalk.dim('No');
|
|
37
|
+
if (typeof value === 'number') {
|
|
38
|
+
// Percentage-like keys
|
|
39
|
+
if (key && /percent|change|pnl|roi|rate/i.test(key)) {
|
|
40
|
+
const sign = value >= 0 ? '+' : '';
|
|
41
|
+
const color = value >= 0 ? chalk.green : chalk.red;
|
|
42
|
+
return color(`${sign}${value.toLocaleString('en-US', { maximumFractionDigits: 4 })}%`);
|
|
43
|
+
}
|
|
44
|
+
// Price / USD-like keys
|
|
45
|
+
if (key && /price|value|usd|amount|equity|margin|balance|fee|cost|cap/i.test(key)) {
|
|
46
|
+
return `$${value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })}`;
|
|
47
|
+
}
|
|
48
|
+
// Unix timestamps – ms (13 digits ~2001–2100) or seconds (10 digits ~2001–2100)
|
|
49
|
+
if (key && /timestamp|time|date|createdAt|updatedAt|expir/i.test(key)) {
|
|
50
|
+
const ms = value > 1e12 ? value : value * 1000;
|
|
51
|
+
if (ms > 946684800000 && ms < 4102444800000) { // 2000-01-01 to 2100-01-01
|
|
52
|
+
return new Date(ms).toLocaleString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return value.toLocaleString('en-US', { maximumFractionDigits: 6 });
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'string') {
|
|
58
|
+
// Numeric string that looks like a price / amount
|
|
59
|
+
const num = Number(value);
|
|
60
|
+
if (!isNaN(num) && value.trim() !== '') {
|
|
61
|
+
return formatValue(num, key);
|
|
62
|
+
}
|
|
63
|
+
// Timestamps (ISO / unix seconds)
|
|
64
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
65
|
+
return new Date(value).toLocaleString();
|
|
66
|
+
}
|
|
67
|
+
// Hex addresses
|
|
68
|
+
if (/^0x[0-9a-fA-F]{20,}$/.test(value))
|
|
69
|
+
return chalk.yellow(value);
|
|
70
|
+
// URLs
|
|
71
|
+
if (value.startsWith('http'))
|
|
72
|
+
return chalk.cyan.underline(value);
|
|
73
|
+
// Status-like
|
|
74
|
+
if (/^(success|completed|filled|running|active)$/i.test(value))
|
|
75
|
+
return chalk.green(value);
|
|
76
|
+
if (/^(failed|error|rejected|cancelled|canceled)$/i.test(value))
|
|
77
|
+
return chalk.red(value);
|
|
78
|
+
if (/^(pending|open|processing|paused)$/i.test(value))
|
|
79
|
+
return chalk.yellow(value);
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
if (value.length === 0)
|
|
84
|
+
return chalk.dim('—');
|
|
85
|
+
if (value.every((v) => typeof v === 'string' || typeof v === 'number')) {
|
|
86
|
+
return value.join(', ');
|
|
87
|
+
}
|
|
88
|
+
return chalk.dim(`[${value.length} items]`);
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === 'object') {
|
|
91
|
+
// Shallow nested — show as inline key=value
|
|
92
|
+
const entries = Object.entries(value).filter(([, v]) => v !== null && v !== undefined);
|
|
93
|
+
if (entries.length <= 3) {
|
|
94
|
+
return entries.map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`).join(' ');
|
|
95
|
+
}
|
|
96
|
+
return chalk.dim(JSON.stringify(value));
|
|
97
|
+
}
|
|
98
|
+
return String(value);
|
|
99
|
+
}
|
|
100
|
+
// ─── printKV ─────────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Print an object as aligned key-value pairs.
|
|
103
|
+
*
|
|
104
|
+
* ```
|
|
105
|
+
* Transaction Id : 0x1234…abcd
|
|
106
|
+
* Status : pending
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function printKV(data, indent = 2) {
|
|
110
|
+
if (_rawJson) {
|
|
111
|
+
console.log(JSON.stringify(data, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const entries = Object.entries(data).filter(([k, v]) => v !== undefined && v !== null && v !== '' && !HIDDEN_KEYS.test(k));
|
|
115
|
+
if (entries.length === 0) {
|
|
116
|
+
console.log(chalk.dim(`${' '.repeat(indent)}No data.`));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const labels = entries.map(([k]) => formatLabel(k));
|
|
120
|
+
const maxLen = Math.max(...labels.map((l) => l.length));
|
|
121
|
+
const pad = ' '.repeat(indent);
|
|
122
|
+
for (let i = 0; i < entries.length; i++) {
|
|
123
|
+
const [key, value] = entries[i];
|
|
124
|
+
const label = labels[i].padEnd(maxLen);
|
|
125
|
+
console.log(`${pad}${chalk.dim(label)} : ${formatValue(value, key)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ─── printTable ──────────────────────────────────────────────────────────
|
|
129
|
+
function getNestedValue(obj, path) {
|
|
130
|
+
return path.split('.').reduce((o, k) => {
|
|
131
|
+
if (o && typeof o === 'object' && k in o) {
|
|
132
|
+
return o[k];
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}, obj);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Auto-detect columns from array data.
|
|
139
|
+
* Picks keys that appear in most rows and are not deeply nested.
|
|
140
|
+
*/
|
|
141
|
+
function autoColumns(data) {
|
|
142
|
+
const keyCounts = new Map();
|
|
143
|
+
for (const row of data) {
|
|
144
|
+
for (const k of Object.keys(row)) {
|
|
145
|
+
keyCounts.set(k, (keyCounts.get(k) ?? 0) + 1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Keep keys present in at least half the rows, exclude hidden keys & complex nested objects
|
|
149
|
+
return [...keyCounts.entries()]
|
|
150
|
+
.filter(([k, count]) => count >= data.length / 2 && !HIDDEN_KEYS.test(k))
|
|
151
|
+
.filter(([k]) => {
|
|
152
|
+
const sample = data.find((r) => r[k] !== undefined)?.[k];
|
|
153
|
+
// Skip deeply nested objects / large arrays
|
|
154
|
+
if (Array.isArray(sample) && sample.length > 3)
|
|
155
|
+
return false;
|
|
156
|
+
if (sample && typeof sample === 'object' && !Array.isArray(sample)) {
|
|
157
|
+
const keys = Object.keys(sample);
|
|
158
|
+
return keys.length <= 3;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
})
|
|
162
|
+
.map(([key]) => ({ key }));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Print an array of objects as a CLI table.
|
|
166
|
+
*
|
|
167
|
+
* If `columns` is omitted, columns are auto-detected from the data.
|
|
168
|
+
*/
|
|
169
|
+
export function printTable(data, columns) {
|
|
170
|
+
if (_rawJson) {
|
|
171
|
+
console.log(JSON.stringify(data, null, 2));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!data || data.length === 0) {
|
|
175
|
+
console.log(chalk.dim(' No data.'));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const rows = data;
|
|
179
|
+
const cols = columns ?? autoColumns(rows);
|
|
180
|
+
if (cols.length === 0) {
|
|
181
|
+
// fallback — just printKV for each item
|
|
182
|
+
for (const row of data) {
|
|
183
|
+
printKV(row);
|
|
184
|
+
console.log('');
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const table = new Table({
|
|
189
|
+
head: cols.map((c) => chalk.white.bold(c.label ?? formatLabel(c.key))),
|
|
190
|
+
style: { head: [], border: ['dim'] },
|
|
191
|
+
wordWrap: true,
|
|
192
|
+
});
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
table.push(cols.map((c) => {
|
|
195
|
+
const raw = getNestedValue(row, c.key);
|
|
196
|
+
if (c.format)
|
|
197
|
+
return c.format(raw, row);
|
|
198
|
+
const str = formatValue(raw, c.key);
|
|
199
|
+
if (c.maxWidth && str.length > c.maxWidth) {
|
|
200
|
+
return str.slice(0, c.maxWidth - 1) + '…';
|
|
201
|
+
}
|
|
202
|
+
return str;
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
console.log(table.toString());
|
|
206
|
+
}
|
|
207
|
+
// ─── printTxResult ───────────────────────────────────────────────────────
|
|
208
|
+
/**
|
|
209
|
+
* Pretty-print a transaction result (deposit, withdraw, swap, transfer, order, etc.)
|
|
210
|
+
* Falls back silently if data is empty/undefined.
|
|
211
|
+
*/
|
|
212
|
+
export function printTxResult(data) {
|
|
213
|
+
if (!data)
|
|
214
|
+
return;
|
|
215
|
+
if (_rawJson) {
|
|
216
|
+
console.log(JSON.stringify(data, null, 2));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (typeof data !== 'object') {
|
|
220
|
+
console.log(chalk.dim(` ${data}`));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const obj = data;
|
|
224
|
+
console.log('');
|
|
225
|
+
printKV(obj);
|
|
226
|
+
}
|
|
227
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
228
|
+
// Pre-built column configs for known data types
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
+
/** Spot wallet assets (WalletAsset[]) */
|
|
231
|
+
export const ASSET_COLUMNS = [
|
|
232
|
+
{ key: 'symbol', label: 'Symbol', format: (v, row) => chalk.bold(String(v ?? row.tokenSymbol ?? '—')) },
|
|
233
|
+
{ key: 'balance', label: 'Balance', format: (v, row) => String(v ?? row.amount ?? '—') },
|
|
234
|
+
{ key: 'chain', label: 'Chain', format: (v, row) => chalk.cyan(String(v ?? row.chainName ?? '—')) },
|
|
235
|
+
{ key: 'usdValue', label: 'USD Value', format: (v) => v ? `$${Number(v).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : chalk.dim('—') },
|
|
236
|
+
{ key: 'tokenAddress', label: 'Token Address', format: (v) => v ? chalk.dim(truncate(String(v), 16)) : chalk.dim('—') },
|
|
237
|
+
];
|
|
238
|
+
/** Perps positions (PerpsPosition[]) */
|
|
239
|
+
export const POSITION_COLUMNS = [
|
|
240
|
+
{ key: 'symbol', label: 'Symbol', format: (v) => chalk.bold(String(v ?? '—')) },
|
|
241
|
+
{ key: 'side', label: 'Side', format: (v) => {
|
|
242
|
+
const s = String(v ?? '').toLowerCase();
|
|
243
|
+
return s === 'long' || s === 'buy' ? chalk.green.bold(String(v)) : chalk.red.bold(String(v));
|
|
244
|
+
} },
|
|
245
|
+
{ key: 'size', label: 'Size' },
|
|
246
|
+
{ key: 'entryPrice', label: 'Entry', format: (v) => formatValue(v, 'price') },
|
|
247
|
+
{ key: 'markPrice', label: 'Mark', format: (v) => formatValue(v, 'price') },
|
|
248
|
+
{ key: 'pnl', label: 'PnL', format: (v) => {
|
|
249
|
+
if (!v && v !== 0)
|
|
250
|
+
return chalk.dim('—');
|
|
251
|
+
const n = Number(v);
|
|
252
|
+
const color = n >= 0 ? chalk.green : chalk.red;
|
|
253
|
+
return color(`${n >= 0 ? '+' : ''}$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`);
|
|
254
|
+
} },
|
|
255
|
+
{ key: 'leverage', label: 'Lev', format: (v) => v ? `${v}x` : chalk.dim('—') },
|
|
256
|
+
];
|
|
257
|
+
/** Limit orders (LimitOrderInfo[]) */
|
|
258
|
+
export const LIMIT_ORDER_COLUMNS = [
|
|
259
|
+
{ key: 'id', label: 'ID', format: (v) => chalk.dim(truncate(String(v ?? ''), 12)) },
|
|
260
|
+
{ key: 'side', label: 'Side', format: (v) => {
|
|
261
|
+
const s = String(v ?? '').toLowerCase();
|
|
262
|
+
return s === 'buy' ? chalk.green.bold('BUY') : chalk.red.bold('SELL');
|
|
263
|
+
} },
|
|
264
|
+
{ key: 'chain', label: 'Chain', format: (v) => chalk.cyan(String(v ?? '—')) },
|
|
265
|
+
{ key: 'targetTokenCA', label: 'Token', format: (v) => v ? chalk.yellow(truncate(String(v), 14)) : chalk.dim('—') },
|
|
266
|
+
{ key: 'priceCondition', label: 'Condition', format: (v, row) => `${v ?? '?'} $${row.targetPrice ?? '?'}` },
|
|
267
|
+
{ key: 'amount', label: 'Amount', format: (v) => v ? `$${v}` : chalk.dim('—') },
|
|
268
|
+
{ key: 'status', label: 'Status', format: (v) => formatValue(v, 'status') },
|
|
269
|
+
];
|
|
270
|
+
/** Copy trades (CopyTradeInfo[]) */
|
|
271
|
+
export const COPY_TRADE_COLUMNS = [
|
|
272
|
+
{ key: 'id', label: 'ID', format: (v) => chalk.dim(truncate(String(v ?? ''), 12)) },
|
|
273
|
+
{ key: 'name', label: 'Name', format: (v) => String(v ?? chalk.dim('—')) },
|
|
274
|
+
{ key: 'chain', label: 'Chain', format: (v) => chalk.cyan(String(v ?? '—')) },
|
|
275
|
+
{ key: 'targetAddress', label: 'Target', format: (v) => v ? chalk.yellow(truncate(String(v), 14)) : chalk.dim('—') },
|
|
276
|
+
{ key: 'fixedAmount', label: 'Amount', format: (v) => v ? `$${v}` : chalk.dim('—') },
|
|
277
|
+
{ key: 'copySell', label: 'Copy Sell', format: (v) => v ? chalk.green('Yes') : chalk.dim('No') },
|
|
278
|
+
{ key: 'status', label: 'Status', format: (v) => formatValue(v, 'status') },
|
|
279
|
+
];
|
|
280
|
+
/** Format large numbers as $1.23B / $456.78M / $12.3K */
|
|
281
|
+
function compactUsd(v) {
|
|
282
|
+
if (!v && v !== 0)
|
|
283
|
+
return chalk.dim('—');
|
|
284
|
+
const n = Number(v);
|
|
285
|
+
if (isNaN(n))
|
|
286
|
+
return chalk.dim('—');
|
|
287
|
+
if (n >= 1e9)
|
|
288
|
+
return `$${(n / 1e9).toFixed(2)}B`;
|
|
289
|
+
if (n >= 1e6)
|
|
290
|
+
return `$${(n / 1e6).toFixed(2)}M`;
|
|
291
|
+
if (n >= 1e3)
|
|
292
|
+
return `$${(n / 1e3).toFixed(1)}K`;
|
|
293
|
+
return `$${n.toFixed(2)}`;
|
|
294
|
+
}
|
|
295
|
+
/** Trending / search tokens (TokenInfo[]) */
|
|
296
|
+
export const TOKEN_COLUMNS = [
|
|
297
|
+
{ key: 'symbol', label: 'Symbol', format: (v) => chalk.bold(String(v ?? '—')) },
|
|
298
|
+
{ key: 'chain', label: 'Chain', format: (v) => v ? chalk.cyan(String(v)) : chalk.dim('—') },
|
|
299
|
+
{ key: 'price', label: 'Price', format: (v) => formatValue(v, 'price') },
|
|
300
|
+
{ key: 'priceChange24H', label: '24h %', format: (v) => formatValue(v, 'change') },
|
|
301
|
+
{ key: 'volume24H', label: 'Volume 24h', format: compactUsd },
|
|
302
|
+
{ key: 'marketCap', label: 'Market Cap', format: compactUsd },
|
|
303
|
+
];
|
|
304
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
305
|
+
// Specialised display helpers for discover commands
|
|
306
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
307
|
+
/**
|
|
308
|
+
* Pretty-print Fear & Greed Index — hides redundant `timestamp` and `price`.
|
|
309
|
+
*/
|
|
310
|
+
export function printFearGreed(data) {
|
|
311
|
+
if (_rawJson) {
|
|
312
|
+
console.log(JSON.stringify(data, null, 2));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const entries = Object.entries(data).filter(([k, v]) => v !== undefined && v !== null && v !== '' && !HIDDEN_KEYS.test(k) && !FEAR_GREED_HIDDEN.has(k));
|
|
316
|
+
if (entries.length === 0) {
|
|
317
|
+
console.log(chalk.dim(' No data.'));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const labels = entries.map(([k]) => formatLabel(k));
|
|
321
|
+
const maxLen = Math.max(...labels.map((l) => l.length));
|
|
322
|
+
for (let i = 0; i < entries.length; i++) {
|
|
323
|
+
const [key, value] = entries[i];
|
|
324
|
+
const label = labels[i].padEnd(maxLen);
|
|
325
|
+
console.log(` ${chalk.dim(label)} : ${formatValue(value, key)}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Pretty-print BTC/crypto metrics — flatten currentQuote, skip ohlcvQuotes.
|
|
330
|
+
*/
|
|
331
|
+
export function printCryptoMetrics(data) {
|
|
332
|
+
if (_rawJson) {
|
|
333
|
+
console.log(JSON.stringify(data, null, 2));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const quote = data.currentQuote;
|
|
337
|
+
const change24h = data.priceChange24h;
|
|
338
|
+
// Build a flat display object from the quote
|
|
339
|
+
const display = [];
|
|
340
|
+
if (quote) {
|
|
341
|
+
if (quote.close !== undefined)
|
|
342
|
+
display.push(['Current Price', formatValue(quote.close, 'price')]);
|
|
343
|
+
if (quote.open !== undefined)
|
|
344
|
+
display.push(['Open (24h)', formatValue(quote.open, 'price')]);
|
|
345
|
+
if (quote.high !== undefined)
|
|
346
|
+
display.push(['High (24h)', formatValue(quote.high, 'price')]);
|
|
347
|
+
if (quote.low !== undefined)
|
|
348
|
+
display.push(['Low (24h)', formatValue(quote.low, 'price')]);
|
|
349
|
+
if (quote.high_timestamp)
|
|
350
|
+
display.push(['High At', formatValue(quote.high_timestamp, 'timestamp')]);
|
|
351
|
+
if (quote.low_timestamp)
|
|
352
|
+
display.push(['Low At', formatValue(quote.low_timestamp, 'timestamp')]);
|
|
353
|
+
}
|
|
354
|
+
if (change24h !== undefined) {
|
|
355
|
+
display.push(['Price Change 24h', formatValue(change24h, 'change')]);
|
|
356
|
+
}
|
|
357
|
+
else if (quote?.percent_change !== undefined) {
|
|
358
|
+
display.push(['Price Change 24h', formatValue(quote.percent_change, 'change')]);
|
|
359
|
+
}
|
|
360
|
+
if (quote?.price_change !== undefined) {
|
|
361
|
+
display.push(['Δ Price (USD)', formatValue(quote.price_change, 'price')]);
|
|
362
|
+
}
|
|
363
|
+
if (display.length === 0) {
|
|
364
|
+
// fallback to generic printKV
|
|
365
|
+
printKV(data);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const maxLen = Math.max(...display.map(([l]) => l.length));
|
|
369
|
+
for (const [label, val] of display) {
|
|
370
|
+
console.log(` ${chalk.dim(label.padEnd(maxLen))} : ${val}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
374
|
+
function truncate(str, len) {
|
|
375
|
+
return str.length > len ? str.slice(0, len - 1) + '…' : str;
|
|
376
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,13 +18,21 @@ import { copyTradeCommand } from './commands/copy-trade.js';
|
|
|
18
18
|
import { chatCommand } from './commands/chat.js';
|
|
19
19
|
import { discoverCommand } from './commands/discover.js';
|
|
20
20
|
import { configCommand } from './commands/config.js';
|
|
21
|
+
import { premiumCommand } from './commands/premium.js';
|
|
22
|
+
import { setRawJson } from './formatters.js';
|
|
21
23
|
const program = new Command();
|
|
22
24
|
program
|
|
23
25
|
.name('minara')
|
|
24
26
|
.version(version)
|
|
27
|
+
.option('--json', 'Output raw JSON instead of formatted tables')
|
|
25
28
|
.description(chalk.bold('Minara CLI') +
|
|
26
29
|
' — Your AI-powered digital finance assistant in the terminal.\n\n' +
|
|
27
|
-
' Login, swap, trade perps, copy-trade, and chat with Minara AI.')
|
|
30
|
+
' Login, swap, trade perps, copy-trade, and chat with Minara AI.')
|
|
31
|
+
.hook('preAction', (thisCommand) => {
|
|
32
|
+
const opts = thisCommand.optsWithGlobals();
|
|
33
|
+
if (opts.json)
|
|
34
|
+
setRawJson(true);
|
|
35
|
+
});
|
|
28
36
|
// ── Auth & Account ───────────────────────────────────────────────────────
|
|
29
37
|
program.addCommand(loginCommand);
|
|
30
38
|
program.addCommand(logoutCommand);
|
|
@@ -45,6 +53,8 @@ program.addCommand(copyTradeCommand);
|
|
|
45
53
|
program.addCommand(chatCommand);
|
|
46
54
|
// ── Market ───────────────────────────────────────────────────────────────
|
|
47
55
|
program.addCommand(discoverCommand);
|
|
56
|
+
// ── Premium ─────────────────────────────────────────────────────────────
|
|
57
|
+
program.addCommand(premiumCommand);
|
|
48
58
|
// ── Config ───────────────────────────────────────────────────────────────
|
|
49
59
|
program.addCommand(configCommand);
|
|
50
60
|
// Default: show help
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether Touch ID hardware is available on this machine.
|
|
3
|
+
* Returns `false` on non-macOS or when hardware is absent / not enrolled.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isTouchIdAvailable(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Perform a Touch ID verification.
|
|
8
|
+
* Resolves on success, throws on failure or cancellation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyTouchId(reason?: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* If Touch ID is enabled in config, prompt the user for biometric
|
|
13
|
+
* verification. Exits the process on failure.
|
|
14
|
+
*
|
|
15
|
+
* Call this before any sensitive financial operation.
|
|
16
|
+
* On non-macOS platforms a warning is shown and execution continues.
|
|
17
|
+
*/
|
|
18
|
+
export declare function requireTouchId(): Promise<void>;
|