@trading-boy/cli 0.3.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +5 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +37 -3
- package/dist/api-client.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +42 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts +0 -15
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +17 -79
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/behavioral.d.ts +34 -1
- package/dist/commands/behavioral.d.ts.map +1 -1
- package/dist/commands/behavioral.js +56 -76
- package/dist/commands/behavioral.js.map +1 -1
- package/dist/commands/benchmark-cmd.js +2 -2
- package/dist/commands/benchmark-cmd.js.map +1 -1
- package/dist/commands/billing.d.ts.map +1 -1
- package/dist/commands/billing.js +15 -10
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/catalysts.d.ts +10 -1
- package/dist/commands/catalysts.d.ts.map +1 -1
- package/dist/commands/catalysts.js +15 -84
- package/dist/commands/catalysts.js.map +1 -1
- package/dist/commands/coaching-cmd.d.ts.map +1 -1
- package/dist/commands/coaching-cmd.js +21 -10
- package/dist/commands/coaching-cmd.js.map +1 -1
- package/dist/commands/config-cmd.d.ts.map +1 -1
- package/dist/commands/config-cmd.js +39 -23
- package/dist/commands/config-cmd.js.map +1 -1
- package/dist/commands/context.d.ts +0 -7
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +51 -199
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/decisions.d.ts +38 -2
- package/dist/commands/decisions.d.ts.map +1 -1
- package/dist/commands/decisions.js +35 -134
- package/dist/commands/decisions.js.map +1 -1
- package/dist/commands/edge-cmd.d.ts +55 -45
- package/dist/commands/edge-cmd.d.ts.map +1 -1
- package/dist/commands/edge-cmd.js +76 -32
- package/dist/commands/edge-cmd.js.map +1 -1
- package/dist/commands/edge-guard-cmd.d.ts.map +1 -1
- package/dist/commands/edge-guard-cmd.js +7 -3
- package/dist/commands/edge-guard-cmd.js.map +1 -1
- package/dist/commands/events.d.ts.map +1 -1
- package/dist/commands/events.js +78 -162
- package/dist/commands/events.js.map +1 -1
- package/dist/commands/infra.d.ts +2 -4
- package/dist/commands/infra.d.ts.map +1 -1
- package/dist/commands/infra.js +65 -163
- package/dist/commands/infra.js.map +1 -1
- package/dist/commands/journal.d.ts.map +1 -1
- package/dist/commands/journal.js +138 -597
- package/dist/commands/journal.js.map +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/narratives.d.ts.map +1 -1
- package/dist/commands/narratives.js +91 -311
- package/dist/commands/narratives.js.map +1 -1
- package/dist/commands/query.d.ts +0 -7
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +35 -157
- package/dist/commands/query.js.map +1 -1
- package/dist/commands/replay-cmd.d.ts.map +1 -1
- package/dist/commands/replay-cmd.js +6 -8
- package/dist/commands/replay-cmd.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +45 -83
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/risk.d.ts +29 -1
- package/dist/commands/risk.d.ts.map +1 -1
- package/dist/commands/risk.js +20 -89
- package/dist/commands/risk.js.map +1 -1
- package/dist/commands/social.d.ts +36 -2
- package/dist/commands/social.d.ts.map +1 -1
- package/dist/commands/social.js +74 -148
- package/dist/commands/social.js.map +1 -1
- package/dist/commands/strategy-cmd.d.ts.map +1 -1
- package/dist/commands/strategy-cmd.js +2 -4
- package/dist/commands/strategy-cmd.js.map +1 -1
- package/dist/commands/subscribe.js +4 -4
- package/dist/commands/subscribe.js.map +1 -1
- package/dist/commands/suggestions-cmd.d.ts +24 -0
- package/dist/commands/suggestions-cmd.d.ts.map +1 -0
- package/dist/commands/suggestions-cmd.js +142 -0
- package/dist/commands/suggestions-cmd.js.map +1 -0
- package/dist/commands/thesis-cmd.js +4 -4
- package/dist/commands/thesis-cmd.js.map +1 -1
- package/dist/commands/trader.d.ts +18 -1
- package/dist/commands/trader.d.ts.map +1 -1
- package/dist/commands/trader.js +363 -218
- package/dist/commands/trader.js.map +1 -1
- package/dist/commands/watch.d.ts +0 -10
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +5 -66
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +18 -2
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +45 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +97 -0
- package/dist/utils.js.map +1 -1
- package/package.json +15 -18
package/dist/commands/trader.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Option } from 'commander';
|
|
1
3
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
|
|
6
|
-
import { createTrader, listTraders, updateTrader, linkWallet, resolveTrader, getTraderWallets, } from '@trading-boy/graph-db';
|
|
4
|
+
import { createLogger } from '@trading-boy/core';
|
|
5
|
+
import { padRight } from '../utils.js';
|
|
6
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
7
7
|
// ─── Logger ───
|
|
8
8
|
const logger = createLogger('cli-trader');
|
|
9
|
-
// ─── Neo4j Client Factory ───
|
|
10
|
-
function createNeo4jClient() {
|
|
11
|
-
const config = getConfig();
|
|
12
|
-
return new Neo4jClient({
|
|
13
|
-
uri: config.NEO4J_URI,
|
|
14
|
-
username: config.NEO4J_USER,
|
|
15
|
-
password: config.NEO4J_PASSWORD,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
9
|
// ─── Formatters ───
|
|
19
10
|
/**
|
|
20
11
|
* Format a single trader record for display.
|
|
@@ -27,10 +18,11 @@ export function formatTraderOutput(trader, wallets) {
|
|
|
27
18
|
lines.push('');
|
|
28
19
|
lines.push(` ${chalk.gray('Name:')} ${chalk.white(trader.name)}`);
|
|
29
20
|
lines.push(` ${chalk.gray('ID:')} ${chalk.dim(trader.id)}`);
|
|
30
|
-
lines.push(` ${chalk.gray('Max Drawdown:')} ${chalk.yellow(String(trader.riskToleranceMax) + '%')}`);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
lines.push(` ${chalk.gray('Max Drawdown:')} ${chalk.yellow(String(trader.riskToleranceMax ?? trader.maxDrawdownPct ?? 'n/a') + '%')}`);
|
|
22
|
+
const walletAddrs = trader.walletAddresses ?? [];
|
|
23
|
+
lines.push(` ${chalk.gray('Wallets:')} ${chalk.white(String(walletAddrs.length))}`);
|
|
24
|
+
if (walletAddrs.length > 0) {
|
|
25
|
+
for (const addr of walletAddrs) {
|
|
34
26
|
lines.push(` ${chalk.dim('\u2022')} ${chalk.white(addr)}`);
|
|
35
27
|
}
|
|
36
28
|
}
|
|
@@ -94,52 +86,31 @@ export function registerTraderCommand(program) {
|
|
|
94
86
|
.requiredOption('--name <name>', 'Trader display name')
|
|
95
87
|
.option('--wallet <address>', 'Initial Solana wallet address')
|
|
96
88
|
.option('--max-drawdown <pct>', 'Max acceptable drawdown percentage', parseFloatOption)
|
|
89
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
97
90
|
.action(async (options) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
try {
|
|
92
|
+
const result = await apiRequest('/api/v1/traders', {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
body: {
|
|
95
|
+
name: options.name,
|
|
96
|
+
wallet: options.wallet,
|
|
97
|
+
maxDrawdown: options.maxDrawdown,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (options.format === 'json') {
|
|
101
|
+
console.log(JSON.stringify(result, null, 2));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
109
104
|
console.log(formatTraderOutput(result));
|
|
110
105
|
console.log(chalk.green(' Trader registered successfully.'));
|
|
111
106
|
console.log('');
|
|
112
107
|
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
115
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
116
|
-
process.exitCode = 1;
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const client = createNeo4jClient();
|
|
121
|
-
try {
|
|
122
|
-
await client.connect();
|
|
123
|
-
const result = await createTrader(client, {
|
|
124
|
-
name: options.name,
|
|
125
|
-
walletAddress: options.wallet,
|
|
126
|
-
riskToleranceMax: options.maxDrawdown,
|
|
127
|
-
});
|
|
128
|
-
console.log(formatTraderOutput(result));
|
|
129
|
-
console.log(chalk.green(' Trader registered successfully.'));
|
|
130
|
-
console.log('');
|
|
131
108
|
}
|
|
132
109
|
catch (error) {
|
|
133
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
134
111
|
logger.error({ error: message }, 'Failed to register trader');
|
|
135
112
|
console.error(chalk.red(`Error: ${message}`));
|
|
136
|
-
|
|
137
|
-
if (guidance)
|
|
138
|
-
console.error(guidance);
|
|
139
|
-
process.exitCode = 1;
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
await client.disconnect();
|
|
113
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
143
114
|
}
|
|
144
115
|
});
|
|
145
116
|
// ─── show ───
|
|
@@ -147,86 +118,50 @@ export function registerTraderCommand(program) {
|
|
|
147
118
|
.command('show')
|
|
148
119
|
.description('Show a trader profile by name or ID')
|
|
149
120
|
.argument('<name-or-id>', 'Trader name or ID')
|
|
150
|
-
.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
console.log(formatTraderOutput(trader, wallets));
|
|
121
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
122
|
+
.action(async (nameOrId, options) => {
|
|
123
|
+
try {
|
|
124
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}`);
|
|
125
|
+
if (options.format === 'json') {
|
|
126
|
+
console.log(JSON.stringify(result, null, 2));
|
|
157
127
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
165
|
-
}
|
|
166
|
-
process.exitCode = 1;
|
|
128
|
+
else {
|
|
129
|
+
const { wallets, ...traderData } = result;
|
|
130
|
+
console.log(formatTraderOutput(traderData, wallets));
|
|
167
131
|
}
|
|
168
|
-
return;
|
|
169
132
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await client.connect();
|
|
173
|
-
const result = await resolveTrader(client, nameOrId);
|
|
174
|
-
if (!result) {
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
175
135
|
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
176
|
-
process.exitCode = 1;
|
|
177
|
-
return;
|
|
178
136
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
186
|
-
const guidance = formatConnectionError(message);
|
|
187
|
-
if (guidance)
|
|
188
|
-
console.error(guidance);
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
}
|
|
191
|
-
finally {
|
|
192
|
-
await client.disconnect();
|
|
137
|
+
else {
|
|
138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
139
|
+
logger.error({ error: message }, 'Failed to show trader');
|
|
140
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
141
|
+
}
|
|
142
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
193
143
|
}
|
|
194
144
|
});
|
|
195
145
|
// ─── list ───
|
|
196
146
|
trader
|
|
197
147
|
.command('list')
|
|
198
148
|
.description('List all registered traders')
|
|
199
|
-
.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
console.log(
|
|
149
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
150
|
+
.action(async (options) => {
|
|
151
|
+
try {
|
|
152
|
+
const result = await apiRequest('/api/v1/traders');
|
|
153
|
+
if (options.format === 'json') {
|
|
154
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
155
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
209
|
-
process.exitCode = 1;
|
|
156
|
+
else {
|
|
157
|
+
console.log(formatTraderListOutput(result.traders));
|
|
210
158
|
}
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const client = createNeo4jClient();
|
|
214
|
-
try {
|
|
215
|
-
await client.connect();
|
|
216
|
-
const traders = await listTraders(client);
|
|
217
|
-
console.log(formatTraderListOutput(traders));
|
|
218
159
|
}
|
|
219
160
|
catch (error) {
|
|
220
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
221
162
|
logger.error({ error: message }, 'Failed to list traders');
|
|
222
163
|
console.error(chalk.red(`Error: ${message}`));
|
|
223
|
-
|
|
224
|
-
if (guidance)
|
|
225
|
-
console.error(guidance);
|
|
226
|
-
process.exitCode = 1;
|
|
227
|
-
}
|
|
228
|
-
finally {
|
|
229
|
-
await client.disconnect();
|
|
164
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
230
165
|
}
|
|
231
166
|
});
|
|
232
167
|
// ─── update ───
|
|
@@ -236,68 +171,40 @@ export function registerTraderCommand(program) {
|
|
|
236
171
|
.argument('<name-or-id>', 'Trader name or ID')
|
|
237
172
|
.option('--name <new-name>', 'New display name')
|
|
238
173
|
.option('--max-drawdown <pct>', 'New max acceptable drawdown percentage', parseFloatOption)
|
|
174
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
239
175
|
.action(async (nameOrId, options) => {
|
|
240
176
|
if (options.name === undefined && options.maxDrawdown === undefined) {
|
|
241
177
|
console.error(chalk.yellow('No updates provided. Use --name or --max-drawdown.'));
|
|
242
178
|
process.exitCode = 1;
|
|
243
179
|
return;
|
|
244
180
|
}
|
|
245
|
-
// ─── Remote API mode ───
|
|
246
|
-
if (await isRemoteMode()) {
|
|
247
|
-
try {
|
|
248
|
-
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}`, {
|
|
249
|
-
method: 'PATCH',
|
|
250
|
-
body: {
|
|
251
|
-
name: options.name,
|
|
252
|
-
maxDrawdown: options.maxDrawdown,
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
console.log(formatTraderOutput(result));
|
|
256
|
-
console.log(chalk.green(' Trader updated successfully.'));
|
|
257
|
-
console.log('');
|
|
258
|
-
}
|
|
259
|
-
catch (error) {
|
|
260
|
-
if (error instanceof ApiError && error.status === 404) {
|
|
261
|
-
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
265
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
266
|
-
}
|
|
267
|
-
process.exitCode = 1;
|
|
268
|
-
}
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const client = createNeo4jClient();
|
|
272
181
|
try {
|
|
273
|
-
await
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
const result = await updateTrader(client, existing.id, {
|
|
281
|
-
name: options.name,
|
|
282
|
-
riskToleranceMax: options.maxDrawdown,
|
|
182
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}`, {
|
|
183
|
+
method: 'PATCH',
|
|
184
|
+
body: {
|
|
185
|
+
name: options.name,
|
|
186
|
+
maxDrawdown: options.maxDrawdown,
|
|
187
|
+
},
|
|
283
188
|
});
|
|
284
|
-
if (
|
|
189
|
+
if (options.format === 'json') {
|
|
190
|
+
console.log(JSON.stringify(result, null, 2));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
285
193
|
console.log(formatTraderOutput(result));
|
|
286
194
|
console.log(chalk.green(' Trader updated successfully.'));
|
|
287
195
|
console.log('');
|
|
288
196
|
}
|
|
289
197
|
}
|
|
290
198
|
catch (error) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
await client.disconnect();
|
|
199
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
200
|
+
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
logger.error({ error: message }, 'Failed to update trader');
|
|
205
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
206
|
+
}
|
|
207
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
301
208
|
}
|
|
302
209
|
});
|
|
303
210
|
// ─── preferences ───
|
|
@@ -305,6 +212,7 @@ export function registerTraderCommand(program) {
|
|
|
305
212
|
.command('preferences')
|
|
306
213
|
.description('Update notification preferences')
|
|
307
214
|
.option('--summary-time <hour>', 'Hour (0-23 UTC) to receive daily email summary', parseInt)
|
|
215
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
308
216
|
.action(async (options) => {
|
|
309
217
|
if (options.summaryTime === undefined) {
|
|
310
218
|
console.error(chalk.yellow('No updates provided. Use --summary-time <hour>.'));
|
|
@@ -317,27 +225,26 @@ export function registerTraderCommand(program) {
|
|
|
317
225
|
process.exitCode = 1;
|
|
318
226
|
return;
|
|
319
227
|
}
|
|
320
|
-
if (!(await isRemoteMode())) {
|
|
321
|
-
console.error(chalk.yellow('Preferences require a remote API connection.'));
|
|
322
|
-
console.error(chalk.dim(' Run: trading-boy subscribe -e you@email.com'));
|
|
323
|
-
process.exitCode = 1;
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
228
|
try {
|
|
327
229
|
const result = await apiRequest('/api/v1/preferences/email', {
|
|
328
230
|
method: 'PATCH',
|
|
329
231
|
body: { summaryHourUtc: options.summaryTime },
|
|
330
232
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
233
|
+
if (options.format === 'json') {
|
|
234
|
+
console.log(JSON.stringify(result, null, 2));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.log('');
|
|
238
|
+
console.log(chalk.green(' Email preferences updated.'));
|
|
239
|
+
console.log(` ${chalk.gray('Email:')} ${chalk.white(result.email)}`);
|
|
240
|
+
console.log(` ${chalk.gray('Summary time:')} ${chalk.white(String(result.summaryHourUtc).padStart(2, '0') + ':00 UTC')}`);
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
336
243
|
}
|
|
337
244
|
catch (error) {
|
|
338
245
|
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
339
246
|
console.error(chalk.red(`Error: ${message}`));
|
|
340
|
-
process.exitCode = 1;
|
|
247
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
341
248
|
}
|
|
342
249
|
});
|
|
343
250
|
// ─── link-wallet ───
|
|
@@ -347,65 +254,303 @@ export function registerTraderCommand(program) {
|
|
|
347
254
|
.argument('<name-or-id>', 'Trader name or ID')
|
|
348
255
|
.argument('<wallet-address>', 'Solana wallet address')
|
|
349
256
|
.option('--chain <chain>', 'Blockchain name', 'solana')
|
|
257
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
350
258
|
.action(async (nameOrId, walletAddress, options) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
259
|
+
try {
|
|
260
|
+
await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/wallets`, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
body: {
|
|
263
|
+
address: walletAddress,
|
|
264
|
+
chain: options.chain,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
if (options.format === 'json') {
|
|
268
|
+
console.log(JSON.stringify({ linked: true, address: walletAddress, chain: options.chain }, null, 2));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
361
271
|
console.log('');
|
|
362
272
|
console.log(chalk.green(` Wallet linked: ${walletAddress}`));
|
|
363
273
|
console.log('');
|
|
364
274
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
278
|
+
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
logger.error({ error: message }, 'Failed to link wallet');
|
|
283
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
284
|
+
}
|
|
285
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// ─── soul ───
|
|
289
|
+
trader
|
|
290
|
+
.command('soul')
|
|
291
|
+
.description('Show or upload a SOUL document')
|
|
292
|
+
.argument('<name-or-id>', 'Trader name or ID')
|
|
293
|
+
.option('--file <path>', 'Path to SOUL document file to upload')
|
|
294
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
295
|
+
.action(async (nameOrId, options) => {
|
|
296
|
+
try {
|
|
297
|
+
if (options.file) {
|
|
298
|
+
// Upload SOUL document
|
|
299
|
+
const document = readFileSync(options.file, 'utf-8');
|
|
300
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/soul`, { method: 'PUT', body: { document } });
|
|
301
|
+
if (options.format === 'json') {
|
|
302
|
+
console.log(JSON.stringify(result, null, 2));
|
|
368
303
|
}
|
|
369
304
|
else {
|
|
370
|
-
|
|
371
|
-
console.
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log(chalk.green(' SOUL document uploaded successfully.'));
|
|
307
|
+
const parsed = result.parsed;
|
|
308
|
+
if (parsed) {
|
|
309
|
+
if (parsed.tradingPersonality)
|
|
310
|
+
console.log(` ${chalk.gray('Personality:')} ${chalk.white(String(parsed.tradingPersonality))}`);
|
|
311
|
+
if (parsed.riskTolerance)
|
|
312
|
+
console.log(` ${chalk.gray('Risk:')} ${chalk.white(String(parsed.riskTolerance))}`);
|
|
313
|
+
const biases = parsed.biases;
|
|
314
|
+
if (biases && biases.length > 0) {
|
|
315
|
+
console.log(` ${chalk.gray('Biases:')} ${chalk.yellow(biases.map(b => b.canonicalName).join(', '))}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
console.log('');
|
|
372
319
|
}
|
|
373
|
-
process.exitCode = 1;
|
|
374
320
|
}
|
|
375
|
-
|
|
321
|
+
else {
|
|
322
|
+
// Show current SOUL document
|
|
323
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/soul`);
|
|
324
|
+
if (options.format === 'json') {
|
|
325
|
+
console.log(JSON.stringify(result, null, 2));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.log('');
|
|
329
|
+
console.log(chalk.bold.cyan(' SOUL Document'));
|
|
330
|
+
console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
331
|
+
console.log('');
|
|
332
|
+
console.log(result.rawDocument.split('\n').map(l => ' ' + l).join('\n'));
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(` ${chalk.gray('Updated:')} ${chalk.dim(result.updatedAt)}`);
|
|
335
|
+
console.log('');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
341
|
+
console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No SOUL document found.'));
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
345
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
346
|
+
}
|
|
347
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
376
348
|
}
|
|
377
|
-
|
|
349
|
+
});
|
|
350
|
+
// ─── purpose ───
|
|
351
|
+
trader
|
|
352
|
+
.command('purpose')
|
|
353
|
+
.description('Show or upload a PURPOSE document')
|
|
354
|
+
.argument('<name-or-id>', 'Trader name or ID')
|
|
355
|
+
.option('--file <path>', 'Path to PURPOSE document file to upload')
|
|
356
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
357
|
+
.action(async (nameOrId, options) => {
|
|
378
358
|
try {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
359
|
+
if (options.file) {
|
|
360
|
+
const document = readFileSync(options.file, 'utf-8');
|
|
361
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/purpose`, { method: 'PUT', body: { document } });
|
|
362
|
+
if (options.format === 'json') {
|
|
363
|
+
console.log(JSON.stringify(result, null, 2));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.log('');
|
|
367
|
+
console.log(chalk.green(' PURPOSE document uploaded successfully.'));
|
|
368
|
+
const parsed = result.parsed;
|
|
369
|
+
if (parsed) {
|
|
370
|
+
if (parsed.mission)
|
|
371
|
+
console.log(` ${chalk.gray('Mission:')} ${chalk.white(String(parsed.mission).slice(0, 80))}`);
|
|
372
|
+
const scope = parsed.assetScope;
|
|
373
|
+
if (scope && scope.length > 0) {
|
|
374
|
+
console.log(` ${chalk.gray('Scope:')} ${chalk.white(scope.join(', '))}`);
|
|
375
|
+
}
|
|
376
|
+
if (parsed.timeHorizon)
|
|
377
|
+
console.log(` ${chalk.gray('Horizon:')} ${chalk.white(String(parsed.timeHorizon))}`);
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/purpose`);
|
|
384
|
+
if (options.format === 'json') {
|
|
385
|
+
console.log(JSON.stringify(result, null, 2));
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log(chalk.bold.cyan(' PURPOSE Document'));
|
|
390
|
+
console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
391
|
+
console.log('');
|
|
392
|
+
console.log(result.rawDocument.split('\n').map(l => ' ' + l).join('\n'));
|
|
393
|
+
console.log('');
|
|
394
|
+
console.log(` ${chalk.gray('Updated:')} ${chalk.dim(result.updatedAt)}`);
|
|
395
|
+
console.log('');
|
|
396
|
+
}
|
|
385
397
|
}
|
|
386
|
-
await linkWallet(client, existing.id, walletAddress, options.chain);
|
|
387
|
-
console.log('');
|
|
388
|
-
console.log(chalk.green(` Wallet linked to ${existing.name}: ${walletAddress}`));
|
|
389
|
-
console.log('');
|
|
390
398
|
}
|
|
391
399
|
catch (error) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
console.error(
|
|
398
|
-
|
|
400
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
401
|
+
console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No PURPOSE document found.'));
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
405
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
406
|
+
}
|
|
407
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// ─── identity ───
|
|
411
|
+
trader
|
|
412
|
+
.command('identity')
|
|
413
|
+
.description('Show combined identity summary with bias-signal matrix')
|
|
414
|
+
.argument('<name-or-id>', 'Trader name or ID')
|
|
415
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
416
|
+
.action(async (nameOrId, options) => {
|
|
417
|
+
try {
|
|
418
|
+
const traderResult = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}`);
|
|
419
|
+
// Fetch both documents (they may not exist)
|
|
420
|
+
let soulDoc = null;
|
|
421
|
+
let purposeDoc = null;
|
|
422
|
+
try {
|
|
423
|
+
soulDoc = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderResult.id)}/soul`);
|
|
424
|
+
}
|
|
425
|
+
catch { /* no soul doc */ }
|
|
426
|
+
try {
|
|
427
|
+
purposeDoc = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderResult.id)}/purpose`);
|
|
428
|
+
}
|
|
429
|
+
catch { /* no purpose doc */ }
|
|
430
|
+
const combined = {
|
|
431
|
+
trader: { id: traderResult.id, name: traderResult.name },
|
|
432
|
+
soul: soulDoc?.parsed ?? null,
|
|
433
|
+
purpose: purposeDoc?.parsed ?? null,
|
|
434
|
+
hasSoul: soulDoc !== null,
|
|
435
|
+
hasPurpose: purposeDoc !== null,
|
|
436
|
+
};
|
|
437
|
+
if (options.format === 'json') {
|
|
438
|
+
console.log(JSON.stringify(combined, null, 2));
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
console.log('');
|
|
442
|
+
console.log(chalk.bold.cyan(` Identity: ${traderResult.name}`));
|
|
443
|
+
console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
444
|
+
console.log('');
|
|
445
|
+
if (soulDoc?.parsed) {
|
|
446
|
+
const p = soulDoc.parsed;
|
|
447
|
+
console.log(` ${chalk.bold('SOUL')}`);
|
|
448
|
+
if (p.tradingPersonality)
|
|
449
|
+
console.log(` ${chalk.gray('Personality:')} ${chalk.white(String(p.tradingPersonality))}`);
|
|
450
|
+
if (p.riskTolerance)
|
|
451
|
+
console.log(` ${chalk.gray('Risk:')} ${chalk.white(String(p.riskTolerance))}`);
|
|
452
|
+
if (p.decisionStyle)
|
|
453
|
+
console.log(` ${chalk.gray('Decisions:')} ${chalk.white(String(p.decisionStyle))}`);
|
|
454
|
+
const biases = p.biases;
|
|
455
|
+
if (biases && biases.length > 0) {
|
|
456
|
+
console.log(` ${chalk.gray('Biases:')}`);
|
|
457
|
+
for (const b of biases) {
|
|
458
|
+
const color = b.severity === 'high' ? chalk.red : b.severity === 'medium' ? chalk.yellow : chalk.dim;
|
|
459
|
+
console.log(` ${chalk.dim('\u2022')} ${color(b.canonicalName)} ${chalk.dim(`(${b.severity})`)}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
console.log(` ${chalk.dim('No SOUL document. Upload with: trader soul <id> --file <path>')}`);
|
|
465
|
+
}
|
|
466
|
+
console.log('');
|
|
467
|
+
if (purposeDoc?.parsed) {
|
|
468
|
+
const p = purposeDoc.parsed;
|
|
469
|
+
console.log(` ${chalk.bold('PURPOSE')}`);
|
|
470
|
+
if (p.mission)
|
|
471
|
+
console.log(` ${chalk.gray('Mission:')} ${chalk.white(String(p.mission).slice(0, 100))}`);
|
|
472
|
+
const scope = p.assetScope;
|
|
473
|
+
if (scope && scope.length > 0)
|
|
474
|
+
console.log(` ${chalk.gray('Scope:')} ${chalk.white(scope.join(', '))}`);
|
|
475
|
+
const outOfScope = p.outOfScope;
|
|
476
|
+
if (outOfScope && outOfScope.length > 0)
|
|
477
|
+
console.log(` ${chalk.gray('Out of scope:')} ${chalk.dim(outOfScope.join(', '))}`);
|
|
478
|
+
if (p.timeHorizon)
|
|
479
|
+
console.log(` ${chalk.gray('Horizon:')} ${chalk.white(String(p.timeHorizon))}`);
|
|
480
|
+
const goals = p.goals;
|
|
481
|
+
if (goals && goals.length > 0) {
|
|
482
|
+
console.log(` ${chalk.gray('Goals:')}`);
|
|
483
|
+
for (const g of goals)
|
|
484
|
+
console.log(` ${chalk.dim('\u2022')} ${chalk.white(g)}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
console.log(` ${chalk.dim('No PURPOSE document. Upload with: trader purpose <id> --file <path>')}`);
|
|
489
|
+
}
|
|
490
|
+
console.log('');
|
|
491
|
+
}
|
|
399
492
|
}
|
|
400
|
-
|
|
401
|
-
|
|
493
|
+
catch (error) {
|
|
494
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
495
|
+
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
499
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
500
|
+
}
|
|
501
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
// ─── history ───
|
|
505
|
+
trader
|
|
506
|
+
.command('history')
|
|
507
|
+
.description('Show document revision history')
|
|
508
|
+
.argument('<name-or-id>', 'Trader name or ID')
|
|
509
|
+
.option('--type <type>', 'Filter by document type (SOUL or PURPOSE)')
|
|
510
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
511
|
+
.action(async (nameOrId, options) => {
|
|
512
|
+
try {
|
|
513
|
+
let url = `/api/v1/traders/${encodeURIComponent(nameOrId)}/documents/history`;
|
|
514
|
+
if (options.type)
|
|
515
|
+
url += `?type=${options.type.toUpperCase()}`;
|
|
516
|
+
const result = await apiRequest(url);
|
|
517
|
+
if (options.format === 'json') {
|
|
518
|
+
console.log(JSON.stringify(result, null, 2));
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
console.log('');
|
|
522
|
+
console.log(chalk.bold.cyan(' Document Revision History'));
|
|
523
|
+
console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
524
|
+
console.log('');
|
|
525
|
+
if (result.revisions.length === 0) {
|
|
526
|
+
console.log(` ${chalk.dim('No document revisions found.')}`);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
console.log(' ' + padRight('Type', 12) + padRight('Version', 10) + 'Created');
|
|
530
|
+
console.log(' ' + '\u2500'.repeat(50));
|
|
531
|
+
for (const r of result.revisions) {
|
|
532
|
+
console.log(' ' +
|
|
533
|
+
padRight(r.documentType, 12) +
|
|
534
|
+
padRight(`v${r.version}`, 10) +
|
|
535
|
+
chalk.dim(r.createdAt));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
543
|
+
console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
547
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
548
|
+
}
|
|
549
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
402
550
|
}
|
|
403
551
|
});
|
|
404
552
|
}
|
|
405
553
|
// ─── Helpers ───
|
|
406
|
-
function padRight(str, len) {
|
|
407
|
-
return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
|
|
408
|
-
}
|
|
409
554
|
function parseFloatOption(value) {
|
|
410
555
|
return parseFloat(value);
|
|
411
556
|
}
|