@bloomai/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,805 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/auth.ts
7
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ var CONFIG_DIR = join(homedir(), ".config", "bloom-cli");
11
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
12
+ function readConfig() {
13
+ if (!existsSync(CONFIG_FILE)) return {};
14
+ try {
15
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
16
+ } catch {
17
+ return {};
18
+ }
19
+ }
20
+ function writeConfig(config) {
21
+ try {
22
+ mkdirSync(CONFIG_DIR, { recursive: true });
23
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
24
+ mode: 384
25
+ });
26
+ } catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ console.error(
29
+ `Error: Could not write config to ${CONFIG_FILE}: ${msg}
30
+ Try using BLOOM_API_KEY environment variable instead.`
31
+ );
32
+ process.exit(1);
33
+ }
34
+ }
35
+ function getApiKey() {
36
+ return process.env.BLOOM_API_KEY || readConfig().api_key;
37
+ }
38
+ function requireApiKey() {
39
+ const key = getApiKey();
40
+ if (!key) {
41
+ console.error(
42
+ "Error: No API key found. Set BLOOM_API_KEY or run: bloom auth login"
43
+ );
44
+ process.exit(1);
45
+ }
46
+ return key;
47
+ }
48
+ function login(apiKey) {
49
+ const config = readConfig();
50
+ config.api_key = apiKey;
51
+ writeConfig(config);
52
+ console.log("API key saved to " + CONFIG_FILE);
53
+ }
54
+ function logout() {
55
+ if (existsSync(CONFIG_FILE)) {
56
+ const config = readConfig();
57
+ delete config.api_key;
58
+ writeConfig(config);
59
+ }
60
+ console.log("Logged out. API key cleared.");
61
+ }
62
+ function status() {
63
+ const envKey = process.env.BLOOM_API_KEY;
64
+ const storedKey = readConfig().api_key;
65
+ if (envKey) {
66
+ console.log(`Authenticated via BLOOM_API_KEY (${maskKey(envKey)})`);
67
+ } else if (storedKey) {
68
+ console.log(`Authenticated via ${CONFIG_FILE} (${maskKey(storedKey)})`);
69
+ } else {
70
+ console.log("Not authenticated. Set BLOOM_API_KEY or run: bloom auth login");
71
+ }
72
+ }
73
+ function maskKey(key) {
74
+ if (key.length <= 8) return "****";
75
+ return key.slice(0, 4) + "..." + key.slice(-4);
76
+ }
77
+
78
+ // src/commands/auth.ts
79
+ import { createInterface } from "readline";
80
+ function registerAuthCommand(program2) {
81
+ const auth = program2.command("auth").description("Manage API authentication");
82
+ auth.command("login").description("Store API key").argument("[key]", "API key (or pass via stdin)").action(async (key) => {
83
+ if (!key) {
84
+ const rl = createInterface({
85
+ input: process.stdin,
86
+ output: process.stderr
87
+ });
88
+ key = await new Promise((resolve) => {
89
+ rl.question("API key: ", (answer) => {
90
+ rl.close();
91
+ resolve(answer.trim());
92
+ });
93
+ });
94
+ }
95
+ if (!key) {
96
+ console.error("Error: No API key provided");
97
+ process.exit(1);
98
+ }
99
+ login(key);
100
+ });
101
+ auth.command("status").description("Show current auth state").action(() => {
102
+ status();
103
+ });
104
+ auth.command("logout").description("Clear stored credentials").action(() => {
105
+ logout();
106
+ });
107
+ }
108
+
109
+ // src/api.ts
110
+ var MCP_URL = "https://api.getbloom.app/mcp/";
111
+ var requestId = 0;
112
+ async function callTool(toolName, args) {
113
+ const apiKey = requireApiKey();
114
+ requestId++;
115
+ const body = {
116
+ jsonrpc: "2.0",
117
+ method: "tools/call",
118
+ params: {
119
+ name: toolName,
120
+ arguments: args
121
+ },
122
+ id: requestId
123
+ };
124
+ const res = await fetch(MCP_URL, {
125
+ method: "POST",
126
+ headers: {
127
+ Authorization: `Bearer ${apiKey}`,
128
+ "Content-Type": "application/json"
129
+ },
130
+ body: JSON.stringify(body)
131
+ });
132
+ if (!res.ok) {
133
+ const text2 = await res.text().catch(() => "");
134
+ throw new Error(`API error ${res.status}: ${text2 || res.statusText}`);
135
+ }
136
+ const data = await res.json();
137
+ if (data.error) {
138
+ throw new Error(`MCP error ${data.error.code}: ${data.error.message}`);
139
+ }
140
+ if (!data.result?.content?.length) {
141
+ throw new Error("Empty response from API");
142
+ }
143
+ const text = data.result.content[0].text;
144
+ try {
145
+ return JSON.parse(text);
146
+ } catch {
147
+ return text;
148
+ }
149
+ }
150
+
151
+ // src/format.ts
152
+ import Table from "cli-table3";
153
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
154
+ import { dirname } from "path";
155
+ function resolveFormat(opts) {
156
+ if (opts.format) return opts.format;
157
+ if (opts.output || !process.stdout.isTTY) return "json";
158
+ return "table";
159
+ }
160
+ function outputResult(data, opts, tableFormatter) {
161
+ if (opts.raw) {
162
+ emit(JSON.stringify(data, null, 2), opts);
163
+ return;
164
+ }
165
+ const fmt = resolveFormat(opts);
166
+ if (fmt === "json") {
167
+ emit(JSON.stringify(data, null, 2), opts);
168
+ return;
169
+ }
170
+ if (fmt === "csv") {
171
+ emit(toCSV(data), opts);
172
+ return;
173
+ }
174
+ if (tableFormatter) {
175
+ emit(tableFormatter(data, opts), opts);
176
+ } else {
177
+ emit(autoTable(data, opts), opts);
178
+ }
179
+ }
180
+ function emit(content, opts) {
181
+ if (opts.output) {
182
+ mkdirSync2(dirname(opts.output), { recursive: true });
183
+ writeFileSync2(opts.output, content + "\n");
184
+ if (!opts.quiet) {
185
+ console.error(`Written to ${opts.output}`);
186
+ }
187
+ } else {
188
+ console.log(content);
189
+ }
190
+ }
191
+ function toCSV(data) {
192
+ const rows = toRows(data);
193
+ if (rows.length === 0) return "";
194
+ const headers = Object.keys(rows[0]);
195
+ const lines = [headers.join(",")];
196
+ for (const row of rows) {
197
+ lines.push(
198
+ headers.map((h) => {
199
+ const val = String(row[h] ?? "");
200
+ return val.includes(",") || val.includes('"') || val.includes("\n") ? `"${val.replace(/"/g, '""')}"` : val;
201
+ }).join(",")
202
+ );
203
+ }
204
+ return lines.join("\n");
205
+ }
206
+ function toRows(data) {
207
+ const flatten = (v) => flattenObj(v);
208
+ if (Array.isArray(data)) return data.map(flatten);
209
+ if (data && typeof data === "object") {
210
+ const obj = data;
211
+ for (const key of Object.keys(obj)) {
212
+ const val = obj[key];
213
+ if (Array.isArray(val) && val.length > 0 && typeof val[0] === "object" && val[0] !== null) {
214
+ return val.map(flatten);
215
+ }
216
+ }
217
+ for (const key of Object.keys(obj)) {
218
+ if (Array.isArray(obj[key]) && obj[key].length > 0) {
219
+ return obj[key].map(flatten);
220
+ }
221
+ }
222
+ return [flattenObj(obj)];
223
+ }
224
+ return [{ value: data }];
225
+ }
226
+ function flattenObj(obj, prefix = "") {
227
+ if (!obj || typeof obj !== "object") return { value: obj };
228
+ const result = {};
229
+ for (const [k, v] of Object.entries(obj)) {
230
+ const key = prefix ? `${prefix}.${k}` : k;
231
+ if (v && typeof v === "object" && !Array.isArray(v)) {
232
+ Object.assign(result, flattenObj(v, key));
233
+ } else if (Array.isArray(v)) {
234
+ if (v.length <= 3) {
235
+ result[key] = v.map(
236
+ (item) => item && typeof item === "object" ? JSON.stringify(item) : String(item)
237
+ ).join(", ");
238
+ } else {
239
+ result[key] = `[${v.length} items]`;
240
+ }
241
+ } else {
242
+ result[key] = v;
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ function autoTable(data, opts) {
248
+ const rows = toRows(data);
249
+ if (rows.length === 0) return "(no data)";
250
+ const headers = Object.keys(rows[0]);
251
+ const table = new Table({
252
+ head: opts?.quiet ? [] : headers,
253
+ style: { head: ["cyan"] },
254
+ wordWrap: true
255
+ });
256
+ for (const row of rows) {
257
+ table.push(headers.map((h) => truncate(String(row[h] ?? ""), 80)));
258
+ }
259
+ return table.toString();
260
+ }
261
+ function makeTable(headers, rows, opts) {
262
+ const table = new Table({
263
+ head: opts?.quiet ? [] : headers,
264
+ style: { head: ["cyan"] },
265
+ wordWrap: true
266
+ });
267
+ for (const row of rows) {
268
+ table.push(row);
269
+ }
270
+ return table.toString();
271
+ }
272
+ function truncate(s, max) {
273
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
274
+ }
275
+ function parseIntSafe(value, fallback) {
276
+ const n = parseInt(value, 10);
277
+ return Number.isNaN(n) ? fallback : n;
278
+ }
279
+
280
+ // src/global-opts.ts
281
+ function addGlobalOptions(cmd) {
282
+ return cmd.option("-o, --output <file>", "Write output to file").option("-f, --format <fmt>", "Output format: json, csv, table").option("-q, --quiet", "Suppress headers, data only").option("--raw", "Raw JSON from API");
283
+ }
284
+
285
+ // src/commands/info.ts
286
+ var VALID_FIELDS = [
287
+ "basic_metadata",
288
+ "bottom_line_ratings",
289
+ "embedded_expectations",
290
+ "peers",
291
+ "key_events",
292
+ "holdings"
293
+ ];
294
+ function registerInfoCommand(program2) {
295
+ addGlobalOptions(
296
+ program2.command("info").description("Company fundamentals, ratings, peers, holdings").argument("<symbols...>", "Ticker symbols (e.g. AAPL MSFT)").option("--fields <fields>", "Comma-separated: " + VALID_FIELDS.join(","))
297
+ ).action(async (symbols, opts) => {
298
+ const args = {
299
+ symbols: symbols.map((s) => s.toUpperCase())
300
+ };
301
+ if (opts.fields) {
302
+ args.fields = opts.fields.split(",").map((f) => f.trim());
303
+ }
304
+ const data = await callTool("get_symbols_metadata", args);
305
+ outputResult(data, opts, (d) => formatInfo(d));
306
+ });
307
+ }
308
+ function formatInfo(data) {
309
+ if (!data || typeof data !== "object") return String(data);
310
+ const obj = data;
311
+ const sections = [];
312
+ for (const [key, val] of Object.entries(obj)) {
313
+ if (key === "timestamp" || key === "source") continue;
314
+ if (val && typeof val === "object") {
315
+ sections.push(`
316
+ === ${key.toUpperCase()} ===`);
317
+ sections.push(formatSection(val));
318
+ }
319
+ }
320
+ return sections.length > 0 ? sections.join("\n") : JSON.stringify(data, null, 2);
321
+ }
322
+ function formatSection(obj, indent = "") {
323
+ const lines = [];
324
+ for (const [k, v] of Object.entries(obj)) {
325
+ if (v && typeof v === "object" && !Array.isArray(v)) {
326
+ lines.push(`${indent}${k}:`);
327
+ lines.push(formatSection(v, indent + " "));
328
+ } else if (Array.isArray(v)) {
329
+ if (v.length === 0) continue;
330
+ if (typeof v[0] === "string" || typeof v[0] === "number") {
331
+ lines.push(`${indent}${k}: ${v.join(", ")}`);
332
+ } else {
333
+ lines.push(`${indent}${k}:`);
334
+ for (const item of v) {
335
+ if (typeof item === "object" && item) {
336
+ const summary = Object.entries(item).slice(0, 4).map(([ik, iv]) => `${ik}=${iv}`).join(" ");
337
+ lines.push(`${indent} - ${summary}`);
338
+ } else {
339
+ lines.push(`${indent} - ${item}`);
340
+ }
341
+ }
342
+ }
343
+ } else {
344
+ lines.push(`${indent}${k}: ${v}`);
345
+ }
346
+ }
347
+ return lines.join("\n");
348
+ }
349
+
350
+ // src/commands/news.ts
351
+ function registerNewsCommand(program2) {
352
+ addGlobalOptions(
353
+ program2.command("news").description("Recent news from Benzinga channels").argument("<symbols...>", "Ticker symbols").option("--limit <n>", "Max articles (default: 15)", "15")
354
+ ).action(async (symbols, opts) => {
355
+ const data = await callTool("get_news_for_symbols", {
356
+ symbols: symbols.map((s) => s.toUpperCase()),
357
+ limit: parseIntSafe(opts.limit, 15)
358
+ });
359
+ outputResult(data, opts, (d, o) => formatNews(d, o));
360
+ });
361
+ }
362
+ function formatNews(data, opts) {
363
+ if (!data || typeof data !== "object") return String(data);
364
+ const obj = data;
365
+ let articles = [];
366
+ if (Array.isArray(data)) {
367
+ articles = data;
368
+ } else {
369
+ for (const val of Object.values(obj)) {
370
+ if (Array.isArray(val) && val.length > 0) {
371
+ articles = val;
372
+ break;
373
+ }
374
+ }
375
+ }
376
+ if (articles.length === 0) return "(no articles)";
377
+ const rows = articles.map((a) => [
378
+ String(a.date || a.published || a.created || "").slice(0, 16),
379
+ String(a.title || a.headline || "").slice(0, 80),
380
+ String(a.symbols || a.tickers || a.stocks || "").slice(0, 20),
381
+ String(a.channel || a.source || "")
382
+ ]);
383
+ return makeTable(["Date", "Title", "Symbols", "Channel"], rows, opts);
384
+ }
385
+
386
+ // src/commands/price.ts
387
+ function registerPriceCommand(program2) {
388
+ addGlobalOptions(
389
+ program2.command("price").description("Historical price data").argument("<symbol>", "Ticker symbol").option("--timeframes <tf>", "Comma-separated: 1d,1w,1m,3m,6m,ytd,1y,2y,5y", "1d,1m,1y").option("--ohlc", "Include full OHLC data")
390
+ ).action(async (symbol, opts) => {
391
+ const data = await callTool("get_price_charts", {
392
+ symbol: symbol.toUpperCase(),
393
+ timeframes: opts.timeframes.split(",").map((t) => t.trim()),
394
+ include_ohlc: !!opts.ohlc
395
+ });
396
+ outputResult(data, opts, (d, o) => formatPrices(d, o));
397
+ });
398
+ }
399
+ function formatPrices(data, opts) {
400
+ if (!data || typeof data !== "object") return String(data);
401
+ const obj = data;
402
+ const sections = [];
403
+ for (const [tf, val] of Object.entries(obj)) {
404
+ if (tf === "symbol" || tf === "timestamp" || tf === "source" || tf === "type") continue;
405
+ if (!val || typeof val !== "object") continue;
406
+ const tfData = val;
407
+ let points = [];
408
+ if (Array.isArray(tfData)) {
409
+ points = tfData;
410
+ } else if (tfData.data && Array.isArray(tfData.data)) {
411
+ points = tfData.data;
412
+ } else if (tfData.prices && Array.isArray(tfData.prices)) {
413
+ points = tfData.prices;
414
+ } else {
415
+ for (const v of Object.values(tfData)) {
416
+ if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null) {
417
+ points = v;
418
+ break;
419
+ }
420
+ }
421
+ }
422
+ if (points.length === 0) continue;
423
+ sections.push(`
424
+ --- ${tf.toUpperCase()} (${points.length} points) ---`);
425
+ const show = points.length <= 12 ? points : [
426
+ ...points.slice(0, 5),
427
+ { date: "...", price: "..." },
428
+ ...points.slice(-5)
429
+ ];
430
+ const rows = show.map((p) => [
431
+ String(p.date ?? p.datetime ?? p.timestamp ?? ""),
432
+ String(p.price ?? p.close ?? ""),
433
+ p.open != null ? String(p.open) : "",
434
+ p.high != null ? String(p.high) : "",
435
+ p.low != null ? String(p.low) : "",
436
+ p.volume != null ? String(p.volume) : ""
437
+ ]);
438
+ const headers = ["Date", "Price", "Open", "High", "Low", "Volume"];
439
+ sections.push(makeTable(headers, rows, opts));
440
+ }
441
+ return sections.join("\n") || JSON.stringify(data, null, 2);
442
+ }
443
+
444
+ // src/commands/financials.ts
445
+ var CHART_TYPES = [
446
+ "revenue",
447
+ "revenue_growth",
448
+ "operating_margin",
449
+ "net_earnings",
450
+ "net_earnings_growth",
451
+ "free_cash_flow",
452
+ "free_cash_flow_growth"
453
+ ];
454
+ function registerFinancialsCommand(program2) {
455
+ addGlobalOptions(
456
+ program2.command("financials").description("Quarterly/annual financial data with YoY growth").argument("<symbol>", "Ticker symbol").option("--type <type>", "Chart type: " + CHART_TYPES.join(", "), "revenue")
457
+ ).action(async (symbol, opts) => {
458
+ const data = await callTool("get_financial_charts", {
459
+ symbol: symbol.toUpperCase(),
460
+ chart_type: opts.type
461
+ });
462
+ outputResult(data, opts);
463
+ });
464
+ }
465
+
466
+ // src/commands/sentiment.ts
467
+ var SECTIONS = [
468
+ "aaii_sentiment",
469
+ "cnn_fear_greed",
470
+ "volatility_analysis",
471
+ "momentum_analysis"
472
+ ];
473
+ function registerSentimentCommand(program2) {
474
+ addGlobalOptions(
475
+ program2.command("sentiment").description("Market mood: AAII, Fear&Greed, VIX, momentum").option("--sections <s>", "Comma-separated: " + SECTIONS.join(","))
476
+ ).action(async (opts) => {
477
+ const args = {};
478
+ if (opts.sections) {
479
+ args.sections = opts.sections.split(",").map((s) => s.trim());
480
+ }
481
+ const data = await callTool("get_market_sentiment", args);
482
+ outputResult(data, opts, (d) => formatSentiment(d));
483
+ });
484
+ }
485
+ function formatSentiment(data) {
486
+ if (!data || typeof data !== "object") return String(data);
487
+ const obj = data;
488
+ const lines = [];
489
+ for (const [section, val] of Object.entries(obj)) {
490
+ if (section === "timestamp" || section === "source") continue;
491
+ if (!val || typeof val !== "object") continue;
492
+ lines.push(`
493
+ === ${section.replace(/_/g, " ").toUpperCase()} ===`);
494
+ for (const [k, v] of Object.entries(val)) {
495
+ if (v && typeof v === "object" && !Array.isArray(v)) {
496
+ lines.push(` ${k}:`);
497
+ for (const [ik, iv] of Object.entries(v)) {
498
+ lines.push(` ${ik}: ${iv}`);
499
+ }
500
+ } else {
501
+ lines.push(` ${k}: ${v}`);
502
+ }
503
+ }
504
+ }
505
+ return lines.join("\n") || JSON.stringify(data, null, 2);
506
+ }
507
+
508
+ // src/commands/market.ts
509
+ function registerMarketCommand(program2) {
510
+ addGlobalOptions(
511
+ program2.command("market").description("Market performance, sectors, global").option("--type <type>", "top_movers|sector_breakdown|global_markets|major_indexes", "top_movers").option("--period <p>", "1d|1w|1m|3m|6m|ytd|1y|5y", "1d").option("--limit <n>", "Max stocks for top_movers", "24")
512
+ ).action(async (opts) => {
513
+ const data = await callTool("get_market_map", {
514
+ map_type: opts.type,
515
+ period: opts.period,
516
+ limit: parseIntSafe(opts.limit, 24)
517
+ });
518
+ outputResult(data, opts);
519
+ });
520
+ }
521
+
522
+ // src/commands/collections.ts
523
+ function registerCollectionsCommand(program2) {
524
+ addGlobalOptions(
525
+ program2.command("collections").description("Curated stock collections by theme").option("--section <s>", "GET_STARTED|VALUE|TRENDS|BEAT_UP|FAMOUS_INVESTORS|HIGH_FLYERS|SECTORS|GEO|LEADERBOARDS", "GET_STARTED").option("--limit <n>", "Max collections", "6")
526
+ ).action(async (opts) => {
527
+ const data = await callTool("get_collections", {
528
+ section: opts.section,
529
+ limit: parseIntSafe(opts.limit, 6)
530
+ });
531
+ outputResult(data, opts);
532
+ });
533
+ }
534
+
535
+ // src/commands/screen.ts
536
+ function registerScreenCommand(program2) {
537
+ addGlobalOptions(
538
+ program2.command("screen").description("Quantitative stock screener").argument("<filters...>", 'Filter expressions, e.g. "market_cap > 10B" "pe_ratio < 25"').option("--or", "Use OR logic (default: AND)").option("--limit <n>", "Max results", "50").option("--symbols <s>", "Comma-separated symbols to filter from").option("--fields <f>", "Comma-separated fields (use * for all)")
539
+ ).action(
540
+ async (filters, opts) => {
541
+ const args = {
542
+ filter_expressions: filters,
543
+ and_query: !opts.or,
544
+ limit: parseIntSafe(opts.limit, 50)
545
+ };
546
+ if (opts.symbols) {
547
+ args.symbols = opts.symbols.split(",").map((s) => s.trim().toUpperCase());
548
+ }
549
+ if (opts.fields) {
550
+ args.fields = opts.fields.split(",").map((f) => f.trim());
551
+ }
552
+ const data = await callTool("filter_stocks", args);
553
+ outputResult(data, opts, (d, o) => formatScreen(d, o));
554
+ }
555
+ );
556
+ }
557
+ function formatScreen(data, opts) {
558
+ if (!data || typeof data !== "object") return String(data);
559
+ const obj = data;
560
+ let stocks = [];
561
+ if (Array.isArray(data)) {
562
+ stocks = data;
563
+ } else {
564
+ for (const val of Object.values(obj)) {
565
+ if (Array.isArray(val) && val.length > 0) {
566
+ stocks = val;
567
+ break;
568
+ }
569
+ }
570
+ }
571
+ if (stocks.length === 0) return "(no matches)";
572
+ const headers = Object.keys(stocks[0]).filter(
573
+ (k) => !k.startsWith("_") && k !== "id"
574
+ );
575
+ const rows = stocks.map(
576
+ (s) => headers.map((h) => {
577
+ const v = s[h];
578
+ if (typeof v === "number") {
579
+ if (Math.abs(v) >= 1e9) return `${(v / 1e9).toFixed(1)}B`;
580
+ if (Math.abs(v) >= 1e6) return `${(v / 1e6).toFixed(1)}M`;
581
+ return v.toFixed(2);
582
+ }
583
+ return String(v ?? "");
584
+ })
585
+ );
586
+ return makeTable(headers, rows, opts);
587
+ }
588
+
589
+ // src/commands/earnings.ts
590
+ function registerEarningsCommand(program2) {
591
+ addGlobalOptions(
592
+ program2.command("earnings").description("Earnings history, estimates, surprises").argument("<symbols...>", "Ticker symbols (up to 5)")
593
+ ).action(async (symbols, opts) => {
594
+ const data = await callTool("analyze_earnings", {
595
+ symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5)
596
+ });
597
+ outputResult(data, opts);
598
+ });
599
+ }
600
+
601
+ // src/commands/technicals.ts
602
+ var SECTIONS2 = ["technical_indicators", "options_analysis", "insider_activity"];
603
+ function registerTechnicalsCommand(program2) {
604
+ addGlobalOptions(
605
+ program2.command("technicals").description("RSI, MAs, patterns, options flow, insider trades").argument("<symbols...>", "Ticker symbols (up to 5)").option("--sections <s>", "Comma-separated: " + SECTIONS2.join(","))
606
+ ).action(async (symbols, opts) => {
607
+ const args = {
608
+ symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5)
609
+ };
610
+ if (opts.sections) {
611
+ args.sections = opts.sections.split(",").map((s) => s.trim());
612
+ }
613
+ const data = await callTool("analyze_technicals", args);
614
+ outputResult(data, opts);
615
+ });
616
+ }
617
+
618
+ // src/commands/options.ts
619
+ function registerOptionsCommand(program2) {
620
+ addGlobalOptions(
621
+ program2.command("options-history").description("Options sentiment evolution over time").argument("<symbol>", "Ticker symbol").option("--days <n>", "Lookback days (7-90)", "30").option("--weekly", "Use weekly sampling (default)").option("--daily", "Use daily data").option("--greeks", "Include advanced Greeks analysis")
622
+ ).action(
623
+ async (symbol, opts) => {
624
+ const data = await callTool("analyze_options_history", {
625
+ symbol: symbol.toUpperCase(),
626
+ lookback_days: parseIntSafe(opts.days, 30),
627
+ weekly_sampling: opts.weekly ?? !opts.daily,
628
+ include_greeks_analysis: !!opts.greeks
629
+ });
630
+ outputResult(data, opts);
631
+ }
632
+ );
633
+ }
634
+
635
+ // src/commands/portfolio.ts
636
+ function registerPortfolioCommand(program2) {
637
+ addGlobalOptions(
638
+ program2.command("portfolio").description("Portfolio risk-return analysis").argument("<holdings>", `JSON holdings, e.g. '{"AAPL":0.3,"MSFT":0.7}'`).option("--strategy <s>", "Investment strategy context")
639
+ ).action(async (holdingsStr, opts) => {
640
+ let holdings;
641
+ try {
642
+ holdings = JSON.parse(holdingsStr);
643
+ } catch {
644
+ console.error(`Error: Invalid JSON for holdings. Use format: '{"AAPL":0.3,"MSFT":0.7}'`);
645
+ process.exit(1);
646
+ }
647
+ const args = { holdings };
648
+ if (opts.strategy) args.strategy = opts.strategy;
649
+ const data = await callTool("analyze_portfolio", args);
650
+ outputResult(data, opts);
651
+ });
652
+ }
653
+
654
+ // src/commands/trades.ts
655
+ function registerTradesCommand(program2) {
656
+ addGlobalOptions(
657
+ program2.command("trades").description("AI trade evaluation").argument("<trades...>", 'Trade descriptions: "Buy AAPL 30% because..." "Sell MSFT 20% because..."')
658
+ ).action(async (tradeStrs, opts) => {
659
+ const trades = tradeStrs.map(parseTrade);
660
+ const data = await callTool("analyze_trades", { trades });
661
+ outputResult(data, opts);
662
+ });
663
+ }
664
+ function parseTrade(str) {
665
+ const match = str.match(
666
+ /^(buy|sell|hold)\s+([A-Z]+)\s+(\d+)%?\s+(?:because\s+)?(.+)$/i
667
+ );
668
+ if (match) {
669
+ return {
670
+ action: match[1].toLowerCase(),
671
+ symbol: match[2].toUpperCase(),
672
+ portfolio_weight: parseInt(match[3], 10) / 100,
673
+ thesis: match[4]
674
+ };
675
+ }
676
+ try {
677
+ return JSON.parse(str);
678
+ } catch {
679
+ console.error(
680
+ `Error: Could not parse trade "${str}"
681
+ Format: "Buy AAPL 30% because thesis" or JSON object`
682
+ );
683
+ process.exit(1);
684
+ }
685
+ }
686
+
687
+ // src/commands/catalysts.ts
688
+ function registerCatalystsCommand(program2) {
689
+ addGlobalOptions(
690
+ program2.command("catalysts").description("Forward-looking value catalysts").argument("<symbols...>", "Ticker symbols (up to 5)")
691
+ ).action(async (symbols, opts) => {
692
+ const data = await callTool("generate_catalysts", {
693
+ symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5)
694
+ });
695
+ outputResult(data, opts);
696
+ });
697
+ }
698
+
699
+ // src/commands/position.ts
700
+ function registerPositionCommand(program2) {
701
+ addGlobalOptions(
702
+ program2.command("position-size").description("Kelly criterion position sizing").requiredOption("--bull <n>", "Expected bull return (decimal, e.g. 0.6)").requiredOption("--bear <n>", "Expected bear return (decimal, e.g. -0.15)").requiredOption("--conviction <n>", "Conviction level 1-10").requiredOption("--value <n>", "Portfolio value in dollars").option("--symbol <s>", "Optional symbol for valuation context")
703
+ ).action(
704
+ async (opts) => {
705
+ const bullReturn = parseFloat(opts.bull);
706
+ const bearReturn = parseFloat(opts.bear);
707
+ const portfolioValue = parseFloat(opts.value);
708
+ if (isNaN(bullReturn)) {
709
+ console.error("Error: --bull must be a decimal number (e.g. 0.6)");
710
+ process.exit(1);
711
+ }
712
+ if (isNaN(bearReturn)) {
713
+ console.error("Error: --bear must be a decimal number (e.g. -0.15)");
714
+ process.exit(1);
715
+ }
716
+ if (isNaN(portfolioValue)) {
717
+ console.error("Error: --value must be a number");
718
+ process.exit(1);
719
+ }
720
+ const args = {
721
+ bull_return: bullReturn,
722
+ bear_return: bearReturn,
723
+ conviction: parseIntSafe(opts.conviction, 5),
724
+ portfolio_value: portfolioValue
725
+ };
726
+ if (opts.symbol) args.symbol = opts.symbol.toUpperCase();
727
+ const data = await callTool("calculate_position_size", args);
728
+ outputResult(data, opts);
729
+ }
730
+ );
731
+ }
732
+
733
+ // src/commands/transcript.ts
734
+ function registerTranscriptCommand(program2) {
735
+ addGlobalOptions(
736
+ program2.command("transcript").description("Search earnings call transcripts").argument("<symbol>", "Ticker symbol").argument("<keywords...>", "Keywords to search (OR logic)").option("--quarter <q>", 'Fiscal quarter, e.g. "Q1 2024"').option("--max <n>", "Max results", "10")
737
+ ).action(
738
+ async (symbol, keywords, opts) => {
739
+ const args = {
740
+ symbol: symbol.toUpperCase(),
741
+ keywords,
742
+ max_results: parseIntSafe(opts.max, 10)
743
+ };
744
+ if (opts.quarter) args.fiscal_quarter = opts.quarter;
745
+ const data = await callTool("search_transcript", args);
746
+ outputResult(data, opts, (d, o) => formatTranscript(d, o));
747
+ }
748
+ );
749
+ }
750
+ function formatTranscript(data, opts) {
751
+ if (!data || typeof data !== "object") return String(data);
752
+ const obj = data;
753
+ let matches = [];
754
+ if (Array.isArray(data)) {
755
+ matches = data;
756
+ } else {
757
+ for (const val of Object.values(obj)) {
758
+ if (Array.isArray(val) && val.length > 0) {
759
+ matches = val;
760
+ break;
761
+ }
762
+ if (val && typeof val === "object" && !Array.isArray(val)) {
763
+ for (const nested of Object.values(val)) {
764
+ if (Array.isArray(nested) && nested.length > 0) {
765
+ matches = nested;
766
+ break;
767
+ }
768
+ }
769
+ }
770
+ if (matches.length > 0) break;
771
+ }
772
+ }
773
+ if (matches.length === 0) return "(no matches)";
774
+ const rows = matches.map((m) => [
775
+ String(m.speaker || m.name || ""),
776
+ String(m.role || m.title || ""),
777
+ String(m.content_with_highlights || m.text || m.content || m.exact_quote || m.quote || "").slice(0, 120)
778
+ ]);
779
+ return makeTable(["Speaker", "Role", "Text"], rows, opts);
780
+ }
781
+
782
+ // src/index.ts
783
+ var program = new Command();
784
+ program.name("bloom").description("Bloom CLI \u2014 financial data for coding agents and humans").version("0.1.0");
785
+ registerAuthCommand(program);
786
+ registerInfoCommand(program);
787
+ registerNewsCommand(program);
788
+ registerPriceCommand(program);
789
+ registerFinancialsCommand(program);
790
+ registerSentimentCommand(program);
791
+ registerMarketCommand(program);
792
+ registerCollectionsCommand(program);
793
+ registerScreenCommand(program);
794
+ registerEarningsCommand(program);
795
+ registerTechnicalsCommand(program);
796
+ registerOptionsCommand(program);
797
+ registerPortfolioCommand(program);
798
+ registerTradesCommand(program);
799
+ registerCatalystsCommand(program);
800
+ registerPositionCommand(program);
801
+ registerTranscriptCommand(program);
802
+ program.parseAsync().catch((err) => {
803
+ console.error("Error:", err.message || err);
804
+ process.exit(1);
805
+ });