@trading-boy/cli 1.12.0 → 2.0.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/README.md +50 -22
- package/dist/api-client.d.ts +4 -7
- package/dist/api-client.js +8 -13
- package/dist/cli.bundle.js +1896 -33657
- package/dist/credentials.js +1 -1
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -24
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +12 -0
- package/dist/utils.js +3 -3
- package/package.json +20 -5
- package/dist/cli.d.ts +0 -5
- package/dist/cli.js +0 -157
- package/dist/commands/agent-cmd.d.ts +0 -9
- package/dist/commands/agent-cmd.js +0 -567
- package/dist/commands/audit.d.ts +0 -18
- package/dist/commands/audit.js +0 -73
- package/dist/commands/behavioral.d.ts +0 -73
- package/dist/commands/behavioral.js +0 -349
- package/dist/commands/benchmark-cmd.d.ts +0 -3
- package/dist/commands/benchmark-cmd.js +0 -191
- package/dist/commands/billing.d.ts +0 -12
- package/dist/commands/billing.js +0 -142
- package/dist/commands/catalysts.d.ts +0 -17
- package/dist/commands/catalysts.js +0 -151
- package/dist/commands/coaching-cmd.d.ts +0 -16
- package/dist/commands/coaching-cmd.js +0 -222
- package/dist/commands/config-cmd.d.ts +0 -30
- package/dist/commands/config-cmd.js +0 -515
- package/dist/commands/connect-chatgpt.d.ts +0 -5
- package/dist/commands/connect-chatgpt.js +0 -293
- package/dist/commands/connect-claude.d.ts +0 -5
- package/dist/commands/connect-claude.js +0 -280
- package/dist/commands/context.d.ts +0 -41
- package/dist/commands/context.js +0 -405
- package/dist/commands/cron-cmd.d.ts +0 -3
- package/dist/commands/cron-cmd.js +0 -305
- package/dist/commands/decisions.d.ts +0 -57
- package/dist/commands/decisions.js +0 -364
- package/dist/commands/edge-cmd.d.ts +0 -78
- package/dist/commands/edge-cmd.js +0 -183
- package/dist/commands/edge-guard-cmd.d.ts +0 -36
- package/dist/commands/edge-guard-cmd.js +0 -169
- package/dist/commands/events.d.ts +0 -3
- package/dist/commands/events.js +0 -117
- package/dist/commands/infra.d.ts +0 -24
- package/dist/commands/infra.js +0 -137
- package/dist/commands/journal.d.ts +0 -3
- package/dist/commands/journal.js +0 -302
- package/dist/commands/login.d.ts +0 -18
- package/dist/commands/login.js +0 -127
- package/dist/commands/logout.d.ts +0 -8
- package/dist/commands/logout.js +0 -108
- package/dist/commands/narratives.d.ts +0 -3
- package/dist/commands/narratives.js +0 -259
- package/dist/commands/onboarding.d.ts +0 -7
- package/dist/commands/onboarding.js +0 -281
- package/dist/commands/query.d.ts +0 -32
- package/dist/commands/query.js +0 -135
- package/dist/commands/replay-cmd.d.ts +0 -43
- package/dist/commands/replay-cmd.js +0 -184
- package/dist/commands/review.d.ts +0 -3
- package/dist/commands/review.js +0 -443
- package/dist/commands/risk.d.ts +0 -47
- package/dist/commands/risk.js +0 -158
- package/dist/commands/social.d.ts +0 -43
- package/dist/commands/social.js +0 -318
- package/dist/commands/soul-wizard.d.ts +0 -29
- package/dist/commands/soul-wizard.js +0 -155
- package/dist/commands/strategy-cmd.d.ts +0 -44
- package/dist/commands/strategy-cmd.js +0 -335
- package/dist/commands/subscribe.d.ts +0 -78
- package/dist/commands/subscribe.js +0 -552
- package/dist/commands/suggestions-cmd.d.ts +0 -24
- package/dist/commands/suggestions-cmd.js +0 -148
- package/dist/commands/thesis-cmd.d.ts +0 -3
- package/dist/commands/thesis-cmd.js +0 -129
- package/dist/commands/trader.d.ts +0 -30
- package/dist/commands/trader.js +0 -971
- package/dist/commands/watch.d.ts +0 -16
- package/dist/commands/watch.js +0 -104
- package/dist/commands/whoami.d.ts +0 -14
- package/dist/commands/whoami.js +0 -105
package/dist/commands/journal.js
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { Option } from 'commander';
|
|
2
|
-
import { padRight, formatConnectionError } from '../utils.js';
|
|
3
|
-
import { apiRequest, ApiError } from '../api-client.js';
|
|
4
|
-
import { registerReviewCommand } from './review.js';
|
|
5
|
-
// ─── Default Trader ───
|
|
6
|
-
const DEFAULT_TRADER_ID = 'default';
|
|
7
|
-
// ─── Command Registration ───
|
|
8
|
-
export function registerJournalCommand(program) {
|
|
9
|
-
const journal = program
|
|
10
|
-
.command('journal')
|
|
11
|
-
.description('Trade journaling commands');
|
|
12
|
-
// Register review subcommands
|
|
13
|
-
registerReviewCommand(journal);
|
|
14
|
-
const log = journal
|
|
15
|
-
.command('log')
|
|
16
|
-
.description('Log trade decisions')
|
|
17
|
-
.action(() => {
|
|
18
|
-
log.help();
|
|
19
|
-
});
|
|
20
|
-
// ─── journal log entry <symbol> ───
|
|
21
|
-
log
|
|
22
|
-
.command('entry <symbol>')
|
|
23
|
-
.description('Log a trade entry')
|
|
24
|
-
.option('--thesis <thesis>', 'The reasoning behind the trade')
|
|
25
|
-
.option('--setup <setup>', 'Setup type (BREAKOUT, MOMENTUM, etc.)')
|
|
26
|
-
.option('--direction <direction>', 'Trade direction (LONG, SHORT)')
|
|
27
|
-
.option('--size <size>', 'Position size in token units', parseFloat)
|
|
28
|
-
.option('--size-usd <sizeUsd>', 'Position size in USD', parseFloat)
|
|
29
|
-
.option('--emotion <emotion>', 'Emotional tag (CONVICTION, FOMO, REVENGE, IMPULSE, FEAR, GREED, NEUTRAL)')
|
|
30
|
-
.option('--confidence <confidence>', 'Confidence level (0-1)', parseFloat)
|
|
31
|
-
.option('--trader <traderId>', 'Trader ID', DEFAULT_TRADER_ID)
|
|
32
|
-
.option('--mint <mint>', 'Token mint address')
|
|
33
|
-
.option('--tx-hash <txHash>', 'Solana transaction signature')
|
|
34
|
-
.option('--triggers <triggers...>', 'What initiated the trade')
|
|
35
|
-
.option('--plan <planId>', 'Link to a pre-trade plan (auto-fills fields)')
|
|
36
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
37
|
-
.action(async (symbol, options) => {
|
|
38
|
-
// ─── Input validation ───
|
|
39
|
-
if (options.direction !== undefined && !['LONG', 'SHORT'].includes(options.direction.toUpperCase())) {
|
|
40
|
-
console.error('Error: --direction must be LONG or SHORT');
|
|
41
|
-
process.exitCode = 1;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (options.confidence !== undefined && (!Number.isFinite(options.confidence) || options.confidence < 0 || options.confidence > 1)) {
|
|
45
|
-
console.error('Error: --confidence must be a number between 0 and 1');
|
|
46
|
-
process.exitCode = 1;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const result = await apiRequest('/api/v1/decisions', {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
body: {
|
|
53
|
-
traderId: options.trader,
|
|
54
|
-
decisionType: 'TRADE_ENTRY',
|
|
55
|
-
tokenSymbol: symbol.toUpperCase(),
|
|
56
|
-
...(options.mint ? { tokenMint: options.mint } : {}),
|
|
57
|
-
direction: options.direction?.toUpperCase(),
|
|
58
|
-
size: options.size,
|
|
59
|
-
sizeUsd: options.sizeUsd,
|
|
60
|
-
thesis: options.thesis,
|
|
61
|
-
setupType: options.setup,
|
|
62
|
-
emotionalTag: options.emotion,
|
|
63
|
-
confidence: options.confidence,
|
|
64
|
-
triggers: options.triggers,
|
|
65
|
-
txHash: options.txHash,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
if (options.format === 'json') {
|
|
69
|
-
console.log(JSON.stringify(result, null, 2));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
console.log(`Entry logged: id=${result.id} hash=${result.hash}`);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
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
|
-
}
|
|
82
|
-
console.error(`Error: ${message}`);
|
|
83
|
-
if (error instanceof ApiError && error.body && typeof error.body === 'object') {
|
|
84
|
-
const detail = error.body.error ?? error.body.code;
|
|
85
|
-
if (detail)
|
|
86
|
-
console.error(`Detail: ${detail}`);
|
|
87
|
-
}
|
|
88
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
// ─── journal log exit <symbol> ───
|
|
92
|
-
log
|
|
93
|
-
.command('exit <symbol>')
|
|
94
|
-
.description('Log a trade exit')
|
|
95
|
-
.option('--link-entry <entryId>', 'ID of the entry DecisionEvent to link')
|
|
96
|
-
.option('--trader <traderId>', 'Trader ID', DEFAULT_TRADER_ID)
|
|
97
|
-
.option('--mint <mint>', 'Token mint address')
|
|
98
|
-
.option('--tx-hash <txHash>', 'Solana transaction signature')
|
|
99
|
-
.option('--thesis <thesis>', 'Exit reasoning')
|
|
100
|
-
.option('--size <size>', 'Position size in token units', parseFloat)
|
|
101
|
-
.option('--size-usd <sizeUsd>', 'Position size in USD', parseFloat)
|
|
102
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
103
|
-
.action(async (symbol, options) => {
|
|
104
|
-
try {
|
|
105
|
-
const result = await apiRequest('/api/v1/decisions', {
|
|
106
|
-
method: 'POST',
|
|
107
|
-
body: {
|
|
108
|
-
traderId: options.trader,
|
|
109
|
-
decisionType: 'TRADE_EXIT',
|
|
110
|
-
tokenSymbol: symbol.toUpperCase(),
|
|
111
|
-
...(options.mint ? { tokenMint: options.mint } : {}),
|
|
112
|
-
thesis: options.thesis,
|
|
113
|
-
size: options.size,
|
|
114
|
-
sizeUsd: options.sizeUsd,
|
|
115
|
-
txHash: options.txHash,
|
|
116
|
-
linkedEntryId: options.linkEntry,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
if (options.format === 'json') {
|
|
120
|
-
console.log(JSON.stringify(result, null, 2));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
console.log(`Exit logged: id=${result.id} hash=${result.hash}`);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
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
|
-
}
|
|
133
|
-
console.error(`Error: ${message}`);
|
|
134
|
-
if (error instanceof ApiError && error.body && typeof error.body === 'object') {
|
|
135
|
-
const detail = error.body.error ?? error.body.code;
|
|
136
|
-
if (detail)
|
|
137
|
-
console.error(`Detail: ${detail}`);
|
|
138
|
-
}
|
|
139
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
// ─── journal list ───
|
|
143
|
-
journal
|
|
144
|
-
.command('list')
|
|
145
|
-
.description('List recent trade decisions')
|
|
146
|
-
.option('--token <symbol>', 'Filter by token symbol')
|
|
147
|
-
.option('--limit <n>', 'Maximum number of results', parseInt, 10)
|
|
148
|
-
.option('--trader <traderId>', 'Trader ID', DEFAULT_TRADER_ID)
|
|
149
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
150
|
-
.action(async (options) => {
|
|
151
|
-
try {
|
|
152
|
-
const params = new URLSearchParams();
|
|
153
|
-
if (options.trader)
|
|
154
|
-
params.set('traderId', options.trader);
|
|
155
|
-
if (options.token)
|
|
156
|
-
params.set('tokenSymbol', options.token.toUpperCase());
|
|
157
|
-
if (options.limit)
|
|
158
|
-
params.set('limit', String(options.limit));
|
|
159
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
160
|
-
const result = await apiRequest(`/api/v1/decisions${qs}`);
|
|
161
|
-
const decisions = result.decisions ?? [];
|
|
162
|
-
if (decisions.length === 0) {
|
|
163
|
-
if (options.format === 'json') {
|
|
164
|
-
console.log(JSON.stringify({ decisions: [] }, null, 2));
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
console.log('No decisions found.');
|
|
168
|
-
}
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (options.format === 'json') {
|
|
172
|
-
console.log(JSON.stringify(result, null, 2));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
// Print formatted table
|
|
176
|
-
console.log('');
|
|
177
|
-
console.log(padRight('ID', 38) +
|
|
178
|
-
padRight('Type', 20) +
|
|
179
|
-
padRight('Token', 8) +
|
|
180
|
-
padRight('Direction', 10) +
|
|
181
|
-
padRight('Price', 12) +
|
|
182
|
-
padRight('Confidence', 12) +
|
|
183
|
-
padRight('Hash', 10));
|
|
184
|
-
console.log('-'.repeat(110));
|
|
185
|
-
for (const d of decisions) {
|
|
186
|
-
const price = (d.entryPrice ?? d.exitPrice);
|
|
187
|
-
console.log(padRight(String(d.id ?? ''), 38) +
|
|
188
|
-
padRight(String(d.decisionType ?? ''), 20) +
|
|
189
|
-
padRight(String(d.tokenSymbol ?? ''), 8) +
|
|
190
|
-
padRight(String(d.direction ?? '-'), 10) +
|
|
191
|
-
padRight(price !== null && price !== undefined ? Number(price).toFixed(2) : '-', 12) +
|
|
192
|
-
padRight(d.confidence !== null && d.confidence !== undefined ? Number(d.confidence).toFixed(2) : '-', 12) +
|
|
193
|
-
(d.hash ? String(d.hash).slice(0, 8) + '...' : '-'));
|
|
194
|
-
}
|
|
195
|
-
console.log('');
|
|
196
|
-
console.log(`Total: ${decisions.length} decision(s)`);
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
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
|
-
}
|
|
206
|
-
console.error(`Error: ${message}`);
|
|
207
|
-
if (error instanceof ApiError && error.body && typeof error.body === 'object') {
|
|
208
|
-
const detail = error.body.error ?? error.body.code;
|
|
209
|
-
if (detail)
|
|
210
|
-
console.error(`Detail: ${detail}`);
|
|
211
|
-
}
|
|
212
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
// ─── journal plan ───
|
|
216
|
-
journal
|
|
217
|
-
.command('plan')
|
|
218
|
-
.description('List pre-trade plans')
|
|
219
|
-
.option('--trader <traderId>', 'Trader ID', DEFAULT_TRADER_ID)
|
|
220
|
-
.option('--token <symbol>', 'Filter by token symbol')
|
|
221
|
-
.option('--status <status>', 'Filter by status: PENDING, EXECUTED, CANCELLED, EXPIRED')
|
|
222
|
-
.option('--limit <n>', 'Maximum results', parseInt, 50)
|
|
223
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
224
|
-
.action(async (options) => {
|
|
225
|
-
try {
|
|
226
|
-
const params = new URLSearchParams();
|
|
227
|
-
params.set('traderId', options.trader);
|
|
228
|
-
if (options.token)
|
|
229
|
-
params.set('tokenSymbol', options.token);
|
|
230
|
-
if (options.status)
|
|
231
|
-
params.set('status', options.status);
|
|
232
|
-
params.set('limit', String(options.limit));
|
|
233
|
-
const data = await apiRequest(`/api/v1/journal/plan?${params.toString()}`);
|
|
234
|
-
if (options.format === 'json') {
|
|
235
|
-
console.log(JSON.stringify(data, null, 2));
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
if (data.plans.length === 0) {
|
|
239
|
-
console.log(' No pre-trade plans found.');
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
console.log(`\n Pre-Trade Plans (${data.count}):\n`);
|
|
243
|
-
for (const plan of data.plans) {
|
|
244
|
-
console.log(` ${padRight(String(plan.id ?? ''), 14)} ${padRight(String(plan.tokenSymbol ?? ''), 8)} ${padRight(String(plan.status ?? ''), 12)} ${plan.direction ?? ''}`);
|
|
245
|
-
}
|
|
246
|
-
console.log('');
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
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
|
-
}
|
|
258
|
-
console.error(`Error: ${message}`);
|
|
259
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
// ─── journal size ───
|
|
263
|
-
journal
|
|
264
|
-
.command('size')
|
|
265
|
-
.description('Kelly criterion position sizing recommendation')
|
|
266
|
-
.requiredOption('--trader <traderId>', 'Trader ID')
|
|
267
|
-
.option('--capital <usd>', 'Portfolio capital in USD', parseFloat, 10000)
|
|
268
|
-
.option('--fraction <f>', 'Kelly fraction (0-1, default 0.5 = Half Kelly)', parseFloat, 0.5)
|
|
269
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
270
|
-
.action(async (options) => {
|
|
271
|
-
try {
|
|
272
|
-
const params = new URLSearchParams();
|
|
273
|
-
params.set('traderId', options.trader);
|
|
274
|
-
params.set('capitalUsd', String(options.capital));
|
|
275
|
-
params.set('fraction', String(options.fraction));
|
|
276
|
-
const data = await apiRequest(`/api/v1/journal/size?${params.toString()}`);
|
|
277
|
-
if (options.format === 'json') {
|
|
278
|
-
console.log(JSON.stringify(data, null, 2));
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
console.log('\n Kelly Position Sizing:\n');
|
|
282
|
-
for (const [key, value] of Object.entries(data)) {
|
|
283
|
-
console.log(` ${padRight(key, 24)} ${value}`);
|
|
284
|
-
}
|
|
285
|
-
console.log('');
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch (error) {
|
|
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
|
-
}
|
|
296
|
-
console.error(`Error: ${message}`);
|
|
297
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
// PlanOptions and SizeOptions removed — commands hidden until API endpoints exist
|
|
302
|
-
//# sourceMappingURL=journal.js.map
|
package/dist/commands/login.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
interface VerifyResponse {
|
|
3
|
-
valid: boolean;
|
|
4
|
-
email?: string;
|
|
5
|
-
plan?: string;
|
|
6
|
-
keyId?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function validateApiKeyFormat(key: string): boolean;
|
|
9
|
-
export declare function verifyApiKey(apiKey: string): Promise<VerifyResponse>;
|
|
10
|
-
export declare function executeLogin(apiKey: string): Promise<{
|
|
11
|
-
success: boolean;
|
|
12
|
-
email?: string;
|
|
13
|
-
plan?: string;
|
|
14
|
-
keyId?: string;
|
|
15
|
-
}>;
|
|
16
|
-
export declare function registerLoginCommand(program: Command): void;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=login.d.ts.map
|
package/dist/commands/login.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { Option } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import { storeCredentials, redactApiKey } from '../credentials.js';
|
|
5
|
-
import { getApiBase } from '../api-client.js';
|
|
6
|
-
const logger = createLogger('cli-login');
|
|
7
|
-
// ─── API Key Validation ───
|
|
8
|
-
const API_KEY_PATTERN = /^tb_(live|test|free)_[a-f0-9]{32}$/;
|
|
9
|
-
export function validateApiKeyFormat(key) {
|
|
10
|
-
return API_KEY_PATTERN.test(key);
|
|
11
|
-
}
|
|
12
|
-
// ─── Key Verification ───
|
|
13
|
-
export async function verifyApiKey(apiKey) {
|
|
14
|
-
const apiBase = getApiBase();
|
|
15
|
-
const response = await fetch(`${apiBase}/api/v1/auth/verify`, {
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: {
|
|
18
|
-
'Content-Type': 'application/json',
|
|
19
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
20
|
-
},
|
|
21
|
-
body: '{}',
|
|
22
|
-
});
|
|
23
|
-
if (!response.ok) {
|
|
24
|
-
if (response.status === 401) {
|
|
25
|
-
return { valid: false };
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`Verification failed: ${response.status}`);
|
|
28
|
-
}
|
|
29
|
-
return (await response.json());
|
|
30
|
-
}
|
|
31
|
-
// ─── Login Logic ───
|
|
32
|
-
export async function executeLogin(apiKey) {
|
|
33
|
-
// Validate format
|
|
34
|
-
if (!validateApiKeyFormat(apiKey)) {
|
|
35
|
-
return { success: false };
|
|
36
|
-
}
|
|
37
|
-
// Verify with API
|
|
38
|
-
const result = await verifyApiKey(apiKey);
|
|
39
|
-
if (!result.valid) {
|
|
40
|
-
return { success: false };
|
|
41
|
-
}
|
|
42
|
-
// Store credentials
|
|
43
|
-
await storeCredentials(apiKey, {
|
|
44
|
-
keyId: result.keyId,
|
|
45
|
-
email: result.email,
|
|
46
|
-
plan: result.plan,
|
|
47
|
-
});
|
|
48
|
-
return {
|
|
49
|
-
success: true,
|
|
50
|
-
email: result.email,
|
|
51
|
-
plan: result.plan,
|
|
52
|
-
keyId: result.keyId,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// ─── Command Registration ───
|
|
56
|
-
export function registerLoginCommand(program) {
|
|
57
|
-
program
|
|
58
|
-
.command('login')
|
|
59
|
-
.description('Authenticate with your Trading Boy API key')
|
|
60
|
-
.addOption(new Option('--api-key <key>', 'API key (deprecated — use TRADING_BOY_API_KEY env var)').hideHelp())
|
|
61
|
-
.action(async (opts) => {
|
|
62
|
-
try {
|
|
63
|
-
let apiKey;
|
|
64
|
-
if (opts.apiKey) {
|
|
65
|
-
// Non-interactive mode — warn about shell history exposure
|
|
66
|
-
process.stderr.write(chalk.yellow('Warning: --api-key is visible in shell history. Prefer TRADING_BOY_API_KEY env var.\n'));
|
|
67
|
-
apiKey = opts.apiKey;
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
// Interactive mode — dynamic import to avoid loading inquirer for non-interactive use
|
|
71
|
-
const { password } = await import('@inquirer/prompts');
|
|
72
|
-
console.log('');
|
|
73
|
-
console.log(chalk.bold(' Trading Boy — Login'));
|
|
74
|
-
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
75
|
-
console.log(chalk.dim(' Don\'t have a key? Run: trading-boy subscribe'));
|
|
76
|
-
console.log('');
|
|
77
|
-
apiKey = await password({
|
|
78
|
-
message: 'Enter your API key',
|
|
79
|
-
mask: '*',
|
|
80
|
-
validate: (input) => {
|
|
81
|
-
if (!input)
|
|
82
|
-
return 'API key is required';
|
|
83
|
-
if (!validateApiKeyFormat(input)) {
|
|
84
|
-
return 'Invalid key format. Expected: tb_live_<32hex>, tb_test_<32hex>, or tb_free_<32hex>';
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
// Validate format before making network request
|
|
91
|
-
if (!validateApiKeyFormat(apiKey)) {
|
|
92
|
-
console.error(chalk.red(' Invalid key format. Expected: tb_live_<32hex>, tb_test_<32hex>, or tb_free_<32hex>'));
|
|
93
|
-
process.exitCode = 1;
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// Verify with spinner (silent when piped)
|
|
97
|
-
const { createSpinner } = await import('../utils.js');
|
|
98
|
-
const spinner = (await createSpinner(' Verifying key...')).start();
|
|
99
|
-
const result = await executeLogin(apiKey);
|
|
100
|
-
if (!result.success) {
|
|
101
|
-
spinner.fail(' Invalid API key');
|
|
102
|
-
process.exitCode = 1;
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
spinner.succeed(' Authenticated successfully');
|
|
106
|
-
console.log('');
|
|
107
|
-
if (result.email) {
|
|
108
|
-
console.log(` ${chalk.gray('Account:')} ${result.email}`);
|
|
109
|
-
}
|
|
110
|
-
if (result.plan) {
|
|
111
|
-
console.log(` ${chalk.gray('Plan:')} ${result.plan}`);
|
|
112
|
-
}
|
|
113
|
-
if (result.keyId) {
|
|
114
|
-
console.log(` ${chalk.gray('Key ID:')} ${result.keyId}`);
|
|
115
|
-
}
|
|
116
|
-
console.log(` ${chalk.gray('Key:')} ${redactApiKey(apiKey)}`);
|
|
117
|
-
console.log('');
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
-
logger.error({ error: message }, 'Login failed');
|
|
122
|
-
console.error(chalk.red(` Error: ${message}`));
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
//# sourceMappingURL=login.js.map
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
export declare function executeLogout(): Promise<{
|
|
3
|
-
wasAuthenticated: boolean;
|
|
4
|
-
redactedKey?: string;
|
|
5
|
-
keychainCleared: boolean;
|
|
6
|
-
}>;
|
|
7
|
-
export declare function registerLogoutCommand(program: Command): void;
|
|
8
|
-
//# sourceMappingURL=logout.d.ts.map
|
package/dist/commands/logout.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { Option } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import { clearCredentials, loadCredentials, redactApiKey } from '../credentials.js';
|
|
5
|
-
import { confirm } from '@inquirer/prompts';
|
|
6
|
-
const logger = createLogger('cli-logout');
|
|
7
|
-
// ─── Logout Logic ───
|
|
8
|
-
export async function executeLogout() {
|
|
9
|
-
const existing = await loadCredentials();
|
|
10
|
-
const wasAuthenticated = existing !== null;
|
|
11
|
-
const redactedKey = existing ? redactApiKey(existing.apiKey) : undefined;
|
|
12
|
-
const { keychainCleared } = await clearCredentials();
|
|
13
|
-
return { wasAuthenticated, redactedKey, keychainCleared };
|
|
14
|
-
}
|
|
15
|
-
// ─── Command Registration ───
|
|
16
|
-
export function registerLogoutCommand(program) {
|
|
17
|
-
program
|
|
18
|
-
.command('logout')
|
|
19
|
-
.description('Clear stored API key and credentials')
|
|
20
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
21
|
-
.addOption(new Option('--force', 'Skip confirmation prompt'))
|
|
22
|
-
.addOption(new Option('--reveal-key', 'Include full API key in JSON output (requires --force --format json)'))
|
|
23
|
-
.action(async (options) => {
|
|
24
|
-
try {
|
|
25
|
-
const jsonMode = options.format === 'json';
|
|
26
|
-
const existing = await loadCredentials();
|
|
27
|
-
if (!existing) {
|
|
28
|
-
if (jsonMode) {
|
|
29
|
-
console.log(JSON.stringify({ loggedOut: false, reason: 'no credentials' }));
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
console.log('');
|
|
33
|
-
console.log(chalk.dim(' No credentials found — already logged out.'));
|
|
34
|
-
console.log('');
|
|
35
|
-
}
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// JSON mode requires --force to skip interactive prompts
|
|
39
|
-
if (jsonMode && !options.force) {
|
|
40
|
-
console.error(JSON.stringify({ error: '--force is required with --format json' }));
|
|
41
|
-
process.exitCode = 1;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (jsonMode) {
|
|
45
|
-
// Non-interactive JSON path
|
|
46
|
-
const fullKey = existing.apiKey;
|
|
47
|
-
const storageMethod = existing.storageMethod;
|
|
48
|
-
const result = await executeLogout();
|
|
49
|
-
const output = {
|
|
50
|
-
loggedOut: true,
|
|
51
|
-
redactedKey: result.redactedKey,
|
|
52
|
-
};
|
|
53
|
-
if (options.revealKey) {
|
|
54
|
-
output.apiKey = fullKey;
|
|
55
|
-
}
|
|
56
|
-
if (!result.keychainCleared && storageMethod === 'keychain') {
|
|
57
|
-
output.keychainWarning = 'Could not remove key from OS keychain. You may need to manually remove the \'trading-boy\' entry from your keychain.';
|
|
58
|
-
}
|
|
59
|
-
console.log(JSON.stringify(output));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
// Interactive text path
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log(chalk.yellow(' Warning: Your API key will be cleared from this machine.'));
|
|
65
|
-
console.log(chalk.yellow(' You will need your API key to log back in.'));
|
|
66
|
-
console.log(chalk.yellow(' There is no way to recover a lost key — a new one must be provisioned.'));
|
|
67
|
-
console.log('');
|
|
68
|
-
const proceed = options.force || await confirm({ message: 'Are you sure you want to logout?' });
|
|
69
|
-
if (!proceed) {
|
|
70
|
-
console.log(chalk.dim(' Logout cancelled.'));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Offer to reveal the full key before deletion
|
|
74
|
-
const revealKey = await confirm({
|
|
75
|
-
message: 'Would you like to see your full API key before it\'s deleted? (This is your last chance to copy it)',
|
|
76
|
-
});
|
|
77
|
-
if (revealKey) {
|
|
78
|
-
console.log('');
|
|
79
|
-
console.log(chalk.cyan(' Your API key:'));
|
|
80
|
-
console.log(` ${chalk.bold(existing.apiKey)}`);
|
|
81
|
-
console.log('');
|
|
82
|
-
}
|
|
83
|
-
const storageMethod = existing.storageMethod;
|
|
84
|
-
const result = await executeLogout();
|
|
85
|
-
console.log('');
|
|
86
|
-
console.log(chalk.green(` Logged out successfully.`));
|
|
87
|
-
console.log(` ${chalk.gray('Cleared key:')} ${result.redactedKey}`);
|
|
88
|
-
if (!result.keychainCleared && storageMethod === 'keychain') {
|
|
89
|
-
console.log('');
|
|
90
|
-
console.log(chalk.yellow(' Warning: Could not remove key from OS keychain.'));
|
|
91
|
-
console.log(chalk.yellow(' You may need to manually remove the \'trading-boy\' entry from your keychain.'));
|
|
92
|
-
}
|
|
93
|
-
console.log('');
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
-
logger.error({ error: message }, 'Logout failed');
|
|
98
|
-
if (options.format === 'json') {
|
|
99
|
-
console.error(JSON.stringify({ error: message }));
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
console.error(chalk.red(` Error: ${message}`));
|
|
103
|
-
}
|
|
104
|
-
process.exitCode = 1;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
//# sourceMappingURL=logout.js.map
|