@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.
Files changed (109) hide show
  1. package/dist/api-client.d.ts +5 -0
  2. package/dist/api-client.d.ts.map +1 -1
  3. package/dist/api-client.js +37 -3
  4. package/dist/api-client.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +42 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/audit.d.ts +0 -15
  9. package/dist/commands/audit.d.ts.map +1 -1
  10. package/dist/commands/audit.js +17 -79
  11. package/dist/commands/audit.js.map +1 -1
  12. package/dist/commands/behavioral.d.ts +34 -1
  13. package/dist/commands/behavioral.d.ts.map +1 -1
  14. package/dist/commands/behavioral.js +56 -76
  15. package/dist/commands/behavioral.js.map +1 -1
  16. package/dist/commands/benchmark-cmd.js +2 -2
  17. package/dist/commands/benchmark-cmd.js.map +1 -1
  18. package/dist/commands/billing.d.ts.map +1 -1
  19. package/dist/commands/billing.js +15 -10
  20. package/dist/commands/billing.js.map +1 -1
  21. package/dist/commands/catalysts.d.ts +10 -1
  22. package/dist/commands/catalysts.d.ts.map +1 -1
  23. package/dist/commands/catalysts.js +15 -84
  24. package/dist/commands/catalysts.js.map +1 -1
  25. package/dist/commands/coaching-cmd.d.ts.map +1 -1
  26. package/dist/commands/coaching-cmd.js +21 -10
  27. package/dist/commands/coaching-cmd.js.map +1 -1
  28. package/dist/commands/config-cmd.d.ts.map +1 -1
  29. package/dist/commands/config-cmd.js +39 -23
  30. package/dist/commands/config-cmd.js.map +1 -1
  31. package/dist/commands/context.d.ts +0 -7
  32. package/dist/commands/context.d.ts.map +1 -1
  33. package/dist/commands/context.js +51 -199
  34. package/dist/commands/context.js.map +1 -1
  35. package/dist/commands/decisions.d.ts +38 -2
  36. package/dist/commands/decisions.d.ts.map +1 -1
  37. package/dist/commands/decisions.js +35 -134
  38. package/dist/commands/decisions.js.map +1 -1
  39. package/dist/commands/edge-cmd.d.ts +55 -45
  40. package/dist/commands/edge-cmd.d.ts.map +1 -1
  41. package/dist/commands/edge-cmd.js +76 -32
  42. package/dist/commands/edge-cmd.js.map +1 -1
  43. package/dist/commands/edge-guard-cmd.d.ts.map +1 -1
  44. package/dist/commands/edge-guard-cmd.js +7 -3
  45. package/dist/commands/edge-guard-cmd.js.map +1 -1
  46. package/dist/commands/events.d.ts.map +1 -1
  47. package/dist/commands/events.js +78 -162
  48. package/dist/commands/events.js.map +1 -1
  49. package/dist/commands/infra.d.ts +2 -4
  50. package/dist/commands/infra.d.ts.map +1 -1
  51. package/dist/commands/infra.js +65 -163
  52. package/dist/commands/infra.js.map +1 -1
  53. package/dist/commands/journal.d.ts.map +1 -1
  54. package/dist/commands/journal.js +138 -597
  55. package/dist/commands/journal.js.map +1 -1
  56. package/dist/commands/login.js +3 -3
  57. package/dist/commands/login.js.map +1 -1
  58. package/dist/commands/narratives.d.ts.map +1 -1
  59. package/dist/commands/narratives.js +91 -311
  60. package/dist/commands/narratives.js.map +1 -1
  61. package/dist/commands/query.d.ts +0 -7
  62. package/dist/commands/query.d.ts.map +1 -1
  63. package/dist/commands/query.js +35 -157
  64. package/dist/commands/query.js.map +1 -1
  65. package/dist/commands/replay-cmd.d.ts.map +1 -1
  66. package/dist/commands/replay-cmd.js +6 -8
  67. package/dist/commands/replay-cmd.js.map +1 -1
  68. package/dist/commands/review.d.ts.map +1 -1
  69. package/dist/commands/review.js +45 -83
  70. package/dist/commands/review.js.map +1 -1
  71. package/dist/commands/risk.d.ts +29 -1
  72. package/dist/commands/risk.d.ts.map +1 -1
  73. package/dist/commands/risk.js +20 -89
  74. package/dist/commands/risk.js.map +1 -1
  75. package/dist/commands/social.d.ts +36 -2
  76. package/dist/commands/social.d.ts.map +1 -1
  77. package/dist/commands/social.js +74 -148
  78. package/dist/commands/social.js.map +1 -1
  79. package/dist/commands/strategy-cmd.d.ts.map +1 -1
  80. package/dist/commands/strategy-cmd.js +2 -4
  81. package/dist/commands/strategy-cmd.js.map +1 -1
  82. package/dist/commands/subscribe.js +4 -4
  83. package/dist/commands/subscribe.js.map +1 -1
  84. package/dist/commands/suggestions-cmd.d.ts +24 -0
  85. package/dist/commands/suggestions-cmd.d.ts.map +1 -0
  86. package/dist/commands/suggestions-cmd.js +142 -0
  87. package/dist/commands/suggestions-cmd.js.map +1 -0
  88. package/dist/commands/thesis-cmd.js +4 -4
  89. package/dist/commands/thesis-cmd.js.map +1 -1
  90. package/dist/commands/trader.d.ts +18 -1
  91. package/dist/commands/trader.d.ts.map +1 -1
  92. package/dist/commands/trader.js +363 -218
  93. package/dist/commands/trader.js.map +1 -1
  94. package/dist/commands/watch.d.ts +0 -10
  95. package/dist/commands/watch.d.ts.map +1 -1
  96. package/dist/commands/watch.js +5 -66
  97. package/dist/commands/watch.js.map +1 -1
  98. package/dist/commands/whoami.d.ts.map +1 -1
  99. package/dist/commands/whoami.js +18 -2
  100. package/dist/commands/whoami.js.map +1 -1
  101. package/dist/index.d.ts +4 -4
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +4 -4
  104. package/dist/index.js.map +1 -1
  105. package/dist/utils.d.ts +45 -0
  106. package/dist/utils.d.ts.map +1 -1
  107. package/dist/utils.js +97 -0
  108. package/dist/utils.js.map +1 -1
  109. package/package.json +15 -18
@@ -1,20 +1,11 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { Option } from 'commander';
1
3
  import chalk from 'chalk';
2
- import { getConfig, createLogger } from '@trading-boy/core';
3
- import { Neo4jClient } from '@trading-boy/graph-db';
4
- import { formatConnectionError } from '../utils.js';
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
- lines.push(` ${chalk.gray('Wallets:')} ${chalk.white(String(trader.walletAddresses.length))}`);
32
- if (trader.walletAddresses.length > 0) {
33
- for (const addr of trader.walletAddresses) {
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
- // ─── Remote API mode ───
99
- if (await isRemoteMode()) {
100
- try {
101
- const result = await apiRequest('/api/v1/traders', {
102
- method: 'POST',
103
- body: {
104
- name: options.name,
105
- wallet: options.wallet,
106
- maxDrawdown: options.maxDrawdown,
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
- const guidance = formatConnectionError(message);
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
- .action(async (nameOrId) => {
151
- // ─── Remote API mode ───
152
- if (await isRemoteMode()) {
153
- try {
154
- const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}`);
155
- const { wallets, ...trader } = result;
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
- catch (error) {
159
- if (error instanceof ApiError && error.status === 404) {
160
- console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
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
- const client = createNeo4jClient();
171
- try {
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
- const wallets = await getTraderWallets(client, result.id);
180
- console.log(formatTraderOutput(result, wallets));
181
- }
182
- catch (error) {
183
- const message = error instanceof Error ? error.message : String(error);
184
- logger.error({ error: message }, 'Failed to show trader');
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
- .action(async () => {
200
- // ─── Remote API mode ───
201
- if (await isRemoteMode()) {
202
- try {
203
- const result = await apiRequest('/api/v1/traders');
204
- console.log(formatTraderListOutput(result.traders));
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
- catch (error) {
207
- const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
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
- const guidance = formatConnectionError(message);
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 client.connect();
274
- const existing = await resolveTrader(client, nameOrId);
275
- if (!existing) {
276
- console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
277
- process.exitCode = 1;
278
- return;
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 (result) {
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
- const message = error instanceof Error ? error.message : String(error);
292
- logger.error({ error: message }, 'Failed to update trader');
293
- console.error(chalk.red(`Error: ${message}`));
294
- const guidance = formatConnectionError(message);
295
- if (guidance)
296
- console.error(guidance);
297
- process.exitCode = 1;
298
- }
299
- finally {
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
- console.log('');
332
- console.log(chalk.green(' Email preferences updated.'));
333
- console.log(` ${chalk.gray('Email:')} ${chalk.white(result.email)}`);
334
- console.log(` ${chalk.gray('Summary time:')} ${chalk.white(String(result.summaryHourUtc).padStart(2, '0') + ':00 UTC')}`);
335
- console.log('');
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
- // ─── Remote API mode ───
352
- if (await isRemoteMode()) {
353
- try {
354
- await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/wallets`, {
355
- method: 'POST',
356
- body: {
357
- address: walletAddress,
358
- chain: options.chain,
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
- catch (error) {
366
- if (error instanceof ApiError && error.status === 404) {
367
- console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
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
- const message = error instanceof Error ? error.message : String(error);
371
- console.error(chalk.red(`Error: ${message}`));
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
- return;
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
- const client = createNeo4jClient();
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
- await client.connect();
380
- const existing = await resolveTrader(client, nameOrId);
381
- if (!existing) {
382
- console.error(chalk.yellow(`Trader not found: "${nameOrId}"`));
383
- process.exitCode = 1;
384
- return;
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
- const message = error instanceof Error ? error.message : String(error);
393
- logger.error({ error: message }, 'Failed to link wallet');
394
- console.error(chalk.red(`Error: ${message}`));
395
- const guidance = formatConnectionError(message);
396
- if (guidance)
397
- console.error(guidance);
398
- process.exitCode = 1;
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
- finally {
401
- await client.disconnect();
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
  }