@trading-boy/cli 1.11.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.
Files changed (83) hide show
  1. package/README.md +50 -22
  2. package/dist/api-client.d.ts +4 -7
  3. package/dist/api-client.js +8 -13
  4. package/dist/cli.bundle.js +1977 -33976
  5. package/dist/credentials.js +1 -1
  6. package/dist/index.d.ts +0 -28
  7. package/dist/index.js +0 -24
  8. package/dist/logger.d.ts +8 -0
  9. package/dist/logger.js +12 -0
  10. package/dist/utils.js +3 -3
  11. package/package.json +20 -5
  12. package/dist/cli.d.ts +0 -5
  13. package/dist/cli.js +0 -157
  14. package/dist/commands/agent-cmd.d.ts +0 -9
  15. package/dist/commands/agent-cmd.js +0 -572
  16. package/dist/commands/audit.d.ts +0 -18
  17. package/dist/commands/audit.js +0 -73
  18. package/dist/commands/behavioral.d.ts +0 -73
  19. package/dist/commands/behavioral.js +0 -349
  20. package/dist/commands/benchmark-cmd.d.ts +0 -3
  21. package/dist/commands/benchmark-cmd.js +0 -191
  22. package/dist/commands/billing.d.ts +0 -12
  23. package/dist/commands/billing.js +0 -142
  24. package/dist/commands/catalysts.d.ts +0 -17
  25. package/dist/commands/catalysts.js +0 -151
  26. package/dist/commands/coaching-cmd.d.ts +0 -16
  27. package/dist/commands/coaching-cmd.js +0 -222
  28. package/dist/commands/config-cmd.d.ts +0 -30
  29. package/dist/commands/config-cmd.js +0 -515
  30. package/dist/commands/connect-chatgpt.d.ts +0 -5
  31. package/dist/commands/connect-chatgpt.js +0 -293
  32. package/dist/commands/connect-claude.d.ts +0 -5
  33. package/dist/commands/connect-claude.js +0 -280
  34. package/dist/commands/context.d.ts +0 -41
  35. package/dist/commands/context.js +0 -405
  36. package/dist/commands/cron-cmd.d.ts +0 -3
  37. package/dist/commands/cron-cmd.js +0 -305
  38. package/dist/commands/decisions.d.ts +0 -57
  39. package/dist/commands/decisions.js +0 -364
  40. package/dist/commands/edge-cmd.d.ts +0 -78
  41. package/dist/commands/edge-cmd.js +0 -183
  42. package/dist/commands/edge-guard-cmd.d.ts +0 -36
  43. package/dist/commands/edge-guard-cmd.js +0 -169
  44. package/dist/commands/events.d.ts +0 -3
  45. package/dist/commands/events.js +0 -117
  46. package/dist/commands/infra.d.ts +0 -24
  47. package/dist/commands/infra.js +0 -137
  48. package/dist/commands/journal.d.ts +0 -3
  49. package/dist/commands/journal.js +0 -302
  50. package/dist/commands/login.d.ts +0 -18
  51. package/dist/commands/login.js +0 -127
  52. package/dist/commands/logout.d.ts +0 -8
  53. package/dist/commands/logout.js +0 -108
  54. package/dist/commands/narratives.d.ts +0 -3
  55. package/dist/commands/narratives.js +0 -259
  56. package/dist/commands/onboarding.d.ts +0 -7
  57. package/dist/commands/onboarding.js +0 -298
  58. package/dist/commands/query.d.ts +0 -32
  59. package/dist/commands/query.js +0 -135
  60. package/dist/commands/replay-cmd.d.ts +0 -43
  61. package/dist/commands/replay-cmd.js +0 -184
  62. package/dist/commands/review.d.ts +0 -3
  63. package/dist/commands/review.js +0 -443
  64. package/dist/commands/risk.d.ts +0 -47
  65. package/dist/commands/risk.js +0 -158
  66. package/dist/commands/social.d.ts +0 -43
  67. package/dist/commands/social.js +0 -318
  68. package/dist/commands/soul-wizard.d.ts +0 -29
  69. package/dist/commands/soul-wizard.js +0 -155
  70. package/dist/commands/strategy-cmd.d.ts +0 -44
  71. package/dist/commands/strategy-cmd.js +0 -340
  72. package/dist/commands/subscribe.d.ts +0 -78
  73. package/dist/commands/subscribe.js +0 -552
  74. package/dist/commands/suggestions-cmd.d.ts +0 -24
  75. package/dist/commands/suggestions-cmd.js +0 -148
  76. package/dist/commands/thesis-cmd.d.ts +0 -3
  77. package/dist/commands/thesis-cmd.js +0 -129
  78. package/dist/commands/trader.d.ts +0 -30
  79. package/dist/commands/trader.js +0 -971
  80. package/dist/commands/watch.d.ts +0 -16
  81. package/dist/commands/watch.js +0 -104
  82. package/dist/commands/whoami.d.ts +0 -14
  83. package/dist/commands/whoami.js +0 -105
@@ -1,552 +0,0 @@
1
- // ─── Subscribe Command ───
2
- //
3
- // Handles the checkout flow for new subscribers:
4
- //
5
- // Stripe (default):
6
- // 1. POST /api/v1/billing/checkout with user email
7
- // 2. Open browser to Stripe Checkout
8
- // 3. Poll GET /api/v1/billing/provision/:token every 3s until API key is returned
9
- // 4. Store key locally via credential storage
10
- //
11
- // Crypto (--pay crypto):
12
- // 1. POST /billing/crypto-checkout with user email
13
- // 2. Display USDC payment instructions (wallet, amount, network)
14
- // 3. Poll GET /billing/crypto-verify/:reference every 5s until confirmed
15
- // 4. Poll GET /api/v1/billing/provision/:token for API key
16
- // 5. Store key locally via credential storage
17
- import chalk from 'chalk';
18
- import { createLogger } from '@trading-boy/core';
19
- import { storeCredentials } from '../credentials.js';
20
- import { getApiBase } from '../api-client.js';
21
- import { validateApiKeyFormat } from './login.js';
22
- import { runOnboarding } from './onboarding.js';
23
- // ─── Logger ───
24
- const logger = createLogger('cli-subscribe');
25
- // ─── Constants ───
26
- const POLL_INTERVAL_MS = 3_000;
27
- const POLL_TIMEOUT_MS = 5 * 60 * 1_000; // 5 minutes
28
- // ─── API Helpers ───
29
- /**
30
- * Create a Stripe Checkout session via the API.
31
- * This is a public endpoint — no auth needed.
32
- */
33
- export async function createCheckoutSession(email, plan = 'starter') {
34
- const response = await fetch(`${getApiBase()}/api/v1/billing/checkout`, {
35
- method: 'POST',
36
- headers: { 'Content-Type': 'application/json' },
37
- body: JSON.stringify({ email, plan }),
38
- });
39
- if (!response.ok) {
40
- const body = await response.text();
41
- throw new Error(`Checkout request failed (${response.status}): ${body}`);
42
- }
43
- return response.json();
44
- }
45
- /**
46
- * Poll the provisioning endpoint for the API key.
47
- * Returns normalized ProvisionResponse regardless of HTTP status.
48
- */
49
- export async function pollProvisioningToken(token) {
50
- const response = await fetch(`${getApiBase()}/api/v1/billing/provision/${encodeURIComponent(token)}`);
51
- if (response.status === 404) {
52
- return { status: 'not_found' };
53
- }
54
- if (response.status === 410) {
55
- const body = await response.json();
56
- return { status: body.status === 'already_retrieved' ? 'already_retrieved' : 'expired' };
57
- }
58
- if (!response.ok) {
59
- throw new Error(`Provision check failed (${response.status})`);
60
- }
61
- return response.json();
62
- }
63
- /**
64
- * Create a crypto (USDC) checkout session via the API.
65
- * Public endpoint — no auth needed.
66
- */
67
- export async function createCryptoCheckout(email, plan = 'starter') {
68
- const response = await fetch(`${getApiBase()}/billing/crypto-checkout`, {
69
- method: 'POST',
70
- headers: { 'Content-Type': 'application/json' },
71
- body: JSON.stringify({ email, plan }),
72
- });
73
- if (!response.ok) {
74
- const body = await response.text();
75
- throw new Error(`Crypto checkout failed (${response.status}): ${body}`);
76
- }
77
- return response.json();
78
- }
79
- /**
80
- * Check if a USDC payment has been confirmed on Solana.
81
- */
82
- export async function checkCryptoPayment(reference) {
83
- const response = await fetch(`${getApiBase()}/billing/crypto-verify/${encodeURIComponent(reference)}`);
84
- if (response.status === 404)
85
- return { status: 'not_found' };
86
- if (response.status === 410)
87
- return { status: 'expired' };
88
- if (!response.ok)
89
- throw new Error(`Crypto verify failed (${response.status})`);
90
- return response.json();
91
- }
92
- /**
93
- * Poll until USDC payment is confirmed on Solana, then poll for provisioned API key.
94
- */
95
- export async function pollCryptoPayment(reference, provisioningToken, onTick) {
96
- const start = Date.now();
97
- const CRYPTO_POLL_INTERVAL = 5_000; // 5s — Solana confirmation takes a few seconds
98
- // Phase 1: Wait for Solana payment confirmation
99
- while (Date.now() - start < POLL_TIMEOUT_MS) {
100
- onTick?.(Date.now() - start, 'payment');
101
- try {
102
- const result = await checkCryptoPayment(reference);
103
- if (result.status === 'confirmed')
104
- break;
105
- if (result.status === 'expired' || result.status === 'not_found') {
106
- return { success: false, error: 'Payment expired or not found. Please try again.' };
107
- }
108
- }
109
- catch (err) {
110
- logger.debug({ error: err }, 'Crypto poll attempt failed, retrying');
111
- }
112
- await sleep(CRYPTO_POLL_INTERVAL);
113
- }
114
- if (Date.now() - start >= POLL_TIMEOUT_MS) {
115
- return { success: false, error: 'Timed out waiting for USDC payment. If you sent the payment, contact support.' };
116
- }
117
- // Phase 2: Payment confirmed — poll for provisioned API key
118
- while (Date.now() - start < POLL_TIMEOUT_MS) {
119
- onTick?.(Date.now() - start, 'provisioning');
120
- try {
121
- const result = await pollProvisioningToken(provisioningToken);
122
- if (result.status === 'ready' && result.apiKey) {
123
- return { success: true, apiKey: result.apiKey, keyPrefix: result.keyPrefix, email: result.email, plan: result.plan };
124
- }
125
- if (result.status === 'expired' || result.status === 'not_found') {
126
- return { success: false, error: 'Provisioning token expired after payment. Contact support.' };
127
- }
128
- }
129
- catch (err) {
130
- logger.debug({ error: err }, 'Provision poll attempt failed, retrying');
131
- }
132
- await sleep(POLL_INTERVAL_MS);
133
- }
134
- return { success: false, error: 'Timed out waiting for API key provisioning. Contact support.' };
135
- }
136
- // ─── Polling Logic ───
137
- /**
138
- * Poll the provisioning endpoint until a key is ready or timeout.
139
- */
140
- export async function pollForApiKey(token, onTick) {
141
- const start = Date.now();
142
- let elapsed = Date.now() - start;
143
- while (elapsed < POLL_TIMEOUT_MS) {
144
- onTick?.(elapsed);
145
- try {
146
- const result = await pollProvisioningToken(token);
147
- if (result.status === 'ready' && result.apiKey) {
148
- return {
149
- success: true,
150
- apiKey: result.apiKey,
151
- keyPrefix: result.keyPrefix,
152
- email: result.email,
153
- plan: result.plan,
154
- };
155
- }
156
- if (result.status === 'expired' || result.status === 'not_found') {
157
- return {
158
- success: false,
159
- error: 'Provisioning token expired. Your API key will be emailed to you shortly. Then run: `trading-boy login`',
160
- };
161
- }
162
- if (result.status === 'already_retrieved') {
163
- return {
164
- success: false,
165
- error: 'API key was already retrieved. Use `trading-boy login` if you need to re-enter your key.',
166
- };
167
- }
168
- // status === 'pending' — keep polling
169
- }
170
- catch (err) {
171
- logger.debug({ error: err }, 'Poll attempt failed, retrying');
172
- }
173
- await sleep(POLL_INTERVAL_MS);
174
- elapsed = Date.now() - start;
175
- }
176
- return {
177
- success: false,
178
- error: 'Timed out waiting for payment confirmation. Your API key will be emailed to you shortly. Then run: `trading-boy login`',
179
- };
180
- }
181
- // ─── Credential Storage ───
182
- /**
183
- * Save API credentials using the credential storage module.
184
- */
185
- export async function saveApiKey(apiKey, metadata) {
186
- if (!validateApiKeyFormat(apiKey)) {
187
- throw new Error('Server returned invalid API key format. Key not stored.');
188
- }
189
- await storeCredentials(apiKey, {
190
- keyId: metadata.keyPrefix,
191
- email: metadata.email,
192
- plan: metadata.plan,
193
- });
194
- }
195
- // ─── Formatters ───
196
- function formatElapsed(ms) {
197
- const seconds = Math.floor(ms / 1000);
198
- const minutes = Math.floor(seconds / 60);
199
- const remaining = seconds % 60;
200
- if (minutes > 0)
201
- return `${minutes}m ${remaining}s`;
202
- return `${seconds}s`;
203
- }
204
- export function formatSubscribeSuccess(result) {
205
- const lines = [];
206
- lines.push('');
207
- lines.push(chalk.green.bold(' Subscription activated!'));
208
- lines.push(chalk.gray(' ' + '\u2500'.repeat(50)));
209
- lines.push('');
210
- if (result.email) {
211
- lines.push(` ${chalk.bold('Account:')} ${result.email}`);
212
- }
213
- if (result.plan) {
214
- lines.push(` ${chalk.bold('Plan:')} ${result.plan}`);
215
- }
216
- if (result.apiKey) {
217
- lines.push(` ${chalk.bold('API Key:')} ${chalk.yellow(result.apiKey)}`);
218
- lines.push('');
219
- lines.push(chalk.dim(' ⚠️ Copy this key now — it will not be shown again.'));
220
- lines.push(chalk.dim(' Connect Telegram: ') + chalk.dim.underline('https://t.me/TradingBoy1_Bot'));
221
- lines.push(chalk.dim(' Send /start and paste this key when prompted.'));
222
- }
223
- else if (result.keyPrefix) {
224
- lines.push(` ${chalk.bold('Key ID:')} ${result.keyPrefix}`);
225
- }
226
- lines.push('');
227
- lines.push(chalk.dim(' Your API key has been stored locally.'));
228
- lines.push(chalk.dim(' You can also view it anytime: trading-boy whoami --show-key'));
229
- lines.push('');
230
- lines.push(chalk.cyan(' Try it: trading-boy context SOL'));
231
- lines.push('');
232
- return lines.join('\n');
233
- }
234
- // ─── Command Registration ───
235
- export function registerSubscribeCommand(program) {
236
- program
237
- .command('subscribe')
238
- .description('Subscribe to Trading Boy and get your API key')
239
- .requiredOption('-e, --email <email>', 'Your email address')
240
- .option('--plan <plan>', 'Subscription plan: starter, pro, or edge')
241
- .option('--pay <method>', 'Payment method: crypto (USDC on Solana) or stripe')
242
- .action(async (options) => {
243
- const { email } = options;
244
- let { plan, pay } = options;
245
- if (!isValidEmail(email)) {
246
- console.error(chalk.red(' Invalid email address.'));
247
- process.exitCode = 1;
248
- return;
249
- }
250
- try {
251
- const { select } = await import('@inquirer/prompts');
252
- console.log('');
253
- console.log(chalk.bold.cyan(' Trading Boy \u2014 Subscribe'));
254
- console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
255
- console.log('');
256
- // Interactive plan picker if not specified
257
- if (!plan) {
258
- plan = await select({
259
- message: 'Choose your plan',
260
- choices: [
261
- { name: 'Starter — Free (core context + journal)', value: 'starter' },
262
- { name: 'Pro — $29/mo (+ coaching + thesis extraction)', value: 'pro' },
263
- { name: 'Edge — $99/mo (+ edge analysis + full analytics)', value: 'edge' },
264
- ],
265
- });
266
- }
267
- const validPlans = ['starter', 'pro', 'edge'];
268
- if (!validPlans.includes(plan)) {
269
- console.error(chalk.red(` Invalid plan: ${plan}. Choose from: ${validPlans.join(', ')}`));
270
- process.exitCode = 1;
271
- return;
272
- }
273
- const planLabels = { starter: 'Starter (Free)', pro: 'Pro ($29/mo)', edge: 'Edge ($99/mo)' };
274
- // Free tier — skip payment entirely
275
- if (plan === 'starter') {
276
- console.log('');
277
- console.log(` ${chalk.bold('Plan:')} ${planLabels[plan]}`);
278
- console.log('');
279
- await handleFreeRegistration(email);
280
- return;
281
- }
282
- // Paid plans — pick payment method
283
- if (!pay) {
284
- pay = await select({
285
- message: 'How would you like to pay?',
286
- choices: [
287
- { name: 'USDC on Solana (instant, no fees)', value: 'crypto' },
288
- { name: 'Credit card via Stripe', value: 'stripe' },
289
- ],
290
- });
291
- }
292
- const validPayMethods = ['stripe', 'crypto'];
293
- if (!validPayMethods.includes(pay)) {
294
- console.error(chalk.red(` Invalid payment method: ${pay}. Choose: stripe, crypto`));
295
- process.exitCode = 1;
296
- return;
297
- }
298
- console.log('');
299
- console.log(` ${chalk.bold('Plan:')} ${planLabels[plan] ?? plan}`);
300
- console.log(` ${chalk.bold('Pay:')} ${pay === 'crypto' ? 'USDC on Solana' : 'Stripe'}`);
301
- console.log('');
302
- if (pay === 'crypto') {
303
- await handleCryptoCheckout(email, plan);
304
- }
305
- else {
306
- await handleStripeCheckout(email, plan);
307
- }
308
- }
309
- catch (error) {
310
- // Handle Ctrl+C gracefully during prompts
311
- if (error instanceof Error && error.message.includes('User force closed')) {
312
- console.log('');
313
- return;
314
- }
315
- const message = error instanceof Error ? error.message : String(error);
316
- logger.error({ error: message }, 'Subscribe command failed');
317
- console.error(chalk.red(` Error: ${message}`));
318
- process.exitCode = 1;
319
- }
320
- });
321
- }
322
- async function handleFreeRegistration(email) {
323
- console.log(chalk.white(' Creating your free account...'));
324
- let result;
325
- try {
326
- const response = await fetch(`${getApiBase()}/api/v1/auth/register`, {
327
- method: 'POST',
328
- headers: { 'Content-Type': 'application/json' },
329
- body: JSON.stringify({ email }),
330
- });
331
- if (!response.ok) {
332
- const body = await response.text();
333
- throw new Error(`Registration failed (${response.status}): ${body}`);
334
- }
335
- result = await response.json();
336
- }
337
- catch (err) {
338
- const message = err instanceof Error ? err.message : String(err);
339
- console.error(chalk.red(` Failed to register: ${message}`));
340
- const hint = formatConnectionError(message);
341
- if (hint)
342
- console.error(hint);
343
- process.exitCode = 1;
344
- return;
345
- }
346
- await storeAndDisplayKey({
347
- success: true,
348
- apiKey: result.apiKey,
349
- keyPrefix: result.keyPrefix,
350
- email: result.email,
351
- plan: result.plan,
352
- });
353
- }
354
- // ─── Checkout Flows ───
355
- async function handleStripeCheckout(email, plan) {
356
- // Step 1: Create checkout session
357
- let checkout;
358
- try {
359
- checkout = await createCheckoutSession(email, plan);
360
- }
361
- catch (err) {
362
- const message = err instanceof Error ? err.message : String(err);
363
- console.error(chalk.red(` Failed to create checkout session: ${message}`));
364
- const hint = formatConnectionError(message);
365
- if (hint)
366
- console.error(hint);
367
- process.exitCode = 1;
368
- return;
369
- }
370
- // Step 2: Open browser
371
- console.log(chalk.white(' Opening Stripe Checkout in your browser...'));
372
- await openBrowser(checkout.checkoutUrl);
373
- console.log(chalk.dim(' Complete payment in your browser to continue.'));
374
- console.log('');
375
- // Step 3: Poll for API key
376
- let pollResult;
377
- try {
378
- const { createSpinner } = await import('../utils.js');
379
- const spinner = (await createSpinner(' Waiting for payment confirmation...')).start();
380
- pollResult = await pollForApiKey(checkout.provisioningToken, (elapsed) => {
381
- spinner.text = ` Waiting for payment confirmation... (${formatElapsed(elapsed)})`;
382
- });
383
- if (pollResult.success) {
384
- spinner.succeed(' Payment confirmed!');
385
- }
386
- else {
387
- spinner.fail(' Payment not received');
388
- }
389
- }
390
- catch {
391
- // ora not available — simple polling
392
- console.log(chalk.gray(' Waiting for payment confirmation...'));
393
- pollResult = await pollForApiKey(checkout.provisioningToken);
394
- }
395
- if (!pollResult.success) {
396
- console.error('');
397
- console.error(chalk.red(` ${pollResult.error}`));
398
- process.exitCode = 1;
399
- return;
400
- }
401
- await storeAndDisplayKey(pollResult);
402
- }
403
- async function handleCryptoCheckout(email, plan) {
404
- // Step 1: Create crypto checkout
405
- let checkout;
406
- try {
407
- checkout = await createCryptoCheckout(email, plan);
408
- }
409
- catch (err) {
410
- const message = err instanceof Error ? err.message : String(err);
411
- console.error(chalk.red(` Failed to create crypto checkout: ${message}`));
412
- const hint = formatConnectionError(message);
413
- if (hint)
414
- console.error(hint);
415
- process.exitCode = 1;
416
- return;
417
- }
418
- // Step 2: Display QR code + payment instructions
419
- console.log(chalk.bold.white(' Scan with Phantom or any Solana wallet to pay:'));
420
- console.log('');
421
- try {
422
- const { createRequire } = await import('node:module');
423
- const require = createRequire(import.meta.url);
424
- const qrcode = require('qrcode-terminal');
425
- await new Promise((resolve) => {
426
- qrcode.generate(checkout.solanaPayUrl, { small: true }, (code) => {
427
- for (const line of code.split('\n')) {
428
- console.log(` ${line}`);
429
- }
430
- resolve();
431
- });
432
- });
433
- }
434
- catch {
435
- console.log(` ${chalk.cyan.underline(checkout.solanaPayUrl)}`);
436
- }
437
- console.log('');
438
- console.log(` ${chalk.bold('To:')} ${chalk.green(checkout.merchantWallet)}`);
439
- console.log(` ${chalk.bold('Amount:')} ${checkout.amountUsdc} USDC`);
440
- console.log(` ${chalk.bold('Network:')} Solana (mainnet)`);
441
- console.log(` ${chalk.bold('Token:')} USDC (SPL)`);
442
- console.log('');
443
- console.log(chalk.dim(' Payment expires in 30 minutes.'));
444
- console.log('');
445
- // Step 3: Poll for payment + provisioning
446
- let pollResult;
447
- try {
448
- const { createSpinner } = await import('../utils.js');
449
- const spinner = (await createSpinner(' Waiting for USDC payment on Solana...')).start();
450
- pollResult = await pollCryptoPayment(checkout.reference, checkout.provisioningToken, (elapsed, phase) => {
451
- if (phase === 'payment') {
452
- spinner.text = ` Waiting for USDC payment on Solana... (${formatElapsed(elapsed)})`;
453
- }
454
- else {
455
- spinner.text = ` Payment confirmed! Provisioning API key... (${formatElapsed(elapsed)})`;
456
- }
457
- });
458
- if (pollResult.success) {
459
- spinner.succeed(' Payment confirmed & API key provisioned!');
460
- }
461
- else {
462
- spinner.fail(' Payment not received');
463
- }
464
- }
465
- catch {
466
- console.log(chalk.gray(' Waiting for USDC payment on Solana...'));
467
- pollResult = await pollCryptoPayment(checkout.reference, checkout.provisioningToken);
468
- }
469
- if (!pollResult.success) {
470
- console.error('');
471
- console.error(chalk.red(` ${pollResult.error}`));
472
- process.exitCode = 1;
473
- return;
474
- }
475
- await storeAndDisplayKey(pollResult);
476
- }
477
- async function storeAndDisplayKey(pollResult) {
478
- try {
479
- await saveApiKey(pollResult.apiKey, {
480
- keyPrefix: pollResult.keyPrefix,
481
- email: pollResult.email,
482
- plan: pollResult.plan,
483
- });
484
- }
485
- catch (err) {
486
- console.error('');
487
- console.error(chalk.yellow(' Warning: Could not save API key automatically.'));
488
- console.error(chalk.yellow(` Your API key: ${pollResult.apiKey}`));
489
- console.error(chalk.yellow(' Save this key and run: trading-boy login --api-key <key>'));
490
- logger.error({ error: err }, 'Failed to save credentials');
491
- process.exitCode = 1;
492
- return;
493
- }
494
- console.log(formatSubscribeSuccess(pollResult));
495
- // Run onboarding wizard for new subscribers
496
- try {
497
- await runOnboarding();
498
- }
499
- catch {
500
- // Onboarding is best-effort — don't fail the subscribe command
501
- }
502
- }
503
- // ─── Helpers ───
504
- function isValidEmail(email) {
505
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
506
- }
507
- function sleep(ms) {
508
- return new Promise((resolve) => setTimeout(resolve, ms));
509
- }
510
- /** Allowed domains for browser-opened URLs. */
511
- const ALLOWED_CHECKOUT_DOMAINS = new Set([
512
- 'checkout.stripe.com',
513
- 'billing.stripe.com',
514
- 'api.cabal.ventures',
515
- ]);
516
- function validateBrowserUrl(url) {
517
- const parsed = new URL(url);
518
- if (parsed.protocol !== 'https:') {
519
- throw new Error(`Refusing to open non-HTTPS URL: ${url}`);
520
- }
521
- if (!ALLOWED_CHECKOUT_DOMAINS.has(parsed.hostname)) {
522
- throw new Error(`Refusing to open URL with untrusted domain: ${parsed.hostname}`);
523
- }
524
- }
525
- async function openBrowser(url) {
526
- try {
527
- validateBrowserUrl(url);
528
- const { default: open } = await import('open');
529
- await open(url);
530
- }
531
- catch (err) {
532
- const message = err instanceof Error ? err.message : String(err);
533
- if (message.startsWith('Refusing to open')) {
534
- console.error(chalk.red(`\n ${message}`));
535
- return;
536
- }
537
- console.log(chalk.yellow(`\n Could not open browser automatically.`));
538
- console.log(chalk.yellow(` Please open this URL manually:\n`));
539
- console.log(` ${chalk.cyan.underline(url)}\n`);
540
- }
541
- }
542
- function formatConnectionError(message) {
543
- const lower = message.toLowerCase();
544
- if (lower.includes('econnrefused') || lower.includes('fetch failed') || lower.includes('enotfound')) {
545
- return [
546
- chalk.dim(' \u2192 Is the API server running? Try: pnpm start:api'),
547
- chalk.dim(' \u2192 Or set TRADING_BOY_API_URL to your API endpoint'),
548
- ].join('\n');
549
- }
550
- return null;
551
- }
552
- //# sourceMappingURL=subscribe.js.map
@@ -1,24 +0,0 @@
1
- import { Command } from 'commander';
2
- interface Suggestion {
3
- id: string;
4
- strategy_id: string;
5
- parameter: string;
6
- current_value: unknown;
7
- suggested_value: unknown;
8
- reason: string;
9
- confidence: number;
10
- status: string;
11
- created_at: string;
12
- resolved_at: string | null;
13
- resolved_by: string | null;
14
- }
15
- interface ListResponse {
16
- count: number;
17
- limit: number;
18
- offset: number;
19
- suggestions: Suggestion[];
20
- }
21
- export declare function formatSuggestionsList(data: ListResponse): string;
22
- export declare function registerSuggestionsCommand(program: Command): void;
23
- export {};
24
- //# sourceMappingURL=suggestions-cmd.d.ts.map