@trading-boy/cli 1.6.1 → 1.8.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 +264 -345
- package/dist/commands/agent-cmd.js +48 -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)
|
|
@@ -133,6 +97,7 @@ export function registerAgentCommand(program) {
|
|
|
133
97
|
.option('--purpose-override <text>', 'Custom purpose/mission for this agent')
|
|
134
98
|
.option('--soul-file <path>', 'Load soul from a file')
|
|
135
99
|
.option('--purpose-file <path>', 'Load purpose from a file')
|
|
100
|
+
.option('--exit-reasoner', 'Enable LLM-powered exit reasoning for this agent')
|
|
136
101
|
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
137
102
|
.action(async (options) => {
|
|
138
103
|
if (!(await ensureRemote()))
|
|
@@ -193,6 +158,8 @@ export function registerAgentCommand(program) {
|
|
|
193
158
|
body.decideModel = options.decideModel;
|
|
194
159
|
if (options.assetClass)
|
|
195
160
|
body.assetClass = options.assetClass;
|
|
161
|
+
if (options.exitReasoner)
|
|
162
|
+
body.exitReasoner = true;
|
|
196
163
|
// Soul override — file takes precedence over inline text
|
|
197
164
|
if (options.soulFile) {
|
|
198
165
|
const path = resolve(options.soulFile);
|
|
@@ -241,7 +208,7 @@ export function registerAgentCommand(program) {
|
|
|
241
208
|
}
|
|
242
209
|
}
|
|
243
210
|
catch (error) {
|
|
244
|
-
handleApiError(error, 'Agent create failed');
|
|
211
|
+
handleApiError(error, 'Agent create failed', logger);
|
|
245
212
|
}
|
|
246
213
|
});
|
|
247
214
|
// ── list ────────────────────────────────────────────────────────────────────
|
|
@@ -295,7 +262,7 @@ export function registerAgentCommand(program) {
|
|
|
295
262
|
console.log('');
|
|
296
263
|
}
|
|
297
264
|
catch (error) {
|
|
298
|
-
handleApiError(error, 'Agent list failed');
|
|
265
|
+
handleApiError(error, 'Agent list failed', logger);
|
|
299
266
|
}
|
|
300
267
|
});
|
|
301
268
|
// ── show ────────────────────────────────────────────────────────────────────
|
|
@@ -375,58 +342,76 @@ export function registerAgentCommand(program) {
|
|
|
375
342
|
console.log('');
|
|
376
343
|
}
|
|
377
344
|
catch (error) {
|
|
378
|
-
handleApiError(error, 'Agent show failed');
|
|
345
|
+
handleApiError(error, 'Agent show failed', logger);
|
|
379
346
|
}
|
|
380
347
|
});
|
|
381
348
|
// ── pause ───────────────────────────────────────────────────────────────────
|
|
382
349
|
agent
|
|
383
350
|
.command('pause <agentId>')
|
|
384
351
|
.description('Pause an agent')
|
|
385
|
-
.
|
|
352
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
353
|
+
.action(async (agentId, options) => {
|
|
386
354
|
if (!(await ensureRemote()))
|
|
387
355
|
return;
|
|
388
356
|
try {
|
|
389
357
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, {
|
|
390
358
|
method: 'POST',
|
|
391
359
|
});
|
|
392
|
-
|
|
360
|
+
if (options.format === 'json') {
|
|
361
|
+
console.log(JSON.stringify({ agentId, status: 'paused' }, null, 2));
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
console.log(chalk.green(` Agent ${agentId} paused`));
|
|
365
|
+
}
|
|
393
366
|
}
|
|
394
367
|
catch (error) {
|
|
395
|
-
handleApiError(error, 'Agent pause failed');
|
|
368
|
+
handleApiError(error, 'Agent pause failed', logger);
|
|
396
369
|
}
|
|
397
370
|
});
|
|
398
371
|
// ── resume ──────────────────────────────────────────────────────────────────
|
|
399
372
|
agent
|
|
400
373
|
.command('resume <agentId>')
|
|
401
374
|
.description('Resume a paused agent')
|
|
402
|
-
.
|
|
375
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
376
|
+
.action(async (agentId, options) => {
|
|
403
377
|
if (!(await ensureRemote()))
|
|
404
378
|
return;
|
|
405
379
|
try {
|
|
406
380
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, {
|
|
407
381
|
method: 'POST',
|
|
408
382
|
});
|
|
409
|
-
|
|
383
|
+
if (options.format === 'json') {
|
|
384
|
+
console.log(JSON.stringify({ agentId, status: 'active' }, null, 2));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
console.log(chalk.green(` Agent ${agentId} resumed`));
|
|
388
|
+
}
|
|
410
389
|
}
|
|
411
390
|
catch (error) {
|
|
412
|
-
handleApiError(error, 'Agent resume failed');
|
|
391
|
+
handleApiError(error, 'Agent resume failed', logger);
|
|
413
392
|
}
|
|
414
393
|
});
|
|
415
394
|
// ── delete ──────────────────────────────────────────────────────────────────
|
|
416
395
|
agent
|
|
417
396
|
.command('delete <agentId>')
|
|
418
397
|
.description('Delete an agent')
|
|
419
|
-
.
|
|
398
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
399
|
+
.action(async (agentId, options) => {
|
|
420
400
|
if (!(await ensureRemote()))
|
|
421
401
|
return;
|
|
422
402
|
try {
|
|
423
403
|
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
424
404
|
method: 'DELETE',
|
|
425
405
|
});
|
|
426
|
-
|
|
406
|
+
if (options.format === 'json') {
|
|
407
|
+
console.log(JSON.stringify({ agentId, status: 'deleted' }, null, 2));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log(chalk.green(` Agent ${agentId} deleted`));
|
|
411
|
+
}
|
|
427
412
|
}
|
|
428
413
|
catch (error) {
|
|
429
|
-
handleApiError(error, 'Agent delete failed');
|
|
414
|
+
handleApiError(error, 'Agent delete failed', logger);
|
|
430
415
|
}
|
|
431
416
|
});
|
|
432
417
|
// ── exit ───────────────────────────────────────────────────────────────────
|
|
@@ -464,7 +449,7 @@ export function registerAgentCommand(program) {
|
|
|
464
449
|
}
|
|
465
450
|
}
|
|
466
451
|
catch (error) {
|
|
467
|
-
handleApiError(error, 'Position exit failed');
|
|
452
|
+
handleApiError(error, 'Position exit failed', logger);
|
|
468
453
|
}
|
|
469
454
|
});
|
|
470
455
|
// ── update ──────────────────────────────────────────────────────────────────
|
|
@@ -488,12 +473,16 @@ export function registerAgentCommand(program) {
|
|
|
488
473
|
.option('--purpose-override <text>', 'Custom purpose/mission for this agent')
|
|
489
474
|
.option('--soul-file <path>', 'Load soul from a file')
|
|
490
475
|
.option('--purpose-file <path>', 'Load purpose from a file')
|
|
476
|
+
.option('--exit-reasoner', 'Enable LLM-powered exit reasoning for this agent')
|
|
477
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
491
478
|
.action(async (agentId, options) => {
|
|
492
479
|
if (!(await ensureRemote()))
|
|
493
480
|
return;
|
|
494
481
|
const body = {};
|
|
495
482
|
if (options.name)
|
|
496
483
|
body.name = options.name;
|
|
484
|
+
if (options.exitReasoner)
|
|
485
|
+
body.exitReasoner = true;
|
|
497
486
|
if (options.autonomy)
|
|
498
487
|
body.autonomyLevel = options.autonomy;
|
|
499
488
|
// Resolve scan interval: --scan-interval-human takes precedence
|
|
@@ -564,14 +553,19 @@ export function registerAgentCommand(program) {
|
|
|
564
553
|
return;
|
|
565
554
|
}
|
|
566
555
|
try {
|
|
567
|
-
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
556
|
+
const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}`, {
|
|
568
557
|
method: 'PATCH',
|
|
569
558
|
body,
|
|
570
559
|
});
|
|
571
|
-
|
|
560
|
+
if (options.format === 'json') {
|
|
561
|
+
console.log(JSON.stringify(result, null, 2));
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
console.log(chalk.green(` Agent ${agentId} updated`));
|
|
565
|
+
}
|
|
572
566
|
}
|
|
573
567
|
catch (error) {
|
|
574
|
-
handleApiError(error, 'Agent update failed');
|
|
568
|
+
handleApiError(error, 'Agent update failed', logger);
|
|
575
569
|
}
|
|
576
570
|
});
|
|
577
571
|
}
|
|
@@ -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
|