@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.
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 +1896 -33657
  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 -567
  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 -281
  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 -335
  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,293 +0,0 @@
1
- // ─── Connect ChatGPT Command ───
2
- //
3
- // Connects a user's ChatGPT subscription via Codex OAuth (PKCE).
4
- //
5
- // Two completion paths run in parallel (whichever wins first):
6
- // A. Local callback: localhost:1455/auth/callback catches code → POST to API
7
- // B. Polling: GET /api/v1/llm-config until codexConnected: true
8
- // (handles production redirect where API server catches the callback directly)
9
- //
10
- // Also supports disconnect via --disconnect flag.
11
- import http from 'node:http';
12
- import { URL } from 'node:url';
13
- import chalk from 'chalk';
14
- import { createLogger } from '@trading-boy/core';
15
- import { apiRequest, ApiError, isRemoteMode } from '../api-client.js';
16
- import { formatConnectionError } from '../utils.js';
17
- // ─── Logger ───
18
- const logger = createLogger('cli-connect-chatgpt');
19
- // ─── Constants ───
20
- const CALLBACK_PORT = 1455;
21
- const CALLBACK_PATH = '/auth/callback';
22
- const AUTH_TIMEOUT_MS = 3 * 60 * 1_000; // 3 minutes
23
- const POLL_INTERVAL_MS = 2_000;
24
- // ─── Command ───
25
- export function registerConnectChatgptCommand(program) {
26
- program
27
- .command('connect-chatgpt')
28
- .description('Connect your ChatGPT subscription as your LLM provider (no API key needed)')
29
- .option('--disconnect', 'Disconnect your ChatGPT account')
30
- .action(async (options) => {
31
- try {
32
- if (!(await isRemoteMode())) {
33
- console.error(chalk.yellow(' Requires API connection. Run: trading-boy login'));
34
- process.exitCode = 1;
35
- return;
36
- }
37
- if (options.disconnect) {
38
- await handleDisconnect();
39
- }
40
- else {
41
- await handleConnect();
42
- }
43
- }
44
- catch (error) {
45
- const msg = error instanceof Error ? error.message : String(error);
46
- logger.error({ error: msg }, 'connect-chatgpt failed');
47
- const connHelp = formatConnectionError(msg);
48
- if (connHelp) {
49
- console.error(chalk.red(`\n Connection failed.\n`));
50
- console.error(connHelp);
51
- }
52
- else {
53
- console.error(chalk.red(`\n Error: ${msg}`));
54
- }
55
- process.exitCode = error instanceof ApiError ? 2 : 1;
56
- }
57
- });
58
- }
59
- // ─── Connect Flow ───
60
- async function handleConnect() {
61
- // Check if already connected
62
- try {
63
- const current = await apiRequest('/api/v1/llm-config');
64
- if (current.codexConnected) {
65
- console.log('');
66
- console.log(chalk.green(' ✓ ChatGPT is already connected'));
67
- console.log(chalk.dim(` Account: ${current.codexAccountId ?? 'unknown'}`));
68
- console.log(chalk.dim(` Provider: codex / ${current.model}`));
69
- console.log('');
70
- console.log(chalk.dim(' To disconnect: trading-boy connect-chatgpt --disconnect'));
71
- return;
72
- }
73
- }
74
- catch {
75
- // No config yet — that's fine, proceed with connect
76
- }
77
- console.log('');
78
- console.log(chalk.bold.cyan(' Connect ChatGPT'));
79
- console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
80
- console.log(chalk.dim(' Use your ChatGPT subscription to power your trading agents.'));
81
- console.log(chalk.dim(' No API key needed — authenticates via your OpenAI account.'));
82
- console.log('');
83
- // Step 1: Get auth URL from API (generates PKCE + stores verifier in Redis)
84
- const { authUrl, state } = await apiRequest('/api/v1/codex/auth-url');
85
- // Step 2: Race local callback server vs polling
86
- // - Local callback wins when redirect_uri points to localhost (dev or matching Codex client)
87
- // - Polling wins when redirect_uri points to production API server
88
- const result = await raceForAuth(authUrl, state);
89
- // Step 3: Handle result
90
- if (result.via === 'local') {
91
- // Local server caught the code — forward to API for token exchange
92
- console.log(chalk.dim(' Exchanging authorization code...'));
93
- const exchangeResult = await apiRequest('/api/v1/codex/callback', {
94
- method: 'POST',
95
- body: { code: result.code, state: result.state },
96
- });
97
- printSuccess(exchangeResult.accountId, null);
98
- }
99
- else {
100
- // Polling detected connection — API server handled the callback directly
101
- printSuccess(result.config.codexAccountId, result.config.model);
102
- }
103
- }
104
- function printSuccess(accountId, model) {
105
- console.log('');
106
- console.log(chalk.green(' ✓ ChatGPT connected!'));
107
- console.log('');
108
- if (accountId)
109
- console.log(` ${chalk.gray('Account:')} ${accountId}`);
110
- console.log(` ${chalk.gray('Provider:')} codex`);
111
- if (model)
112
- console.log(` ${chalk.gray('Model:')} ${model}`);
113
- console.log('');
114
- console.log(chalk.dim(' Your agents will now use your ChatGPT subscription for LLM calls.'));
115
- console.log(chalk.dim(' Change model: trading-boy config set-llm-key --provider codex --model <model>'));
116
- }
117
- // ─── Race: Local Callback vs Polling ───
118
- async function raceForAuth(authUrl, state) {
119
- const controller = new AbortController();
120
- const localCallback = startLocalCallbackServer(state, controller.signal);
121
- const polling = pollForConnection(controller.signal);
122
- // Open browser after server is ready (small delay to ensure listen completes)
123
- console.log(chalk.white(' Opening OpenAI login in your browser...'));
124
- await openBrowser(authUrl);
125
- console.log(chalk.dim(' Complete sign-in in your browser to continue.'));
126
- console.log(chalk.dim(' Waiting for authentication...'));
127
- console.log('');
128
- try {
129
- const result = await Promise.race([localCallback, polling]);
130
- controller.abort(); // cancel the loser
131
- return result;
132
- }
133
- catch (err) {
134
- controller.abort();
135
- throw err;
136
- }
137
- }
138
- function startLocalCallbackServer(state, signal) {
139
- return new Promise((resolve, reject) => {
140
- if (signal.aborted) {
141
- reject(new Error('Cancelled'));
142
- return;
143
- }
144
- const timeout = setTimeout(() => {
145
- server.close();
146
- reject(new Error('Timed out waiting for OpenAI authentication (3 minutes)'));
147
- }, AUTH_TIMEOUT_MS);
148
- signal.addEventListener('abort', () => {
149
- clearTimeout(timeout);
150
- server.close();
151
- }, { once: true });
152
- const server = http.createServer((req, res) => {
153
- const url = new URL(req.url ?? '/', `http://localhost:${CALLBACK_PORT}`);
154
- if (url.pathname !== CALLBACK_PATH) {
155
- res.writeHead(404, { 'Content-Type': 'text/plain' });
156
- res.end('Not found');
157
- return;
158
- }
159
- const errorParam = url.searchParams.get('error');
160
- const errorDesc = url.searchParams.get('error_description');
161
- const codeParam = url.searchParams.get('code');
162
- if (errorParam) {
163
- res.writeHead(200, { 'Content-Type': 'text/html' });
164
- res.end(callbackHtml(false, errorDesc || errorParam));
165
- clearTimeout(timeout);
166
- server.close();
167
- reject(new Error(`OpenAI returned error: ${errorDesc || errorParam}`));
168
- return;
169
- }
170
- if (!codeParam) {
171
- res.writeHead(200, { 'Content-Type': 'text/html' });
172
- res.end(callbackHtml(false, 'Missing authorization code'));
173
- clearTimeout(timeout);
174
- server.close();
175
- reject(new Error('No authorization code in callback'));
176
- return;
177
- }
178
- // Success — send HTML to browser, resolve
179
- res.writeHead(200, { 'Content-Type': 'text/html' });
180
- res.end(callbackHtml(true));
181
- clearTimeout(timeout);
182
- server.close();
183
- resolve({ via: 'local', code: codeParam, state });
184
- });
185
- server.on('error', (err) => {
186
- clearTimeout(timeout);
187
- if (err.code === 'EADDRINUSE') {
188
- // Port in use — don't fail hard, let polling take over
189
- logger.debug('Port %d in use, falling back to polling only', CALLBACK_PORT);
190
- // Return a never-resolving promise (polling will win)
191
- return;
192
- }
193
- reject(err);
194
- });
195
- server.listen(CALLBACK_PORT, '127.0.0.1');
196
- });
197
- }
198
- async function pollForConnection(signal) {
199
- const deadline = Date.now() + AUTH_TIMEOUT_MS;
200
- while (Date.now() < deadline) {
201
- if (signal.aborted)
202
- throw new Error('Cancelled');
203
- await sleep(POLL_INTERVAL_MS);
204
- if (signal.aborted)
205
- throw new Error('Cancelled');
206
- try {
207
- const config = await apiRequest('/api/v1/llm-config');
208
- if (config.codexConnected) {
209
- return { via: 'poll', config };
210
- }
211
- }
212
- catch {
213
- // Poll failures are expected — API might briefly be unavailable
214
- logger.debug('Poll attempt failed, retrying...');
215
- }
216
- }
217
- throw new Error('Timed out waiting for OpenAI authentication (3 minutes)');
218
- }
219
- // ─── Disconnect Flow ───
220
- async function handleDisconnect() {
221
- const { confirm } = await import('@inquirer/prompts');
222
- const yes = await confirm({
223
- message: 'Disconnect your ChatGPT account?',
224
- default: false,
225
- });
226
- if (!yes) {
227
- console.log(chalk.dim(' Cancelled.'));
228
- return;
229
- }
230
- await apiRequest('/api/v1/codex/disconnect', {
231
- method: 'POST',
232
- });
233
- console.log(chalk.green(' ✓ ChatGPT disconnected.'));
234
- console.log(chalk.dim(' Your agents will need an API key to continue. Set one:'));
235
- console.log(chalk.dim(' trading-boy config set-llm-key <your-api-key>'));
236
- }
237
- // ─── HTML for Browser Callback ───
238
- function callbackHtml(success, errorMsg) {
239
- const title = success ? 'ChatGPT Connected' : 'Connection Failed';
240
- const icon = success ? '&#10003;' : '&#10007;';
241
- const color = success ? '#22c55e' : '#ef4444';
242
- const message = success
243
- ? 'Your ChatGPT subscription is now connected to Trading Boy.<br>You can close this tab.'
244
- : `Something went wrong: ${escapeHtml(errorMsg ?? 'Unknown error')}.<br>Please try again.`;
245
- return `<!DOCTYPE html>
246
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
247
- <title>${title} — Trading Boy</title>
248
- <style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#0a0a0a;color:#e5e5e5}
249
- .card{text-align:center;padding:3rem;max-width:480px}.icon{font-size:4rem;color:${color};margin-bottom:1rem}h1{font-size:1.5rem;margin-bottom:1rem}p{color:#a3a3a3;line-height:1.6}</style>
250
- </head><body><div class="card"><div class="icon">${icon}</div><h1>${title}</h1><p>${message}</p></div></body></html>`;
251
- }
252
- function escapeHtml(str) {
253
- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
254
- }
255
- // ─── Helpers ───
256
- function sleep(ms) {
257
- return new Promise((resolve) => setTimeout(resolve, ms));
258
- }
259
- /** Allowed domains for the OAuth browser redirect. */
260
- const ALLOWED_AUTH_DOMAINS = new Set([
261
- 'auth.openai.com',
262
- 'auth0.openai.com',
263
- 'chat.openai.com',
264
- ]);
265
- function validateAuthUrl(url) {
266
- const parsed = new URL(url);
267
- if (parsed.protocol !== 'https:') {
268
- throw new Error(`Refusing to open non-HTTPS URL: ${url}`);
269
- }
270
- if (!ALLOWED_AUTH_DOMAINS.has(parsed.hostname)) {
271
- throw new Error(`Refusing to open URL with untrusted domain: ${parsed.hostname}`);
272
- }
273
- }
274
- async function openBrowser(url) {
275
- try {
276
- validateAuthUrl(url);
277
- const { default: open } = await import('open');
278
- await open(url);
279
- }
280
- catch (err) {
281
- const message = err instanceof Error ? err.message : String(err);
282
- if (message.startsWith('Refusing to open')) {
283
- console.error(chalk.red(`\n ${message}`));
284
- throw err;
285
- }
286
- console.log(chalk.yellow(`\n Could not open browser automatically.`));
287
- console.log(chalk.yellow(` Please open this URL manually:\n`));
288
- console.log(` ${chalk.cyan.underline(url)}\n`);
289
- }
290
- }
291
- // ─── Exported for onboarding ───
292
- export { handleConnect as connectChatgpt };
293
- //# sourceMappingURL=connect-chatgpt.js.map
@@ -1,5 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerConnectClaudeCommand(program: Command): void;
3
- declare function handleConnect(): Promise<void>;
4
- export { handleConnect as connectClaude };
5
- //# sourceMappingURL=connect-claude.d.ts.map
@@ -1,280 +0,0 @@
1
- // ─── Connect Claude Command ───
2
- //
3
- // Connects a user's Claude subscription via Anthropic OAuth (PKCE).
4
- //
5
- // Two completion paths run in parallel (whichever wins first):
6
- // A. Local callback: localhost:1456/auth/callback catches code → POST to API
7
- // B. Polling: GET /api/v1/llm-config until anthropicOAuthConnected: true
8
- // (handles production redirect where API server catches the callback directly)
9
- //
10
- // Also supports disconnect via --disconnect flag.
11
- import http from 'node:http';
12
- import { URL } from 'node:url';
13
- import chalk from 'chalk';
14
- import { createLogger } from '@trading-boy/core';
15
- import { apiRequest, ApiError, isRemoteMode } from '../api-client.js';
16
- import { formatConnectionError } from '../utils.js';
17
- // ─── Logger ───
18
- const logger = createLogger('cli-connect-claude');
19
- // ─── Constants ───
20
- const CALLBACK_PORT = 1456;
21
- const CALLBACK_PATH = '/callback';
22
- const AUTH_TIMEOUT_MS = 3 * 60 * 1_000; // 3 minutes
23
- const POLL_INTERVAL_MS = 2_000;
24
- // ─── Command ───
25
- export function registerConnectClaudeCommand(program) {
26
- program
27
- .command('connect-claude')
28
- .description('Connect your Claude subscription as your LLM provider (no API key needed)')
29
- .option('--disconnect', 'Disconnect your Claude account')
30
- .action(async (options) => {
31
- try {
32
- if (!(await isRemoteMode())) {
33
- console.error(chalk.yellow(' Requires API connection. Run: trading-boy login'));
34
- process.exitCode = 1;
35
- return;
36
- }
37
- if (options.disconnect) {
38
- await handleDisconnect();
39
- }
40
- else {
41
- await handleConnect();
42
- }
43
- }
44
- catch (error) {
45
- const msg = error instanceof Error ? error.message : String(error);
46
- logger.error({ error: msg }, 'connect-claude failed');
47
- const connHelp = formatConnectionError(msg);
48
- if (connHelp) {
49
- console.error(chalk.red(`\n Connection failed.\n`));
50
- console.error(connHelp);
51
- }
52
- else {
53
- console.error(chalk.red(`\n Error: ${msg}`));
54
- }
55
- process.exitCode = error instanceof ApiError ? 2 : 1;
56
- }
57
- });
58
- }
59
- // ─── Connect Flow ───
60
- async function handleConnect() {
61
- // Check if already connected
62
- try {
63
- const current = await apiRequest('/api/v1/llm-config');
64
- if (current.anthropicOAuthConnected) {
65
- console.log('');
66
- console.log(chalk.green(' ✓ Claude is already connected'));
67
- console.log(chalk.dim(` Provider: anthropic / ${current.model}`));
68
- console.log('');
69
- console.log(chalk.dim(' To disconnect: trading-boy connect-claude --disconnect'));
70
- return;
71
- }
72
- }
73
- catch {
74
- // No config yet — that's fine, proceed with connect
75
- }
76
- console.log('');
77
- console.log(chalk.bold.cyan(' Connect Claude'));
78
- console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
79
- console.log(chalk.dim(' Use your Claude subscription to power your trading agents.'));
80
- console.log(chalk.dim(' No API key needed — authenticates via your Anthropic account.'));
81
- console.log('');
82
- // Step 1: Get auth URL from API (generates PKCE + stores verifier in Redis)
83
- const { authUrl, state } = await apiRequest('/api/v1/anthropic-oauth/auth-url');
84
- // Step 2: Race local callback server vs polling
85
- const result = await raceForAuth(authUrl, state);
86
- // Step 3: Handle result
87
- if (result.via === 'local') {
88
- console.log(chalk.dim(' Exchanging authorization code...'));
89
- await apiRequest('/api/v1/anthropic-oauth/callback', {
90
- method: 'POST',
91
- body: { code: result.code, state: result.state },
92
- });
93
- printSuccess(null);
94
- }
95
- else {
96
- printSuccess(result.config.model);
97
- }
98
- }
99
- function printSuccess(model) {
100
- console.log('');
101
- console.log(chalk.green(' ✓ Claude connected!'));
102
- console.log('');
103
- console.log(` ${chalk.gray('Provider:')} anthropic`);
104
- if (model)
105
- console.log(` ${chalk.gray('Model:')} ${model}`);
106
- console.log('');
107
- console.log(chalk.dim(' Your agents will now use your Claude subscription for LLM calls.'));
108
- console.log(chalk.dim(' Change model: trading-boy config set-llm-key --provider anthropic --model <model>'));
109
- }
110
- // ─── Race: Local Callback vs Polling ───
111
- async function raceForAuth(authUrl, state) {
112
- const controller = new AbortController();
113
- const localCallback = startLocalCallbackServer(state, controller.signal);
114
- const polling = pollForConnection(controller.signal);
115
- console.log(chalk.white(' Opening Claude login in your browser...'));
116
- await openBrowser(authUrl);
117
- console.log(chalk.dim(' Complete sign-in in your browser to continue.'));
118
- console.log(chalk.dim(' Waiting for authentication...'));
119
- console.log('');
120
- try {
121
- const result = await Promise.race([localCallback, polling]);
122
- controller.abort();
123
- return result;
124
- }
125
- catch (err) {
126
- controller.abort();
127
- throw err;
128
- }
129
- }
130
- function startLocalCallbackServer(state, signal) {
131
- return new Promise((resolve, reject) => {
132
- if (signal.aborted) {
133
- reject(new Error('Cancelled'));
134
- return;
135
- }
136
- const timeout = setTimeout(() => {
137
- server.close();
138
- reject(new Error('Timed out waiting for Claude authentication (3 minutes)'));
139
- }, AUTH_TIMEOUT_MS);
140
- signal.addEventListener('abort', () => {
141
- clearTimeout(timeout);
142
- server.close();
143
- }, { once: true });
144
- const server = http.createServer((req, res) => {
145
- const url = new URL(req.url ?? '/', `http://localhost:${CALLBACK_PORT}`);
146
- if (url.pathname !== CALLBACK_PATH) {
147
- res.writeHead(404, { 'Content-Type': 'text/plain' });
148
- res.end('Not found');
149
- return;
150
- }
151
- const errorParam = url.searchParams.get('error');
152
- const errorDesc = url.searchParams.get('error_description');
153
- const codeParam = url.searchParams.get('code');
154
- if (errorParam) {
155
- res.writeHead(200, { 'Content-Type': 'text/html' });
156
- res.end(callbackHtml(false, errorDesc || errorParam));
157
- clearTimeout(timeout);
158
- server.close();
159
- reject(new Error(`Anthropic returned error: ${errorDesc || errorParam}`));
160
- return;
161
- }
162
- if (!codeParam) {
163
- res.writeHead(200, { 'Content-Type': 'text/html' });
164
- res.end(callbackHtml(false, 'Missing authorization code'));
165
- clearTimeout(timeout);
166
- server.close();
167
- reject(new Error('No authorization code in callback'));
168
- return;
169
- }
170
- res.writeHead(200, { 'Content-Type': 'text/html' });
171
- res.end(callbackHtml(true));
172
- clearTimeout(timeout);
173
- server.close();
174
- resolve({ via: 'local', code: codeParam, state });
175
- });
176
- server.on('error', (err) => {
177
- clearTimeout(timeout);
178
- if (err.code === 'EADDRINUSE') {
179
- logger.debug('Port %d in use, falling back to polling only', CALLBACK_PORT);
180
- return;
181
- }
182
- reject(err);
183
- });
184
- server.listen(CALLBACK_PORT, '127.0.0.1');
185
- });
186
- }
187
- async function pollForConnection(signal) {
188
- const deadline = Date.now() + AUTH_TIMEOUT_MS;
189
- while (Date.now() < deadline) {
190
- if (signal.aborted)
191
- throw new Error('Cancelled');
192
- await sleep(POLL_INTERVAL_MS);
193
- if (signal.aborted)
194
- throw new Error('Cancelled');
195
- try {
196
- const config = await apiRequest('/api/v1/llm-config');
197
- if (config.anthropicOAuthConnected) {
198
- return { via: 'poll', config };
199
- }
200
- }
201
- catch {
202
- logger.debug('Poll attempt failed, retrying...');
203
- }
204
- }
205
- throw new Error('Timed out waiting for Claude authentication (3 minutes)');
206
- }
207
- // ─── Disconnect Flow ───
208
- async function handleDisconnect() {
209
- const { confirm } = await import('@inquirer/prompts');
210
- const yes = await confirm({
211
- message: 'Disconnect your Claude account?',
212
- default: false,
213
- });
214
- if (!yes) {
215
- console.log(chalk.dim(' Cancelled.'));
216
- return;
217
- }
218
- await apiRequest('/api/v1/anthropic-oauth/disconnect', {
219
- method: 'POST',
220
- });
221
- console.log(chalk.green(' ✓ Claude disconnected.'));
222
- console.log(chalk.dim(' Your agents will need an API key to continue. Set one:'));
223
- console.log(chalk.dim(' trading-boy config set-llm-key <your-api-key>'));
224
- }
225
- // ─── HTML for Browser Callback ───
226
- function callbackHtml(success, errorMsg) {
227
- const title = success ? 'Claude Connected' : 'Connection Failed';
228
- const icon = success ? '&#10003;' : '&#10007;';
229
- const color = success ? '#22c55e' : '#ef4444';
230
- const message = success
231
- ? 'Your Claude subscription is now connected to Trading Boy.<br>You can close this tab.'
232
- : `Something went wrong: ${escapeHtml(errorMsg ?? 'Unknown error')}.<br>Please try again.`;
233
- return `<!DOCTYPE html>
234
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
235
- <title>${title} — Trading Boy</title>
236
- <style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#0a0a0a;color:#e5e5e5}
237
- .card{text-align:center;padding:3rem;max-width:480px}.icon{font-size:4rem;color:${color};margin-bottom:1rem}h1{font-size:1.5rem;margin-bottom:1rem}p{color:#a3a3a3;line-height:1.6}</style>
238
- </head><body><div class="card"><div class="icon">${icon}</div><h1>${title}</h1><p>${message}</p></div></body></html>`;
239
- }
240
- function escapeHtml(str) {
241
- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
242
- }
243
- // ─── Helpers ───
244
- function sleep(ms) {
245
- return new Promise((resolve) => setTimeout(resolve, ms));
246
- }
247
- const ALLOWED_AUTH_DOMAINS = new Set([
248
- 'platform.claude.com',
249
- 'claude.ai',
250
- 'claude.com',
251
- ]);
252
- function validateAuthUrl(url) {
253
- const parsed = new URL(url);
254
- if (parsed.protocol !== 'https:') {
255
- throw new Error(`Refusing to open non-HTTPS URL: ${url}`);
256
- }
257
- if (!ALLOWED_AUTH_DOMAINS.has(parsed.hostname)) {
258
- throw new Error(`Refusing to open URL with untrusted domain: ${parsed.hostname}`);
259
- }
260
- }
261
- async function openBrowser(url) {
262
- try {
263
- validateAuthUrl(url);
264
- const { default: open } = await import('open');
265
- await open(url);
266
- }
267
- catch (err) {
268
- const message = err instanceof Error ? err.message : String(err);
269
- if (message.startsWith('Refusing to open')) {
270
- console.error(chalk.red(`\n ${message}`));
271
- throw err;
272
- }
273
- console.log(chalk.yellow(`\n Could not open browser automatically.`));
274
- console.log(chalk.yellow(` Please open this URL manually:\n`));
275
- console.log(` ${chalk.cyan.underline(url)}\n`);
276
- }
277
- }
278
- // ─── Exported for onboarding ───
279
- export { handleConnect as connectClaude };
280
- //# sourceMappingURL=connect-claude.js.map
@@ -1,41 +0,0 @@
1
- import { Command } from 'commander';
2
- import type { AssembledContextPackage, MarketData, OnchainData, DefiRiskProfile, Catalysts, SocialContext, Synthesis, ContextMeta } from '@trading-boy/core';
3
- /**
4
- * Format the market section of a ContextPackage.
5
- */
6
- export declare function formatMarketSection(market: MarketData | null): string;
7
- /**
8
- * Format the onchain section of a ContextPackage.
9
- */
10
- export declare function formatOnchainSection(onchain: OnchainData | null): string;
11
- /**
12
- * Format the DeFi risk section of a ContextPackage.
13
- */
14
- export declare function formatRiskSection(defiRisk: DefiRiskProfile | null): string;
15
- /**
16
- * Format the catalysts section of a ContextPackage.
17
- */
18
- export declare function formatCatalystsSection(catalysts: Catalysts | null): string;
19
- /**
20
- * Format the social section of a ContextPackage.
21
- */
22
- export declare function formatSocialSection(social: SocialContext | null): string;
23
- /**
24
- * Format the synthesis section of a ContextPackage.
25
- */
26
- export declare function formatSynthesisSection(synthesis: Synthesis | null): string;
27
- /**
28
- * Format the _meta section of a ContextPackage.
29
- */
30
- export declare function formatMetaSection(meta: ContextMeta): string;
31
- /**
32
- * Format a full AssembledContextPackage for colored terminal output.
33
- */
34
- export declare function formatContextOutput(pkg: AssembledContextPackage): string;
35
- export declare function formatSnapshotRange(snapshots: Array<{
36
- time: string;
37
- regime: string | null;
38
- context: AssembledContextPackage;
39
- }>, symbol: string): string;
40
- export declare function registerContextCommand(program: Command): void;
41
- //# sourceMappingURL=context.d.ts.map