@trading-boy/cli 1.12.0 → 2.0.1

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 (84) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +64 -29
  3. package/dist/api-client.d.ts +4 -7
  4. package/dist/api-client.js +8 -13
  5. package/dist/cli.bundle.js +2314 -33711
  6. package/dist/credentials.js +1 -1
  7. package/dist/index.d.ts +0 -28
  8. package/dist/index.js +0 -24
  9. package/dist/logger.d.ts +8 -0
  10. package/dist/logger.js +12 -0
  11. package/dist/utils.js +3 -3
  12. package/package.json +30 -16
  13. package/dist/cli.d.ts +0 -5
  14. package/dist/cli.js +0 -157
  15. package/dist/commands/agent-cmd.d.ts +0 -9
  16. package/dist/commands/agent-cmd.js +0 -567
  17. package/dist/commands/audit.d.ts +0 -18
  18. package/dist/commands/audit.js +0 -73
  19. package/dist/commands/behavioral.d.ts +0 -73
  20. package/dist/commands/behavioral.js +0 -349
  21. package/dist/commands/benchmark-cmd.d.ts +0 -3
  22. package/dist/commands/benchmark-cmd.js +0 -191
  23. package/dist/commands/billing.d.ts +0 -12
  24. package/dist/commands/billing.js +0 -142
  25. package/dist/commands/catalysts.d.ts +0 -17
  26. package/dist/commands/catalysts.js +0 -151
  27. package/dist/commands/coaching-cmd.d.ts +0 -16
  28. package/dist/commands/coaching-cmd.js +0 -222
  29. package/dist/commands/config-cmd.d.ts +0 -30
  30. package/dist/commands/config-cmd.js +0 -515
  31. package/dist/commands/connect-chatgpt.d.ts +0 -5
  32. package/dist/commands/connect-chatgpt.js +0 -293
  33. package/dist/commands/connect-claude.d.ts +0 -5
  34. package/dist/commands/connect-claude.js +0 -280
  35. package/dist/commands/context.d.ts +0 -41
  36. package/dist/commands/context.js +0 -405
  37. package/dist/commands/cron-cmd.d.ts +0 -3
  38. package/dist/commands/cron-cmd.js +0 -305
  39. package/dist/commands/decisions.d.ts +0 -57
  40. package/dist/commands/decisions.js +0 -364
  41. package/dist/commands/edge-cmd.d.ts +0 -78
  42. package/dist/commands/edge-cmd.js +0 -183
  43. package/dist/commands/edge-guard-cmd.d.ts +0 -36
  44. package/dist/commands/edge-guard-cmd.js +0 -169
  45. package/dist/commands/events.d.ts +0 -3
  46. package/dist/commands/events.js +0 -117
  47. package/dist/commands/infra.d.ts +0 -24
  48. package/dist/commands/infra.js +0 -137
  49. package/dist/commands/journal.d.ts +0 -3
  50. package/dist/commands/journal.js +0 -302
  51. package/dist/commands/login.d.ts +0 -18
  52. package/dist/commands/login.js +0 -127
  53. package/dist/commands/logout.d.ts +0 -8
  54. package/dist/commands/logout.js +0 -108
  55. package/dist/commands/narratives.d.ts +0 -3
  56. package/dist/commands/narratives.js +0 -259
  57. package/dist/commands/onboarding.d.ts +0 -7
  58. package/dist/commands/onboarding.js +0 -281
  59. package/dist/commands/query.d.ts +0 -32
  60. package/dist/commands/query.js +0 -135
  61. package/dist/commands/replay-cmd.d.ts +0 -43
  62. package/dist/commands/replay-cmd.js +0 -184
  63. package/dist/commands/review.d.ts +0 -3
  64. package/dist/commands/review.js +0 -443
  65. package/dist/commands/risk.d.ts +0 -47
  66. package/dist/commands/risk.js +0 -158
  67. package/dist/commands/social.d.ts +0 -43
  68. package/dist/commands/social.js +0 -318
  69. package/dist/commands/soul-wizard.d.ts +0 -29
  70. package/dist/commands/soul-wizard.js +0 -155
  71. package/dist/commands/strategy-cmd.d.ts +0 -44
  72. package/dist/commands/strategy-cmd.js +0 -335
  73. package/dist/commands/subscribe.d.ts +0 -78
  74. package/dist/commands/subscribe.js +0 -552
  75. package/dist/commands/suggestions-cmd.d.ts +0 -24
  76. package/dist/commands/suggestions-cmd.js +0 -148
  77. package/dist/commands/thesis-cmd.d.ts +0 -3
  78. package/dist/commands/thesis-cmd.js +0 -129
  79. package/dist/commands/trader.d.ts +0 -30
  80. package/dist/commands/trader.js +0 -971
  81. package/dist/commands/watch.d.ts +0 -16
  82. package/dist/commands/watch.js +0 -104
  83. package/dist/commands/whoami.d.ts +0 -14
  84. 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