@nexstone/rift-cli 0.1.1
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 +201 -0
- package/bin/run.js +22 -0
- package/dist/commands/algo.d.ts +32 -0
- package/dist/commands/algo.js +719 -0
- package/dist/commands/audit.d.ts +13 -0
- package/dist/commands/audit.js +37 -0
- package/dist/commands/auth-status.d.ts +14 -0
- package/dist/commands/auth-status.js +118 -0
- package/dist/commands/auth.d.ts +14 -0
- package/dist/commands/auth.js +275 -0
- package/dist/commands/backtest.d.ts +26 -0
- package/dist/commands/backtest.js +283 -0
- package/dist/commands/collect/start.d.ts +11 -0
- package/dist/commands/collect/start.js +78 -0
- package/dist/commands/collect/status.d.ts +6 -0
- package/dist/commands/collect/status.js +60 -0
- package/dist/commands/compare.d.ts +16 -0
- package/dist/commands/compare.js +130 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.js +143 -0
- package/dist/commands/cost.d.ts +20 -0
- package/dist/commands/cost.js +104 -0
- package/dist/commands/cross-asset.d.ts +14 -0
- package/dist/commands/cross-asset.js +39 -0
- package/dist/commands/data/fetch.d.ts +15 -0
- package/dist/commands/data/fetch.js +82 -0
- package/dist/commands/data/list.d.ts +6 -0
- package/dist/commands/data/list.js +28 -0
- package/dist/commands/data-inventory.d.ts +9 -0
- package/dist/commands/data-inventory.js +24 -0
- package/dist/commands/deposit.d.ts +10 -0
- package/dist/commands/deposit.js +222 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.js +87 -0
- package/dist/commands/funding-browser.d.ts +12 -0
- package/dist/commands/funding-browser.js +33 -0
- package/dist/commands/guide.d.ts +6 -0
- package/dist/commands/guide.js +15 -0
- package/dist/commands/home.d.ts +23 -0
- package/dist/commands/home.js +210 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +122 -0
- package/dist/commands/install.d.ts +9 -0
- package/dist/commands/install.js +89 -0
- package/dist/commands/interactive.d.ts +17 -0
- package/dist/commands/interactive.js +179 -0
- package/dist/commands/lessons.d.ts +12 -0
- package/dist/commands/lessons.js +33 -0
- package/dist/commands/montecarlo.d.ts +19 -0
- package/dist/commands/montecarlo.js +168 -0
- package/dist/commands/more.d.ts +11 -0
- package/dist/commands/more.js +227 -0
- package/dist/commands/new.d.ts +14 -0
- package/dist/commands/new.js +306 -0
- package/dist/commands/pairs.d.ts +22 -0
- package/dist/commands/pairs.js +147 -0
- package/dist/commands/perp/close.d.ts +12 -0
- package/dist/commands/perp/close.js +57 -0
- package/dist/commands/perp/long.d.ts +14 -0
- package/dist/commands/perp/long.js +38 -0
- package/dist/commands/perp/short.d.ts +14 -0
- package/dist/commands/perp/short.js +27 -0
- package/dist/commands/perp/status.d.ts +9 -0
- package/dist/commands/perp/status.js +26 -0
- package/dist/commands/portfolio/alerts.d.ts +6 -0
- package/dist/commands/portfolio/alerts.js +47 -0
- package/dist/commands/portfolio/backtest.d.ts +12 -0
- package/dist/commands/portfolio/backtest.js +178 -0
- package/dist/commands/portfolio/create.d.ts +7 -0
- package/dist/commands/portfolio/create.js +195 -0
- package/dist/commands/portfolio/start.d.ts +9 -0
- package/dist/commands/portfolio/start.js +64 -0
- package/dist/commands/portfolio/status.d.ts +6 -0
- package/dist/commands/portfolio/status.js +128 -0
- package/dist/commands/portfolio/stop.d.ts +6 -0
- package/dist/commands/portfolio/stop.js +81 -0
- package/dist/commands/portfolio-backtest.d.ts +13 -0
- package/dist/commands/portfolio-backtest.js +37 -0
- package/dist/commands/portfolio-matrix.d.ts +12 -0
- package/dist/commands/portfolio-matrix.js +30 -0
- package/dist/commands/quick-test.d.ts +17 -0
- package/dist/commands/quick-test.js +45 -0
- package/dist/commands/research.d.ts +57 -0
- package/dist/commands/research.js +1976 -0
- package/dist/commands/scout.d.ts +14 -0
- package/dist/commands/scout.js +184 -0
- package/dist/commands/serve.d.ts +9 -0
- package/dist/commands/serve.js +1176 -0
- package/dist/commands/setup/proxy.d.ts +10 -0
- package/dist/commands/setup/proxy.js +267 -0
- package/dist/commands/spot/buy.d.ts +14 -0
- package/dist/commands/spot/buy.js +38 -0
- package/dist/commands/spot/sell.d.ts +14 -0
- package/dist/commands/spot/sell.js +39 -0
- package/dist/commands/strategies/list.d.ts +6 -0
- package/dist/commands/strategies/list.js +34 -0
- package/dist/commands/sweep.d.ts +19 -0
- package/dist/commands/sweep.js +137 -0
- package/dist/commands/sync.d.ts +17 -0
- package/dist/commands/sync.js +54 -0
- package/dist/commands/test-trade.d.ts +6 -0
- package/dist/commands/test-trade.js +97 -0
- package/dist/commands/trade.d.ts +26 -0
- package/dist/commands/trade.js +274 -0
- package/dist/commands/transfer.d.ts +13 -0
- package/dist/commands/transfer.js +65 -0
- package/dist/commands/verify.d.ts +16 -0
- package/dist/commands/verify.js +38 -0
- package/dist/commands/walkforward.d.ts +20 -0
- package/dist/commands/walkforward.js +191 -0
- package/dist/commands/withdraw.d.ts +12 -0
- package/dist/commands/withdraw.js +55 -0
- package/dist/commands/workbench-create.d.ts +13 -0
- package/dist/commands/workbench-create.js +39 -0
- package/dist/lib/account-mode.d.ts +44 -0
- package/dist/lib/account-mode.js +96 -0
- package/dist/lib/analyzer.d.ts +4 -0
- package/dist/lib/analyzer.js +62 -0
- package/dist/lib/base-command.d.ts +35 -0
- package/dist/lib/base-command.js +49 -0
- package/dist/lib/credentials.d.ts +46 -0
- package/dist/lib/credentials.js +137 -0
- package/dist/lib/engine-passthrough.d.ts +28 -0
- package/dist/lib/engine-passthrough.js +60 -0
- package/dist/lib/fees.d.ts +52 -0
- package/dist/lib/fees.js +97 -0
- package/dist/lib/python-bridge.d.ts +24 -0
- package/dist/lib/python-bridge.js +182 -0
- package/dist/lib/setup-status.d.ts +32 -0
- package/dist/lib/setup-status.js +121 -0
- package/dist/lib/status-footer.d.ts +35 -0
- package/dist/lib/status-footer.js +101 -0
- package/dist/lib/tui.d.ts +130 -0
- package/dist/lib/tui.js +300 -0
- package/dist/lib/walletconnect.d.ts +70 -0
- package/dist/lib/walletconnect.js +407 -0
- package/package.json +49 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { runEngine } from '../lib/python-bridge.js';
|
|
4
|
+
import { hasCredentials } from '../lib/credentials.js';
|
|
5
|
+
import { hasApprovedFees, approveFees, BUILDER_FEE_DISPLAY } from '../lib/fees.js';
|
|
6
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
7
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
8
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
9
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
10
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
11
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
12
|
+
function ask(question) {
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
rl.question(question, answer => {
|
|
16
|
+
rl.close();
|
|
17
|
+
resolve(answer.trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export default class Init extends GatedCommand {
|
|
22
|
+
static description = 'Set up RIFT — wallet, sample data, and first backtest in under 60 seconds';
|
|
23
|
+
static examples = [
|
|
24
|
+
'$ rift init',
|
|
25
|
+
];
|
|
26
|
+
static flags = {};
|
|
27
|
+
async run() {
|
|
28
|
+
const { flags } = await this.parse(Init);
|
|
29
|
+
this.log('');
|
|
30
|
+
this.log(` ${bold('Welcome to RIFT')} ${dim('— Research / Iteration / Forecast / Trade')}`);
|
|
31
|
+
this.log(` ${dim('─'.repeat(50))}`);
|
|
32
|
+
this.log('');
|
|
33
|
+
// Step 1: Builder fee approval
|
|
34
|
+
this.log(` ${dim('1/4')} Builder fee agreement...`);
|
|
35
|
+
if (hasApprovedFees()) {
|
|
36
|
+
this.log(` ${green('✔')} Builder fee already approved`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.log('');
|
|
40
|
+
this.log(` RIFT is free and open-source. To support ongoing`);
|
|
41
|
+
this.log(` development, a ${bold(BUILDER_FEE_DISPLAY)} builder fee is applied to live`);
|
|
42
|
+
this.log(` trades executed through RIFT on Hyperliquid.`);
|
|
43
|
+
this.log('');
|
|
44
|
+
this.log(` ${dim('Backtesting, simulation, and analysis are always free.')}`);
|
|
45
|
+
this.log(` ${dim('Example: on a $10,000 perp trade, the fee is $3.')}`);
|
|
46
|
+
this.log('');
|
|
47
|
+
const answer = await ask(` ${cyan('Do you agree?')} ${dim('(yes/no)')}: `);
|
|
48
|
+
if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
|
|
49
|
+
approveFees();
|
|
50
|
+
this.log(` ${green('✔')} Builder fee approved. Thank you for supporting RIFT.`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.log('');
|
|
54
|
+
this.log(` ${red('RIFT requires builder fee approval to operate.')}`);
|
|
55
|
+
this.log(` ${dim('Run rift init again when you\'re ready.')}`);
|
|
56
|
+
this.log('');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.log('');
|
|
61
|
+
// Step 2: Check/generate wallet
|
|
62
|
+
this.log(` ${dim('2/4')} Setting up wallet...`);
|
|
63
|
+
if (hasCredentials()) {
|
|
64
|
+
this.log(` ${green('✔')} Wallet already configured`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const { generatePrivateKey, privateKeyToAccount } = await import('viem/accounts');
|
|
68
|
+
const { saveCredentials } = await import('../lib/credentials.js');
|
|
69
|
+
const privateKey = generatePrivateKey();
|
|
70
|
+
const account = privateKeyToAccount(privateKey);
|
|
71
|
+
saveCredentials({
|
|
72
|
+
private_key: privateKey.replace(/^0x/, ''),
|
|
73
|
+
address: account.address.toLowerCase(),
|
|
74
|
+
account_address: account.address.toLowerCase(),
|
|
75
|
+
type: 'generated',
|
|
76
|
+
network: 'mainnet',
|
|
77
|
+
registered_at: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
this.log(` ${green('✔')} Wallet generated: ${dim(account.address)}`);
|
|
80
|
+
this.log(` ${yellow('!')} Save your private key: ${privateKey}`);
|
|
81
|
+
}
|
|
82
|
+
this.log('');
|
|
83
|
+
// Step 3: Fetch sample data
|
|
84
|
+
this.log(` ${dim('3/4')} Fetching BTC 1h data from Hyperliquid...`);
|
|
85
|
+
try {
|
|
86
|
+
await runEngine('fetch', ['BTC', '--tf', '1h'], (msg) => {
|
|
87
|
+
if (msg.type === 'result') {
|
|
88
|
+
this.log(` ${green('✔')} Cached ${msg.candles} candles`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
this.log(` ${yellow('!')} Could not fetch data (offline?). You can run 'rift data fetch' later.`);
|
|
94
|
+
}
|
|
95
|
+
this.log('');
|
|
96
|
+
// Step 4: Run a quick backtest
|
|
97
|
+
this.log(` ${dim('4/4')} Running sample backtest...`);
|
|
98
|
+
try {
|
|
99
|
+
await runEngine('backtest', ['trend_follow', '--pair', 'BTC', '--tf', '4h'], (msg) => {
|
|
100
|
+
if (msg.type === 'result') {
|
|
101
|
+
const ret = msg.total_return_pct;
|
|
102
|
+
const trades = msg.num_trades;
|
|
103
|
+
this.log(` ${green('✔')} Backtest complete: ${ret > 0 ? green(`+${ret}%`) : `${ret}%`} return, ${trades} trades`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
this.log(` ${yellow('!')} Backtest skipped. Run manually: rift backtest trend_follow --pair BTC --tf 4h`);
|
|
109
|
+
}
|
|
110
|
+
this.log('');
|
|
111
|
+
this.log(` ${dim('─'.repeat(50))}`);
|
|
112
|
+
this.log(` ${green('✔')} ${bold('RIFT is ready.')}`);
|
|
113
|
+
this.log('');
|
|
114
|
+
this.log(` ${dim('Try these commands:')}`);
|
|
115
|
+
this.log(` ${cyan('rift strategies list')} ${dim('— see available strategies')}`);
|
|
116
|
+
this.log(` ${cyan('rift backtest trend_follow --pair BTC --tf 4h')} ${dim('— run a backtest')}`);
|
|
117
|
+
this.log(` ${cyan('rift guide')} ${dim('— 9-step research-to-trade journey')}`);
|
|
118
|
+
this.log(` ${cyan('rift new my-strategy')} ${dim('— create your own')}`);
|
|
119
|
+
this.log(` ${cyan('rift doctor')} ${dim('— check system health')}`);
|
|
120
|
+
this.log('');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Install extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
source: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
10
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
11
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
12
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
13
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
14
|
+
function findProjectRoot() {
|
|
15
|
+
let dir = path.resolve(__dirname);
|
|
16
|
+
for (let i = 0; i < 10; i++) {
|
|
17
|
+
if (fs.existsSync(path.join(dir, 'engine', 'pyproject.toml')))
|
|
18
|
+
return dir;
|
|
19
|
+
dir = path.dirname(dir);
|
|
20
|
+
}
|
|
21
|
+
throw new Error('Cannot find project root');
|
|
22
|
+
}
|
|
23
|
+
export default class Install extends GatedCommand {
|
|
24
|
+
static description = 'Install a community strategy from GitHub';
|
|
25
|
+
static examples = [
|
|
26
|
+
'$ rift install https://github.com/user/rift-strategy-bollinger',
|
|
27
|
+
'$ rift install user/rift-strategy-macd',
|
|
28
|
+
];
|
|
29
|
+
static args = {
|
|
30
|
+
source: Args.string({
|
|
31
|
+
description: 'GitHub repo URL or user/repo shorthand',
|
|
32
|
+
required: true,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { args } = await this.parse(Install);
|
|
37
|
+
const source = args.source;
|
|
38
|
+
// Normalize to full URL
|
|
39
|
+
let repoUrl = source;
|
|
40
|
+
if (!source.startsWith('http')) {
|
|
41
|
+
repoUrl = `https://github.com/${source}`;
|
|
42
|
+
}
|
|
43
|
+
// Extract repo name for strategy directory name
|
|
44
|
+
const repoName = repoUrl.split('/').pop()?.replace(/\.git$/, '') || 'unknown';
|
|
45
|
+
const strategyName = repoName.replace(/^rift-strategy-/, '').replace(/^rift-/, '');
|
|
46
|
+
const projectRoot = findProjectRoot();
|
|
47
|
+
const strategiesDir = path.join(projectRoot, 'strategies');
|
|
48
|
+
const targetDir = path.join(strategiesDir, strategyName);
|
|
49
|
+
if (fs.existsSync(targetDir)) {
|
|
50
|
+
this.log(` ${red('✘')} Strategy "${strategyName}" already exists at ${targetDir}`);
|
|
51
|
+
this.log(` ${dim('Remove it first: rm -rf ' + targetDir)}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.log('');
|
|
55
|
+
this.log(` Installing ${bold(strategyName)} from ${dim(repoUrl)}...`);
|
|
56
|
+
this.log('');
|
|
57
|
+
try {
|
|
58
|
+
// Clone into strategies dir
|
|
59
|
+
execSync(`git clone --depth 1 ${repoUrl} ${targetDir}`, { stdio: 'pipe' });
|
|
60
|
+
// Remove .git dir (it's now part of our project)
|
|
61
|
+
const gitDir = path.join(targetDir, '.git');
|
|
62
|
+
if (fs.existsSync(gitDir)) {
|
|
63
|
+
fs.rmSync(gitDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
// Check for strategy.py
|
|
66
|
+
const hasStrategy = fs.existsSync(path.join(targetDir, 'strategy.py'));
|
|
67
|
+
const hasPy = fs.readdirSync(targetDir).some(f => f.endsWith('.py'));
|
|
68
|
+
if (hasStrategy || hasPy) {
|
|
69
|
+
this.log(` ${green('✔')} Strategy ${bold(strategyName)} installed`);
|
|
70
|
+
this.log('');
|
|
71
|
+
// List files
|
|
72
|
+
const files = fs.readdirSync(targetDir).filter(f => !f.startsWith('.'));
|
|
73
|
+
for (const f of files) {
|
|
74
|
+
this.log(` ${f}`);
|
|
75
|
+
}
|
|
76
|
+
this.log('');
|
|
77
|
+
this.log(` ${dim('Run:')} ${cyan(`rift backtest ${strategyName} --pair BTC-PERP --tf 1h`)}`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.log(` ${red('✘')} No .py strategy files found in the repo`);
|
|
81
|
+
fs.rmSync(targetDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.log(` ${red('✘')} Failed to install: ${error.message.split('\n')[0]}`);
|
|
86
|
+
}
|
|
87
|
+
this.log('');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Interactive extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
run(): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Run a subcommand and pause briefly if it exited fast (likely a
|
|
8
|
+
* precondition failure that printed an error). Prevents the menu redraw
|
|
9
|
+
* from clobbering "Trade requires wallet setup" etc.
|
|
10
|
+
*/
|
|
11
|
+
private dispatch;
|
|
12
|
+
private interactiveBacktest;
|
|
13
|
+
private interactiveCompare;
|
|
14
|
+
private interactiveFetch;
|
|
15
|
+
private interactivePortfolio;
|
|
16
|
+
private interactiveNew;
|
|
17
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
4
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
5
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
6
|
+
function ask(question) {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise(resolve => {
|
|
9
|
+
rl.question(question, answer => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(answer.trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function menu(title, options) {
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(` ${bold(title)}`);
|
|
18
|
+
console.log('');
|
|
19
|
+
for (const opt of options) {
|
|
20
|
+
console.log(` ${cyan(opt.key)} ${opt.label} ${dim(opt.desc)}`);
|
|
21
|
+
}
|
|
22
|
+
console.log('');
|
|
23
|
+
}
|
|
24
|
+
export default class Interactive extends GatedCommand {
|
|
25
|
+
static description = 'Launch interactive mode';
|
|
26
|
+
static examples = [
|
|
27
|
+
'$ rift interactive',
|
|
28
|
+
];
|
|
29
|
+
async run() {
|
|
30
|
+
// Main loop — after each action the user returns to the menu.
|
|
31
|
+
// Exit via `0`, `q`, `quit`, or Ctrl-C.
|
|
32
|
+
while (true) {
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(` ${bold('⬡ RIFT')} ${dim('v0.1.0')}`);
|
|
35
|
+
console.log(` ${dim('Research / Iteration / Forecast / Trade')}`);
|
|
36
|
+
menu('What would you like to do?', [
|
|
37
|
+
{ key: '1', label: 'Scout the market', desc: '— scan for opportunities now' },
|
|
38
|
+
{ key: '2', label: 'Quick trade', desc: '— manual trade with stop loss' },
|
|
39
|
+
{ key: '3', label: 'Backtest a strategy', desc: '— test on historical data' },
|
|
40
|
+
{ key: '4', label: 'Compare strategies', desc: '— head-to-head comparison' },
|
|
41
|
+
{ key: '5', label: 'Fetch market data', desc: '— download candles from Hyperliquid' },
|
|
42
|
+
{ key: '6', label: 'Create a new strategy', desc: '— scaffold from template' },
|
|
43
|
+
{ key: '7', label: 'Portfolio manager', desc: '— multi-strategy algo trading' },
|
|
44
|
+
{ key: '8', label: 'System health check', desc: '— run rift doctor' },
|
|
45
|
+
{ key: '9', label: 'Quick start', desc: '— set up everything' },
|
|
46
|
+
{ key: '0', label: 'Exit', desc: '' },
|
|
47
|
+
]);
|
|
48
|
+
const action = await ask(` ${cyan('>')} `);
|
|
49
|
+
switch (action) {
|
|
50
|
+
case '0':
|
|
51
|
+
case 'q':
|
|
52
|
+
case 'quit':
|
|
53
|
+
case 'exit':
|
|
54
|
+
console.log('');
|
|
55
|
+
return;
|
|
56
|
+
case '1':
|
|
57
|
+
await this.dispatch('scout');
|
|
58
|
+
break;
|
|
59
|
+
case '2':
|
|
60
|
+
await this.dispatch('trade');
|
|
61
|
+
break;
|
|
62
|
+
case '3':
|
|
63
|
+
await this.interactiveBacktest();
|
|
64
|
+
break;
|
|
65
|
+
case '4':
|
|
66
|
+
await this.interactiveCompare();
|
|
67
|
+
break;
|
|
68
|
+
case '5':
|
|
69
|
+
await this.interactiveFetch();
|
|
70
|
+
break;
|
|
71
|
+
case '6':
|
|
72
|
+
await this.interactiveNew();
|
|
73
|
+
break;
|
|
74
|
+
case '7':
|
|
75
|
+
await this.interactivePortfolio();
|
|
76
|
+
break;
|
|
77
|
+
case '8':
|
|
78
|
+
await this.dispatch('doctor');
|
|
79
|
+
break;
|
|
80
|
+
case '9':
|
|
81
|
+
await this.dispatch('init');
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
if (action) {
|
|
85
|
+
console.log(dim(' Invalid selection. Pick 0-9.'));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run a subcommand and pause briefly if it exited fast (likely a
|
|
92
|
+
* precondition failure that printed an error). Prevents the menu redraw
|
|
93
|
+
* from clobbering "Trade requires wallet setup" etc.
|
|
94
|
+
*/
|
|
95
|
+
async dispatch(cmd, args = []) {
|
|
96
|
+
const start = Date.now();
|
|
97
|
+
try {
|
|
98
|
+
await this.config.runCommand(cmd, args);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.log(`\n ${dim('Command threw:')} ${err?.message ?? err}\n`);
|
|
102
|
+
}
|
|
103
|
+
const elapsed = Date.now() - start;
|
|
104
|
+
if (elapsed < 1500) {
|
|
105
|
+
await ask(` ${dim('Press Enter to return to menu...')} `);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async interactiveBacktest() {
|
|
109
|
+
const strategy = await ask(` ${cyan('Strategy name')} ${dim('(e.g. trend_follow, or run rift strategies to see all)')}: `);
|
|
110
|
+
if (!strategy) {
|
|
111
|
+
console.log(dim(' Cancelled — no strategy name given.'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const pair = await ask(` ${cyan('Ticker')} ${dim('(BTC)')}: `) || 'BTC';
|
|
115
|
+
const tf = await ask(` ${cyan('Timeframe')} ${dim('(1h)')}: `) || '1h';
|
|
116
|
+
console.log('');
|
|
117
|
+
await this.config.runCommand('backtest', [strategy, '--pair', pair, '--tf', tf]);
|
|
118
|
+
}
|
|
119
|
+
async interactiveCompare() {
|
|
120
|
+
const input = await ask(` ${cyan('Strategies')} ${dim('(comma-separated, e.g. trend_follow,my_strategy)')}: `);
|
|
121
|
+
if (!input) {
|
|
122
|
+
console.log(dim(' Cancelled — no strategies given.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const pair = await ask(` ${cyan('Ticker')} ${dim('(BTC)')}: `) || 'BTC';
|
|
126
|
+
const tf = await ask(` ${cyan('Timeframe')} ${dim('(1h)')}: `) || '1h';
|
|
127
|
+
console.log('');
|
|
128
|
+
await this.config.runCommand('compare', [input, '--pair', pair, '--tf', tf]);
|
|
129
|
+
}
|
|
130
|
+
async interactiveFetch() {
|
|
131
|
+
const pair = await ask(` ${cyan('Ticker')} ${dim('(BTC)')}: `) || 'BTC';
|
|
132
|
+
const tf = await ask(` ${cyan('Timeframe')} ${dim('(1h)')}: `) || '1h';
|
|
133
|
+
console.log('');
|
|
134
|
+
await this.config.runCommand('data:fetch', ['--pair', pair, '--tf', tf]);
|
|
135
|
+
}
|
|
136
|
+
async interactivePortfolio() {
|
|
137
|
+
menu('Portfolio Manager', [
|
|
138
|
+
{ key: '1', label: 'Create portfolio', desc: '— build a portfolio config' },
|
|
139
|
+
{ key: '2', label: 'Start portfolio', desc: '— launch supervisor + strategies' },
|
|
140
|
+
{ key: '3', label: 'Portfolio status', desc: '— view running portfolio' },
|
|
141
|
+
{ key: '4', label: 'Stop portfolio', desc: '— stop all strategies' },
|
|
142
|
+
{ key: '5', label: 'View alerts', desc: '— recent trading alerts' },
|
|
143
|
+
{ key: '0', label: 'Back', desc: '' },
|
|
144
|
+
]);
|
|
145
|
+
const choice = await ask(` ${cyan('>')} `);
|
|
146
|
+
switch (choice) {
|
|
147
|
+
case '0':
|
|
148
|
+
case 'q':
|
|
149
|
+
case '':
|
|
150
|
+
return;
|
|
151
|
+
case '1':
|
|
152
|
+
await this.config.runCommand('portfolio:create');
|
|
153
|
+
break;
|
|
154
|
+
case '2':
|
|
155
|
+
await this.config.runCommand('portfolio:start');
|
|
156
|
+
break;
|
|
157
|
+
case '3':
|
|
158
|
+
await this.config.runCommand('portfolio:status');
|
|
159
|
+
break;
|
|
160
|
+
case '4':
|
|
161
|
+
await this.config.runCommand('portfolio:stop');
|
|
162
|
+
break;
|
|
163
|
+
case '5':
|
|
164
|
+
await this.config.runCommand('portfolio:alerts');
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
console.log(dim(' Invalid selection. Pick 0-5.'));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async interactiveNew() {
|
|
171
|
+
const name = await ask(` ${cyan('Strategy name')}: `);
|
|
172
|
+
if (!name) {
|
|
173
|
+
console.log(dim(' Cancelled — no strategy name given.'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
await this.config.runCommand('new', [name]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Lessons extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
coin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
strategy: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
limit: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import { passthroughToEngine } from '../lib/engine-passthrough.js';
|
|
4
|
+
export default class Lessons extends GatedCommand {
|
|
5
|
+
static description = 'Show captured trading lessons (post-trade learnings)';
|
|
6
|
+
static examples = [
|
|
7
|
+
'$ rift lessons',
|
|
8
|
+
'$ rift lessons --strategy trend_follow',
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
coin: Flags.string({ description: 'Filter by coin', default: '' }),
|
|
12
|
+
strategy: Flags.string({ description: 'Filter by strategy name', default: '' }),
|
|
13
|
+
limit: Flags.string({ description: 'Number of lessons to show', default: '20' }),
|
|
14
|
+
json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { flags } = await this.parse(Lessons);
|
|
18
|
+
const args = [];
|
|
19
|
+
if (flags.coin)
|
|
20
|
+
args.push('--coin', flags.coin);
|
|
21
|
+
if (flags.strategy)
|
|
22
|
+
args.push('--strategy', flags.strategy);
|
|
23
|
+
args.push('--limit', flags.limit);
|
|
24
|
+
await passthroughToEngine({
|
|
25
|
+
command: 'lessons',
|
|
26
|
+
args,
|
|
27
|
+
log: (m) => this.log(m),
|
|
28
|
+
error: (m) => this.error(m),
|
|
29
|
+
exit: (c) => this.exit(c),
|
|
30
|
+
jsonOnly: flags.json,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class MonteCarlo extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
strategy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
pair: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
tf: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
runs: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
equity: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
private renderResult;
|
|
17
|
+
private row;
|
|
18
|
+
private rowColored;
|
|
19
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Flags, Args } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import { runEngine } from '../lib/python-bridge.js';
|
|
4
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
5
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
6
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
7
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
8
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
9
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
10
|
+
function colorNum(val, suffix = '') {
|
|
11
|
+
const str = `${val}${suffix}`;
|
|
12
|
+
if (val > 0)
|
|
13
|
+
return green(str);
|
|
14
|
+
if (val < 0)
|
|
15
|
+
return red(str);
|
|
16
|
+
return yellow(str);
|
|
17
|
+
}
|
|
18
|
+
export default class MonteCarlo extends GatedCommand {
|
|
19
|
+
static description = 'Run Monte Carlo simulation to test how much of your backtest was luck vs edge';
|
|
20
|
+
static examples = [
|
|
21
|
+
'$ rift montecarlo trend_follow --pair BTC --tf 4h',
|
|
22
|
+
'$ rift montecarlo trend_follow --pair BTC --tf 4h --runs 50000',
|
|
23
|
+
];
|
|
24
|
+
static args = {
|
|
25
|
+
strategy: Args.string({ description: 'Strategy name', required: true }),
|
|
26
|
+
};
|
|
27
|
+
static flags = {
|
|
28
|
+
pair: Flags.string({ description: 'Trading pair', default: 'BTC-PERP' }),
|
|
29
|
+
tf: Flags.string({ description: 'Timeframe', default: '1h' }),
|
|
30
|
+
runs: Flags.integer({ description: 'Number of simulations', default: 10000 }),
|
|
31
|
+
equity: Flags.integer({ description: 'Starting equity', default: 10000 }),
|
|
32
|
+
leverage: Flags.integer({ description: 'Leverage multiplier', default: 1 }),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args, flags } = await this.parse(MonteCarlo);
|
|
36
|
+
this.log('');
|
|
37
|
+
this.log(` ${bold('Monte Carlo Simulation')}`);
|
|
38
|
+
this.log(` ${dim(`${args.strategy} on ${flags.pair} ${flags.tf} — ${flags.runs.toLocaleString()} simulations`)}`);
|
|
39
|
+
this.log('');
|
|
40
|
+
const engineArgs = [
|
|
41
|
+
args.strategy,
|
|
42
|
+
'--pair', flags.pair,
|
|
43
|
+
'--tf', flags.tf,
|
|
44
|
+
'--runs', String(flags.runs),
|
|
45
|
+
'--equity', String(flags.equity),
|
|
46
|
+
'--leverage', String(flags.leverage),
|
|
47
|
+
];
|
|
48
|
+
await runEngine('montecarlo', engineArgs, (msg) => {
|
|
49
|
+
if (msg.type === 'progress' && msg.msg) {
|
|
50
|
+
process.stdout.write(`\r ${dim(String(msg.msg))}${''.padEnd(20)}`);
|
|
51
|
+
}
|
|
52
|
+
else if (msg.type === 'result') {
|
|
53
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
54
|
+
this.renderResult(msg);
|
|
55
|
+
}
|
|
56
|
+
else if (msg.type === 'error') {
|
|
57
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
58
|
+
this.error(msg.msg);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
renderResult(msg) {
|
|
63
|
+
const ret = msg.return_distribution;
|
|
64
|
+
const dd = msg.drawdown_distribution;
|
|
65
|
+
const histogram = msg.histogram;
|
|
66
|
+
const probProfit = msg.prob_profit;
|
|
67
|
+
const probRuin = msg.prob_ruin;
|
|
68
|
+
const originalReturn = msg.original_return_pct;
|
|
69
|
+
const numTrades = msg.num_trades;
|
|
70
|
+
const numSims = msg.num_simulations;
|
|
71
|
+
// ASCII histogram
|
|
72
|
+
this.log(dim(' ── Return Distribution ──'));
|
|
73
|
+
this.log('');
|
|
74
|
+
const maxCount = Math.max(...histogram.map(h => h.count));
|
|
75
|
+
const barWidth = 30;
|
|
76
|
+
for (const bucket of histogram) {
|
|
77
|
+
const barLen = Math.round((bucket.count / maxCount) * barWidth);
|
|
78
|
+
const bar = '█'.repeat(barLen);
|
|
79
|
+
const label = `${bucket.low.toFixed(0)}%`.padStart(8);
|
|
80
|
+
const countStr = dim(`(${bucket.pct}%)`);
|
|
81
|
+
// Color bar based on whether bucket is positive or negative
|
|
82
|
+
const midpoint = (bucket.low + bucket.high) / 2;
|
|
83
|
+
const coloredBar = midpoint >= 0 ? green(bar) : red(bar);
|
|
84
|
+
this.log(` ${label} ${coloredBar} ${countStr}`);
|
|
85
|
+
}
|
|
86
|
+
this.log('');
|
|
87
|
+
// Summary box
|
|
88
|
+
const w = 55;
|
|
89
|
+
const hr = '─'.repeat(w - 2);
|
|
90
|
+
this.log(` ${dim('┌' + hr + '┐')}`);
|
|
91
|
+
this.log(` ${dim('│')} ${bold('MONTE CARLO RESULTS')}${' '.repeat(w - 22)}${dim('│')}`);
|
|
92
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
93
|
+
this.log(this.row('Strategy', String(msg.strategy), w));
|
|
94
|
+
this.log(this.row('Pair / Interval', `${msg.pair} ${msg.interval}`, w));
|
|
95
|
+
this.log(this.row('Trades', String(numTrades), w));
|
|
96
|
+
this.log(this.row('Simulations', numSims.toLocaleString(), w));
|
|
97
|
+
this.log(this.rowColored('Original Return', originalReturn, '%', w));
|
|
98
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
99
|
+
this.log(` ${dim('│')} ${dim('RETURN PERCENTILES')}${' '.repeat(w - 22)}${dim('│')}`);
|
|
100
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
101
|
+
this.log(this.rowColored('5th (worst case)', ret.p5, '%', w));
|
|
102
|
+
this.log(this.rowColored('10th', ret.p10, '%', w));
|
|
103
|
+
this.log(this.rowColored('25th', ret.p25, '%', w));
|
|
104
|
+
this.log(this.rowColored('50th (median)', ret.p50, '%', w));
|
|
105
|
+
this.log(this.rowColored('75th', ret.p75, '%', w));
|
|
106
|
+
this.log(this.rowColored('90th', ret.p90, '%', w));
|
|
107
|
+
this.log(this.rowColored('95th (best case)', ret.p95, '%', w));
|
|
108
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
109
|
+
this.log(` ${dim('│')} ${dim('MAX DRAWDOWN PERCENTILES')}${' '.repeat(w - 28)}${dim('│')}`);
|
|
110
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
111
|
+
this.log(this.rowColored('5th (worst)', dd.p5, '%', w));
|
|
112
|
+
this.log(this.rowColored('25th', dd.p25, '%', w));
|
|
113
|
+
this.log(this.rowColored('50th (median)', dd.p50, '%', w));
|
|
114
|
+
this.log(this.rowColored('75th', dd.p75, '%', w));
|
|
115
|
+
this.log(this.rowColored('95th (best)', dd.p95, '%', w));
|
|
116
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
117
|
+
this.log(` ${dim('│')} ${bold('RISK ASSESSMENT')}${' '.repeat(w - 19)}${dim('│')}`);
|
|
118
|
+
this.log(` ${dim('├' + hr + '┤')}`);
|
|
119
|
+
this.log(this.row('Probability of Profit', `${probProfit}%`, w, probProfit >= 60 ? 'green' : probProfit >= 40 ? 'yellow' : 'red'));
|
|
120
|
+
this.log(this.row('Probability of Ruin (>50% DD)', `${probRuin}%`, w, probRuin <= 10 ? 'green' : probRuin <= 30 ? 'yellow' : 'red'));
|
|
121
|
+
this.log(this.rowColored('Median Sharpe', msg.median_sharpe, '', w));
|
|
122
|
+
this.log(` ${dim('└' + hr + '┘')}`);
|
|
123
|
+
this.log('');
|
|
124
|
+
// Interpretation
|
|
125
|
+
this.log(dim(' ── Interpretation ──'));
|
|
126
|
+
this.log('');
|
|
127
|
+
if (ret.p5 > 0) {
|
|
128
|
+
this.log(` ${green('Even in the worst 5% of scenarios, this strategy is profitable.')}`);
|
|
129
|
+
this.log(` ${dim('The edge is robust — not dependent on lucky trade ordering.')}`);
|
|
130
|
+
}
|
|
131
|
+
else if (ret.p25 > 0) {
|
|
132
|
+
this.log(` ${yellow('The strategy is profitable in most scenarios, but the bottom 25% lose money.')}`);
|
|
133
|
+
this.log(` ${dim('Some of the backtest return may be from favorable trade sequencing.')}`);
|
|
134
|
+
}
|
|
135
|
+
else if (ret.p50 > 0) {
|
|
136
|
+
this.log(` ${yellow('Median outcome is positive, but there is significant downside risk.')}`);
|
|
137
|
+
this.log(` ${dim('The strategy has an edge but is sensitive to trade ordering. Consider tighter risk management.')}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.log(` ${red('The median outcome is negative. The original backtest result was likely lucky.')}`);
|
|
141
|
+
this.log(` ${dim('This strategy does not have a reliable edge. Do not trade this.')}`);
|
|
142
|
+
}
|
|
143
|
+
if (probRuin > 30) {
|
|
144
|
+
this.log(` ${red(`Warning: ${probRuin}% chance of >50% drawdown. This is extremely risky.`)}`);
|
|
145
|
+
}
|
|
146
|
+
this.log('');
|
|
147
|
+
}
|
|
148
|
+
row(label, value, width, color) {
|
|
149
|
+
const labelStr = ` ${label}:`;
|
|
150
|
+
const cleanVal = value.replace(/\x1b\[[0-9;]*m/g, '');
|
|
151
|
+
const padding = width - labelStr.length - cleanVal.length - 3;
|
|
152
|
+
let coloredVal = value;
|
|
153
|
+
if (color === 'green')
|
|
154
|
+
coloredVal = green(value);
|
|
155
|
+
else if (color === 'red')
|
|
156
|
+
coloredVal = red(value);
|
|
157
|
+
else if (color === 'yellow')
|
|
158
|
+
coloredVal = yellow(value);
|
|
159
|
+
return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${coloredVal} ${dim('│')}`;
|
|
160
|
+
}
|
|
161
|
+
rowColored(label, value, suffix, width) {
|
|
162
|
+
const coloredStr = colorNum(value, suffix);
|
|
163
|
+
const cleanStr = `${value}${suffix}`;
|
|
164
|
+
const labelStr = ` ${label}:`;
|
|
165
|
+
const padding = width - labelStr.length - cleanStr.length - 3;
|
|
166
|
+
return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${coloredStr} ${dim('│')}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class More extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
command: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static strict: boolean;
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
private renderCatalog;
|
|
11
|
+
}
|