@trading-boy/cli 1.6.1 → 1.7.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.js +7 -2
- package/dist/cli.bundle.js +259 -344
- package/dist/commands/agent-cmd.js +42 -54
- package/dist/commands/benchmark-cmd.js +4 -9
- package/dist/commands/billing.js +21 -1
- package/dist/commands/coaching-cmd.js +6 -15
- package/dist/commands/context.js +3 -7
- package/dist/commands/cron-cmd.js +10 -46
- package/dist/commands/edge-cmd.js +3 -5
- package/dist/commands/edge-guard-cmd.js +4 -9
- package/dist/commands/query.js +3 -7
- package/dist/commands/replay-cmd.js +5 -36
- package/dist/commands/risk.js +3 -7
- package/dist/commands/strategy-cmd.js +14 -51
- package/dist/commands/subscribe.js +2 -2
- package/dist/commands/suggestions-cmd.js +5 -17
- package/dist/commands/thesis-cmd.js +3 -6
- package/dist/commands/whoami.js +3 -1
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +51 -0
- package/package.json +1 -1
|
@@ -12,45 +12,9 @@ import { resolve } from 'node:path';
|
|
|
12
12
|
import { Option } from 'commander';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { createLogger } from '@trading-boy/core';
|
|
15
|
-
import {
|
|
16
|
-
import { padRight } from '../utils.js';
|
|
15
|
+
import { apiRequest } from '../api-client.js';
|
|
16
|
+
import { padRight, handleApiError, ensureRemote } from '../utils.js';
|
|
17
17
|
const logger = createLogger('cli-agent');
|
|
18
|
-
// ─── Error Handler ───
|
|
19
|
-
function handleApiError(error, context) {
|
|
20
|
-
if (error instanceof ApiError) {
|
|
21
|
-
switch (error.status) {
|
|
22
|
-
case 401:
|
|
23
|
-
console.error(chalk.red('Error: API key invalid or expired. Run `trading-boy login` to re-authenticate.'));
|
|
24
|
-
break;
|
|
25
|
-
case 403:
|
|
26
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
27
|
-
break;
|
|
28
|
-
case 404:
|
|
29
|
-
console.error(chalk.red(`Error: Not found — ${error.message}`));
|
|
30
|
-
break;
|
|
31
|
-
case 429:
|
|
32
|
-
console.error(chalk.red(`Error: Limit reached — ${error.message}`));
|
|
33
|
-
break;
|
|
34
|
-
default:
|
|
35
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
-
logger.error({ error: message }, context);
|
|
41
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
42
|
-
}
|
|
43
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
44
|
-
}
|
|
45
|
-
async function ensureRemote() {
|
|
46
|
-
if (!(await isRemoteMode())) {
|
|
47
|
-
console.error(chalk.yellow('Agent commands require a remote API connection.'));
|
|
48
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
49
|
-
process.exitCode = 1;
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
18
|
// ─── Formatters ───
|
|
55
19
|
function formatShortDate(isoString) {
|
|
56
20
|
if (!isoString)
|
|
@@ -241,7 +205,7 @@ export function registerAgentCommand(program) {
|
|
|
241
205
|
}
|
|
242
206
|
}
|
|
243
207
|
catch (error) {
|
|
244
|
-
handleApiError(error, 'Agent create failed');
|
|
208
|
+
handleApiError(error, 'Agent create failed', logger);
|
|
245
209
|
}
|
|
246
210
|
});
|
|
247
211
|
// ── list ────────────────────────────────────────────────────────────────────
|
|
@@ -295,7 +259,7 @@ export function registerAgentCommand(program) {
|
|
|
295
259
|
console.log('');
|
|
296
260
|
}
|
|
297
261
|
catch (error) {
|
|
298
|
-
handleApiError(error, 'Agent list failed');
|
|
262
|
+
handleApiError(error, 'Agent list failed', logger);
|
|
299
263
|
}
|
|
300
264
|
});
|
|
301
265
|
// ── show ────────────────────────────────────────────────────────────────────
|
|
@@ -375,58 +339,76 @@ export function registerAgentCommand(program) {
|
|
|
375
339
|
console.log('');
|
|
376
340
|
}
|
|
377
341
|
catch (error) {
|
|
378
|
-
handleApiError(error, 'Agent show failed');
|
|
342
|
+
handleApiError(error, 'Agent show failed', logger);
|
|
379
343
|
}
|
|
380
344
|
});
|
|
381
345
|
// ── pause ───────────────────────────────────────────────────────────────────
|
|
382
346
|
agent
|
|
383
347
|
.command('pause <agentId>')
|
|
384
348
|
.description('Pause an agent')
|
|
385
|
-
.
|
|
349
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
350
|
+
.action(async (agentId, options) => {
|
|
386
351
|
if (!(await ensureRemote()))
|
|
387
352
|
return;
|
|
388
353
|
try {
|
|
389
354
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, {
|
|
390
355
|
method: 'POST',
|
|
391
356
|
});
|
|
392
|
-
|
|
357
|
+
if (options.format === 'json') {
|
|
358
|
+
console.log(JSON.stringify({ agentId, status: 'paused' }, null, 2));
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
console.log(chalk.green(` Agent ${agentId} paused`));
|
|
362
|
+
}
|
|
393
363
|
}
|
|
394
364
|
catch (error) {
|
|
395
|
-
handleApiError(error, 'Agent pause failed');
|
|
365
|
+
handleApiError(error, 'Agent pause failed', logger);
|
|
396
366
|
}
|
|
397
367
|
});
|
|
398
368
|
// ── resume ──────────────────────────────────────────────────────────────────
|
|
399
369
|
agent
|
|
400
370
|
.command('resume <agentId>')
|
|
401
371
|
.description('Resume a paused agent')
|
|
402
|
-
.
|
|
372
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
373
|
+
.action(async (agentId, options) => {
|
|
403
374
|
if (!(await ensureRemote()))
|
|
404
375
|
return;
|
|
405
376
|
try {
|
|
406
377
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, {
|
|
407
378
|
method: 'POST',
|
|
408
379
|
});
|
|
409
|
-
|
|
380
|
+
if (options.format === 'json') {
|
|
381
|
+
console.log(JSON.stringify({ agentId, status: 'active' }, null, 2));
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
console.log(chalk.green(` Agent ${agentId} resumed`));
|
|
385
|
+
}
|
|
410
386
|
}
|
|
411
387
|
catch (error) {
|
|
412
|
-
handleApiError(error, 'Agent resume failed');
|
|
388
|
+
handleApiError(error, 'Agent resume failed', logger);
|
|
413
389
|
}
|
|
414
390
|
});
|
|
415
391
|
// ── delete ──────────────────────────────────────────────────────────────────
|
|
416
392
|
agent
|
|
417
393
|
.command('delete <agentId>')
|
|
418
394
|
.description('Delete an agent')
|
|
419
|
-
.
|
|
395
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
396
|
+
.action(async (agentId, options) => {
|
|
420
397
|
if (!(await ensureRemote()))
|
|
421
398
|
return;
|
|
422
399
|
try {
|
|
423
400
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
424
401
|
method: 'DELETE',
|
|
425
402
|
});
|
|
426
|
-
|
|
403
|
+
if (options.format === 'json') {
|
|
404
|
+
console.log(JSON.stringify({ agentId, status: 'deleted' }, null, 2));
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
console.log(chalk.green(` Agent ${agentId} deleted`));
|
|
408
|
+
}
|
|
427
409
|
}
|
|
428
410
|
catch (error) {
|
|
429
|
-
handleApiError(error, 'Agent delete failed');
|
|
411
|
+
handleApiError(error, 'Agent delete failed', logger);
|
|
430
412
|
}
|
|
431
413
|
});
|
|
432
414
|
// ── exit ───────────────────────────────────────────────────────────────────
|
|
@@ -464,7 +446,7 @@ export function registerAgentCommand(program) {
|
|
|
464
446
|
}
|
|
465
447
|
}
|
|
466
448
|
catch (error) {
|
|
467
|
-
handleApiError(error, 'Position exit failed');
|
|
449
|
+
handleApiError(error, 'Position exit failed', logger);
|
|
468
450
|
}
|
|
469
451
|
});
|
|
470
452
|
// ── update ──────────────────────────────────────────────────────────────────
|
|
@@ -488,6 +470,7 @@ export function registerAgentCommand(program) {
|
|
|
488
470
|
.option('--purpose-override <text>', 'Custom purpose/mission for this agent')
|
|
489
471
|
.option('--soul-file <path>', 'Load soul from a file')
|
|
490
472
|
.option('--purpose-file <path>', 'Load purpose from a file')
|
|
473
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
491
474
|
.action(async (agentId, options) => {
|
|
492
475
|
if (!(await ensureRemote()))
|
|
493
476
|
return;
|
|
@@ -564,14 +547,19 @@ export function registerAgentCommand(program) {
|
|
|
564
547
|
return;
|
|
565
548
|
}
|
|
566
549
|
try {
|
|
567
|
-
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
550
|
+
const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
568
551
|
method: 'PATCH',
|
|
569
552
|
body,
|
|
570
553
|
});
|
|
571
|
-
|
|
554
|
+
if (options.format === 'json') {
|
|
555
|
+
console.log(JSON.stringify(result, null, 2));
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
console.log(chalk.green(` Agent ${agentId} updated`));
|
|
559
|
+
}
|
|
572
560
|
}
|
|
573
561
|
catch (error) {
|
|
574
|
-
handleApiError(error, 'Agent update failed');
|
|
562
|
+
handleApiError(error, 'Agent update failed', logger);
|
|
575
563
|
}
|
|
576
564
|
});
|
|
577
565
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// trading-boy benchmark recompute # Trigger recompute
|
|
9
9
|
import { Option } from 'commander';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
-
import {
|
|
11
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
12
|
+
import { ensureRemote } from '../utils.js';
|
|
12
13
|
// ─── Formatters ───
|
|
13
14
|
function formatMetric(value, decimals = 2) {
|
|
14
15
|
if (value === null)
|
|
@@ -118,11 +119,8 @@ export function registerBenchmarkCommand(program) {
|
|
|
118
119
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
119
120
|
.action(async (options) => {
|
|
120
121
|
try {
|
|
121
|
-
if (!(await
|
|
122
|
-
console.error(chalk.red('Error: benchmark command requires remote mode. Run `trading-boy login` first.'));
|
|
123
|
-
process.exitCode = 1;
|
|
122
|
+
if (!(await ensureRemote()))
|
|
124
123
|
return;
|
|
125
|
-
}
|
|
126
124
|
if (options.entity) {
|
|
127
125
|
// Single entity
|
|
128
126
|
const query = new URLSearchParams();
|
|
@@ -175,11 +173,8 @@ export function registerBenchmarkCommand(program) {
|
|
|
175
173
|
.description('Trigger on-demand benchmark recompute')
|
|
176
174
|
.action(async () => {
|
|
177
175
|
try {
|
|
178
|
-
if (!(await
|
|
179
|
-
console.error(chalk.red('Error: benchmark recompute requires remote mode. Run `trading-boy login` first.'));
|
|
180
|
-
process.exitCode = 1;
|
|
176
|
+
if (!(await ensureRemote()))
|
|
181
177
|
return;
|
|
182
|
-
}
|
|
183
178
|
console.log(chalk.dim('Recomputing benchmarks...'));
|
|
184
179
|
const data = await apiRequest('/api/v1/benchmark/recompute', { method: 'POST', body: {} });
|
|
185
180
|
console.log(chalk.green(`\n ${data.message}`));
|
package/dist/commands/billing.js
CHANGED
|
@@ -4,6 +4,11 @@ import { createLogger } from '@trading-boy/core';
|
|
|
4
4
|
import { apiRequest, ApiError } from '../api-client.js';
|
|
5
5
|
import { padRight } from '../utils.js';
|
|
6
6
|
const logger = createLogger('cli-billing');
|
|
7
|
+
// ─── Security ───
|
|
8
|
+
const ALLOWED_PORTAL_DOMAINS = new Set([
|
|
9
|
+
'billing.stripe.com',
|
|
10
|
+
'checkout.stripe.com',
|
|
11
|
+
]);
|
|
7
12
|
// ─── Formatters ───
|
|
8
13
|
function formatStatusLabel(status) {
|
|
9
14
|
switch (status) {
|
|
@@ -75,11 +80,14 @@ export function registerBillingCommand(program) {
|
|
|
75
80
|
method: 'POST',
|
|
76
81
|
});
|
|
77
82
|
spinner.succeed(' Opening billing portal...');
|
|
78
|
-
// Validate URL before opening browser (
|
|
83
|
+
// Validate URL before opening browser (prevent phishing via MITM)
|
|
79
84
|
const portalUrl = new URL(result.url);
|
|
80
85
|
if (portalUrl.protocol !== 'https:') {
|
|
81
86
|
throw new Error(`Refusing to open non-HTTPS portal URL: ${result.url}`);
|
|
82
87
|
}
|
|
88
|
+
if (!ALLOWED_PORTAL_DOMAINS.has(portalUrl.hostname)) {
|
|
89
|
+
throw new Error(`Refusing to open URL with untrusted domain: ${portalUrl.hostname}`);
|
|
90
|
+
}
|
|
83
91
|
const { default: open } = await import('open');
|
|
84
92
|
await open(result.url);
|
|
85
93
|
console.log('');
|
|
@@ -112,6 +120,18 @@ export function registerBillingCommand(program) {
|
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
catch (error) {
|
|
123
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
124
|
+
if (options.format === 'json') {
|
|
125
|
+
console.log(JSON.stringify({ plan: 'starter', status: 'free' }, null, 2));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(chalk.dim(' You\'re on the free Starter plan.'));
|
|
130
|
+
console.log(chalk.dim(' Run: trading-boy subscribe to upgrade.'));
|
|
131
|
+
console.log('');
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
115
135
|
const message = error instanceof Error ? error.message : String(error);
|
|
116
136
|
logger.error({ error: message }, 'Billing status failed');
|
|
117
137
|
console.error(chalk.red(` Error: ${message}`));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import {
|
|
4
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
5
|
+
import { ensureRemote } from '../utils.js';
|
|
5
6
|
// ─── Logger ───
|
|
6
7
|
const logger = createLogger('cli-coaching');
|
|
7
8
|
// ─── BYOK Error Hint ───
|
|
@@ -20,16 +21,6 @@ function isByokError(error) {
|
|
|
20
21
|
}
|
|
21
22
|
return false;
|
|
22
23
|
}
|
|
23
|
-
// ─── Remote Mode Guard ───
|
|
24
|
-
async function requireRemote() {
|
|
25
|
-
if (!(await isRemoteMode())) {
|
|
26
|
-
console.error(chalk.yellow('Coaching requires a remote API connection.'));
|
|
27
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
24
|
// ─── Formatters ───
|
|
34
25
|
export function formatIntervention(response) {
|
|
35
26
|
const lines = [];
|
|
@@ -80,7 +71,7 @@ export function registerCoachingCommand(program) {
|
|
|
80
71
|
.option('--direction <dir>', 'Trade direction (LONG or SHORT)')
|
|
81
72
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
82
73
|
.action(async (traderId, options) => {
|
|
83
|
-
if (!(await
|
|
74
|
+
if (!(await ensureRemote()))
|
|
84
75
|
return;
|
|
85
76
|
// Build extraction object from flags
|
|
86
77
|
const extraction = {
|
|
@@ -143,7 +134,7 @@ export function registerCoachingCommand(program) {
|
|
|
143
134
|
.option('--flags <flags>', 'Active behavioral flags (comma-separated)')
|
|
144
135
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
145
136
|
.action(async (traderId, options) => {
|
|
146
|
-
if (!(await
|
|
137
|
+
if (!(await ensureRemote()))
|
|
147
138
|
return;
|
|
148
139
|
const body = {
|
|
149
140
|
tiltScore: options.tiltScore ?? 0,
|
|
@@ -191,7 +182,7 @@ export function registerCoachingCommand(program) {
|
|
|
191
182
|
.description('Acknowledge a coaching intervention')
|
|
192
183
|
.requiredOption('--reason <reason>', 'Reason for acknowledgment')
|
|
193
184
|
.action(async (traderId, options) => {
|
|
194
|
-
if (!(await
|
|
185
|
+
if (!(await ensureRemote()))
|
|
195
186
|
return;
|
|
196
187
|
try {
|
|
197
188
|
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/coaching/acknowledge`, { method: 'POST', body: { reason: options.reason } });
|
|
@@ -212,7 +203,7 @@ export function registerCoachingCommand(program) {
|
|
|
212
203
|
.command('reset <traderId>')
|
|
213
204
|
.description('Reset coaching session state')
|
|
214
205
|
.action(async (traderId) => {
|
|
215
|
-
if (!(await
|
|
206
|
+
if (!(await ensureRemote()))
|
|
216
207
|
return;
|
|
217
208
|
try {
|
|
218
209
|
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/coaching/reset-session`, { method: 'POST', body: {} });
|
package/dist/commands/context.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import { formatConnectionError, colorChange, colorSentiment, colorRiskScore, formatUsd } from '../utils.js';
|
|
5
|
-
import {
|
|
4
|
+
import { formatConnectionError, colorChange, colorSentiment, colorRiskScore, formatUsd, ensureRemote } from '../utils.js';
|
|
5
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
6
6
|
// ─── Logger ───
|
|
7
7
|
const logger = createLogger('cli-context');
|
|
8
8
|
// ─── Formatters ───
|
|
@@ -325,12 +325,8 @@ export function registerContextCommand(program) {
|
|
|
325
325
|
.action(async (symbol, options) => {
|
|
326
326
|
try {
|
|
327
327
|
// ─── Auth pre-flight ───
|
|
328
|
-
if (!(await
|
|
329
|
-
console.error(chalk.yellow('This command requires a remote API connection.'));
|
|
330
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
331
|
-
process.exitCode = 1;
|
|
328
|
+
if (!(await ensureRemote()))
|
|
332
329
|
return;
|
|
333
|
-
}
|
|
334
330
|
// ─── Validate range options ───
|
|
335
331
|
if ((options.from && !options.to) || (!options.from && options.to)) {
|
|
336
332
|
console.error(chalk.red('Error: Both --from and --to must be provided for range queries.'));
|
|
@@ -11,45 +11,9 @@
|
|
|
11
11
|
import { Option } from 'commander';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { createLogger } from '@trading-boy/core';
|
|
14
|
-
import {
|
|
15
|
-
import { padRight } from '../utils.js';
|
|
14
|
+
import { apiRequest } from '../api-client.js';
|
|
15
|
+
import { padRight, handleApiError, ensureRemote } from '../utils.js';
|
|
16
16
|
const logger = createLogger('cli-cron');
|
|
17
|
-
// ─── Error Handler ───
|
|
18
|
-
function handleApiError(error, context) {
|
|
19
|
-
if (error instanceof ApiError) {
|
|
20
|
-
switch (error.status) {
|
|
21
|
-
case 401:
|
|
22
|
-
console.error(chalk.red('Error: API key invalid or expired. Run `trading-boy login` to re-authenticate.'));
|
|
23
|
-
break;
|
|
24
|
-
case 403:
|
|
25
|
-
console.error(chalk.red('Error: Subscription inactive. Run `trading-boy billing manage` to update your billing.'));
|
|
26
|
-
break;
|
|
27
|
-
case 404:
|
|
28
|
-
console.error(chalk.red(`Error: Not found — ${error.message}`));
|
|
29
|
-
break;
|
|
30
|
-
case 429:
|
|
31
|
-
console.error(chalk.red(`Error: Limit reached — ${error.message}`));
|
|
32
|
-
break;
|
|
33
|
-
default:
|
|
34
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
-
logger.error({ error: message }, context);
|
|
40
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
41
|
-
}
|
|
42
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
43
|
-
}
|
|
44
|
-
async function ensureRemote() {
|
|
45
|
-
if (!(await isRemoteMode())) {
|
|
46
|
-
console.error(chalk.yellow('Cron commands require a remote API connection.'));
|
|
47
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
48
|
-
process.exitCode = 1;
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
17
|
// ─── Formatters ───
|
|
54
18
|
function formatShortDate(isoString) {
|
|
55
19
|
if (!isoString)
|
|
@@ -137,7 +101,7 @@ export function registerCronCommand(program) {
|
|
|
137
101
|
}
|
|
138
102
|
}
|
|
139
103
|
catch (error) {
|
|
140
|
-
handleApiError(error, 'Cron create failed');
|
|
104
|
+
handleApiError(error, 'Cron create failed', logger);
|
|
141
105
|
}
|
|
142
106
|
});
|
|
143
107
|
// ── list ────────────────────────────────────────────────────────────────────
|
|
@@ -183,7 +147,7 @@ export function registerCronCommand(program) {
|
|
|
183
147
|
console.log('');
|
|
184
148
|
}
|
|
185
149
|
catch (error) {
|
|
186
|
-
handleApiError(error, 'Cron list failed');
|
|
150
|
+
handleApiError(error, 'Cron list failed', logger);
|
|
187
151
|
}
|
|
188
152
|
});
|
|
189
153
|
// ── show ────────────────────────────────────────────────────────────────────
|
|
@@ -217,7 +181,7 @@ export function registerCronCommand(program) {
|
|
|
217
181
|
console.log('');
|
|
218
182
|
}
|
|
219
183
|
catch (error) {
|
|
220
|
-
handleApiError(error, 'Cron show failed');
|
|
184
|
+
handleApiError(error, 'Cron show failed', logger);
|
|
221
185
|
}
|
|
222
186
|
});
|
|
223
187
|
// ── pause ───────────────────────────────────────────────────────────────────
|
|
@@ -235,7 +199,7 @@ export function registerCronCommand(program) {
|
|
|
235
199
|
console.log(chalk.green(` Job ${jobId} paused`));
|
|
236
200
|
}
|
|
237
201
|
catch (error) {
|
|
238
|
-
handleApiError(error, 'Cron pause failed');
|
|
202
|
+
handleApiError(error, 'Cron pause failed', logger);
|
|
239
203
|
}
|
|
240
204
|
});
|
|
241
205
|
// ── resume ──────────────────────────────────────────────────────────────────
|
|
@@ -253,7 +217,7 @@ export function registerCronCommand(program) {
|
|
|
253
217
|
console.log(chalk.green(` Job ${jobId} resumed`));
|
|
254
218
|
}
|
|
255
219
|
catch (error) {
|
|
256
|
-
handleApiError(error, 'Cron resume failed');
|
|
220
|
+
handleApiError(error, 'Cron resume failed', logger);
|
|
257
221
|
}
|
|
258
222
|
});
|
|
259
223
|
// ── delete ──────────────────────────────────────────────────────────────────
|
|
@@ -270,7 +234,7 @@ export function registerCronCommand(program) {
|
|
|
270
234
|
console.log(chalk.green(` Job ${jobId} deleted`));
|
|
271
235
|
}
|
|
272
236
|
catch (error) {
|
|
273
|
-
handleApiError(error, 'Cron delete failed');
|
|
237
|
+
handleApiError(error, 'Cron delete failed', logger);
|
|
274
238
|
}
|
|
275
239
|
});
|
|
276
240
|
// ── run ─────────────────────────────────────────────────────────────────────
|
|
@@ -287,7 +251,7 @@ export function registerCronCommand(program) {
|
|
|
287
251
|
console.log(chalk.green(` Job ${jobId} execution triggered`));
|
|
288
252
|
}
|
|
289
253
|
catch (error) {
|
|
290
|
-
handleApiError(error, 'Cron run failed');
|
|
254
|
+
handleApiError(error, 'Cron run failed', logger);
|
|
291
255
|
}
|
|
292
256
|
});
|
|
293
257
|
// ── history ─────────────────────────────────────────────────────────────────
|
|
@@ -334,7 +298,7 @@ export function registerCronCommand(program) {
|
|
|
334
298
|
console.log('');
|
|
335
299
|
}
|
|
336
300
|
catch (error) {
|
|
337
|
-
handleApiError(error, 'Cron history failed');
|
|
301
|
+
handleApiError(error, 'Cron history failed', logger);
|
|
338
302
|
}
|
|
339
303
|
});
|
|
340
304
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
4
|
+
import { ensureRemote } from '../utils.js';
|
|
4
5
|
// ─── Formatters ───
|
|
5
6
|
export function formatEdgeOutput(data) {
|
|
6
7
|
const lines = [];
|
|
@@ -145,11 +146,8 @@ export function registerEdgeCommand(program) {
|
|
|
145
146
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
146
147
|
.action(async (traderId, options) => {
|
|
147
148
|
try {
|
|
148
|
-
if (!(await
|
|
149
|
-
console.error(chalk.red('Error: edge command requires remote mode. Run `trading-boy login` first.'));
|
|
150
|
-
process.exitCode = 1;
|
|
149
|
+
if (!(await ensureRemote()))
|
|
151
150
|
return;
|
|
152
|
-
}
|
|
153
151
|
const query = new URLSearchParams();
|
|
154
152
|
if (options.token)
|
|
155
153
|
query.set('tokenSymbol', options.token.toUpperCase());
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
4
|
+
import { ensureRemote } from '../utils.js';
|
|
4
5
|
// ─── Formatters ───
|
|
5
6
|
export function formatAssessmentOutput(data) {
|
|
6
7
|
const lines = [];
|
|
@@ -114,11 +115,8 @@ export function registerEdgeGuardCommand(program) {
|
|
|
114
115
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
115
116
|
.action(async (traderId, options) => {
|
|
116
117
|
try {
|
|
117
|
-
if (!(await
|
|
118
|
-
console.error(chalk.red('Error: edge-guard command requires remote mode. Run `trading-boy login` first.'));
|
|
119
|
-
process.exitCode = 1;
|
|
118
|
+
if (!(await ensureRemote()))
|
|
120
119
|
return;
|
|
121
|
-
}
|
|
122
120
|
if (options.friction) {
|
|
123
121
|
const data = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/edge-guard/friction`);
|
|
124
122
|
if (options.format === 'json') {
|
|
@@ -151,11 +149,8 @@ export function registerEdgeGuardCommand(program) {
|
|
|
151
149
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
152
150
|
.action(async (traderId, ackOptions) => {
|
|
153
151
|
try {
|
|
154
|
-
if (!(await
|
|
155
|
-
console.error(chalk.red('Error: edge-guard command requires remote mode. Run `trading-boy login` first.'));
|
|
156
|
-
process.exitCode = 1;
|
|
152
|
+
if (!(await ensureRemote()))
|
|
157
153
|
return;
|
|
158
|
-
}
|
|
159
154
|
const data = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/edge-guard/acknowledge`, { method: 'POST' });
|
|
160
155
|
if (ackOptions.format === 'json') {
|
|
161
156
|
console.log(JSON.stringify(data, null, 2));
|
package/dist/commands/query.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import { formatConnectionError } from '../utils.js';
|
|
5
|
-
import {
|
|
4
|
+
import { formatConnectionError, ensureRemote } from '../utils.js';
|
|
5
|
+
import { apiRequest, ApiError } from '../api-client.js';
|
|
6
6
|
// ─── Logger ───
|
|
7
7
|
const logger = createLogger('cli-query');
|
|
8
8
|
// ─── Formatter ───
|
|
@@ -103,12 +103,8 @@ export function registerQueryCommand(program) {
|
|
|
103
103
|
.action(async (symbol, options) => {
|
|
104
104
|
try {
|
|
105
105
|
// ─── Auth pre-flight ───
|
|
106
|
-
if (!(await
|
|
107
|
-
console.error(chalk.yellow('This command requires a remote API connection.'));
|
|
108
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
109
|
-
process.exitCode = 1;
|
|
106
|
+
if (!(await ensureRemote()))
|
|
110
107
|
return;
|
|
111
|
-
}
|
|
112
108
|
const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol.toUpperCase())}/context`);
|
|
113
109
|
const result = contextToQueryResult(pkg);
|
|
114
110
|
// Check if the token was actually found
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Option } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import {
|
|
5
|
-
import { padRight } from '../utils.js';
|
|
4
|
+
import { apiRequest } from '../api-client.js';
|
|
5
|
+
import { padRight, handleApiError, ensureRemote } from '../utils.js';
|
|
6
6
|
// ─── Logger ───
|
|
7
7
|
const logger = createLogger('cli-replay');
|
|
8
8
|
// ─── Helpers ───
|
|
@@ -96,33 +96,6 @@ async function pollReplayJob(jobId) {
|
|
|
96
96
|
}
|
|
97
97
|
throw new Error(`Replay job ${jobId} did not complete within ${POLL_TIMEOUT_MS / 1000}s timeout.`);
|
|
98
98
|
}
|
|
99
|
-
// ─── Error Handler ───
|
|
100
|
-
function handleApiError(error, context) {
|
|
101
|
-
if (error instanceof ApiError) {
|
|
102
|
-
switch (error.status) {
|
|
103
|
-
case 401:
|
|
104
|
-
console.error(chalk.red('Error: API key invalid or expired. Run `trading-boy login` to re-authenticate.'));
|
|
105
|
-
break;
|
|
106
|
-
case 403:
|
|
107
|
-
console.error(chalk.red('Error: Subscription inactive. Run `trading-boy billing manage` to update your billing.'));
|
|
108
|
-
break;
|
|
109
|
-
case 404:
|
|
110
|
-
console.error(chalk.red(`Error: Not found — ${error.message}`));
|
|
111
|
-
break;
|
|
112
|
-
case 422:
|
|
113
|
-
console.error(chalk.red(`Error: Validation failed — ${error.message}`));
|
|
114
|
-
break;
|
|
115
|
-
default:
|
|
116
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
-
logger.error({ error: message }, context);
|
|
122
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
123
|
-
}
|
|
124
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
125
|
-
}
|
|
126
99
|
// ─── Command Registration ───
|
|
127
100
|
export function registerReplayCommand(program) {
|
|
128
101
|
program
|
|
@@ -134,12 +107,8 @@ export function registerReplayCommand(program) {
|
|
|
134
107
|
.requiredOption('--to <date>', 'End date (ISO-8601, e.g. 2025-03-01)')
|
|
135
108
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
136
109
|
.action(async (options) => {
|
|
137
|
-
if (!(await
|
|
138
|
-
console.error(chalk.yellow('Replay requires a remote API connection.'));
|
|
139
|
-
console.error(chalk.dim(' Run: trading-boy login'));
|
|
140
|
-
process.exitCode = 1;
|
|
110
|
+
if (!(await ensureRemote()))
|
|
141
111
|
return;
|
|
142
|
-
}
|
|
143
112
|
// Validate dates
|
|
144
113
|
const fromDate = new Date(options.from);
|
|
145
114
|
const toDate = new Date(options.to);
|
|
@@ -178,7 +147,7 @@ export function registerReplayCommand(program) {
|
|
|
178
147
|
}
|
|
179
148
|
catch (error) {
|
|
180
149
|
submitSpinner.fail('Failed to submit replay job');
|
|
181
|
-
handleApiError(error, 'Replay submit failed');
|
|
150
|
+
handleApiError(error, 'Replay submit failed', logger);
|
|
182
151
|
return;
|
|
183
152
|
}
|
|
184
153
|
// ─── Poll until complete ───
|
|
@@ -190,7 +159,7 @@ export function registerReplayCommand(program) {
|
|
|
190
159
|
}
|
|
191
160
|
catch (error) {
|
|
192
161
|
pollSpinner.fail('Replay timed out or encountered an error');
|
|
193
|
-
handleApiError(error, 'Replay polling failed');
|
|
162
|
+
handleApiError(error, 'Replay polling failed', logger);
|
|
194
163
|
return;
|
|
195
164
|
}
|
|
196
165
|
// ─── Display results ───
|