@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.
Files changed (137) hide show
  1. package/LICENSE +201 -0
  2. package/bin/run.js +22 -0
  3. package/dist/commands/algo.d.ts +32 -0
  4. package/dist/commands/algo.js +719 -0
  5. package/dist/commands/audit.d.ts +13 -0
  6. package/dist/commands/audit.js +37 -0
  7. package/dist/commands/auth-status.d.ts +14 -0
  8. package/dist/commands/auth-status.js +118 -0
  9. package/dist/commands/auth.d.ts +14 -0
  10. package/dist/commands/auth.js +275 -0
  11. package/dist/commands/backtest.d.ts +26 -0
  12. package/dist/commands/backtest.js +283 -0
  13. package/dist/commands/collect/start.d.ts +11 -0
  14. package/dist/commands/collect/start.js +78 -0
  15. package/dist/commands/collect/status.d.ts +6 -0
  16. package/dist/commands/collect/status.js +60 -0
  17. package/dist/commands/compare.d.ts +16 -0
  18. package/dist/commands/compare.js +130 -0
  19. package/dist/commands/config.d.ts +16 -0
  20. package/dist/commands/config.js +143 -0
  21. package/dist/commands/cost.d.ts +20 -0
  22. package/dist/commands/cost.js +104 -0
  23. package/dist/commands/cross-asset.d.ts +14 -0
  24. package/dist/commands/cross-asset.js +39 -0
  25. package/dist/commands/data/fetch.d.ts +15 -0
  26. package/dist/commands/data/fetch.js +82 -0
  27. package/dist/commands/data/list.d.ts +6 -0
  28. package/dist/commands/data/list.js +28 -0
  29. package/dist/commands/data-inventory.d.ts +9 -0
  30. package/dist/commands/data-inventory.js +24 -0
  31. package/dist/commands/deposit.d.ts +10 -0
  32. package/dist/commands/deposit.js +222 -0
  33. package/dist/commands/doctor.d.ts +6 -0
  34. package/dist/commands/doctor.js +87 -0
  35. package/dist/commands/funding-browser.d.ts +12 -0
  36. package/dist/commands/funding-browser.js +33 -0
  37. package/dist/commands/guide.d.ts +6 -0
  38. package/dist/commands/guide.js +15 -0
  39. package/dist/commands/home.d.ts +23 -0
  40. package/dist/commands/home.js +210 -0
  41. package/dist/commands/init.d.ts +7 -0
  42. package/dist/commands/init.js +122 -0
  43. package/dist/commands/install.d.ts +9 -0
  44. package/dist/commands/install.js +89 -0
  45. package/dist/commands/interactive.d.ts +17 -0
  46. package/dist/commands/interactive.js +179 -0
  47. package/dist/commands/lessons.d.ts +12 -0
  48. package/dist/commands/lessons.js +33 -0
  49. package/dist/commands/montecarlo.d.ts +19 -0
  50. package/dist/commands/montecarlo.js +168 -0
  51. package/dist/commands/more.d.ts +11 -0
  52. package/dist/commands/more.js +227 -0
  53. package/dist/commands/new.d.ts +14 -0
  54. package/dist/commands/new.js +306 -0
  55. package/dist/commands/pairs.d.ts +22 -0
  56. package/dist/commands/pairs.js +147 -0
  57. package/dist/commands/perp/close.d.ts +12 -0
  58. package/dist/commands/perp/close.js +57 -0
  59. package/dist/commands/perp/long.d.ts +14 -0
  60. package/dist/commands/perp/long.js +38 -0
  61. package/dist/commands/perp/short.d.ts +14 -0
  62. package/dist/commands/perp/short.js +27 -0
  63. package/dist/commands/perp/status.d.ts +9 -0
  64. package/dist/commands/perp/status.js +26 -0
  65. package/dist/commands/portfolio/alerts.d.ts +6 -0
  66. package/dist/commands/portfolio/alerts.js +47 -0
  67. package/dist/commands/portfolio/backtest.d.ts +12 -0
  68. package/dist/commands/portfolio/backtest.js +178 -0
  69. package/dist/commands/portfolio/create.d.ts +7 -0
  70. package/dist/commands/portfolio/create.js +195 -0
  71. package/dist/commands/portfolio/start.d.ts +9 -0
  72. package/dist/commands/portfolio/start.js +64 -0
  73. package/dist/commands/portfolio/status.d.ts +6 -0
  74. package/dist/commands/portfolio/status.js +128 -0
  75. package/dist/commands/portfolio/stop.d.ts +6 -0
  76. package/dist/commands/portfolio/stop.js +81 -0
  77. package/dist/commands/portfolio-backtest.d.ts +13 -0
  78. package/dist/commands/portfolio-backtest.js +37 -0
  79. package/dist/commands/portfolio-matrix.d.ts +12 -0
  80. package/dist/commands/portfolio-matrix.js +30 -0
  81. package/dist/commands/quick-test.d.ts +17 -0
  82. package/dist/commands/quick-test.js +45 -0
  83. package/dist/commands/research.d.ts +57 -0
  84. package/dist/commands/research.js +1976 -0
  85. package/dist/commands/scout.d.ts +14 -0
  86. package/dist/commands/scout.js +184 -0
  87. package/dist/commands/serve.d.ts +9 -0
  88. package/dist/commands/serve.js +1176 -0
  89. package/dist/commands/setup/proxy.d.ts +10 -0
  90. package/dist/commands/setup/proxy.js +267 -0
  91. package/dist/commands/spot/buy.d.ts +14 -0
  92. package/dist/commands/spot/buy.js +38 -0
  93. package/dist/commands/spot/sell.d.ts +14 -0
  94. package/dist/commands/spot/sell.js +39 -0
  95. package/dist/commands/strategies/list.d.ts +6 -0
  96. package/dist/commands/strategies/list.js +34 -0
  97. package/dist/commands/sweep.d.ts +19 -0
  98. package/dist/commands/sweep.js +137 -0
  99. package/dist/commands/sync.d.ts +17 -0
  100. package/dist/commands/sync.js +54 -0
  101. package/dist/commands/test-trade.d.ts +6 -0
  102. package/dist/commands/test-trade.js +97 -0
  103. package/dist/commands/trade.d.ts +26 -0
  104. package/dist/commands/trade.js +274 -0
  105. package/dist/commands/transfer.d.ts +13 -0
  106. package/dist/commands/transfer.js +65 -0
  107. package/dist/commands/verify.d.ts +16 -0
  108. package/dist/commands/verify.js +38 -0
  109. package/dist/commands/walkforward.d.ts +20 -0
  110. package/dist/commands/walkforward.js +191 -0
  111. package/dist/commands/withdraw.d.ts +12 -0
  112. package/dist/commands/withdraw.js +55 -0
  113. package/dist/commands/workbench-create.d.ts +13 -0
  114. package/dist/commands/workbench-create.js +39 -0
  115. package/dist/lib/account-mode.d.ts +44 -0
  116. package/dist/lib/account-mode.js +96 -0
  117. package/dist/lib/analyzer.d.ts +4 -0
  118. package/dist/lib/analyzer.js +62 -0
  119. package/dist/lib/base-command.d.ts +35 -0
  120. package/dist/lib/base-command.js +49 -0
  121. package/dist/lib/credentials.d.ts +46 -0
  122. package/dist/lib/credentials.js +137 -0
  123. package/dist/lib/engine-passthrough.d.ts +28 -0
  124. package/dist/lib/engine-passthrough.js +60 -0
  125. package/dist/lib/fees.d.ts +52 -0
  126. package/dist/lib/fees.js +97 -0
  127. package/dist/lib/python-bridge.d.ts +24 -0
  128. package/dist/lib/python-bridge.js +182 -0
  129. package/dist/lib/setup-status.d.ts +32 -0
  130. package/dist/lib/setup-status.js +121 -0
  131. package/dist/lib/status-footer.d.ts +35 -0
  132. package/dist/lib/status-footer.js +101 -0
  133. package/dist/lib/tui.d.ts +130 -0
  134. package/dist/lib/tui.js +300 -0
  135. package/dist/lib/walletconnect.d.ts +70 -0
  136. package/dist/lib/walletconnect.js +407 -0
  137. 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,6 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Doctor extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -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,6 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Guide extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Init extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {};
6
+ run(): Promise<void>;
7
+ }