@trading-boy/cli 1.5.0 → 1.6.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.
@@ -114,8 +114,8 @@ export function registerAgentCommand(program) {
114
114
  agent
115
115
  .command('create')
116
116
  .description('Create a new agent')
117
- .requiredOption('--trader-id <traderId>', 'Trader ID')
118
- .requiredOption('--strategy-id <strategyId>', 'Strategy ID')
117
+ .option('--trader-id <traderId>', 'Trader ID')
118
+ .option('--strategy-id <strategyId>', 'Strategy ID')
119
119
  .option('--name <name>', 'Agent name')
120
120
  .option('--autonomy <level>', 'Autonomy level: OBSERVE_ONLY, SUGGEST, AUTO_WITH_APPROVAL, FULLY_AUTONOMOUS', 'OBSERVE_ONLY')
121
121
  .option('--scan-interval <ms>', 'Scan interval in ms (min 60000)', '300000')
@@ -137,6 +137,18 @@ export function registerAgentCommand(program) {
137
137
  .action(async (options) => {
138
138
  if (!(await ensureRemote()))
139
139
  return;
140
+ if (!options.traderId) {
141
+ console.error(chalk.red('Error: --trader-id is required.'));
142
+ console.log(chalk.dim(' Find yours with: trading-boy trader list'));
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ if (!options.strategyId) {
147
+ console.error(chalk.red('Error: --strategy-id is required.'));
148
+ console.log(chalk.dim(' Find yours with: trading-boy strategy list'));
149
+ process.exitCode = 1;
150
+ return;
151
+ }
140
152
  const body = {
141
153
  traderId: options.traderId,
142
154
  strategyId: options.strategyId,
@@ -249,7 +261,13 @@ export function registerAgentCommand(program) {
249
261
  return;
250
262
  }
251
263
  if (result.agents.length === 0) {
252
- console.log(chalk.dim(' No agents found'));
264
+ console.log(chalk.dim(' No agents found.'));
265
+ console.log('');
266
+ console.log(chalk.dim(' Create one with:'));
267
+ console.log(chalk.dim(' trading-boy agent create --trader-id <id> --strategy-id <id>'));
268
+ console.log(chalk.dim(' Find your IDs with:'));
269
+ console.log(chalk.dim(' trading-boy trader list'));
270
+ console.log(chalk.dim(' trading-boy strategy list'));
253
271
  return;
254
272
  }
255
273
  console.log('');
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError, colorChange, colorSentiment, colorRiskScore, formatUsd } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-context');
8
8
  // ─── Formatters ───
@@ -324,6 +324,13 @@ export function registerContextCommand(program) {
324
324
  .option('--trader-id <id>', 'Trader ID for personalized context')
325
325
  .action(async (symbol, options) => {
326
326
  try {
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;
332
+ return;
333
+ }
327
334
  // ─── Validate range options ───
328
335
  if ((options.from && !options.to) || (!options.from && options.to)) {
329
336
  console.error(chalk.red('Error: Both --from and --to must be provided for range queries.'));
@@ -368,6 +375,13 @@ export function registerContextCommand(program) {
368
375
  // Live context
369
376
  const traderQs = options.traderId ? `?traderId=${encodeURIComponent(options.traderId)}` : '';
370
377
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol.toUpperCase())}/context${traderQs}`);
378
+ // Check if the token was actually found
379
+ if (pkg.token.name == null && (!Array.isArray(pkg.token.chains) || pkg.token.chains.length === 0) && pkg.market?.price == null) {
380
+ console.error(chalk.red(`Error: Token not found: ${symbol.toUpperCase()}`));
381
+ console.error(chalk.dim(' Check the symbol and try again. Use a symbol like SOL, JUP, or BONK.'));
382
+ process.exitCode = 1;
383
+ return;
384
+ }
371
385
  if (options.format === 'json') {
372
386
  console.log(JSON.stringify(pkg, null, 2));
373
387
  }
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
- import { padRight, parseIntOption, formatDate } from '../utils.js';
4
+ import { padRight, parseIntOption, formatDate, formatConnectionError } from '../utils.js';
5
5
  import { apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-decisions');
@@ -222,6 +222,12 @@ export function registerDecisionsCommand(program) {
222
222
  }
223
223
  catch (error) {
224
224
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
225
+ const connErr = formatConnectionError(message);
226
+ if (connErr) {
227
+ console.error(connErr);
228
+ process.exitCode = 1;
229
+ return;
230
+ }
225
231
  logger.error({ error: message }, 'Failed to fetch decisions');
226
232
  console.error(chalk.red(`Error: ${message}`));
227
233
  process.exitCode = error instanceof ApiError ? 2 : 1;
@@ -262,6 +268,12 @@ export function registerDecisionsCommand(program) {
262
268
  }
263
269
  catch (error) {
264
270
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
271
+ const connErr = formatConnectionError(message);
272
+ if (connErr) {
273
+ console.error(connErr);
274
+ process.exitCode = 1;
275
+ return;
276
+ }
265
277
  logger.error({ error: message }, 'Failed to fetch stats');
266
278
  console.error(chalk.red(`Error: ${message}`));
267
279
  process.exitCode = error instanceof ApiError ? 2 : 1;
@@ -1,5 +1,5 @@
1
1
  import { Option } from 'commander';
2
- import { padRight } from '../utils.js';
2
+ import { padRight, formatConnectionError } from '../utils.js';
3
3
  import { apiRequest, ApiError } from '../api-client.js';
4
4
  import { registerReviewCommand } from './review.js';
5
5
  // ─── Default Trader ───
@@ -73,6 +73,12 @@ export function registerJournalCommand(program) {
73
73
  }
74
74
  catch (error) {
75
75
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
76
+ const connErr = formatConnectionError(message);
77
+ if (connErr) {
78
+ console.error(connErr);
79
+ process.exitCode = 1;
80
+ return;
81
+ }
76
82
  console.error(`Error: ${message}`);
77
83
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
78
84
  const detail = error.body.error ?? error.body.code;
@@ -118,6 +124,12 @@ export function registerJournalCommand(program) {
118
124
  }
119
125
  catch (error) {
120
126
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
127
+ const connErr = formatConnectionError(message);
128
+ if (connErr) {
129
+ console.error(connErr);
130
+ process.exitCode = 1;
131
+ return;
132
+ }
121
133
  console.error(`Error: ${message}`);
122
134
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
123
135
  const detail = error.body.error ?? error.body.code;
@@ -185,6 +197,12 @@ export function registerJournalCommand(program) {
185
197
  }
186
198
  catch (error) {
187
199
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
200
+ const connErr = formatConnectionError(message);
201
+ if (connErr) {
202
+ console.error(connErr);
203
+ process.exitCode = 1;
204
+ return;
205
+ }
188
206
  console.error(`Error: ${message}`);
189
207
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
190
208
  const detail = error.body.error ?? error.body.code;
@@ -231,6 +249,12 @@ export function registerJournalCommand(program) {
231
249
  }
232
250
  catch (error) {
233
251
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
252
+ const connErr = formatConnectionError(message);
253
+ if (connErr) {
254
+ console.error(connErr);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
234
258
  console.error(`Error: ${message}`);
235
259
  process.exitCode = error instanceof ApiError ? 2 : 1;
236
260
  }
@@ -263,6 +287,12 @@ export function registerJournalCommand(program) {
263
287
  }
264
288
  catch (error) {
265
289
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
290
+ const connErr = formatConnectionError(message);
291
+ if (connErr) {
292
+ console.error(connErr);
293
+ process.exitCode = 1;
294
+ return;
295
+ }
266
296
  console.error(`Error: ${message}`);
267
297
  process.exitCode = error instanceof ApiError ? 2 : 1;
268
298
  }
@@ -6,6 +6,7 @@
6
6
  // 3. Next steps cheat sheet
7
7
  import chalk from 'chalk';
8
8
  import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
9
+ import { runSoulWizardAndUpload } from './soul-wizard.js';
9
10
  // ─── Onboarding Flow ───
10
11
  /**
11
12
  * Run the interactive onboarding wizard.
@@ -21,6 +22,7 @@ export async function runOnboarding() {
21
22
  console.log('');
22
23
  // ─── Step 1: Register Trader ───
23
24
  let traderRegistered = false;
25
+ let traderName = 'default';
24
26
  try {
25
27
  const wantsTrader = await confirm({
26
28
  message: 'Register your trader profile?',
@@ -54,6 +56,7 @@ export async function runOnboarding() {
54
56
  console.log(chalk.dim(' This alias is public on the leaderboard. Change it anytime:'));
55
57
  console.log(chalk.dim(' trading-boy trader set-alias <name> <new-alias>'));
56
58
  }
59
+ traderName = name.trim();
57
60
  traderRegistered = true;
58
61
  }
59
62
  else {
@@ -63,6 +66,7 @@ export async function runOnboarding() {
63
66
  catch (error) {
64
67
  if (error instanceof ApiError && error.status === 409) {
65
68
  console.log(chalk.green(' \u2713 Trader profile already exists.'));
69
+ traderName = name.trim();
66
70
  traderRegistered = true;
67
71
  }
68
72
  else {
@@ -86,7 +90,7 @@ export async function runOnboarding() {
86
90
  if (traderRegistered) {
87
91
  try {
88
92
  const wantsIdentity = await confirm({
89
- message: 'Set up your trading identity? (SOUL + PURPOSE documents)',
93
+ message: 'Set up your trading identity? (interactive SOUL wizard)',
90
94
  default: true,
91
95
  });
92
96
  if (wantsIdentity) {
@@ -94,15 +98,19 @@ export async function runOnboarding() {
94
98
  console.log(chalk.dim(' Your identity personalizes context — bias warnings, scope checks,'));
95
99
  console.log(chalk.dim(' and tailored synthesis based on who you are as a trader.'));
96
100
  console.log('');
97
- console.log(` ${chalk.gray('1.')} Create a ${chalk.white('soul.md')} file describing your trading personality, risk tolerance, and biases`);
98
- console.log(` ${chalk.gray('2.')} Create a ${chalk.white('purpose.md')} file defining your mission, asset scope, and goals`);
99
- console.log(` ${chalk.gray('3.')} Upload them:`);
100
- console.log(` ${chalk.white('trading-boy trader soul <name> --file soul.md')}`);
101
- console.log(` ${chalk.white('trading-boy trader purpose <name> --file purpose.md')}`);
102
- console.log(` ${chalk.gray('4.')} View your identity: ${chalk.white('trading-boy trader identity <name>')}`);
101
+ try {
102
+ await runSoulWizardAndUpload(traderName);
103
+ }
104
+ catch (wizardError) {
105
+ if (isUserAbort(wizardError))
106
+ return;
107
+ const msg = wizardError instanceof Error ? wizardError.message : String(wizardError);
108
+ console.log(chalk.yellow(` Could not complete SOUL wizard: ${msg}`));
109
+ console.log(chalk.dim(' You can do this later: trading-boy trader soul-wizard <name>'));
110
+ }
103
111
  }
104
112
  else {
105
- console.log(chalk.dim(' Skipped. Set up later: trading-boy trader soul <name> --file soul.md'));
113
+ console.log(chalk.dim(' Skipped. Run later: trading-boy trader soul-wizard <name>'));
106
114
  }
107
115
  }
108
116
  catch (error) {
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-query');
8
8
  // ─── Formatter ───
@@ -102,6 +102,13 @@ export function registerQueryCommand(program) {
102
102
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
103
103
  .action(async (symbol, options) => {
104
104
  try {
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;
110
+ return;
111
+ }
105
112
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol.toUpperCase())}/context`);
106
113
  const result = contextToQueryResult(pkg);
107
114
  // Check if the token was actually found
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError, padRight } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-risk');
8
8
  // ─── Formatters ───
@@ -127,6 +127,12 @@ export function registerRiskCommand(program) {
127
127
  process.exitCode = 1;
128
128
  return;
129
129
  }
130
+ if (!(await isRemoteMode())) {
131
+ console.error(chalk.yellow('This command requires a remote API connection.'));
132
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
133
+ process.exitCode = 1;
134
+ return;
135
+ }
130
136
  try {
131
137
  const apiResult = await apiRequest(`/api/v1/protocols/${encodeURIComponent(protocol.toLowerCase())}/risk`);
132
138
  const result = {
@@ -0,0 +1,29 @@
1
+ export interface SoulWizardAnswers {
2
+ style: string;
3
+ riskTolerance: string;
4
+ assets: string[];
5
+ timeHorizon: string;
6
+ biases: string;
7
+ }
8
+ /**
9
+ * Run the interactive SOUL wizard and return the generated SOUL text.
10
+ * Throws if user aborts (Ctrl+C).
11
+ */
12
+ export declare function runSoulWizard(): Promise<{
13
+ answers: SoulWizardAnswers;
14
+ soulText: string;
15
+ }>;
16
+ /**
17
+ * Upload a SOUL document to the trader's profile.
18
+ */
19
+ export declare function uploadSoul(traderName: string, soulText: string): Promise<void>;
20
+ /**
21
+ * Print the generated SOUL document with chalk styling.
22
+ */
23
+ export declare function displaySoul(soulText: string): void;
24
+ /**
25
+ * Run wizard + upload + display. Used by both onboarding and standalone command.
26
+ * Returns true if SOUL was uploaded successfully.
27
+ */
28
+ export declare function runSoulWizardAndUpload(traderName: string): Promise<boolean>;
29
+ //# sourceMappingURL=soul-wizard.d.ts.map
@@ -0,0 +1,155 @@
1
+ // ─── SOUL Wizard ───
2
+ //
3
+ // Interactive wizard to generate a SOUL document from guided questions.
4
+ // Shared between onboarding flow and standalone `trader soul-wizard` command.
5
+ import chalk from 'chalk';
6
+ import { apiRequest, ApiError } from '../api-client.js';
7
+ // ─── Labels ───
8
+ const STYLE_LABELS = {
9
+ aggressive_momentum: 'Aggressive Momentum',
10
+ conservative_value: 'Conservative Value',
11
+ balanced: 'Balanced',
12
+ degen: 'Degen / High Risk',
13
+ macro: 'Macro / Fundamentals',
14
+ };
15
+ const RISK_LABELS = {
16
+ low: 'Low (1-3% per trade)',
17
+ medium: 'Medium (3-7%)',
18
+ high: 'High (7-15%)',
19
+ degen: 'Degen (15%+)',
20
+ };
21
+ const ASSET_LABELS = {
22
+ crypto: 'Crypto',
23
+ commodities: 'Commodities',
24
+ equities: 'Equities',
25
+ forex: 'Forex',
26
+ };
27
+ const HORIZON_LABELS = {
28
+ scalping: 'Scalping (minutes)',
29
+ day: 'Day trading (hours)',
30
+ swing: 'Swing (days)',
31
+ position: 'Position (weeks+)',
32
+ };
33
+ // ─── Wizard ───
34
+ /**
35
+ * Run the interactive SOUL wizard and return the generated SOUL text.
36
+ * Throws if user aborts (Ctrl+C).
37
+ */
38
+ export async function runSoulWizard() {
39
+ const { select, checkbox, input } = await import('@inquirer/prompts');
40
+ const style = await select({
41
+ message: 'What\'s your trading style?',
42
+ choices: [
43
+ { value: 'aggressive_momentum', name: 'Aggressive Momentum' },
44
+ { value: 'conservative_value', name: 'Conservative Value' },
45
+ { value: 'balanced', name: 'Balanced' },
46
+ { value: 'degen', name: 'Degen / High Risk' },
47
+ { value: 'macro', name: 'Macro / Fundamentals' },
48
+ ],
49
+ });
50
+ const riskTolerance = await select({
51
+ message: 'Risk tolerance?',
52
+ choices: [
53
+ { value: 'low', name: 'Low (1-3% per trade)' },
54
+ { value: 'medium', name: 'Medium (3-7%)' },
55
+ { value: 'high', name: 'High (7-15%)' },
56
+ { value: 'degen', name: 'Degen (15%+)' },
57
+ ],
58
+ });
59
+ const assets = await checkbox({
60
+ message: 'What assets do you trade?',
61
+ choices: [
62
+ { value: 'crypto', name: 'Crypto' },
63
+ { value: 'commodities', name: 'Commodities' },
64
+ { value: 'equities', name: 'Equities' },
65
+ { value: 'forex', name: 'Forex' },
66
+ ],
67
+ });
68
+ const timeHorizon = await select({
69
+ message: 'Time horizon?',
70
+ choices: [
71
+ { value: 'scalping', name: 'Scalping (minutes)' },
72
+ { value: 'day', name: 'Day trading (hours)' },
73
+ { value: 'swing', name: 'Swing (days)' },
74
+ { value: 'position', name: 'Position (weeks+)' },
75
+ ],
76
+ });
77
+ const biases = await input({
78
+ message: 'Any biases to watch for? (optional, press Enter to skip)',
79
+ });
80
+ const answers = { style, riskTolerance, assets, timeHorizon, biases };
81
+ const soulText = buildSoulDocument(answers);
82
+ return { answers, soulText };
83
+ }
84
+ // ─── SOUL Document Builder ───
85
+ function buildSoulDocument(answers) {
86
+ const styleLabel = STYLE_LABELS[answers.style] ?? answers.style;
87
+ const riskLabel = RISK_LABELS[answers.riskTolerance] ?? answers.riskTolerance;
88
+ const assetLabels = answers.assets.map(a => ASSET_LABELS[a] ?? a).join(', ') || 'Not specified';
89
+ const horizonLabel = HORIZON_LABELS[answers.timeHorizon] ?? answers.timeHorizon;
90
+ const lines = [];
91
+ lines.push(`Trading Style: ${styleLabel}`);
92
+ lines.push(`Risk Tolerance: ${riskLabel}`);
93
+ lines.push(`Asset Classes: ${assetLabels}`);
94
+ lines.push(`Time Horizon: ${horizonLabel}`);
95
+ if (answers.biases.trim()) {
96
+ lines.push(`Known Biases: ${answers.biases.trim()}`);
97
+ }
98
+ lines.push('');
99
+ lines.push(`This trader favors ${styleLabel.toLowerCase()} approaches with ${riskLabel.toLowerCase()} risk tolerance,`);
100
+ lines.push(`primarily trading ${assetLabels.toLowerCase()}. They operate on a ${horizonLabel.toLowerCase()} timeframe.`);
101
+ if (answers.biases.trim()) {
102
+ lines.push(`Watch for: ${answers.biases.trim()}`);
103
+ }
104
+ return lines.join('\n');
105
+ }
106
+ // ─── Upload ───
107
+ /**
108
+ * Upload a SOUL document to the trader's profile.
109
+ */
110
+ export async function uploadSoul(traderName, soulText) {
111
+ await apiRequest(`/api/v1/traders/${encodeURIComponent(traderName)}/soul`, {
112
+ method: 'PUT',
113
+ body: { document: soulText },
114
+ });
115
+ }
116
+ // ─── Display ───
117
+ /**
118
+ * Print the generated SOUL document with chalk styling.
119
+ */
120
+ export function displaySoul(soulText) {
121
+ console.log('');
122
+ console.log(chalk.bold.cyan(' Generated SOUL Document'));
123
+ console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
124
+ console.log('');
125
+ for (const line of soulText.split('\n')) {
126
+ console.log(` ${chalk.white(line)}`);
127
+ }
128
+ console.log('');
129
+ }
130
+ // ─── Combined Flow ───
131
+ /**
132
+ * Run wizard + upload + display. Used by both onboarding and standalone command.
133
+ * Returns true if SOUL was uploaded successfully.
134
+ */
135
+ export async function runSoulWizardAndUpload(traderName) {
136
+ const { soulText } = await runSoulWizard();
137
+ displaySoul(soulText);
138
+ try {
139
+ await uploadSoul(traderName, soulText);
140
+ console.log(chalk.green(` \u2713 SOUL uploaded for trader "${traderName}"`));
141
+ return true;
142
+ }
143
+ catch (error) {
144
+ if (error instanceof ApiError) {
145
+ console.log(chalk.yellow(` Could not upload SOUL: ${error.message}`));
146
+ }
147
+ else {
148
+ const msg = error instanceof Error ? error.message : String(error);
149
+ console.log(chalk.yellow(` Could not upload SOUL: ${msg}`));
150
+ }
151
+ console.log(chalk.dim(' You can upload it later: trading-boy trader soul <name> --file soul.md'));
152
+ return false;
153
+ }
154
+ }
155
+ //# sourceMappingURL=soul-wizard.js.map
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
- import { apiRequest, ApiError } from '../api-client.js';
4
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
5
5
  import { padRight, truncate, formatDate } from '../utils.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-suggestions');
@@ -72,6 +72,12 @@ export function registerSuggestionsCommand(program) {
72
72
  .option('--offset <n>', 'Pagination offset', parseInt, 0)
73
73
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
74
74
  .action(async (options) => {
75
+ if (!(await isRemoteMode())) {
76
+ console.error(chalk.yellow('This command requires a remote API connection.'));
77
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
78
+ process.exitCode = 1;
79
+ return;
80
+ }
75
81
  try {
76
82
  const params = new URLSearchParams();
77
83
  params.set('status', options.status);
@@ -100,6 +106,12 @@ export function registerSuggestionsCommand(program) {
100
106
  .description('Approve a suggestion and auto-apply to strategy')
101
107
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
102
108
  .action(async (id, options) => {
109
+ if (!(await isRemoteMode())) {
110
+ console.error(chalk.yellow('This command requires a remote API connection.'));
111
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
112
+ process.exitCode = 1;
113
+ return;
114
+ }
103
115
  try {
104
116
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/approve`, { method: 'POST' });
105
117
  if (options.format === 'json') {
@@ -122,6 +134,12 @@ export function registerSuggestionsCommand(program) {
122
134
  .description('Reject a suggestion')
123
135
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
124
136
  .action(async (id, options) => {
137
+ if (!(await isRemoteMode())) {
138
+ console.error(chalk.yellow('This command requires a remote API connection.'));
139
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
140
+ process.exitCode = 1;
141
+ return;
142
+ }
125
143
  try {
126
144
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/reject`, { method: 'POST' });
127
145
  if (options.format === 'json') {
@@ -443,6 +443,32 @@ export function registerTraderCommand(program) {
443
443
  process.exitCode = error instanceof ApiError ? 2 : 1;
444
444
  }
445
445
  });
446
+ // ─── soul-wizard ───
447
+ trader
448
+ .command('soul-wizard')
449
+ .description('Interactive wizard to create your trading identity')
450
+ .argument('<name>', 'Trader name')
451
+ .action(async (name) => {
452
+ try {
453
+ const { runSoulWizardAndUpload } = await import('./soul-wizard.js');
454
+ console.log('');
455
+ console.log(chalk.bold.cyan(' SOUL Wizard'));
456
+ console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
457
+ console.log(chalk.dim(' Answer a few questions to generate your trading identity.'));
458
+ console.log('');
459
+ await runSoulWizardAndUpload(name);
460
+ }
461
+ catch (error) {
462
+ if (error instanceof Error && error.message.includes('User force closed')) {
463
+ console.log('');
464
+ return;
465
+ }
466
+ const message = error instanceof Error ? error.message : String(error);
467
+ logger.error({ error: message }, 'Soul wizard failed');
468
+ console.error(chalk.red(` Error: ${message}`));
469
+ process.exitCode = error instanceof ApiError ? 2 : 1;
470
+ }
471
+ });
446
472
  // ─── purpose ───
447
473
  trader
448
474
  .command('purpose')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trading-boy/cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Trading Boy CLI — crypto context intelligence for traders and AI agents. Query real-time prices, funding rates, whale activity, and DeFi risk for 100+ Solana tokens and 229 Hyperliquid perpetuals.",
5
5
  "homepage": "https://cabal.ventures",
6
6
  "repository": {