@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.
- package/dist/cli.bundle.js +47534 -45325
- package/dist/commands/agent-cmd.js +21 -3
- package/dist/commands/context.js +15 -1
- package/dist/commands/decisions.js +13 -1
- package/dist/commands/journal.js +31 -1
- package/dist/commands/onboarding.js +16 -8
- package/dist/commands/query.js +8 -1
- package/dist/commands/risk.js +7 -1
- package/dist/commands/soul-wizard.d.ts +29 -0
- package/dist/commands/soul-wizard.js +155 -0
- package/dist/commands/suggestions-cmd.js +19 -1
- package/dist/commands/trader.js +26 -0
- package/package.json +1 -1
|
@@ -114,8 +114,8 @@ export function registerAgentCommand(program) {
|
|
|
114
114
|
agent
|
|
115
115
|
.command('create')
|
|
116
116
|
.description('Create a new agent')
|
|
117
|
-
.
|
|
118
|
-
.
|
|
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('');
|
package/dist/commands/context.js
CHANGED
|
@@ -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;
|
package/dist/commands/journal.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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.
|
|
113
|
+
console.log(chalk.dim(' Skipped. Run later: trading-boy trader soul-wizard <name>'));
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
catch (error) {
|
package/dist/commands/query.js
CHANGED
|
@@ -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
|
package/dist/commands/risk.js
CHANGED
|
@@ -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') {
|
package/dist/commands/trader.js
CHANGED
|
@@ -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.
|
|
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": {
|