@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.
@@ -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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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
- .action(async (agentId) => {
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
- console.log(chalk.green(` Agent ${agentId} paused`));
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
- .action(async (agentId) => {
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
- console.log(chalk.green(` Agent ${agentId} resumed`));
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
- .action(async (agentId) => {
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
- console.log(chalk.green(` Agent ${agentId} deleted`));
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
- console.log(chalk.green(` Agent ${agentId} updated`));
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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 isRemoteMode())) {
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 isRemoteMode())) {
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}`));
@@ -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 (F1: prevent phishing via MITM)
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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 requireRemote()))
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 requireRemote()))
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 requireRemote()))
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 requireRemote()))
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: {} });
@@ -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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 isRemoteMode())) {
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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 isRemoteMode())) {
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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 isRemoteMode())) {
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 isRemoteMode())) {
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));
@@ -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 { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
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 isRemoteMode())) {
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