@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/README.md +67 -0
- package/SKILL.md +120 -0
- package/dist/index.js +805 -0
- package/package.json +24 -0
- package/src/api.ts +67 -0
- package/src/auth.ts +83 -0
- package/src/commands/auth.ts +45 -0
- package/src/commands/catalysts.ts +18 -0
- package/src/commands/collections.ts +20 -0
- package/src/commands/earnings.ts +18 -0
- package/src/commands/financials.ts +30 -0
- package/src/commands/info.ts +79 -0
- package/src/commands/market.ts +22 -0
- package/src/commands/news.ts +48 -0
- package/src/commands/options.ts +35 -0
- package/src/commands/portfolio.ts +28 -0
- package/src/commands/position.ts +44 -0
- package/src/commands/price.ts +76 -0
- package/src/commands/screen.ts +77 -0
- package/src/commands/sentiment.ts +51 -0
- package/src/commands/technicals.ts +25 -0
- package/src/commands/trades.ts +41 -0
- package/src/commands/transcript.ts +69 -0
- package/src/format.ts +179 -0
- package/src/global-opts.ts +9 -0
- package/src/index.ts +49 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { callTool } from "../api.js";
|
|
3
|
+
import { outputResult, OutputOptions, makeTable, parseIntSafe } from "../format.js";
|
|
4
|
+
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
+
|
|
6
|
+
export function registerScreenCommand(program: Command): void {
|
|
7
|
+
addGlobalOptions(
|
|
8
|
+
program
|
|
9
|
+
.command("screen")
|
|
10
|
+
.description("Quantitative stock screener")
|
|
11
|
+
.argument("<filters...>", 'Filter expressions, e.g. "market_cap > 10B" "pe_ratio < 25"')
|
|
12
|
+
.option("--or", "Use OR logic (default: AND)")
|
|
13
|
+
.option("--limit <n>", "Max results", "50")
|
|
14
|
+
.option("--symbols <s>", "Comma-separated symbols to filter from")
|
|
15
|
+
.option("--fields <f>", "Comma-separated fields (use * for all)")
|
|
16
|
+
).action(
|
|
17
|
+
async (
|
|
18
|
+
filters: string[],
|
|
19
|
+
opts: OutputOptions & {
|
|
20
|
+
or?: boolean;
|
|
21
|
+
limit: string;
|
|
22
|
+
symbols?: string;
|
|
23
|
+
fields?: string;
|
|
24
|
+
}
|
|
25
|
+
) => {
|
|
26
|
+
const args: Record<string, unknown> = {
|
|
27
|
+
filter_expressions: filters,
|
|
28
|
+
and_query: !opts.or,
|
|
29
|
+
limit: parseIntSafe(opts.limit, 50),
|
|
30
|
+
};
|
|
31
|
+
if (opts.symbols) {
|
|
32
|
+
args.symbols = opts.symbols.split(",").map((s) => s.trim().toUpperCase());
|
|
33
|
+
}
|
|
34
|
+
if (opts.fields) {
|
|
35
|
+
args.fields = opts.fields.split(",").map((f) => f.trim());
|
|
36
|
+
}
|
|
37
|
+
const data = await callTool("filter_stocks", args);
|
|
38
|
+
outputResult(data, opts, (d, o) => formatScreen(d, o));
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatScreen(data: unknown, opts: OutputOptions): string {
|
|
44
|
+
if (!data || typeof data !== "object") return String(data);
|
|
45
|
+
const obj = data as Record<string, unknown>;
|
|
46
|
+
|
|
47
|
+
let stocks: Record<string, unknown>[] = [];
|
|
48
|
+
if (Array.isArray(data)) {
|
|
49
|
+
stocks = data as Record<string, unknown>[];
|
|
50
|
+
} else {
|
|
51
|
+
for (const val of Object.values(obj)) {
|
|
52
|
+
if (Array.isArray(val) && val.length > 0) {
|
|
53
|
+
stocks = val as Record<string, unknown>[];
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (stocks.length === 0) return "(no matches)";
|
|
60
|
+
|
|
61
|
+
const headers = Object.keys(stocks[0]).filter(
|
|
62
|
+
(k) => !k.startsWith("_") && k !== "id"
|
|
63
|
+
);
|
|
64
|
+
const rows = stocks.map((s) =>
|
|
65
|
+
headers.map((h) => {
|
|
66
|
+
const v = s[h];
|
|
67
|
+
if (typeof v === "number") {
|
|
68
|
+
if (Math.abs(v) >= 1e9) return `${(v / 1e9).toFixed(1)}B`;
|
|
69
|
+
if (Math.abs(v) >= 1e6) return `${(v / 1e6).toFixed(1)}M`;
|
|
70
|
+
return v.toFixed(2);
|
|
71
|
+
}
|
|
72
|
+
return String(v ?? "");
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return makeTable(headers, rows, opts);
|
|
77
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { callTool } from "../api.js";
|
|
3
|
+
import { outputResult, OutputOptions } from "../format.js";
|
|
4
|
+
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
+
|
|
6
|
+
const SECTIONS = [
|
|
7
|
+
"aaii_sentiment",
|
|
8
|
+
"cnn_fear_greed",
|
|
9
|
+
"volatility_analysis",
|
|
10
|
+
"momentum_analysis",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function registerSentimentCommand(program: Command): void {
|
|
14
|
+
addGlobalOptions(
|
|
15
|
+
program
|
|
16
|
+
.command("sentiment")
|
|
17
|
+
.description("Market mood: AAII, Fear&Greed, VIX, momentum")
|
|
18
|
+
.option("--sections <s>", "Comma-separated: " + SECTIONS.join(","))
|
|
19
|
+
).action(async (opts: OutputOptions & { sections?: string }) => {
|
|
20
|
+
const args: Record<string, unknown> = {};
|
|
21
|
+
if (opts.sections) {
|
|
22
|
+
args.sections = opts.sections.split(",").map((s) => s.trim());
|
|
23
|
+
}
|
|
24
|
+
const data = await callTool("get_market_sentiment", args);
|
|
25
|
+
outputResult(data, opts, (d) => formatSentiment(d));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatSentiment(data: unknown): string {
|
|
30
|
+
if (!data || typeof data !== "object") return String(data);
|
|
31
|
+
const obj = data as Record<string, unknown>;
|
|
32
|
+
const lines: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const [section, val] of Object.entries(obj)) {
|
|
35
|
+
if (section === "timestamp" || section === "source") continue;
|
|
36
|
+
if (!val || typeof val !== "object") continue;
|
|
37
|
+
lines.push(`\n=== ${section.replace(/_/g, " ").toUpperCase()} ===`);
|
|
38
|
+
for (const [k, v] of Object.entries(val as Record<string, unknown>)) {
|
|
39
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
40
|
+
lines.push(` ${k}:`);
|
|
41
|
+
for (const [ik, iv] of Object.entries(v as Record<string, unknown>)) {
|
|
42
|
+
lines.push(` ${ik}: ${iv}`);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
lines.push(` ${k}: ${v}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return lines.join("\n") || JSON.stringify(data, null, 2);
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { callTool } from "../api.js";
|
|
3
|
+
import { outputResult, OutputOptions } from "../format.js";
|
|
4
|
+
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
+
|
|
6
|
+
const SECTIONS = ["technical_indicators", "options_analysis", "insider_activity"];
|
|
7
|
+
|
|
8
|
+
export function registerTechnicalsCommand(program: Command): void {
|
|
9
|
+
addGlobalOptions(
|
|
10
|
+
program
|
|
11
|
+
.command("technicals")
|
|
12
|
+
.description("RSI, MAs, patterns, options flow, insider trades")
|
|
13
|
+
.argument("<symbols...>", "Ticker symbols (up to 5)")
|
|
14
|
+
.option("--sections <s>", "Comma-separated: " + SECTIONS.join(","))
|
|
15
|
+
).action(async (symbols: string[], opts: OutputOptions & { sections?: string }) => {
|
|
16
|
+
const args: Record<string, unknown> = {
|
|
17
|
+
symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5),
|
|
18
|
+
};
|
|
19
|
+
if (opts.sections) {
|
|
20
|
+
args.sections = opts.sections.split(",").map((s) => s.trim());
|
|
21
|
+
}
|
|
22
|
+
const data = await callTool("analyze_technicals", args);
|
|
23
|
+
outputResult(data, opts);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { callTool } from "../api.js";
|
|
3
|
+
import { outputResult, OutputOptions } from "../format.js";
|
|
4
|
+
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
+
|
|
6
|
+
export function registerTradesCommand(program: Command): void {
|
|
7
|
+
addGlobalOptions(
|
|
8
|
+
program
|
|
9
|
+
.command("trades")
|
|
10
|
+
.description("AI trade evaluation")
|
|
11
|
+
.argument("<trades...>", 'Trade descriptions: "Buy AAPL 30% because..." "Sell MSFT 20% because..."')
|
|
12
|
+
).action(async (tradeStrs: string[], opts: OutputOptions) => {
|
|
13
|
+
const trades = tradeStrs.map(parseTrade);
|
|
14
|
+
const data = await callTool("analyze_trades", { trades });
|
|
15
|
+
outputResult(data, opts);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseTrade(str: string): Record<string, unknown> {
|
|
20
|
+
const match = str.match(
|
|
21
|
+
/^(buy|sell|hold)\s+([A-Z]+)\s+(\d+)%?\s+(?:because\s+)?(.+)$/i
|
|
22
|
+
);
|
|
23
|
+
if (match) {
|
|
24
|
+
return {
|
|
25
|
+
action: match[1].toLowerCase(),
|
|
26
|
+
symbol: match[2].toUpperCase(),
|
|
27
|
+
portfolio_weight: parseInt(match[3], 10) / 100,
|
|
28
|
+
thesis: match[4],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(str);
|
|
34
|
+
} catch {
|
|
35
|
+
console.error(
|
|
36
|
+
`Error: Could not parse trade "${str}"\n` +
|
|
37
|
+
'Format: "Buy AAPL 30% because thesis" or JSON object'
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { callTool } from "../api.js";
|
|
3
|
+
import { outputResult, OutputOptions, makeTable, parseIntSafe } from "../format.js";
|
|
4
|
+
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
+
|
|
6
|
+
export function registerTranscriptCommand(program: Command): void {
|
|
7
|
+
addGlobalOptions(
|
|
8
|
+
program
|
|
9
|
+
.command("transcript")
|
|
10
|
+
.description("Search earnings call transcripts")
|
|
11
|
+
.argument("<symbol>", "Ticker symbol")
|
|
12
|
+
.argument("<keywords...>", "Keywords to search (OR logic)")
|
|
13
|
+
.option("--quarter <q>", 'Fiscal quarter, e.g. "Q1 2024"')
|
|
14
|
+
.option("--max <n>", "Max results", "10")
|
|
15
|
+
).action(
|
|
16
|
+
async (
|
|
17
|
+
symbol: string,
|
|
18
|
+
keywords: string[],
|
|
19
|
+
opts: OutputOptions & { quarter?: string; max: string }
|
|
20
|
+
) => {
|
|
21
|
+
const args: Record<string, unknown> = {
|
|
22
|
+
symbol: symbol.toUpperCase(),
|
|
23
|
+
keywords,
|
|
24
|
+
max_results: parseIntSafe(opts.max, 10),
|
|
25
|
+
};
|
|
26
|
+
if (opts.quarter) args.fiscal_quarter = opts.quarter;
|
|
27
|
+
|
|
28
|
+
const data = await callTool("search_transcript", args);
|
|
29
|
+
outputResult(data, opts, (d, o) => formatTranscript(d, o));
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatTranscript(data: unknown, opts: OutputOptions): string {
|
|
35
|
+
if (!data || typeof data !== "object") return String(data);
|
|
36
|
+
const obj = data as Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
let matches: Record<string, unknown>[] = [];
|
|
39
|
+
if (Array.isArray(data)) {
|
|
40
|
+
matches = data as Record<string, unknown>[];
|
|
41
|
+
} else {
|
|
42
|
+
for (const val of Object.values(obj)) {
|
|
43
|
+
if (Array.isArray(val) && val.length > 0) {
|
|
44
|
+
matches = val as Record<string, unknown>[];
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
// Check one level deeper (e.g. transcript_search_results.matches)
|
|
48
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
49
|
+
for (const nested of Object.values(val as Record<string, unknown>)) {
|
|
50
|
+
if (Array.isArray(nested) && nested.length > 0) {
|
|
51
|
+
matches = nested as Record<string, unknown>[];
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (matches.length > 0) break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (matches.length === 0) return "(no matches)";
|
|
61
|
+
|
|
62
|
+
const rows = matches.map((m) => [
|
|
63
|
+
String(m.speaker || m.name || ""),
|
|
64
|
+
String(m.role || m.title || ""),
|
|
65
|
+
String(m.content_with_highlights || m.text || m.content || m.exact_quote || m.quote || "").slice(0, 120),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
return makeTable(["Speaker", "Role", "Text"], rows, opts);
|
|
69
|
+
}
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import Table from "cli-table3";
|
|
2
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface OutputOptions {
|
|
6
|
+
format?: "json" | "csv" | "table";
|
|
7
|
+
output?: string;
|
|
8
|
+
quiet?: boolean;
|
|
9
|
+
raw?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveFormat(opts: OutputOptions): "json" | "csv" | "table" {
|
|
13
|
+
if (opts.format) return opts.format;
|
|
14
|
+
if (opts.output || !process.stdout.isTTY) return "json";
|
|
15
|
+
return "table";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function outputResult(
|
|
19
|
+
data: unknown,
|
|
20
|
+
opts: OutputOptions,
|
|
21
|
+
tableFormatter?: (data: unknown, opts: OutputOptions) => string
|
|
22
|
+
): void {
|
|
23
|
+
if (opts.raw) {
|
|
24
|
+
emit(JSON.stringify(data, null, 2), opts);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fmt = resolveFormat(opts);
|
|
29
|
+
|
|
30
|
+
if (fmt === "json") {
|
|
31
|
+
emit(JSON.stringify(data, null, 2), opts);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fmt === "csv") {
|
|
36
|
+
emit(toCSV(data), opts);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// table format
|
|
41
|
+
if (tableFormatter) {
|
|
42
|
+
emit(tableFormatter(data, opts), opts);
|
|
43
|
+
} else {
|
|
44
|
+
emit(autoTable(data, opts), opts);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function emit(content: string, opts: OutputOptions): void {
|
|
49
|
+
if (opts.output) {
|
|
50
|
+
mkdirSync(dirname(opts.output), { recursive: true });
|
|
51
|
+
writeFileSync(opts.output, content + "\n");
|
|
52
|
+
if (!opts.quiet) {
|
|
53
|
+
console.error(`Written to ${opts.output}`);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
console.log(content);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toCSV(data: unknown): string {
|
|
61
|
+
const rows = toRows(data);
|
|
62
|
+
if (rows.length === 0) return "";
|
|
63
|
+
const headers = Object.keys(rows[0]);
|
|
64
|
+
const lines = [headers.join(",")];
|
|
65
|
+
for (const row of rows) {
|
|
66
|
+
lines.push(
|
|
67
|
+
headers
|
|
68
|
+
.map((h) => {
|
|
69
|
+
const val = String(row[h] ?? "");
|
|
70
|
+
return val.includes(",") || val.includes('"') || val.includes("\n")
|
|
71
|
+
? `"${val.replace(/"/g, '""')}"`
|
|
72
|
+
: val;
|
|
73
|
+
})
|
|
74
|
+
.join(",")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toRows(data: unknown): Record<string, unknown>[] {
|
|
81
|
+
const flatten = (v: unknown) => flattenObj(v);
|
|
82
|
+
if (Array.isArray(data)) return data.map(flatten);
|
|
83
|
+
if (data && typeof data === "object") {
|
|
84
|
+
const obj = data as Record<string, unknown>;
|
|
85
|
+
// Prefer arrays of objects over arrays of primitives
|
|
86
|
+
for (const key of Object.keys(obj)) {
|
|
87
|
+
const val = obj[key];
|
|
88
|
+
if (
|
|
89
|
+
Array.isArray(val) &&
|
|
90
|
+
val.length > 0 &&
|
|
91
|
+
typeof val[0] === "object" &&
|
|
92
|
+
val[0] !== null
|
|
93
|
+
) {
|
|
94
|
+
return val.map(flatten);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Fallback: any non-empty array
|
|
98
|
+
for (const key of Object.keys(obj)) {
|
|
99
|
+
if (Array.isArray(obj[key]) && (obj[key] as unknown[]).length > 0) {
|
|
100
|
+
return (obj[key] as unknown[]).map(flatten);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [flattenObj(obj)];
|
|
104
|
+
}
|
|
105
|
+
return [{ value: data }];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function flattenObj(
|
|
109
|
+
obj: unknown,
|
|
110
|
+
prefix = ""
|
|
111
|
+
): Record<string, unknown> {
|
|
112
|
+
if (!obj || typeof obj !== "object") return { value: obj };
|
|
113
|
+
const result: Record<string, unknown> = {};
|
|
114
|
+
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
115
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
116
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
117
|
+
Object.assign(result, flattenObj(v, key));
|
|
118
|
+
} else if (Array.isArray(v)) {
|
|
119
|
+
if (v.length <= 3) {
|
|
120
|
+
result[key] = v
|
|
121
|
+
.map((item) =>
|
|
122
|
+
item && typeof item === "object" ? JSON.stringify(item) : String(item)
|
|
123
|
+
)
|
|
124
|
+
.join(", ");
|
|
125
|
+
} else {
|
|
126
|
+
result[key] = `[${v.length} items]`;
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
result[key] = v;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function autoTable(data: unknown, opts?: OutputOptions): string {
|
|
136
|
+
const rows = toRows(data);
|
|
137
|
+
if (rows.length === 0) return "(no data)";
|
|
138
|
+
|
|
139
|
+
const headers = Object.keys(rows[0]);
|
|
140
|
+
const table = new Table({
|
|
141
|
+
head: opts?.quiet ? [] : headers,
|
|
142
|
+
style: { head: ["cyan"] },
|
|
143
|
+
wordWrap: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
table.push(headers.map((h) => truncate(String(row[h] ?? ""), 80)));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return table.toString();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function makeTable(
|
|
154
|
+
headers: string[],
|
|
155
|
+
rows: string[][],
|
|
156
|
+
opts?: { quiet?: boolean }
|
|
157
|
+
): string {
|
|
158
|
+
const table = new Table({
|
|
159
|
+
head: opts?.quiet ? [] : headers,
|
|
160
|
+
style: { head: ["cyan"] },
|
|
161
|
+
wordWrap: true,
|
|
162
|
+
});
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
table.push(row);
|
|
165
|
+
}
|
|
166
|
+
return table.toString();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function truncate(s: string, max: number): string {
|
|
170
|
+
return s.length > max ? s.slice(0, max - 1) + "…" : s;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse a string as an integer, returning `fallback` if the result is NaN.
|
|
175
|
+
*/
|
|
176
|
+
export function parseIntSafe(value: string, fallback: number): number {
|
|
177
|
+
const n = parseInt(value, 10);
|
|
178
|
+
return Number.isNaN(n) ? fallback : n;
|
|
179
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
export function addGlobalOptions(cmd: Command): Command {
|
|
4
|
+
return cmd
|
|
5
|
+
.option("-o, --output <file>", "Write output to file")
|
|
6
|
+
.option("-f, --format <fmt>", "Output format: json, csv, table")
|
|
7
|
+
.option("-q, --quiet", "Suppress headers, data only")
|
|
8
|
+
.option("--raw", "Raw JSON from API");
|
|
9
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { registerAuthCommand } from "./commands/auth.js";
|
|
3
|
+
import { registerInfoCommand } from "./commands/info.js";
|
|
4
|
+
import { registerNewsCommand } from "./commands/news.js";
|
|
5
|
+
import { registerPriceCommand } from "./commands/price.js";
|
|
6
|
+
import { registerFinancialsCommand } from "./commands/financials.js";
|
|
7
|
+
import { registerSentimentCommand } from "./commands/sentiment.js";
|
|
8
|
+
import { registerMarketCommand } from "./commands/market.js";
|
|
9
|
+
import { registerCollectionsCommand } from "./commands/collections.js";
|
|
10
|
+
import { registerScreenCommand } from "./commands/screen.js";
|
|
11
|
+
import { registerEarningsCommand } from "./commands/earnings.js";
|
|
12
|
+
import { registerTechnicalsCommand } from "./commands/technicals.js";
|
|
13
|
+
import { registerOptionsCommand } from "./commands/options.js";
|
|
14
|
+
import { registerPortfolioCommand } from "./commands/portfolio.js";
|
|
15
|
+
import { registerTradesCommand } from "./commands/trades.js";
|
|
16
|
+
import { registerCatalystsCommand } from "./commands/catalysts.js";
|
|
17
|
+
import { registerPositionCommand } from "./commands/position.js";
|
|
18
|
+
import { registerTranscriptCommand } from "./commands/transcript.js";
|
|
19
|
+
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.name("bloom")
|
|
24
|
+
.description("Bloom CLI — financial data for coding agents and humans")
|
|
25
|
+
.version("0.1.0");
|
|
26
|
+
|
|
27
|
+
// Register all commands (each command adds its own global options via addGlobalOptions)
|
|
28
|
+
registerAuthCommand(program);
|
|
29
|
+
registerInfoCommand(program);
|
|
30
|
+
registerNewsCommand(program);
|
|
31
|
+
registerPriceCommand(program);
|
|
32
|
+
registerFinancialsCommand(program);
|
|
33
|
+
registerSentimentCommand(program);
|
|
34
|
+
registerMarketCommand(program);
|
|
35
|
+
registerCollectionsCommand(program);
|
|
36
|
+
registerScreenCommand(program);
|
|
37
|
+
registerEarningsCommand(program);
|
|
38
|
+
registerTechnicalsCommand(program);
|
|
39
|
+
registerOptionsCommand(program);
|
|
40
|
+
registerPortfolioCommand(program);
|
|
41
|
+
registerTradesCommand(program);
|
|
42
|
+
registerCatalystsCommand(program);
|
|
43
|
+
registerPositionCommand(program);
|
|
44
|
+
registerTranscriptCommand(program);
|
|
45
|
+
|
|
46
|
+
program.parseAsync().catch((err) => {
|
|
47
|
+
console.error("Error:", err.message || err);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|