@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,222 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import { getExistingSession } from '../lib/walletconnect.js';
|
|
4
|
+
import { loadCredentials } from '../lib/credentials.js';
|
|
5
|
+
import { green, red, cyan, dim } from '../lib/tui.js';
|
|
6
|
+
// Bridge contract address (mainnet)
|
|
7
|
+
const BRIDGE_MAINNET = '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7';
|
|
8
|
+
// USDC contract address (mainnet)
|
|
9
|
+
const USDC_MAINNET = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
|
|
10
|
+
// Arbitrum mainnet chain ID
|
|
11
|
+
const ARB_MAINNET_CHAIN_ID = 42161;
|
|
12
|
+
// Minimal ABI for batchedDepositWithPermit
|
|
13
|
+
const BRIDGE_ABI_FRAGMENT = [
|
|
14
|
+
{
|
|
15
|
+
inputs: [{
|
|
16
|
+
components: [
|
|
17
|
+
{ name: 'user', type: 'address' },
|
|
18
|
+
{ name: 'usd', type: 'uint64' },
|
|
19
|
+
{ name: 'deadline', type: 'uint64' },
|
|
20
|
+
{
|
|
21
|
+
components: [
|
|
22
|
+
{ name: 'v', type: 'uint8' },
|
|
23
|
+
{ name: 'r', type: 'uint256' },
|
|
24
|
+
{ name: 's', type: 'uint256' },
|
|
25
|
+
],
|
|
26
|
+
name: 'signature',
|
|
27
|
+
type: 'tuple',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
name: 'deposits',
|
|
31
|
+
type: 'tuple[]',
|
|
32
|
+
}],
|
|
33
|
+
name: 'batchedDepositWithPermit',
|
|
34
|
+
outputs: [],
|
|
35
|
+
stateMutability: 'nonpayable',
|
|
36
|
+
type: 'function',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
// Minimal ABI for USDC nonces query
|
|
40
|
+
const USDC_NONCES_ABI = [
|
|
41
|
+
{
|
|
42
|
+
inputs: [{ name: 'owner', type: 'address' }],
|
|
43
|
+
name: 'nonces',
|
|
44
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
45
|
+
stateMutability: 'view',
|
|
46
|
+
type: 'function',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
export default class Deposit extends GatedCommand {
|
|
50
|
+
static description = 'Deposit USDC from Arbitrum to Hyperliquid';
|
|
51
|
+
static examples = [
|
|
52
|
+
'$ rift deposit 100',
|
|
53
|
+
];
|
|
54
|
+
static args = {
|
|
55
|
+
amount: Args.string({ description: 'USDC amount to deposit (minimum 5)', required: true }),
|
|
56
|
+
};
|
|
57
|
+
static flags = {};
|
|
58
|
+
async run() {
|
|
59
|
+
const { args } = await this.parse(Deposit);
|
|
60
|
+
const amount = parseFloat(args.amount);
|
|
61
|
+
if (amount < 5) {
|
|
62
|
+
this.log(` ${red('✘')} Minimum deposit is 5 USDC. Amounts below 5 are lost permanently.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const creds = loadCredentials();
|
|
66
|
+
if (!creds) {
|
|
67
|
+
this.log(` ${red('✘')} No wallet configured. Run: ${cyan('rift auth setup')}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const session = await getExistingSession();
|
|
71
|
+
if (!session) {
|
|
72
|
+
this.log(` ${red('✘')} No wallet session. Run: ${cyan('rift auth setup')} to reconnect.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const walletAddress = session.account;
|
|
76
|
+
const bridgeAddress = BRIDGE_MAINNET;
|
|
77
|
+
const usdcAddress = USDC_MAINNET;
|
|
78
|
+
const chainId = ARB_MAINNET_CHAIN_ID;
|
|
79
|
+
const chainRef = `eip155:${chainId}`;
|
|
80
|
+
// Convert to raw USDC units (6 decimals)
|
|
81
|
+
const rawAmount = BigInt(Math.round(amount * 1_000_000));
|
|
82
|
+
const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
|
|
83
|
+
this.log('');
|
|
84
|
+
this.log(` Depositing $${amount} USDC to Hyperliquid...`);
|
|
85
|
+
this.log('');
|
|
86
|
+
// Step 1: Get USDC nonce for the permit
|
|
87
|
+
this.log(` ${dim('Step 1/3: Querying USDC permit nonce...')}`);
|
|
88
|
+
let nonce;
|
|
89
|
+
try {
|
|
90
|
+
// Query USDC nonces via eth_call
|
|
91
|
+
const nonceCallData = '0x7ecebe00' + walletAddress.slice(2).padStart(64, '0');
|
|
92
|
+
const nonceResult = await session.client.request({
|
|
93
|
+
topic: session.topic,
|
|
94
|
+
chainId: chainRef,
|
|
95
|
+
request: {
|
|
96
|
+
method: 'eth_call',
|
|
97
|
+
params: [{ to: usdcAddress, data: nonceCallData }, 'latest'],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
nonce = parseInt(nonceResult, 16);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Fallback: assume nonce 0 (first permit)
|
|
104
|
+
nonce = 0;
|
|
105
|
+
}
|
|
106
|
+
// Step 2: Sign USDC permit via WalletConnect
|
|
107
|
+
this.log(` ${dim('Step 2/3: Sign USDC permit...')}`);
|
|
108
|
+
this.log(` ${dim('→ Approve in your wallet (check your phone)')}`);
|
|
109
|
+
const permitDomain = {
|
|
110
|
+
name: 'USD Coin',
|
|
111
|
+
version: '2',
|
|
112
|
+
chainId,
|
|
113
|
+
verifyingContract: usdcAddress,
|
|
114
|
+
};
|
|
115
|
+
const permitTypes = {
|
|
116
|
+
EIP712Domain: [
|
|
117
|
+
{ name: 'name', type: 'string' },
|
|
118
|
+
{ name: 'version', type: 'string' },
|
|
119
|
+
{ name: 'chainId', type: 'uint256' },
|
|
120
|
+
{ name: 'verifyingContract', type: 'address' },
|
|
121
|
+
],
|
|
122
|
+
Permit: [
|
|
123
|
+
{ name: 'owner', type: 'address' },
|
|
124
|
+
{ name: 'spender', type: 'address' },
|
|
125
|
+
{ name: 'value', type: 'uint256' },
|
|
126
|
+
{ name: 'nonce', type: 'uint256' },
|
|
127
|
+
{ name: 'deadline', type: 'uint256' },
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
const permitMessage = {
|
|
131
|
+
owner: walletAddress,
|
|
132
|
+
spender: bridgeAddress,
|
|
133
|
+
value: rawAmount.toString(),
|
|
134
|
+
nonce,
|
|
135
|
+
deadline,
|
|
136
|
+
};
|
|
137
|
+
const permitTypedData = {
|
|
138
|
+
domain: permitDomain,
|
|
139
|
+
types: permitTypes,
|
|
140
|
+
primaryType: 'Permit',
|
|
141
|
+
message: permitMessage,
|
|
142
|
+
};
|
|
143
|
+
let permitSig;
|
|
144
|
+
try {
|
|
145
|
+
permitSig = await session.client.request({
|
|
146
|
+
topic: session.topic,
|
|
147
|
+
chainId: chainRef,
|
|
148
|
+
request: {
|
|
149
|
+
method: 'eth_signTypedData_v4',
|
|
150
|
+
params: [walletAddress, JSON.stringify(permitTypedData)],
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.log(` ${red('✘')} Permit signing failed: ${error?.message || 'User rejected'}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.log(` ${green('✔')} Permit signed`);
|
|
159
|
+
// Parse permit signature
|
|
160
|
+
const sigHex = permitSig.startsWith('0x') ? permitSig.slice(2) : permitSig;
|
|
161
|
+
const permitR = '0x' + sigHex.slice(0, 64);
|
|
162
|
+
const permitS = '0x' + sigHex.slice(64, 128);
|
|
163
|
+
const permitV = parseInt(sigHex.slice(128, 130), 16);
|
|
164
|
+
// Step 3: Send batchedDepositWithPermit transaction via WalletConnect
|
|
165
|
+
this.log(` ${dim('Step 3/3: Submit bridge deposit...')}`);
|
|
166
|
+
this.log(` ${dim('→ Approve transaction in your wallet')}`);
|
|
167
|
+
// Encode batchedDepositWithPermit call data manually
|
|
168
|
+
// Function selector: keccak256("batchedDepositWithPermit((address,uint64,uint64,(uint8,uint256,uint256))[])")
|
|
169
|
+
// We'll use a simplified encoding approach
|
|
170
|
+
// ABI encode the deposit struct array
|
|
171
|
+
const encodedData = encodeBatchedDeposit(walletAddress, rawAmount, BigInt(deadline), permitV, permitR, permitS);
|
|
172
|
+
try {
|
|
173
|
+
const txHash = await session.client.request({
|
|
174
|
+
topic: session.topic,
|
|
175
|
+
chainId: chainRef,
|
|
176
|
+
request: {
|
|
177
|
+
method: 'eth_sendTransaction',
|
|
178
|
+
params: [{
|
|
179
|
+
from: walletAddress,
|
|
180
|
+
to: bridgeAddress,
|
|
181
|
+
data: encodedData,
|
|
182
|
+
// Gas will be estimated by the wallet
|
|
183
|
+
}],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
this.log(` ${green('✔')} Deposit submitted!`);
|
|
187
|
+
this.log(` ${dim(`$${amount} USDC will arrive on Hyperliquid in ~1 minute`)}`);
|
|
188
|
+
this.log(` ${dim(`Tx: ${txHash}`)}`);
|
|
189
|
+
this.log('');
|
|
190
|
+
this.log(` ${dim('Run:')} ${cyan('rift balance')} ${dim('to check when it arrives')}`);
|
|
191
|
+
this.log('');
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.log(` ${red('✘')} Deposit transaction failed: ${error?.message || 'User rejected'}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* ABI-encode the batchedDepositWithPermit call data.
|
|
200
|
+
*
|
|
201
|
+
* This encodes: batchedDepositWithPermit([(user, usd, deadline, (v, r, s))])
|
|
202
|
+
*/
|
|
203
|
+
function encodeBatchedDeposit(user, usd, deadline, v, r, s) {
|
|
204
|
+
// Function selector for batchedDepositWithPermit((address,uint64,uint64,(uint8,uint256,uint256))[])
|
|
205
|
+
// Computed from keccak256 of the signature
|
|
206
|
+
const selector = '0xea7fb094';
|
|
207
|
+
// ABI encoding for dynamic array of structs
|
|
208
|
+
const pad = (hex, bytes = 32) => hex.replace('0x', '').padStart(bytes * 2, '0');
|
|
209
|
+
const toUint = (n) => pad(BigInt(n).toString(16));
|
|
210
|
+
// Offset to array data (32 bytes)
|
|
211
|
+
const arrayOffset = toUint(32n);
|
|
212
|
+
// Array length (1 deposit)
|
|
213
|
+
const arrayLength = toUint(1n);
|
|
214
|
+
// Struct fields (packed, no dynamic types within struct)
|
|
215
|
+
const userPadded = pad(user.toLowerCase());
|
|
216
|
+
const usdPadded = toUint(usd);
|
|
217
|
+
const deadlinePadded = toUint(deadline);
|
|
218
|
+
const vPadded = toUint(BigInt(v));
|
|
219
|
+
const rPadded = pad(r);
|
|
220
|
+
const sPadded = pad(s);
|
|
221
|
+
return selector + arrayOffset + arrayLength + userPadded + usdPadded + deadlinePadded + vPadded + rPadded + sPadded;
|
|
222
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { runEngine } from '../lib/python-bridge.js';
|
|
3
|
+
import { hasCredentials, loadCredentials } from '../lib/credentials.js';
|
|
4
|
+
import { hasApprovedFees, hasOnChainApproval, getFeeStatus } from '../lib/fees.js';
|
|
5
|
+
const ok = (s) => `\x1b[32m✔\x1b[0m ${s}`;
|
|
6
|
+
const fail = (s) => `\x1b[31m✘\x1b[0m ${s}`;
|
|
7
|
+
const warn = (s) => `\x1b[33m!\x1b[0m ${s}`;
|
|
8
|
+
const info = (s) => `\x1b[36m◦\x1b[0m ${s}`;
|
|
9
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
10
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
11
|
+
export default class Doctor extends GatedCommand {
|
|
12
|
+
static description = 'Check system health and diagnose issues';
|
|
13
|
+
static examples = [
|
|
14
|
+
'$ rift doctor',
|
|
15
|
+
];
|
|
16
|
+
async run() {
|
|
17
|
+
this.log('');
|
|
18
|
+
this.log(` ${bold('RIFT Doctor')}`);
|
|
19
|
+
this.log(` ${dim('─'.repeat(40))}`);
|
|
20
|
+
this.log('');
|
|
21
|
+
// Node.js check
|
|
22
|
+
this.log(` ${ok(`Node.js ${process.version}`)}`);
|
|
23
|
+
// Builder fee check (two layers)
|
|
24
|
+
if (hasApprovedFees()) {
|
|
25
|
+
const status = getFeeStatus();
|
|
26
|
+
this.log(` ${ok(`Builder fee consent ${dim(status?.approvedAt ? `(${status.approvedAt.slice(0, 10)})` : '')}`)}`);
|
|
27
|
+
// The "on-chain approved" status lives in two places:
|
|
28
|
+
// - ~/.rift/config.json.fees.onChainApproved (legacy TS-flow flag)
|
|
29
|
+
// - ~/.rift/credentials.builder_fee_approved (canonical, set by
|
|
30
|
+
// agent-pair + standalone approve-builder-fee)
|
|
31
|
+
// Treat either as evidence the approval went through.
|
|
32
|
+
const creds = loadCredentials();
|
|
33
|
+
const onChainOk = hasOnChainApproval() || (creds?.builder_fee_approved === true);
|
|
34
|
+
if (onChainOk) {
|
|
35
|
+
this.log(` ${ok(`On-chain approval ${dim('(ready for live trading)')}`)}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.log(` ${info(`On-chain approval pending ${dim('(needed for live trading only)')}`)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.log(` ${fail(`Builder fee not approved ${dim('— run: rift auth setup')}`)}`);
|
|
43
|
+
}
|
|
44
|
+
// Credentials check
|
|
45
|
+
if (hasCredentials()) {
|
|
46
|
+
const creds = loadCredentials();
|
|
47
|
+
if (creds) {
|
|
48
|
+
// `type` is optional — Python's agent-pair flow doesn't set it.
|
|
49
|
+
// Omit the parenthetical when unknown rather than printing "undefined".
|
|
50
|
+
const detail = creds.type
|
|
51
|
+
? `(${creds.network}, ${creds.type})`
|
|
52
|
+
: `(${creds.network})`;
|
|
53
|
+
this.log(` ${ok(`Wallet configured ${dim(detail)}`)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.log(` ${warn(`No wallet configured ${dim('— run: rift auth setup')}`)}`);
|
|
58
|
+
}
|
|
59
|
+
// Engine checks (Python side)
|
|
60
|
+
try {
|
|
61
|
+
await runEngine('doctor', [], (msg) => {
|
|
62
|
+
if (msg.type === 'result') {
|
|
63
|
+
const checks = msg.checks;
|
|
64
|
+
for (const check of checks) {
|
|
65
|
+
const detail = dim(check.detail);
|
|
66
|
+
if (check.status === 'ok') {
|
|
67
|
+
this.log(` ${ok(`${check.name} ${detail}`)}`);
|
|
68
|
+
}
|
|
69
|
+
else if (check.status === 'warn') {
|
|
70
|
+
this.log(` ${warn(`${check.name} ${detail}`)}`);
|
|
71
|
+
}
|
|
72
|
+
else if (check.status === 'info') {
|
|
73
|
+
this.log(` ${info(`${check.name} ${detail}`)}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.log(` ${fail(`${check.name} ${detail}`)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.log(` ${fail(`Python engine — ${error.message.split('\n')[0]}`)}`);
|
|
84
|
+
}
|
|
85
|
+
this.log('');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class FundingBrowser extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
coins: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
top: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
days: 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 FundingBrowser extends GatedCommand {
|
|
5
|
+
static description = 'Browse funding rates across coins — current + window stats + extremes';
|
|
6
|
+
static examples = [
|
|
7
|
+
'$ rift funding-browser',
|
|
8
|
+
'$ rift funding-browser --top 50',
|
|
9
|
+
'$ rift funding-browser --coins BTC,ETH,SOL --days 30',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
coins: Flags.string({ description: 'Comma-separated coin list (default: all cached)', default: '' }),
|
|
13
|
+
top: Flags.string({ description: 'Number of coins to show, ranked by current funding', default: '20' }),
|
|
14
|
+
days: Flags.string({ description: 'History window in days', default: '7' }),
|
|
15
|
+
json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { flags } = await this.parse(FundingBrowser);
|
|
19
|
+
const args = [];
|
|
20
|
+
if (flags.coins)
|
|
21
|
+
args.push('--coins', flags.coins);
|
|
22
|
+
args.push('--top', flags.top);
|
|
23
|
+
args.push('--days', flags.days);
|
|
24
|
+
await passthroughToEngine({
|
|
25
|
+
command: 'funding-browser',
|
|
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,15 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { passthroughToEngine } from '../lib/engine-passthrough.js';
|
|
3
|
+
export default class Guide extends GatedCommand {
|
|
4
|
+
static description = 'Print the RIFT research-to-trade journey as a quick reference';
|
|
5
|
+
static examples = ['$ rift guide'];
|
|
6
|
+
async run() {
|
|
7
|
+
await passthroughToEngine({
|
|
8
|
+
command: 'guide',
|
|
9
|
+
args: [],
|
|
10
|
+
log: (m) => this.log(m),
|
|
11
|
+
error: (m) => this.error(m),
|
|
12
|
+
exit: (c) => this.exit(c),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Home extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {};
|
|
5
|
+
static flags: {};
|
|
6
|
+
static skipFooter: boolean;
|
|
7
|
+
run(): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Run a subcommand and prevent its output from being clobbered by the
|
|
10
|
+
* next menu redraw.
|
|
11
|
+
*
|
|
12
|
+
* If the command exits in < 1500ms (almost always a precondition failure
|
|
13
|
+
* like "wallet not configured" that prints an error and bails), pause for
|
|
14
|
+
* keypress so the user can actually read what happened before the menu
|
|
15
|
+
* redraws over it.
|
|
16
|
+
*
|
|
17
|
+
* If the command ran for longer, the user was interacting with it and
|
|
18
|
+
* doesn't need a pause — redraw immediately for a clean menu transition.
|
|
19
|
+
*/
|
|
20
|
+
private dispatch;
|
|
21
|
+
private aiIntegrationMenu;
|
|
22
|
+
private settingsMenu;
|
|
23
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { green, cyan, bold, dim, ask, } from '../lib/tui.js';
|
|
3
|
+
// Gradient colors — bright cyan at top, fading to dark blue at bottom
|
|
4
|
+
const LOGO_GRADIENT = [
|
|
5
|
+
'\x1b[1m\x1b[96m', // bright cyan (line 1)
|
|
6
|
+
'\x1b[1m\x1b[36m', // cyan (line 2)
|
|
7
|
+
'\x1b[36m', // cyan normal (line 3)
|
|
8
|
+
'\x1b[34m', // blue (line 4)
|
|
9
|
+
'\x1b[2m\x1b[34m', // dim blue (line 5)
|
|
10
|
+
'\x1b[2m\x1b[34m', // dim blue (line 6)
|
|
11
|
+
];
|
|
12
|
+
const LOGO = [
|
|
13
|
+
'██████╗ ██╗███████╗████████╗',
|
|
14
|
+
'██╔══██╗██║██╔════╝╚══██╔══╝',
|
|
15
|
+
'██████╔╝██║█████╗ ██║ ',
|
|
16
|
+
'██╔══██╗██║██╔══╝ ██║ ',
|
|
17
|
+
'██║ ██║██║██║ ██║ ',
|
|
18
|
+
'╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ',
|
|
19
|
+
];
|
|
20
|
+
export default class Home extends GatedCommand {
|
|
21
|
+
static description = 'RIFT — Research · Iteration · Forecast · Trade';
|
|
22
|
+
static args = {};
|
|
23
|
+
static flags = {};
|
|
24
|
+
// Home renders its own bottom-of-screen status via renderStatusFooter()
|
|
25
|
+
// inline — skip the auto-footer that GatedCommand.finally() adds.
|
|
26
|
+
static skipFooter = true;
|
|
27
|
+
async run() {
|
|
28
|
+
// Main loop — after each action the user returns to the menu.
|
|
29
|
+
// Exit via `0`, `q`, `quit`, `exit`, or Ctrl-C.
|
|
30
|
+
while (true) {
|
|
31
|
+
// Logo with vertical gradient
|
|
32
|
+
this.log('');
|
|
33
|
+
for (let i = 0; i < LOGO.length; i++) {
|
|
34
|
+
this.log(` ${LOGO_GRADIENT[i]}${LOGO[i]}\x1b[0m`);
|
|
35
|
+
}
|
|
36
|
+
this.log('');
|
|
37
|
+
this.log(` ${dim('Research · Iteration · Forecast · Trade')}`);
|
|
38
|
+
this.log(` ${dim('─'.repeat(42))}`);
|
|
39
|
+
this.log('');
|
|
40
|
+
// Menu
|
|
41
|
+
this.log(` ${cyan('1')} ${bold('Scout')} ${dim('scan the market for opportunities')}`);
|
|
42
|
+
this.log(` ${cyan('2')} ${bold('Trade')} ${dim('manual trade with live monitoring')}`);
|
|
43
|
+
this.log(` ${cyan('3')} ${bold('Research Lab')} ${dim('discover, build, test, optimize')}`);
|
|
44
|
+
this.log(` ${cyan('4')} ${bold('Algo Trading')} ${dim('automated strategy trading')}`);
|
|
45
|
+
this.log(` ${cyan('5')} ${bold('Portfolio Manager')} ${dim('multi-strategy algo trading')}`);
|
|
46
|
+
this.log('');
|
|
47
|
+
this.log(` ${cyan('6')} ${dim('Doctor')} ${dim('system health check')}`);
|
|
48
|
+
this.log(` ${cyan('7')} ${dim('Settings')} ${dim('wallet, config, proxy')}`);
|
|
49
|
+
this.log(` ${cyan('8')} ${dim('AI Integration')} ${dim('connect Claude, Cursor, or any AI')}`);
|
|
50
|
+
this.log('');
|
|
51
|
+
this.log(` ${cyan('0')} ${dim('Exit')}`);
|
|
52
|
+
this.log('');
|
|
53
|
+
this.log(` ${dim('Tip:')} ${cyan('rift more')} ${dim('shows every engine command')}`);
|
|
54
|
+
this.log('');
|
|
55
|
+
// Phase 0 status footer — reflects real on-disk state
|
|
56
|
+
const { renderStatusFooter } = await import('../lib/status-footer.js');
|
|
57
|
+
this.log(renderStatusFooter());
|
|
58
|
+
this.log('');
|
|
59
|
+
// Input
|
|
60
|
+
const choice = await ask(` ${cyan('>')} `);
|
|
61
|
+
switch (choice) {
|
|
62
|
+
case '0':
|
|
63
|
+
case 'q':
|
|
64
|
+
case 'quit':
|
|
65
|
+
case 'exit':
|
|
66
|
+
this.log('');
|
|
67
|
+
return;
|
|
68
|
+
case '1':
|
|
69
|
+
await this.dispatch('scout');
|
|
70
|
+
break;
|
|
71
|
+
case '2':
|
|
72
|
+
await this.dispatch('trade');
|
|
73
|
+
break;
|
|
74
|
+
case '3':
|
|
75
|
+
await this.dispatch('research');
|
|
76
|
+
break;
|
|
77
|
+
case '4':
|
|
78
|
+
await this.dispatch('algo');
|
|
79
|
+
break;
|
|
80
|
+
case '5':
|
|
81
|
+
await this.dispatch('portfolio:status');
|
|
82
|
+
break;
|
|
83
|
+
case '6':
|
|
84
|
+
await this.dispatch('doctor');
|
|
85
|
+
break;
|
|
86
|
+
case '7':
|
|
87
|
+
await this.settingsMenu();
|
|
88
|
+
break;
|
|
89
|
+
case '8':
|
|
90
|
+
await this.aiIntegrationMenu();
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
if (choice) {
|
|
94
|
+
this.log(`\n ${dim('Unknown option. Try 0-8.')}\n`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run a subcommand and prevent its output from being clobbered by the
|
|
101
|
+
* next menu redraw.
|
|
102
|
+
*
|
|
103
|
+
* If the command exits in < 1500ms (almost always a precondition failure
|
|
104
|
+
* like "wallet not configured" that prints an error and bails), pause for
|
|
105
|
+
* keypress so the user can actually read what happened before the menu
|
|
106
|
+
* redraws over it.
|
|
107
|
+
*
|
|
108
|
+
* If the command ran for longer, the user was interacting with it and
|
|
109
|
+
* doesn't need a pause — redraw immediately for a clean menu transition.
|
|
110
|
+
*/
|
|
111
|
+
async dispatch(cmd, args = []) {
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
try {
|
|
114
|
+
await this.config.runCommand(cmd, args);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// Surface unexpected throws so the user can see them.
|
|
118
|
+
this.log(`\n ${dim('Command threw:')} ${err?.message ?? err}\n`);
|
|
119
|
+
}
|
|
120
|
+
const elapsed = Date.now() - start;
|
|
121
|
+
if (elapsed < 1500) {
|
|
122
|
+
await ask(` ${dim('Press Enter to return to menu...')} `);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async aiIntegrationMenu() {
|
|
126
|
+
this.log('');
|
|
127
|
+
this.log(` ${bold('AI Integration')} ${dim('— connect AI agents to RIFT')}`);
|
|
128
|
+
this.log(` ${dim('─'.repeat(50))}`);
|
|
129
|
+
this.log('');
|
|
130
|
+
this.log(` RIFT exposes ${bold('59 tools')} via MCP (Model Context Protocol).`);
|
|
131
|
+
this.log(` Any AI agent can research, backtest, optimize, and`);
|
|
132
|
+
this.log(` build strategies through RIFT autonomously.`);
|
|
133
|
+
this.log('');
|
|
134
|
+
this.log(` ${bold('Claude Desktop / Claude Code:')}`);
|
|
135
|
+
this.log('');
|
|
136
|
+
this.log(` Add to your config file:`);
|
|
137
|
+
this.log('');
|
|
138
|
+
this.log(` ${dim('{')}`);
|
|
139
|
+
this.log(` ${dim('"mcpServers":')} ${dim('{')}`);
|
|
140
|
+
this.log(` ${dim('"rift":')} ${dim('{')}`);
|
|
141
|
+
this.log(` ${cyan('"command"')}: ${green('"rift"')},`);
|
|
142
|
+
this.log(` ${cyan('"args"')}: [${green('"serve"')}]`);
|
|
143
|
+
this.log(` ${dim('}')}`);
|
|
144
|
+
this.log(` ${dim('}')}`);
|
|
145
|
+
this.log(` ${dim('}')}`);
|
|
146
|
+
this.log('');
|
|
147
|
+
this.log(` ${dim('Config location:')}`);
|
|
148
|
+
this.log(` ${dim('macOS:')} ~/Library/Application Support/Claude/claude_desktop_config.json`);
|
|
149
|
+
this.log(` ${dim('Windows:')} %APPDATA%/Claude/claude_desktop_config.json`);
|
|
150
|
+
this.log('');
|
|
151
|
+
this.log(` ${bold('Tool categories:')}`);
|
|
152
|
+
this.log(` ${dim('Research backtest, research, compare, sweep, smart_sweep,')}`);
|
|
153
|
+
this.log(` ${dim(' walk_forward, montecarlo, quick_test, verify,')}`);
|
|
154
|
+
this.log(` ${dim(' indicator_stats, feature_importance, tearsheet')}`);
|
|
155
|
+
this.log(` ${dim('Trade scout, scan, manual_trade, buy, sell,')}`);
|
|
156
|
+
this.log(` ${dim(' close_position, reduce_position, tighten_stop')}`);
|
|
157
|
+
this.log(` ${dim('Algo algo_start, algo_status, algo_stop')}`);
|
|
158
|
+
this.log(` ${dim('Portfolio portfolio_start, portfolio_status,')}`);
|
|
159
|
+
this.log(` ${dim(' portfolio_stop, portfolio_alerts')}`);
|
|
160
|
+
this.log(` ${dim('Account balance, holdings, state, transfer,')}`);
|
|
161
|
+
this.log(` ${dim(' deposit, withdraw, auth_setup, auth_status')}`);
|
|
162
|
+
this.log(` ${dim('Reports tca_report, pnl_attribution, var_report,')}`);
|
|
163
|
+
this.log(` ${dim(' generate_report, audit_export, history')}`);
|
|
164
|
+
this.log(` ${dim('Data fetch_data, list_data, data_inventory')}`);
|
|
165
|
+
this.log(` ${dim('Workbench workbench_create, workbench_update,')}`);
|
|
166
|
+
this.log(` ${dim(' workbench_show, save_optimized, strategy_versions')}`);
|
|
167
|
+
this.log(` ${dim('System doctor, health, cost, lessons, add_lesson,')}`);
|
|
168
|
+
this.log(` ${dim(' guide, list_strategies, experiments,')}`);
|
|
169
|
+
this.log(` ${dim(' api_start, watchdog_events')}`);
|
|
170
|
+
this.log('');
|
|
171
|
+
this.log(` ${dim('After adding the config, restart Claude Desktop.')}`);
|
|
172
|
+
this.log(` ${dim('Claude will automatically start RIFT when it needs trading tools.')}`);
|
|
173
|
+
this.log('');
|
|
174
|
+
await ask(` ${dim('Press Enter to go back')} `);
|
|
175
|
+
// Parent run() loop redraws the main menu.
|
|
176
|
+
}
|
|
177
|
+
async settingsMenu() {
|
|
178
|
+
while (true) {
|
|
179
|
+
this.log('');
|
|
180
|
+
this.log(` ${bold('Settings')}`);
|
|
181
|
+
this.log(` ${dim('─'.repeat(30))}`);
|
|
182
|
+
this.log('');
|
|
183
|
+
this.log(` ${cyan('1')} ${bold('Auth')} ${dim('wallet setup for live trading')}`);
|
|
184
|
+
this.log(` ${cyan('2')} ${bold('Config')} ${dim('view/edit configuration')}`);
|
|
185
|
+
this.log(` ${cyan('3')} ${bold('Proxy')} ${dim('network proxy setup')}`);
|
|
186
|
+
this.log(` ${cyan('4')} ${dim('Back')}`);
|
|
187
|
+
this.log('');
|
|
188
|
+
const choice = await ask(` ${cyan('>')} `);
|
|
189
|
+
switch (choice) {
|
|
190
|
+
case '1':
|
|
191
|
+
await this.dispatch('auth', ['setup']);
|
|
192
|
+
break;
|
|
193
|
+
case '2':
|
|
194
|
+
await this.dispatch('config', ['list']);
|
|
195
|
+
break;
|
|
196
|
+
case '3':
|
|
197
|
+
this.log(`\n ${dim('Run:')} ${cyan('rift setup proxy')}\n`);
|
|
198
|
+
break;
|
|
199
|
+
case '4':
|
|
200
|
+
case '0':
|
|
201
|
+
case 'q':
|
|
202
|
+
return;
|
|
203
|
+
default:
|
|
204
|
+
if (choice) {
|
|
205
|
+
this.log(`\n ${dim('Unknown option. Try 1-4.')}\n`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|