@t2000/cli 0.17.23 → 0.17.25
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/dist/commands/address.d.ts +3 -0
- package/dist/commands/address.d.ts.map +1 -0
- package/dist/commands/address.js +26 -0
- package/dist/commands/address.js.map +1 -0
- package/dist/commands/balance.d.ts +3 -0
- package/dist/commands/balance.d.ts.map +1 -0
- package/dist/commands/balance.js +134 -0
- package/dist/commands/balance.js.map +1 -0
- package/dist/commands/borrow.d.ts +3 -0
- package/dist/commands/borrow.d.ts.map +1 -0
- package/dist/commands/borrow.js +40 -0
- package/dist/commands/borrow.js.map +1 -0
- package/dist/commands/claimRewards.d.ts +3 -0
- package/dist/commands/claimRewards.d.ts.map +1 -0
- package/dist/commands/claimRewards.js +49 -0
- package/dist/commands/claimRewards.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +139 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/contacts.d.ts +3 -0
- package/dist/commands/contacts.d.ts.map +1 -0
- package/dist/commands/contacts.js +83 -0
- package/dist/commands/contacts.js.map +1 -0
- package/dist/commands/deposit.d.ts +3 -0
- package/dist/commands/deposit.d.ts.map +1 -0
- package/dist/commands/deposit.js +27 -0
- package/dist/commands/deposit.js.map +1 -0
- package/dist/commands/earn.d.ts +3 -0
- package/dist/commands/earn.d.ts.map +1 -0
- package/dist/commands/earn.js +186 -0
- package/dist/commands/earn.js.map +1 -0
- package/dist/commands/earnings.d.ts +3 -0
- package/dist/commands/earnings.d.ts.map +1 -0
- package/dist/commands/earnings.js +38 -0
- package/dist/commands/earnings.js.map +1 -0
- package/dist/commands/exchange.d.ts +3 -0
- package/dist/commands/exchange.d.ts.map +1 -0
- package/dist/commands/exchange.js +45 -0
- package/dist/commands/exchange.js.map +1 -0
- package/dist/commands/exportKey.d.ts +3 -0
- package/dist/commands/exportKey.d.ts.map +1 -0
- package/dist/commands/exportKey.js +81 -0
- package/dist/commands/exportKey.js.map +1 -0
- package/dist/commands/fundStatus.d.ts +3 -0
- package/dist/commands/fundStatus.d.ts.map +1 -0
- package/dist/commands/fundStatus.js +50 -0
- package/dist/commands/fundStatus.js.map +1 -0
- package/dist/commands/health.d.ts +3 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +44 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +37 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/importKey.d.ts +3 -0
- package/dist/commands/importKey.d.ts.map +1 -0
- package/dist/commands/importKey.js +39 -0
- package/dist/commands/importKey.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/invest.d.ts +3 -0
- package/dist/commands/invest.d.ts.map +1 -0
- package/dist/commands/invest.js +635 -0
- package/dist/commands/invest.js.map +1 -0
- package/dist/commands/lock.d.ts +3 -0
- package/dist/commands/lock.d.ts.map +1 -0
- package/dist/commands/lock.js +66 -0
- package/dist/commands/lock.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +141 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pay.d.ts +3 -0
- package/dist/commands/pay.d.ts.map +1 -0
- package/dist/commands/pay.js +96 -0
- package/dist/commands/pay.js.map +1 -0
- package/dist/commands/portfolio.d.ts +3 -0
- package/dist/commands/portfolio.d.ts.map +1 -0
- package/dist/commands/portfolio.js +104 -0
- package/dist/commands/portfolio.js.map +1 -0
- package/dist/commands/positions.d.ts +3 -0
- package/dist/commands/positions.d.ts.map +1 -0
- package/dist/commands/positions.js +73 -0
- package/dist/commands/positions.js.map +1 -0
- package/dist/commands/rates.d.ts +3 -0
- package/dist/commands/rates.d.ts.map +1 -0
- package/dist/commands/rates.js +64 -0
- package/dist/commands/rates.js.map +1 -0
- package/dist/commands/rebalance.d.ts +3 -0
- package/dist/commands/rebalance.d.ts.map +1 -0
- package/dist/commands/rebalance.js +109 -0
- package/dist/commands/rebalance.js.map +1 -0
- package/dist/commands/repay.d.ts +3 -0
- package/dist/commands/repay.d.ts.map +1 -0
- package/dist/commands/repay.js +35 -0
- package/dist/commands/repay.js.map +1 -0
- package/dist/commands/save.d.ts +3 -0
- package/dist/commands/save.d.ts.map +1 -0
- package/dist/commands/save.js +57 -0
- package/dist/commands/save.js.map +1 -0
- package/dist/commands/send.d.ts +3 -0
- package/dist/commands/send.d.ts.map +1 -0
- package/dist/commands/send.js +52 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/sentinel.d.ts +3 -0
- package/dist/commands/sentinel.d.ts.map +1 -0
- package/dist/commands/sentinel.js +142 -0
- package/dist/commands/sentinel.js.map +1 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +341 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/withdraw.d.ts +3 -0
- package/dist/commands/withdraw.d.ts.map +1 -0
- package/dist/commands/withdraw.js +34 -0
- package/dist/commands/withdraw.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -2707
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +16 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +85 -0
- package/dist/output.js.map +1 -0
- package/dist/program.d.ts +3 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +80 -0
- package/dist/program.js.map +1 -0
- package/dist/prompts.d.ts +16 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +69 -0
- package/dist/prompts.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { T2000, formatUsd, formatAssetAmount, INVESTMENT_ASSETS } from '@t2000/sdk';
|
|
3
|
+
import { resolvePin } from '../prompts.js';
|
|
4
|
+
import { printSuccess, printKeyValue, printBlank, printJson, isJsonMode, handleError, explorerUrl, printHeader, printSeparator, printInfo, printLine } from '../output.js';
|
|
5
|
+
export function registerInvest(program) {
|
|
6
|
+
const investCmd = program
|
|
7
|
+
.command('invest')
|
|
8
|
+
.description('Buy or sell investment assets');
|
|
9
|
+
investCmd
|
|
10
|
+
.command('buy <amount> <asset>')
|
|
11
|
+
.description('Invest USD amount in an asset')
|
|
12
|
+
.option('--key <path>', 'Key file path')
|
|
13
|
+
.option('--slippage <pct>', 'Max slippage percent', '3')
|
|
14
|
+
.action(async (amount, asset, opts) => {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = parseFloat(amount);
|
|
17
|
+
if (isNaN(parsed) || parsed <= 0 || !isFinite(parsed)) {
|
|
18
|
+
console.error(pc.red(' ✗ Amount must be greater than $0'));
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const pin = await resolvePin();
|
|
23
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
24
|
+
const result = await agent.investBuy({
|
|
25
|
+
asset: asset.toUpperCase(),
|
|
26
|
+
usdAmount: parsed,
|
|
27
|
+
maxSlippage: parseFloat(opts.slippage) / 100,
|
|
28
|
+
});
|
|
29
|
+
if (isJsonMode()) {
|
|
30
|
+
printJson(result);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
printBlank();
|
|
34
|
+
const sym = asset.toUpperCase();
|
|
35
|
+
printSuccess(`Bought ${formatAssetAmount(result.amount, sym)} ${sym} at ${formatUsd(result.price)}`);
|
|
36
|
+
printKeyValue('Invested', formatUsd(result.usdValue));
|
|
37
|
+
printKeyValue('Portfolio', `${formatAssetAmount(result.position.totalAmount, sym)} ${sym} (avg ${formatUsd(result.position.avgPrice)})`);
|
|
38
|
+
printKeyValue('Tx', explorerUrl(result.tx));
|
|
39
|
+
printBlank();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
handleError(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
investCmd
|
|
46
|
+
.command('sell <amount> <asset>')
|
|
47
|
+
.description('Sell USD amount of an asset (or "all")')
|
|
48
|
+
.option('--key <path>', 'Key file path')
|
|
49
|
+
.option('--slippage <pct>', 'Max slippage percent', '3')
|
|
50
|
+
.action(async (amount, asset, opts) => {
|
|
51
|
+
try {
|
|
52
|
+
const isAll = amount.toLowerCase() === 'all';
|
|
53
|
+
if (!isAll) {
|
|
54
|
+
const parsed = parseFloat(amount);
|
|
55
|
+
if (isNaN(parsed) || parsed <= 0 || !isFinite(parsed)) {
|
|
56
|
+
console.error(pc.red(' ✗ Amount must be greater than $0'));
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const pin = await resolvePin();
|
|
62
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
63
|
+
const usdAmount = isAll ? 'all' : parseFloat(amount);
|
|
64
|
+
const result = await agent.investSell({
|
|
65
|
+
asset: asset.toUpperCase(),
|
|
66
|
+
usdAmount,
|
|
67
|
+
maxSlippage: parseFloat(opts.slippage) / 100,
|
|
68
|
+
});
|
|
69
|
+
if (isJsonMode()) {
|
|
70
|
+
printJson(result);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
printBlank();
|
|
74
|
+
const sym = asset.toUpperCase();
|
|
75
|
+
printSuccess(`Sold ${formatAssetAmount(result.amount, sym)} ${sym} at ${formatUsd(result.price)}`);
|
|
76
|
+
printKeyValue('Proceeds', formatUsd(result.usdValue));
|
|
77
|
+
if (result.realizedPnL !== undefined) {
|
|
78
|
+
const pnlColor = result.realizedPnL >= 0 ? pc.green : pc.red;
|
|
79
|
+
const pnlSign = result.realizedPnL >= 0 ? '+' : '';
|
|
80
|
+
printKeyValue('Realized P&L', pnlColor(`${pnlSign}${formatUsd(result.realizedPnL)}`));
|
|
81
|
+
}
|
|
82
|
+
if (result.position.totalAmount > 0) {
|
|
83
|
+
printKeyValue('Remaining', `${formatAssetAmount(result.position.totalAmount, sym)} ${sym} (avg ${formatUsd(result.position.avgPrice)})`);
|
|
84
|
+
}
|
|
85
|
+
printKeyValue('Tx', explorerUrl(result.tx));
|
|
86
|
+
printBlank();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
handleError(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
investCmd
|
|
93
|
+
.command('earn <asset>')
|
|
94
|
+
.description('Deposit invested asset into best-rate lending protocol')
|
|
95
|
+
.option('--key <path>', 'Key file path')
|
|
96
|
+
.action(async (asset, opts) => {
|
|
97
|
+
try {
|
|
98
|
+
const pin = await resolvePin();
|
|
99
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
100
|
+
const result = await agent.investEarn({
|
|
101
|
+
asset: asset.toUpperCase(),
|
|
102
|
+
});
|
|
103
|
+
if (isJsonMode()) {
|
|
104
|
+
printJson(result);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
printBlank();
|
|
108
|
+
const sym = asset.toUpperCase();
|
|
109
|
+
if (result.amount === 0 && !result.tx) {
|
|
110
|
+
printSuccess(`${sym} is already fully earning via ${result.protocol} (${result.apy.toFixed(1)}% APY)`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
printSuccess(`${sym} deposited into ${result.protocol} (${result.apy.toFixed(1)}% APY)`);
|
|
114
|
+
printKeyValue('Amount', `${formatAssetAmount(result.amount, sym)} ${sym}`);
|
|
115
|
+
printKeyValue('Protocol', result.protocol);
|
|
116
|
+
printKeyValue('APY', `${result.apy.toFixed(2)}%`);
|
|
117
|
+
printKeyValue('Tx', explorerUrl(result.tx));
|
|
118
|
+
}
|
|
119
|
+
printBlank();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
handleError(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
investCmd
|
|
126
|
+
.command('unearn <asset>')
|
|
127
|
+
.description('Withdraw invested asset from lending (keeps in portfolio)')
|
|
128
|
+
.option('--key <path>', 'Key file path')
|
|
129
|
+
.action(async (asset, opts) => {
|
|
130
|
+
try {
|
|
131
|
+
const pin = await resolvePin();
|
|
132
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
133
|
+
const result = await agent.investUnearn({
|
|
134
|
+
asset: asset.toUpperCase(),
|
|
135
|
+
});
|
|
136
|
+
if (isJsonMode()) {
|
|
137
|
+
printJson(result);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
printBlank();
|
|
141
|
+
const sym = asset.toUpperCase();
|
|
142
|
+
printSuccess(`Withdrew ${formatAssetAmount(result.amount, sym)} ${sym} from ${result.protocol}`);
|
|
143
|
+
printKeyValue('Status', `${sym} remains in investment portfolio (locked)`);
|
|
144
|
+
printKeyValue('Tx', explorerUrl(result.tx));
|
|
145
|
+
printBlank();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
handleError(error);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
investCmd
|
|
152
|
+
.command('rebalance')
|
|
153
|
+
.description('Move earning positions to better-rate protocols')
|
|
154
|
+
.option('--key <path>', 'Key file path')
|
|
155
|
+
.option('--dry-run', 'Preview moves without executing')
|
|
156
|
+
.option('--min-diff <pct>', 'Minimum APY difference to trigger move', '0.5')
|
|
157
|
+
.action(async (opts) => {
|
|
158
|
+
try {
|
|
159
|
+
const pin = await resolvePin();
|
|
160
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
161
|
+
const result = await agent.investRebalance({
|
|
162
|
+
dryRun: opts.dryRun,
|
|
163
|
+
minYieldDiff: opts.minDiff ? parseFloat(opts.minDiff) : undefined,
|
|
164
|
+
});
|
|
165
|
+
if (isJsonMode()) {
|
|
166
|
+
printJson(result);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
printBlank();
|
|
170
|
+
if (result.moves.length === 0) {
|
|
171
|
+
printInfo('All earning positions are already on the best rate');
|
|
172
|
+
if (result.skipped.length > 0) {
|
|
173
|
+
for (const s of result.skipped) {
|
|
174
|
+
printLine(` ${s.asset}: ${s.apy.toFixed(2)}% on ${s.protocol} (best: ${s.bestApy.toFixed(2)}% — ${s.reason.replace(/_/g, ' ')})`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
printBlank();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (opts.dryRun) {
|
|
181
|
+
printHeader('Rebalance Preview');
|
|
182
|
+
for (const m of result.moves) {
|
|
183
|
+
printLine(` ${m.asset}: ${m.fromProtocol} (${m.oldApy.toFixed(2)}%) → ${m.toProtocol} (${m.newApy.toFixed(2)}%)`);
|
|
184
|
+
printLine(` Gain: +${(m.newApy - m.oldApy).toFixed(2)}% APY`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
printSuccess('Rebalanced earning positions');
|
|
189
|
+
printSeparator();
|
|
190
|
+
for (const m of result.moves) {
|
|
191
|
+
printLine(` ${m.asset}: ${m.fromProtocol} (${m.oldApy.toFixed(2)}%) → ${m.toProtocol} (${m.newApy.toFixed(2)}%)`);
|
|
192
|
+
printKeyValue('Amount', `${formatAssetAmount(m.amount, m.asset)} ${m.asset}`);
|
|
193
|
+
printKeyValue('APY gain', `+${(m.newApy - m.oldApy).toFixed(2)}%`);
|
|
194
|
+
if (m.txDigests.length > 0) {
|
|
195
|
+
printKeyValue('Tx', explorerUrl(m.txDigests[m.txDigests.length - 1]));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
printSeparator();
|
|
199
|
+
printKeyValue('Gas', `${result.totalGasCost.toFixed(6)} SUI`);
|
|
200
|
+
}
|
|
201
|
+
if (result.skipped.length > 0) {
|
|
202
|
+
printBlank();
|
|
203
|
+
for (const s of result.skipped) {
|
|
204
|
+
printLine(` ${s.asset}: kept on ${s.protocol} (${s.reason.replace(/_/g, ' ')})`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
printBlank();
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
handleError(error);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// -- Strategy subcommands --
|
|
214
|
+
const strategyCmd = investCmd
|
|
215
|
+
.command('strategy')
|
|
216
|
+
.description('Manage investment strategies');
|
|
217
|
+
strategyCmd
|
|
218
|
+
.command('list')
|
|
219
|
+
.description('List available strategies')
|
|
220
|
+
.option('--key <path>', 'Key file path')
|
|
221
|
+
.action(async (opts) => {
|
|
222
|
+
try {
|
|
223
|
+
const pin = await resolvePin();
|
|
224
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
225
|
+
const all = agent.strategies.getAll();
|
|
226
|
+
if (isJsonMode()) {
|
|
227
|
+
printJson(all);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
printBlank();
|
|
231
|
+
printHeader('Investment Strategies');
|
|
232
|
+
printSeparator();
|
|
233
|
+
for (const [key, def] of Object.entries(all)) {
|
|
234
|
+
const allocs = Object.entries(def.allocations).map(([a, p]) => `${a} ${p}%`).join(', ');
|
|
235
|
+
const tag = def.custom ? pc.dim(' (custom)') : '';
|
|
236
|
+
printKeyValue(key, `${allocs}${tag}`);
|
|
237
|
+
printLine(` ${pc.dim(def.description)}`);
|
|
238
|
+
}
|
|
239
|
+
printSeparator();
|
|
240
|
+
const hasPositions = Object.keys(all).some((k) => agent.portfolio.hasStrategyPositions(k));
|
|
241
|
+
if (!hasPositions) {
|
|
242
|
+
printInfo('Buy into a strategy: t2000 invest strategy buy bluechip 100');
|
|
243
|
+
}
|
|
244
|
+
printBlank();
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
handleError(error);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
strategyCmd
|
|
251
|
+
.command('buy <name> <amount>')
|
|
252
|
+
.description('Buy into a strategy')
|
|
253
|
+
.option('--key <path>', 'Key file path')
|
|
254
|
+
.option('--dry-run', 'Preview allocation without executing')
|
|
255
|
+
.action(async (name, amount, opts) => {
|
|
256
|
+
try {
|
|
257
|
+
const parsed = parseFloat(amount);
|
|
258
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
259
|
+
console.error(pc.red(' ✗ Amount must be greater than $0'));
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const pin = await resolvePin();
|
|
264
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
265
|
+
const result = await agent.investStrategy({ strategy: name.toLowerCase(), usdAmount: parsed, dryRun: opts.dryRun });
|
|
266
|
+
if (isJsonMode()) {
|
|
267
|
+
printJson(result);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
printBlank();
|
|
271
|
+
if (opts.dryRun) {
|
|
272
|
+
printHeader(`Strategy: ${name} — Dry Run (${formatUsd(parsed)})`);
|
|
273
|
+
printSeparator();
|
|
274
|
+
for (const buy of result.buys) {
|
|
275
|
+
printKeyValue(buy.asset, `${formatUsd(buy.usdAmount)} → ~${formatAssetAmount(buy.amount, buy.asset)} ${buy.asset} @ ${formatUsd(buy.price)}`);
|
|
276
|
+
}
|
|
277
|
+
printSeparator();
|
|
278
|
+
printInfo('Run without --dry-run to execute');
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const txDigests = [...new Set(result.buys.map(b => b.tx))];
|
|
282
|
+
const isSingleTx = txDigests.length === 1;
|
|
283
|
+
printSuccess(`Invested ${formatUsd(parsed)} in ${name} strategy`);
|
|
284
|
+
printSeparator();
|
|
285
|
+
for (const buy of result.buys) {
|
|
286
|
+
printKeyValue(buy.asset, `${formatAssetAmount(buy.amount, buy.asset)} @ ${formatUsd(buy.price)}`);
|
|
287
|
+
}
|
|
288
|
+
printSeparator();
|
|
289
|
+
printKeyValue('Total invested', formatUsd(result.totalInvested));
|
|
290
|
+
if (isSingleTx) {
|
|
291
|
+
printKeyValue('Tx', explorerUrl(txDigests[0]));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
for (const buy of result.buys) {
|
|
295
|
+
printLine(` ${pc.dim(`${buy.asset}: ${explorerUrl(buy.tx)}`)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
printBlank();
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
handleError(error);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
strategyCmd
|
|
306
|
+
.command('sell <name>')
|
|
307
|
+
.description('Sell all positions in a strategy')
|
|
308
|
+
.option('--key <path>', 'Key file path')
|
|
309
|
+
.action(async (name, opts) => {
|
|
310
|
+
try {
|
|
311
|
+
const pin = await resolvePin();
|
|
312
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
313
|
+
const result = await agent.sellStrategy({ strategy: name.toLowerCase() });
|
|
314
|
+
if (isJsonMode()) {
|
|
315
|
+
printJson(result);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
printBlank();
|
|
319
|
+
printSuccess(`Sold all ${name} strategy positions`);
|
|
320
|
+
printSeparator();
|
|
321
|
+
for (const sell of result.sells) {
|
|
322
|
+
const pnlColor = sell.realizedPnL >= 0 ? pc.green : pc.red;
|
|
323
|
+
const pnlSign = sell.realizedPnL >= 0 ? '+' : '';
|
|
324
|
+
printKeyValue(sell.asset, `${formatAssetAmount(sell.amount, sell.asset)} → ${formatUsd(sell.usdValue)} ${pnlColor(`${pnlSign}${formatUsd(sell.realizedPnL)}`)}`);
|
|
325
|
+
}
|
|
326
|
+
if (result.failed && result.failed.length > 0) {
|
|
327
|
+
for (const f of result.failed) {
|
|
328
|
+
console.error(pc.yellow(` ⚠ ${f.asset}: ${f.reason}`));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
printSeparator();
|
|
332
|
+
printKeyValue('Total proceeds', formatUsd(result.totalProceeds));
|
|
333
|
+
const rpnlColor = result.realizedPnL >= 0 ? pc.green : pc.red;
|
|
334
|
+
const rpnlSign = result.realizedPnL >= 0 ? '+' : '';
|
|
335
|
+
printKeyValue('Realized P&L', rpnlColor(`${rpnlSign}${formatUsd(result.realizedPnL)}`));
|
|
336
|
+
printBlank();
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
handleError(error);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
strategyCmd
|
|
343
|
+
.command('status <name>')
|
|
344
|
+
.description('Show current status and weights of a strategy')
|
|
345
|
+
.option('--key <path>', 'Key file path')
|
|
346
|
+
.action(async (name, opts) => {
|
|
347
|
+
try {
|
|
348
|
+
const pin = await resolvePin();
|
|
349
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
350
|
+
const status = await agent.getStrategyStatus(name.toLowerCase());
|
|
351
|
+
if (isJsonMode()) {
|
|
352
|
+
printJson(status);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
printBlank();
|
|
356
|
+
printHeader(`Strategy: ${status.definition.name}`);
|
|
357
|
+
printSeparator();
|
|
358
|
+
if (status.positions.length === 0) {
|
|
359
|
+
printInfo('No positions yet. Buy in with: t2000 invest strategy buy ' + name + ' 100');
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
for (const pos of status.positions) {
|
|
363
|
+
const target = status.definition.allocations[pos.asset] ?? 0;
|
|
364
|
+
const actual = status.currentWeights[pos.asset] ?? 0;
|
|
365
|
+
const drift = actual - target;
|
|
366
|
+
const driftColor = Math.abs(drift) > 3 ? pc.yellow : pc.dim;
|
|
367
|
+
const pnlColor = pos.unrealizedPnL >= 0 ? pc.green : pc.red;
|
|
368
|
+
const pnlSign = pos.unrealizedPnL >= 0 ? '+' : '';
|
|
369
|
+
printKeyValue(pos.asset, `${formatAssetAmount(pos.totalAmount, pos.asset)} ${formatUsd(pos.currentValue)} ${pnlColor(`${pnlSign}${formatUsd(pos.unrealizedPnL)}`)} ${driftColor(`${actual.toFixed(0)}% / ${target}% target`)}`);
|
|
370
|
+
}
|
|
371
|
+
printSeparator();
|
|
372
|
+
printKeyValue('Total value', formatUsd(status.totalValue));
|
|
373
|
+
}
|
|
374
|
+
printBlank();
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
handleError(error);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
strategyCmd
|
|
381
|
+
.command('rebalance <name>')
|
|
382
|
+
.description('Rebalance a strategy to target weights')
|
|
383
|
+
.option('--key <path>', 'Key file path')
|
|
384
|
+
.action(async (name, opts) => {
|
|
385
|
+
try {
|
|
386
|
+
const pin = await resolvePin();
|
|
387
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
388
|
+
const result = await agent.rebalanceStrategy({ strategy: name.toLowerCase() });
|
|
389
|
+
if (isJsonMode()) {
|
|
390
|
+
printJson(result);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
printBlank();
|
|
394
|
+
if (result.trades.length === 0) {
|
|
395
|
+
printInfo(`Strategy '${name}' is already balanced (within 3% threshold)`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
printSuccess(`Rebalanced ${name} strategy`);
|
|
399
|
+
printSeparator();
|
|
400
|
+
for (const t of result.trades) {
|
|
401
|
+
const action = t.action === 'buy' ? pc.green('BUY') : pc.red('SELL');
|
|
402
|
+
printKeyValue(t.asset, `${action} ${formatUsd(t.usdAmount)} (${formatAssetAmount(t.amount, t.asset)})`);
|
|
403
|
+
}
|
|
404
|
+
printSeparator();
|
|
405
|
+
printInfo('Weights: ' + Object.entries(result.afterWeights).map(([a, w]) => `${a} ${w.toFixed(0)}%`).join(', '));
|
|
406
|
+
}
|
|
407
|
+
printBlank();
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
handleError(error);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
strategyCmd
|
|
414
|
+
.command('create <name>')
|
|
415
|
+
.description('Create a custom strategy')
|
|
416
|
+
.requiredOption('--alloc <pairs...>', 'Allocations e.g. SUI:40 BTC:20 ETH:20 GOLD:20')
|
|
417
|
+
.option('--description <desc>', 'Strategy description')
|
|
418
|
+
.action(async (name, opts) => {
|
|
419
|
+
try {
|
|
420
|
+
const allocations = {};
|
|
421
|
+
for (const pair of opts.alloc) {
|
|
422
|
+
const [asset, pctStr] = pair.split(':');
|
|
423
|
+
if (!asset || !pctStr) {
|
|
424
|
+
console.error(pc.red(` ✗ Invalid allocation: '${pair}'. Use ASSET:PCT format (e.g. SUI:60)`));
|
|
425
|
+
process.exitCode = 1;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
allocations[asset.toUpperCase()] = parseFloat(pctStr);
|
|
429
|
+
}
|
|
430
|
+
const pin = await resolvePin();
|
|
431
|
+
const agent = await T2000.create({ pin });
|
|
432
|
+
const definition = agent.strategies.create({ name, allocations, description: opts.description });
|
|
433
|
+
if (isJsonMode()) {
|
|
434
|
+
printJson(definition);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
printBlank();
|
|
438
|
+
printSuccess(`Created strategy '${name}'`);
|
|
439
|
+
const allocs = Object.entries(definition.allocations).map(([a, p]) => `${a} ${p}%`).join(', ');
|
|
440
|
+
printKeyValue('Allocations', allocs);
|
|
441
|
+
printBlank();
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
handleError(error);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
strategyCmd
|
|
448
|
+
.command('delete <name>')
|
|
449
|
+
.description('Delete a custom strategy')
|
|
450
|
+
.option('--key <path>', 'Key file path')
|
|
451
|
+
.action(async (name, opts) => {
|
|
452
|
+
try {
|
|
453
|
+
const pin = await resolvePin();
|
|
454
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
455
|
+
if (agent.portfolio.hasStrategyPositions(name.toLowerCase())) {
|
|
456
|
+
console.error(pc.red(` ✗ Strategy '${name}' has open positions. Sell first: t2000 invest strategy sell ${name}`));
|
|
457
|
+
process.exitCode = 1;
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
agent.strategies.delete(name.toLowerCase());
|
|
461
|
+
if (isJsonMode()) {
|
|
462
|
+
printJson({ deleted: name });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
printBlank();
|
|
466
|
+
printSuccess(`Deleted strategy '${name}'`);
|
|
467
|
+
printBlank();
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
handleError(error);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
// -- Auto-Invest subcommands --
|
|
474
|
+
const autoCmd = investCmd
|
|
475
|
+
.command('auto')
|
|
476
|
+
.description('Dollar-cost averaging (DCA) schedules');
|
|
477
|
+
autoCmd
|
|
478
|
+
.command('setup <amount> <frequency> [target]')
|
|
479
|
+
.description('Create a DCA schedule (target = strategy name or asset)')
|
|
480
|
+
.option('--key <path>', 'Key file path')
|
|
481
|
+
.option('--day <num>', 'Day of week (1-7) or month (1-28)')
|
|
482
|
+
.action(async (amount, frequency, target, opts) => {
|
|
483
|
+
try {
|
|
484
|
+
const parsed = parseFloat(amount);
|
|
485
|
+
if (isNaN(parsed) || parsed < 1) {
|
|
486
|
+
console.error(pc.red(' ✗ Amount must be at least $1'));
|
|
487
|
+
process.exitCode = 1;
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!['daily', 'weekly', 'monthly'].includes(frequency)) {
|
|
491
|
+
console.error(pc.red(' ✗ Frequency must be daily, weekly, or monthly'));
|
|
492
|
+
process.exitCode = 1;
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const pin = await resolvePin();
|
|
496
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
497
|
+
const allStrategies = agent.strategies.getAll();
|
|
498
|
+
const isStrategy = target ? target.toLowerCase() in allStrategies : false;
|
|
499
|
+
const isAsset = target ? target.toUpperCase() in INVESTMENT_ASSETS : false;
|
|
500
|
+
if (target && !isStrategy && !isAsset) {
|
|
501
|
+
console.error(pc.red(` ✗ '${target}' is not a valid strategy or asset`));
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const dayNum = opts.day ? parseInt(opts.day, 10) : undefined;
|
|
506
|
+
const schedule = agent.setupAutoInvest({
|
|
507
|
+
amount: parsed,
|
|
508
|
+
frequency: frequency,
|
|
509
|
+
strategy: isStrategy ? target.toLowerCase() : undefined,
|
|
510
|
+
asset: isAsset ? target.toUpperCase() : undefined,
|
|
511
|
+
dayOfWeek: frequency === 'weekly' ? dayNum : undefined,
|
|
512
|
+
dayOfMonth: frequency === 'monthly' ? dayNum : undefined,
|
|
513
|
+
});
|
|
514
|
+
if (isJsonMode()) {
|
|
515
|
+
printJson(schedule);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
printBlank();
|
|
519
|
+
const targetLabel = schedule.strategy ?? schedule.asset ?? 'unknown';
|
|
520
|
+
printSuccess(`Auto-invest created: ${formatUsd(schedule.amount)} ${schedule.frequency} → ${targetLabel}`);
|
|
521
|
+
printKeyValue('Schedule ID', schedule.id);
|
|
522
|
+
printKeyValue('Next run', new Date(schedule.nextRun).toLocaleDateString());
|
|
523
|
+
printInfo('Run manually: t2000 invest auto run');
|
|
524
|
+
printBlank();
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
handleError(error);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
autoCmd
|
|
531
|
+
.command('status')
|
|
532
|
+
.description('Show all DCA schedules')
|
|
533
|
+
.option('--key <path>', 'Key file path')
|
|
534
|
+
.action(async (opts) => {
|
|
535
|
+
try {
|
|
536
|
+
const pin = await resolvePin();
|
|
537
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
538
|
+
const status = agent.getAutoInvestStatus();
|
|
539
|
+
if (isJsonMode()) {
|
|
540
|
+
printJson(status);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
printBlank();
|
|
544
|
+
if (status.schedules.length === 0) {
|
|
545
|
+
printInfo('No auto-invest schedules. Set one up: t2000 invest auto setup 50 weekly bluechip');
|
|
546
|
+
printBlank();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
printHeader('Auto-Invest Schedules');
|
|
550
|
+
printSeparator();
|
|
551
|
+
for (const s of status.schedules) {
|
|
552
|
+
const target = s.strategy ?? s.asset ?? '?';
|
|
553
|
+
const statusTag = s.enabled ? pc.green('active') : pc.dim('paused');
|
|
554
|
+
printKeyValue(s.id, `${formatUsd(s.amount)} ${s.frequency} → ${target} ${statusTag}`);
|
|
555
|
+
printLine(` ${pc.dim(`Next: ${new Date(s.nextRun).toLocaleDateString()} · Runs: ${s.runCount} · Total: ${formatUsd(s.totalInvested)}`)}`);
|
|
556
|
+
}
|
|
557
|
+
printSeparator();
|
|
558
|
+
if (status.pendingRuns.length > 0) {
|
|
559
|
+
printInfo(`${status.pendingRuns.length} pending run(s). Execute: t2000 invest auto run`);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
printInfo('All schedules up to date');
|
|
563
|
+
}
|
|
564
|
+
printBlank();
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
handleError(error);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
autoCmd
|
|
571
|
+
.command('run')
|
|
572
|
+
.description('Execute pending DCA purchases')
|
|
573
|
+
.option('--key <path>', 'Key file path')
|
|
574
|
+
.action(async (opts) => {
|
|
575
|
+
try {
|
|
576
|
+
const pin = await resolvePin();
|
|
577
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
578
|
+
const status = agent.getAutoInvestStatus();
|
|
579
|
+
if (status.pendingRuns.length === 0) {
|
|
580
|
+
if (isJsonMode()) {
|
|
581
|
+
printJson({ executed: [], skipped: [] });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
printBlank();
|
|
585
|
+
printInfo('No pending auto-invest runs. All schedules are up to date.');
|
|
586
|
+
printBlank();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const result = await agent.runAutoInvest();
|
|
590
|
+
if (isJsonMode()) {
|
|
591
|
+
printJson(result);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
printBlank();
|
|
595
|
+
if (result.executed.length > 0) {
|
|
596
|
+
printSuccess(`Executed ${result.executed.length} auto-invest run(s)`);
|
|
597
|
+
for (const exec of result.executed) {
|
|
598
|
+
const target = exec.strategy ?? exec.asset ?? '?';
|
|
599
|
+
printKeyValue(target, formatUsd(exec.amount));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (result.skipped.length > 0) {
|
|
603
|
+
for (const skip of result.skipped) {
|
|
604
|
+
printLine(` ${pc.yellow('⚠')} Skipped ${skip.scheduleId}: ${skip.reason}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
printBlank();
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
handleError(error);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
autoCmd
|
|
614
|
+
.command('stop <id>')
|
|
615
|
+
.description('Stop an auto-invest schedule')
|
|
616
|
+
.option('--key <path>', 'Key file path')
|
|
617
|
+
.action(async (id, opts) => {
|
|
618
|
+
try {
|
|
619
|
+
const pin = await resolvePin();
|
|
620
|
+
const agent = await T2000.create({ pin, keyPath: opts.key });
|
|
621
|
+
agent.stopAutoInvest(id);
|
|
622
|
+
if (isJsonMode()) {
|
|
623
|
+
printJson({ stopped: id });
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
printBlank();
|
|
627
|
+
printSuccess(`Stopped auto-invest schedule ${id}`);
|
|
628
|
+
printBlank();
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
handleError(error);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
//# sourceMappingURL=invest.js.map
|