@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,515 +0,0 @@
1
- import { Option } from 'commander';
2
- import chalk from 'chalk';
3
- import { createLogger } from '@trading-boy/core';
4
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
5
- import { resolve } from 'node:path';
6
- import { apiRequest, ApiError } from '../api-client.js';
7
- import { padRight } from '../utils.js';
8
- // ─── Logger ───
9
- const logger = createLogger('cli-config');
10
- // ─── Sensitive Keys ───
11
- /** Keys whose values should be redacted in `config show` output. */
12
- const SENSITIVE_KEYS = new Set([
13
- 'NEO4J_PASSWORD',
14
- 'TIMESCALE_PASSWORD',
15
- 'REDIS_PASSWORD',
16
- 'HELIUS_API_KEY',
17
- 'COINGECKO_API_KEY',
18
- 'ALCHEMY_API_KEY',
19
- 'GLASSNODE_API_KEY',
20
- 'TWITTER_BEARER_TOKEN',
21
- 'ANTHROPIC_API_KEY',
22
- 'LLM_API_KEY',
23
- 'LLM_FALLBACK_API_KEY',
24
- 'BELIEF_HMAC_KEY',
25
- 'STRIPE_SECRET_KEY',
26
- 'STRIPE_WEBHOOK_SECRET',
27
- 'TELEGRAM_BOT_TOKEN',
28
- 'RESEND_API_KEY',
29
- 'SMTP_PASS',
30
- 'API_KEY_HASHES',
31
- 'LLM_ENCRYPTION_KEY',
32
- ]);
33
- /** Valid config keys (from the Zod schema). */
34
- const VALID_KEYS = [
35
- 'NODE_ENV',
36
- 'LOG_LEVEL',
37
- 'NEO4J_URI',
38
- 'NEO4J_USER',
39
- 'NEO4J_PASSWORD',
40
- 'TIMESCALE_HOST',
41
- 'TIMESCALE_PORT',
42
- 'TIMESCALE_USER',
43
- 'TIMESCALE_PASSWORD',
44
- 'TIMESCALE_DB',
45
- 'REDIS_URL',
46
- 'REDIS_PASSWORD',
47
- 'HELIUS_API_KEY',
48
- 'COINGECKO_API_KEY',
49
- 'ALCHEMY_API_KEY',
50
- 'GLASSNODE_API_KEY',
51
- 'TWITTER_BEARER_TOKEN',
52
- 'ANTHROPIC_API_KEY',
53
- 'LLM_PROVIDER',
54
- 'LLM_API_KEY',
55
- 'LLM_BASE_URL',
56
- 'LLM_MODEL',
57
- 'LLM_FALLBACK_PROVIDER',
58
- 'LLM_FALLBACK_MODEL',
59
- 'LLM_FALLBACK_API_KEY',
60
- 'LLM_MAX_RETRIES',
61
- 'LLM_TIMEOUT_MS',
62
- 'LLM_ENCRYPTION_KEY',
63
- 'BELIEF_HMAC_KEY',
64
- 'ENABLE_LLM',
65
- 'ENABLE_LEARNING',
66
- 'ENABLE_SOCIAL',
67
- 'API_PORT',
68
- 'TELEGRAM_BOT_TOKEN',
69
- 'TELEGRAM_ALLOWED_CHAT_IDS',
70
- 'RESEND_API_KEY',
71
- 'EMAIL_FROM_ADDRESS',
72
- 'EMAIL_FROM_NAME',
73
- 'SMTP_HOST',
74
- 'SMTP_PORT',
75
- 'SMTP_USER',
76
- 'SMTP_PASS',
77
- 'NOTIFICATION_FROM_EMAIL',
78
- 'STRIPE_SECRET_KEY',
79
- 'STRIPE_WEBHOOK_SECRET',
80
- 'STRIPE_PRICE_ID_STARTER',
81
- 'STRIPE_PRICE_ID_PRO',
82
- 'STRIPE_PRICE_ID_EDGE',
83
- 'APP_URL',
84
- 'API_KEY_HASHES',
85
- 'API_KEY_AUTH_ENABLED',
86
- ];
87
- // ─── Formatters ───
88
- /**
89
- * Redact a config value if it's a sensitive key.
90
- * Shows first 3 chars + '****' for non-empty sensitive values.
91
- */
92
- export function redactValue(key, value) {
93
- const strValue = String(value);
94
- if (!SENSITIVE_KEYS.has(key)) {
95
- return strValue;
96
- }
97
- // Empty values shown as placeholder
98
- if (strValue === '') {
99
- return chalk.dim('(empty)');
100
- }
101
- // Redact: show first 3 chars + ****
102
- if (strValue.length <= 3) {
103
- return '****';
104
- }
105
- return strValue.slice(0, 3) + '****';
106
- }
107
- /**
108
- * Format the full config for display.
109
- */
110
- export function formatConfigOutput(config) {
111
- const lines = [];
112
- lines.push('');
113
- lines.push(chalk.bold.cyan(' Configuration'));
114
- lines.push(chalk.gray(' ' + '\u2500'.repeat(60)));
115
- lines.push('');
116
- // Group config keys by category
117
- const groups = [
118
- { name: 'Runtime', keys: ['NODE_ENV', 'LOG_LEVEL'] },
119
- { name: 'Neo4j', keys: ['NEO4J_URI', 'NEO4J_USER', 'NEO4J_PASSWORD'] },
120
- { name: 'TimescaleDB', keys: ['TIMESCALE_HOST', 'TIMESCALE_PORT', 'TIMESCALE_USER', 'TIMESCALE_PASSWORD', 'TIMESCALE_DB'] },
121
- { name: 'Redis', keys: ['REDIS_URL', 'REDIS_PASSWORD'] },
122
- { name: 'Data Sources', keys: ['HELIUS_API_KEY', 'COINGECKO_API_KEY', 'ALCHEMY_API_KEY', 'GLASSNODE_API_KEY', 'TWITTER_BEARER_TOKEN'] },
123
- { name: 'AI / LLM', keys: ['ANTHROPIC_API_KEY', 'LLM_PROVIDER', 'LLM_API_KEY', 'LLM_BASE_URL', 'LLM_MODEL', 'LLM_FALLBACK_PROVIDER', 'LLM_FALLBACK_MODEL', 'LLM_FALLBACK_API_KEY', 'LLM_MAX_RETRIES', 'LLM_TIMEOUT_MS', 'LLM_ENCRYPTION_KEY', 'ENABLE_LLM'] },
124
- { name: 'Learning', keys: ['BELIEF_HMAC_KEY', 'ENABLE_LEARNING', 'ENABLE_SOCIAL'] },
125
- { name: 'API', keys: ['API_PORT', 'API_KEY_HASHES', 'API_KEY_AUTH_ENABLED'] },
126
- { name: 'Billing', keys: ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET', 'STRIPE_PRICE_ID_STARTER', 'STRIPE_PRICE_ID_PRO', 'STRIPE_PRICE_ID_EDGE', 'APP_URL'] },
127
- { name: 'Telegram', keys: ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_ALLOWED_CHAT_IDS'] },
128
- { name: 'Email', keys: ['RESEND_API_KEY', 'EMAIL_FROM_ADDRESS', 'EMAIL_FROM_NAME', 'SMTP_HOST', 'SMTP_PORT', 'SMTP_USER', 'SMTP_PASS', 'NOTIFICATION_FROM_EMAIL'] },
129
- ];
130
- const configRecord = config;
131
- for (const group of groups) {
132
- lines.push(chalk.bold(` ${group.name}`));
133
- for (const key of group.keys) {
134
- const value = configRecord[key];
135
- const displayValue = value !== undefined ? redactValue(key, value) : chalk.dim('(not set)');
136
- lines.push(` ${chalk.gray(padRight(key, 26))} ${displayValue}`);
137
- }
138
- lines.push('');
139
- }
140
- return lines.join('\n');
141
- }
142
- // ─── .env File Operations ───
143
- /**
144
- * Resolve the .env file path from the project root.
145
- */
146
- export function resolveEnvPath() {
147
- return resolve(process.cwd(), '.env');
148
- }
149
- /**
150
- * Parse a .env file into a Map of key-value pairs.
151
- * Preserves comments and blank lines by returning the full lines array too.
152
- */
153
- export function parseEnvFile(filePath) {
154
- const entries = new Map();
155
- const lines = [];
156
- if (!existsSync(filePath)) {
157
- return { entries, lines };
158
- }
159
- const content = readFileSync(filePath, 'utf-8');
160
- const rawLines = content.split('\n');
161
- for (const line of rawLines) {
162
- lines.push(line);
163
- const trimmed = line.trim();
164
- // Skip comments and empty lines
165
- if (trimmed === '' || trimmed.startsWith('#')) {
166
- continue;
167
- }
168
- const eqIndex = trimmed.indexOf('=');
169
- if (eqIndex > 0) {
170
- const key = trimmed.slice(0, eqIndex).trim();
171
- let value = trimmed.slice(eqIndex + 1).trim();
172
- // Remove surrounding quotes if present
173
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
174
- value = value.slice(1, -1);
175
- }
176
- entries.set(key, value);
177
- }
178
- }
179
- return { entries, lines };
180
- }
181
- /**
182
- * Write a key-value pair to the .env file.
183
- * If the key already exists, update the line; otherwise append.
184
- */
185
- export function writeEnvValue(filePath, key, value) {
186
- if (/[\n\r\0]/.test(value)) {
187
- throw new Error('Config value must not contain newlines or null bytes');
188
- }
189
- const { entries, lines } = parseEnvFile(filePath);
190
- if (entries.has(key)) {
191
- // Update existing line
192
- const updatedLines = lines.map((line) => {
193
- const trimmed = line.trim();
194
- if (trimmed.startsWith('#') || trimmed === '')
195
- return line;
196
- const eqIndex = trimmed.indexOf('=');
197
- if (eqIndex > 0) {
198
- const lineKey = trimmed.slice(0, eqIndex).trim();
199
- if (lineKey === key) {
200
- return `${key}=${value}`;
201
- }
202
- }
203
- return line;
204
- });
205
- writeFileSync(filePath, updatedLines.join('\n'));
206
- }
207
- else {
208
- // Append to end of file
209
- const existing = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
210
- const separator = existing.endsWith('\n') || existing === '' ? '' : '\n';
211
- writeFileSync(filePath, existing + separator + `${key}=${value}\n`);
212
- }
213
- }
214
- // ─── Command Registration ───
215
- export function registerConfigCommand(program) {
216
- const configCmd = program
217
- .command('config')
218
- .description('Configuration management commands');
219
- // ─── config show ───
220
- configCmd
221
- .command('show')
222
- .description('Display current configuration')
223
- .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
224
- .addOption(new Option('--all', 'Show all config variables (including server/infrastructure)'))
225
- .action((options) => {
226
- // User-facing config: only what a CLI user cares about
227
- const USER_CONFIG = [
228
- { section: 'Connection', keys: [
229
- { key: 'TRADING_BOY_API_URL', label: 'API URL' },
230
- ] },
231
- { section: 'LLM (Coaching)', keys: [
232
- { key: 'LLM_PROVIDER', label: 'Provider' },
233
- { key: 'LLM_MODEL', label: 'Model' },
234
- { key: 'LLM_API_KEY', label: 'API Key' },
235
- { key: 'LLM_BASE_URL', label: 'Base URL' },
236
- ] },
237
- { section: 'Notifications', keys: [
238
- { key: 'TELEGRAM_BOT_TOKEN', label: 'Telegram Bot' },
239
- { key: 'TELEGRAM_ALLOWED_CHAT_IDS', label: 'Telegram Chat IDs' },
240
- ] },
241
- ];
242
- const getEnv = (key) => {
243
- const value = process.env[key];
244
- if (!value)
245
- return chalk.dim('not set');
246
- return SENSITIVE_KEYS.has(key) ? redactValue(key, value) : value;
247
- };
248
- if (options.all) {
249
- // Full dump for developers
250
- const redacted = {};
251
- for (const key of VALID_KEYS) {
252
- redacted[key] = getEnv(key);
253
- }
254
- if (options.format === 'json') {
255
- console.log(JSON.stringify(redacted, null, 2));
256
- }
257
- else {
258
- for (const [key, value] of Object.entries(redacted)) {
259
- console.log(` ${chalk.cyan(key.padEnd(30))} ${value}`);
260
- }
261
- }
262
- return;
263
- }
264
- if (options.format === 'json') {
265
- const result = {};
266
- for (const section of USER_CONFIG) {
267
- for (const { key } of section.keys) {
268
- result[key] = process.env[key] ?? '';
269
- }
270
- }
271
- console.log(JSON.stringify(result, null, 2));
272
- return;
273
- }
274
- console.log('');
275
- for (const section of USER_CONFIG) {
276
- console.log(chalk.bold(` ${section.section}`));
277
- console.log(chalk.gray(' ' + '─'.repeat(40)));
278
- for (const { key, label } of section.keys) {
279
- console.log(` ${chalk.cyan(label.padEnd(22))} ${getEnv(key)}`);
280
- }
281
- console.log('');
282
- }
283
- console.log(chalk.dim(' Use --all to show all config variables.'));
284
- console.log('');
285
- });
286
- // ─── config set ───
287
- configCmd
288
- .command('set <key> <value>')
289
- .description('Set a config value in the .env file')
290
- .action((key, value) => {
291
- try {
292
- // Validate key
293
- if (!VALID_KEYS.includes(key)) {
294
- console.error(chalk.red(`Error: Unknown config key "${key}".`));
295
- console.error(chalk.dim(`Valid keys: ${VALID_KEYS.join(', ')}`));
296
- process.exitCode = 1;
297
- return;
298
- }
299
- const envPath = resolveEnvPath();
300
- writeEnvValue(envPath, key, value);
301
- console.log('');
302
- console.log(chalk.green(` Config updated: ${key}`));
303
- if (SENSITIVE_KEYS.has(key)) {
304
- console.log(` ${chalk.gray('Value:')} ${redactValue(key, value)}`);
305
- }
306
- else {
307
- console.log(` ${chalk.gray('Value:')} ${value}`);
308
- }
309
- console.log(` ${chalk.gray('File:')} ${envPath}`);
310
- console.log('');
311
- console.log(chalk.dim(' Note: Restart services for changes to take effect.'));
312
- console.log('');
313
- }
314
- catch (error) {
315
- const message = error instanceof Error ? error.message : String(error);
316
- logger.error({ error: message }, 'Failed to set config');
317
- console.error(chalk.red(`Error: ${message}`));
318
- process.exitCode = 1;
319
- }
320
- });
321
- // ─── config set-llm-key ───
322
- configCmd
323
- .command('set-llm-key <apiKey>')
324
- .description('Store your LLM API key for thesis extraction + coaching (BYOK)')
325
- .addOption(new Option('-p, --provider <provider>', 'LLM provider (auto-detected from key prefix if omitted)').choices(['anthropic', 'openai', 'openrouter', 'ollama', 'gemini', 'custom']))
326
- .option('-m, --model <model>', 'Model name (default for all phases)')
327
- .option('--base-url <url>', 'Custom base URL (for openrouter/ollama/custom providers)')
328
- .option('--scan-model <model>', 'Model for market scanning (e.g. claude-haiku-4-5)')
329
- .option('--analyze-model <model>', 'Model for deep analysis (e.g. claude-sonnet-4-6)')
330
- .option('--decide-model <model>', 'Model for trade decisions (e.g. claude-opus-4-6)')
331
- .addOption(new Option('--scan-provider <provider>', 'Provider for scan phase').choices(['anthropic', 'openai', 'openrouter', 'ollama', 'gemini', 'custom']))
332
- .option('--scan-key <key>', 'API key for scan phase provider')
333
- .addOption(new Option('--analyze-provider <provider>', 'Provider for analyze phase').choices(['anthropic', 'openai', 'openrouter', 'ollama', 'gemini', 'custom']))
334
- .option('--analyze-key <key>', 'API key for analyze phase provider')
335
- .addOption(new Option('--decide-provider <provider>', 'Provider for decide phase').choices(['anthropic', 'openai', 'openrouter', 'ollama', 'gemini', 'custom']))
336
- .option('--decide-key <key>', 'API key for decide phase provider')
337
- .action(async (apiKey, opts) => {
338
- try {
339
- const result = await apiRequest('/api/v1/llm-config', {
340
- method: 'PUT',
341
- body: {
342
- apiKey,
343
- ...(opts.provider ? { provider: opts.provider } : {}),
344
- ...(opts.model ? { model: opts.model } : {}),
345
- ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
346
- ...(opts.scanModel ? { scanModel: opts.scanModel } : {}),
347
- ...(opts.analyzeModel ? { analyzeModel: opts.analyzeModel } : {}),
348
- ...(opts.decideModel ? { decideModel: opts.decideModel } : {}),
349
- ...(opts.scanProvider ? { scanProvider: opts.scanProvider } : {}),
350
- ...(opts.scanKey ? { scanApiKey: opts.scanKey } : {}),
351
- ...(opts.analyzeProvider ? { analyzeProvider: opts.analyzeProvider } : {}),
352
- ...(opts.analyzeKey ? { analyzeApiKey: opts.analyzeKey } : {}),
353
- ...(opts.decideProvider ? { decideProvider: opts.decideProvider } : {}),
354
- ...(opts.decideKey ? { decideApiKey: opts.decideKey } : {}),
355
- },
356
- });
357
- console.log('');
358
- console.log(chalk.green(' LLM API key saved successfully'));
359
- console.log(` ${chalk.gray('Provider:')} ${result.provider}`);
360
- console.log(` ${chalk.gray('Model:')} ${result.model}`);
361
- if (result.scanProvider || result.scanModel) {
362
- console.log(` ${chalk.gray('Scan:')} ${result.scanProvider ?? result.provider} / ${result.scanModel ?? result.model}${opts.scanKey ? ' (own key)' : ''}`);
363
- }
364
- if (result.analyzeProvider || result.analyzeModel) {
365
- console.log(` ${chalk.gray('Analyze:')} ${result.analyzeProvider ?? result.provider} / ${result.analyzeModel ?? result.model}${opts.analyzeKey ? ' (own key)' : ''}`);
366
- }
367
- if (result.decideProvider || result.decideModel) {
368
- console.log(` ${chalk.gray('Decide:')} ${result.decideProvider ?? result.provider} / ${result.decideModel ?? result.model}${opts.decideKey ? ' (own key)' : ''}`);
369
- }
370
- console.log(` ${chalk.gray('Key:')} ${apiKey.slice(0, 8)}${'*'.repeat(Math.max(0, apiKey.length - 8))}`);
371
- console.log('');
372
- console.log(chalk.dim(' Your key is encrypted at rest. Thesis extraction and coaching are now enabled.'));
373
- console.log('');
374
- }
375
- catch (error) {
376
- const message = error instanceof Error ? error.message : String(error);
377
- logger.error({ error: message }, 'Failed to set LLM key');
378
- console.error(chalk.red(`Error: ${message}`));
379
- process.exitCode = error instanceof ApiError ? 2 : 1;
380
- }
381
- });
382
- // ─── config set-models ───
383
- configCmd
384
- .command('set-models')
385
- .description('Update per-stage model assignments (no API key needed — works with Codex OAuth)')
386
- .option('-m, --model <model>', 'Default model for all phases')
387
- .option('--scan-model <model>', 'Model for market scanning')
388
- .option('--analyze-model <model>', 'Model for deep analysis')
389
- .option('--decide-model <model>', 'Model for trade decisions')
390
- .option('--exit-heartbeat-model <model>', 'Model for exit heartbeat checks (cheap, every 4h)')
391
- .option('--exit-event-model <model>', 'Model for exit event-driven analysis (quality)')
392
- .action(async (opts) => {
393
- if (!opts.model && !opts.scanModel && !opts.analyzeModel && !opts.decideModel && !opts.exitHeartbeatModel && !opts.exitEventModel) {
394
- console.error(chalk.red(' Error: At least one model flag is required.'));
395
- console.log(chalk.dim(' Example: trading-boy config set-models --scan-model gpt-5.4-mini --analyze-model gpt-5.4'));
396
- process.exitCode = 1;
397
- return;
398
- }
399
- try {
400
- const result = await apiRequest('/api/v1/llm-config/models', {
401
- method: 'PATCH',
402
- body: {
403
- ...(opts.model ? { model: opts.model } : {}),
404
- ...(opts.scanModel ? { scanModel: opts.scanModel } : {}),
405
- ...(opts.analyzeModel ? { analyzeModel: opts.analyzeModel } : {}),
406
- ...(opts.decideModel ? { decideModel: opts.decideModel } : {}),
407
- ...(opts.exitHeartbeatModel ? { exitHeartbeatModel: opts.exitHeartbeatModel } : {}),
408
- ...(opts.exitEventModel ? { exitEventModel: opts.exitEventModel } : {}),
409
- },
410
- });
411
- console.log('');
412
- console.log(chalk.green(' Stage models updated'));
413
- console.log(chalk.gray(' ' + '─'.repeat(40)));
414
- console.log(` ${chalk.gray('Default:')} ${result.model}`);
415
- if (result.scanModel)
416
- console.log(` ${chalk.gray('Scan:')} ${result.scanModel}`);
417
- if (result.analyzeModel)
418
- console.log(` ${chalk.gray('Analyze:')} ${result.analyzeModel}`);
419
- if (result.decideModel)
420
- console.log(` ${chalk.gray('Decide:')} ${result.decideModel}`);
421
- if (result.exitHeartbeatModel)
422
- console.log(` ${chalk.gray('Exit heartbeat:')} ${result.exitHeartbeatModel}`);
423
- if (result.exitEventModel)
424
- console.log(` ${chalk.gray('Exit event:')} ${result.exitEventModel}`);
425
- console.log('');
426
- console.log(chalk.dim(' Agents will use these models on their next tick.'));
427
- console.log('');
428
- }
429
- catch (error) {
430
- const message = error instanceof Error ? error.message : String(error);
431
- logger.error({ error: message }, 'Failed to set stage models');
432
- console.error(chalk.red(`Error: ${message}`));
433
- process.exitCode = error instanceof ApiError ? 2 : 1;
434
- }
435
- });
436
- // ─── config get-llm-key ───
437
- configCmd
438
- .command('get-llm-key')
439
- .description('Show current LLM configuration (key redacted)')
440
- .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
441
- .action(async (options) => {
442
- try {
443
- const result = await apiRequest('/api/v1/llm-config');
444
- if (options.format === 'json') {
445
- console.log(JSON.stringify(result, null, 2));
446
- }
447
- else {
448
- console.log('');
449
- console.log(chalk.bold.cyan(' LLM Configuration'));
450
- console.log(chalk.gray(' ' + '\u2500'.repeat(40)));
451
- console.log(` ${chalk.gray('Provider:')} ${result.provider}`);
452
- console.log(` ${chalk.gray('Model:')} ${result.model}`);
453
- if (result.scanProvider || result.scanModel) {
454
- console.log(` ${chalk.gray('Scan:')} ${result.scanProvider ?? result.provider} / ${result.scanModel ?? result.model}`);
455
- }
456
- if (result.analyzeProvider || result.analyzeModel) {
457
- console.log(` ${chalk.gray('Analyze:')} ${result.analyzeProvider ?? result.provider} / ${result.analyzeModel ?? result.model}`);
458
- }
459
- if (result.decideProvider || result.decideModel) {
460
- console.log(` ${chalk.gray('Decide:')} ${result.decideProvider ?? result.provider} / ${result.decideModel ?? result.model}`);
461
- }
462
- if (result.exitHeartbeatModel) {
463
- console.log(` ${chalk.gray('Exit HB:')} ${result.exitHeartbeatModel}`);
464
- }
465
- if (result.exitEventModel) {
466
- console.log(` ${chalk.gray('Exit Event:')} ${result.exitEventModel}`);
467
- }
468
- if (result.baseUrl) {
469
- console.log(` ${chalk.gray('Base URL:')} ${result.baseUrl}`);
470
- }
471
- console.log(` ${chalk.gray('Updated:')} ${new Date(result.updatedAt).toLocaleString()}`);
472
- console.log('');
473
- }
474
- }
475
- catch (error) {
476
- if (error instanceof ApiError && error.status === 404) {
477
- console.log('');
478
- console.log(chalk.yellow(' No LLM key configured.'));
479
- console.log(chalk.dim(' Run: trading-boy config set-llm-key <your-api-key>'));
480
- console.log('');
481
- }
482
- else {
483
- const message = error instanceof Error ? error.message : String(error);
484
- logger.error({ error: message }, 'Failed to get LLM config');
485
- console.error(chalk.red(`Error: ${message}`));
486
- process.exitCode = error instanceof ApiError ? 2 : 1;
487
- }
488
- }
489
- });
490
- // ─── config remove-llm-key ───
491
- configCmd
492
- .command('remove-llm-key')
493
- .description('Remove your stored LLM API key')
494
- .action(async () => {
495
- try {
496
- await apiRequest('/api/v1/llm-config', { method: 'DELETE' });
497
- console.log('');
498
- console.log(chalk.green(' LLM API key removed.'));
499
- console.log(chalk.dim(' Thesis extraction and coaching features are now disabled.'));
500
- console.log('');
501
- }
502
- catch (error) {
503
- if (error instanceof ApiError && error.status === 404) {
504
- console.log(chalk.yellow(' No LLM key configured.'));
505
- }
506
- else {
507
- const message = error instanceof Error ? error.message : String(error);
508
- logger.error({ error: message }, 'Failed to remove LLM key');
509
- console.error(chalk.red(`Error: ${message}`));
510
- process.exitCode = error instanceof ApiError ? 2 : 1;
511
- }
512
- }
513
- });
514
- }
515
- //# sourceMappingURL=config-cmd.js.map
@@ -1,5 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerConnectChatgptCommand(program: Command): void;
3
- declare function handleConnect(): Promise<void>;
4
- export { handleConnect as connectChatgpt };
5
- //# sourceMappingURL=connect-chatgpt.d.ts.map