@bloomai/cli 0.1.0 → 0.1.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.
- package/README.md +103 -46
- package/SKILL.md +2 -2
- package/package.json +13 -2
- package/src/api.ts +0 -67
- package/src/auth.ts +0 -83
- package/src/commands/auth.ts +0 -45
- package/src/commands/catalysts.ts +0 -18
- package/src/commands/collections.ts +0 -20
- package/src/commands/earnings.ts +0 -18
- package/src/commands/financials.ts +0 -30
- package/src/commands/info.ts +0 -79
- package/src/commands/market.ts +0 -22
- package/src/commands/news.ts +0 -48
- package/src/commands/options.ts +0 -35
- package/src/commands/portfolio.ts +0 -28
- package/src/commands/position.ts +0 -44
- package/src/commands/price.ts +0 -76
- package/src/commands/screen.ts +0 -77
- package/src/commands/sentiment.ts +0 -51
- package/src/commands/technicals.ts +0 -25
- package/src/commands/trades.ts +0 -41
- package/src/commands/transcript.ts +0 -69
- package/src/format.ts +0 -179
- package/src/global-opts.ts +0 -9
- package/src/index.ts +0 -49
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -12
package/README.md
CHANGED
|
@@ -1,67 +1,124 @@
|
|
|
1
1
|
# Bloom CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Financial data CLI for coding agents. Downloads data to files so you can use bash (jq, grep, awk) to extract what you need instead of loading everything into context.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npm
|
|
8
|
+
npx @bloomai/cli --help
|
|
9
|
+
# Or install globally:
|
|
10
|
+
npm install -g @bloomai/cli
|
|
11
|
+
|
|
11
12
|
export BLOOM_API_KEY=<your-key>
|
|
12
|
-
|
|
13
|
+
# Or: bloom auth login
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Quick Research Workflow
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
|
-
#
|
|
19
|
-
bloom info AAPL
|
|
20
|
-
|
|
21
|
-
# Download to file for analysis
|
|
22
|
-
bloom info AAPL -o /tmp/aapl.json
|
|
23
|
-
jq '.symbols[0].basic_metadata | {name, latest_price, pe_ratio}' /tmp/aapl.json
|
|
19
|
+
# 1. Find what you need
|
|
20
|
+
bloom info AAPL # fundamentals, ratings, peers
|
|
21
|
+
bloom info AAPL --fields basic_metadata,bottom_line_ratings # specific sections only
|
|
24
22
|
|
|
25
|
-
#
|
|
26
|
-
bloom
|
|
27
|
-
bloom
|
|
28
|
-
bloom
|
|
23
|
+
# 2. Download to files for analysis
|
|
24
|
+
bloom info AAPL -o /tmp/bloom/aapl-info.json
|
|
25
|
+
bloom news AAPL --limit 10 -o /tmp/bloom/aapl-news.json
|
|
26
|
+
bloom price AAPL --timeframes 1m,1y -o /tmp/bloom/aapl-price.json
|
|
27
|
+
bloom financials AAPL --type revenue -o /tmp/bloom/aapl-revenue.json
|
|
29
28
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
bloom earnings AAPL
|
|
35
|
-
bloom technicals AAPL
|
|
36
|
-
bloom catalysts AAPL
|
|
29
|
+
# 3. Use bash to extract what matters
|
|
30
|
+
jq '.symbols[0].basic_metadata | {name, latest_price, sector, pe_ratio}' /tmp/bloom/aapl-info.json
|
|
31
|
+
jq '.articles[] | .headline' /tmp/bloom/aapl-news.json
|
|
32
|
+
jq '.data[] | select(.revenue_growth > 0.1)' /tmp/bloom/aapl-revenue.json
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
See [SKILL.md](./SKILL.md) for complete workflow patterns for coding agents.
|
|
40
|
-
|
|
41
35
|
## Commands
|
|
42
36
|
|
|
43
|
-
| Command |
|
|
44
|
-
|
|
45
|
-
| `info` | Company fundamentals, ratings, peers |
|
|
46
|
-
| `news` | Recent news headlines |
|
|
47
|
-
| `price` | Historical price data |
|
|
48
|
-
| `financials` | Revenue, margins, cash flow |
|
|
49
|
-
| `sentiment` | Market mood (AAII, Fear&Greed, VIX) |
|
|
50
|
-
| `market` | Market map, sectors, movers |
|
|
51
|
-
| `collections` | Curated stock lists |
|
|
52
|
-
| `screen` | Filter stocks by metrics |
|
|
53
|
-
| `earnings` | Earnings history & estimates |
|
|
54
|
-
| `technicals` | RSI, MAs, patterns, options |
|
|
55
|
-
| `options-history` | Options sentiment over time |
|
|
56
|
-
| `portfolio` | Portfolio risk analysis |
|
|
57
|
-
| `trades` | AI trade evaluation |
|
|
58
|
-
| `catalysts` | Forward-looking value drivers |
|
|
59
|
-
| `position-size` | Kelly criterion sizing |
|
|
60
|
-
| `transcript` | Search earnings call transcripts |
|
|
37
|
+
| Command | What it does | Example |
|
|
38
|
+
|---------|-------------|---------|
|
|
39
|
+
| `info` | Company fundamentals, ratings, peers | `bloom info AAPL MSFT` |
|
|
40
|
+
| `news` | Recent news headlines | `bloom news AAPL --limit 5` |
|
|
41
|
+
| `price` | Historical price data | `bloom price AAPL --timeframes 1d,1m,1y` |
|
|
42
|
+
| `financials` | Revenue, margins, cash flow | `bloom financials AAPL --type revenue` |
|
|
43
|
+
| `sentiment` | Market mood (AAII, Fear&Greed, VIX) | `bloom sentiment` |
|
|
44
|
+
| `market` | Market map, sectors, movers | `bloom market --type top_movers` |
|
|
45
|
+
| `collections` | Curated stock lists by theme | `bloom collections --section VALUE` |
|
|
46
|
+
| `screen` | Filter stocks by metrics | `bloom screen "market_cap > 100B" "pe_ratio < 25"` |
|
|
47
|
+
| `earnings` | Earnings history & estimates | `bloom earnings AAPL` |
|
|
48
|
+
| `technicals` | RSI, MAs, patterns, options flow | `bloom technicals AAPL` |
|
|
49
|
+
| `options-history` | Options sentiment over time | `bloom options-history AAPL --days 30` |
|
|
50
|
+
| `portfolio` | Portfolio risk analysis | `bloom portfolio '{"AAPL":0.5,"MSFT":0.5}'` |
|
|
51
|
+
| `trades` | AI trade evaluation | `bloom trades "Buy AAPL: strong AI catalysts"` |
|
|
52
|
+
| `catalysts` | Forward-looking value drivers | `bloom catalysts AAPL MSFT` |
|
|
53
|
+
| `position-size` | Kelly criterion sizing | `bloom position-size --bull 0.6 --bear -0.15 --conviction 8 --value 100000` |
|
|
54
|
+
| `transcript` | Search earnings call transcripts | `bloom transcript AAPL "AI" "services"` |
|
|
61
55
|
|
|
62
56
|
## Global Flags
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
- `-
|
|
66
|
-
- `-
|
|
58
|
+
Every command supports:
|
|
59
|
+
- `-o, --output <file>` — write to file instead of stdout
|
|
60
|
+
- `-f, --format json|csv|table` — output format (default: json)
|
|
61
|
+
- `-q, --quiet` — suppress headers, data only
|
|
67
62
|
- `--raw` — raw API response
|
|
63
|
+
|
|
64
|
+
## Common Patterns
|
|
65
|
+
|
|
66
|
+
### Compare Multiple Stocks
|
|
67
|
+
```bash
|
|
68
|
+
mkdir -p /tmp/bloom/compare
|
|
69
|
+
for ticker in AAPL MSFT GOOG AMZN; do
|
|
70
|
+
bloom info $ticker -o /tmp/bloom/compare/$ticker.json
|
|
71
|
+
done
|
|
72
|
+
# Extract key metrics
|
|
73
|
+
for f in /tmp/bloom/compare/*.json; do
|
|
74
|
+
ticker=$(basename $f .json)
|
|
75
|
+
pe=$(jq -r '.symbols[0].basic_metadata.pe_ratio // "N/A"' $f)
|
|
76
|
+
price=$(jq -r '.symbols[0].basic_metadata.latest_price' $f)
|
|
77
|
+
echo "$ticker: \$$price (P/E: $pe)"
|
|
78
|
+
done
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Screen → Deep Dive
|
|
82
|
+
```bash
|
|
83
|
+
# Find cheap large-caps
|
|
84
|
+
bloom screen "market_cap > 50B" "pe_ratio < 20" "dividend_yield > 2" -o /tmp/bloom/screened.json
|
|
85
|
+
|
|
86
|
+
# Get the tickers
|
|
87
|
+
tickers=$(jq -r '.results[].symbol' /tmp/bloom/screened.json | head -5 | tr '\n' ' ')
|
|
88
|
+
|
|
89
|
+
# Deep dive on each
|
|
90
|
+
for t in $tickers; do
|
|
91
|
+
bloom earnings $t -o /tmp/bloom/earnings-$t.json
|
|
92
|
+
bloom catalysts $t -o /tmp/bloom/catalysts-$t.json
|
|
93
|
+
done
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Market Check
|
|
97
|
+
```bash
|
|
98
|
+
bloom sentiment -o /tmp/bloom/sentiment.json
|
|
99
|
+
bloom market --type major_indexes -o /tmp/bloom/indexes.json
|
|
100
|
+
bloom market --type top_movers --limit 10 -o /tmp/bloom/movers.json
|
|
101
|
+
|
|
102
|
+
# Quick summary
|
|
103
|
+
jq '{fear_greed: .cnn_fear_greed.index_value, level: .cnn_fear_greed.level, vix: .volatility_analysis.assessment}' /tmp/bloom/sentiment.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Portfolio Review
|
|
107
|
+
```bash
|
|
108
|
+
bloom portfolio '{"AAPL":0.3,"MSFT":0.25,"GOOG":0.25,"AMZN":0.2}' --strategy "tech growth" -o /tmp/bloom/portfolio.json
|
|
109
|
+
jq '.risk_metrics' /tmp/bloom/portfolio.json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Earnings Research
|
|
113
|
+
```bash
|
|
114
|
+
bloom transcript AAPL "AI" "services" "margins" --max 20 -o /tmp/bloom/transcript.json
|
|
115
|
+
jq '.matches[] | {speaker, text}' /tmp/bloom/transcript.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Tips
|
|
119
|
+
|
|
120
|
+
- Use `-o` to download data to files, then `jq` to extract. Saves context tokens.
|
|
121
|
+
- Batch multiple tickers: `bloom info AAPL MSFT GOOG` (up to 10).
|
|
122
|
+
- Use `bloom screen` first to narrow your universe, then deep-dive with other commands.
|
|
123
|
+
- `--fields` on `bloom info` reduces response size when you only need specific sections.
|
|
124
|
+
- Pipe to `jq -r '.field'` for clean values usable in scripts.
|
package/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ jq '.data[] | select(.revenue_growth > 0.1)' /tmp/bloom/aapl-revenue.json
|
|
|
39
39
|
| `sentiment` | Market mood (AAII, Fear&Greed, VIX) | `bloom sentiment` |
|
|
40
40
|
| `market` | Market map, sectors, movers | `bloom market --type top_movers` |
|
|
41
41
|
| `collections` | Curated stock lists by theme | `bloom collections --section VALUE` |
|
|
42
|
-
| `screen` | Filter stocks by metrics | `bloom screen "market_cap > 100B" "
|
|
42
|
+
| `screen` | Filter stocks by metrics | `bloom screen "market_cap > 100B" "pe_ratio < 25"` |
|
|
43
43
|
| `earnings` | Earnings history & estimates | `bloom earnings AAPL` |
|
|
44
44
|
| `technicals` | RSI, MAs, patterns, options flow | `bloom technicals AAPL` |
|
|
45
45
|
| `options-history` | Options sentiment over time | `bloom options-history AAPL --days 30` |
|
|
@@ -107,7 +107,7 @@ jq '.risk_metrics' /tmp/bloom/portfolio.json
|
|
|
107
107
|
|
|
108
108
|
### Earnings Research
|
|
109
109
|
```bash
|
|
110
|
-
bloom transcript AAPL "AI" "services" "margins" --max
|
|
110
|
+
bloom transcript AAPL "AI" "services" "margins" --max 20 -o /tmp/bloom/transcript.json
|
|
111
111
|
jq '.matches[] | {speaker, text}' /tmp/bloom/transcript.json
|
|
112
112
|
```
|
|
113
113
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloomai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI tool for Bloom financial data - download market data to files for analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"bloom": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"SKILL.md"
|
|
13
|
+
],
|
|
9
14
|
"scripts": {
|
|
10
15
|
"build": "tsup",
|
|
11
16
|
"dev": "tsup --watch"
|
|
12
17
|
},
|
|
13
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"finance",
|
|
20
|
+
"stocks",
|
|
21
|
+
"cli",
|
|
22
|
+
"bloom",
|
|
23
|
+
"market-data"
|
|
24
|
+
],
|
|
14
25
|
"license": "MIT",
|
|
15
26
|
"dependencies": {
|
|
16
27
|
"cli-table3": "^0.6.5",
|
package/src/api.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { requireApiKey } from "./auth.js";
|
|
2
|
-
|
|
3
|
-
const MCP_URL = "https://api.getbloom.app/mcp/";
|
|
4
|
-
|
|
5
|
-
let requestId = 0;
|
|
6
|
-
|
|
7
|
-
interface McpResponse {
|
|
8
|
-
jsonrpc: string;
|
|
9
|
-
id: number;
|
|
10
|
-
result?: {
|
|
11
|
-
content: Array<{ type: string; text: string }>;
|
|
12
|
-
};
|
|
13
|
-
error?: {
|
|
14
|
-
code: number;
|
|
15
|
-
message: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function callTool(
|
|
20
|
-
toolName: string,
|
|
21
|
-
args: Record<string, unknown>
|
|
22
|
-
): Promise<unknown> {
|
|
23
|
-
const apiKey = requireApiKey();
|
|
24
|
-
requestId++;
|
|
25
|
-
|
|
26
|
-
const body = {
|
|
27
|
-
jsonrpc: "2.0",
|
|
28
|
-
method: "tools/call",
|
|
29
|
-
params: {
|
|
30
|
-
name: toolName,
|
|
31
|
-
arguments: args,
|
|
32
|
-
},
|
|
33
|
-
id: requestId,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const res = await fetch(MCP_URL, {
|
|
37
|
-
method: "POST",
|
|
38
|
-
headers: {
|
|
39
|
-
Authorization: `Bearer ${apiKey}`,
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
},
|
|
42
|
-
body: JSON.stringify(body),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
if (!res.ok) {
|
|
46
|
-
const text = await res.text().catch(() => "");
|
|
47
|
-
throw new Error(`API error ${res.status}: ${text || res.statusText}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const data = (await res.json()) as McpResponse;
|
|
51
|
-
|
|
52
|
-
if (data.error) {
|
|
53
|
-
throw new Error(`MCP error ${data.error.code}: ${data.error.message}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!data.result?.content?.length) {
|
|
57
|
-
throw new Error("Empty response from API");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// The MCP response wraps the data as a JSON string inside content[0].text
|
|
61
|
-
const text = data.result.content[0].text;
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(text);
|
|
64
|
-
} catch {
|
|
65
|
-
return text;
|
|
66
|
-
}
|
|
67
|
-
}
|
package/src/auth.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = join(homedir(), ".config", "bloom-cli");
|
|
6
|
-
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
-
|
|
8
|
-
interface Config {
|
|
9
|
-
api_key?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readConfig(): Config {
|
|
13
|
-
if (!existsSync(CONFIG_FILE)) return {};
|
|
14
|
-
try {
|
|
15
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
16
|
-
} catch {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function writeConfig(config: Config): void {
|
|
22
|
-
try {
|
|
23
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
|
|
25
|
-
mode: 0o600,
|
|
26
|
-
});
|
|
27
|
-
} catch (err) {
|
|
28
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
29
|
-
console.error(
|
|
30
|
-
`Error: Could not write config to ${CONFIG_FILE}: ${msg}\n` +
|
|
31
|
-
`Try using BLOOM_API_KEY environment variable instead.`
|
|
32
|
-
);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getApiKey(): string | undefined {
|
|
38
|
-
return process.env.BLOOM_API_KEY || readConfig().api_key;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function requireApiKey(): string {
|
|
42
|
-
const key = getApiKey();
|
|
43
|
-
if (!key) {
|
|
44
|
-
console.error(
|
|
45
|
-
"Error: No API key found. Set BLOOM_API_KEY or run: bloom auth login"
|
|
46
|
-
);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
return key;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function login(apiKey: string): void {
|
|
53
|
-
const config = readConfig();
|
|
54
|
-
config.api_key = apiKey;
|
|
55
|
-
writeConfig(config);
|
|
56
|
-
console.log("API key saved to " + CONFIG_FILE);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function logout(): void {
|
|
60
|
-
if (existsSync(CONFIG_FILE)) {
|
|
61
|
-
const config = readConfig();
|
|
62
|
-
delete config.api_key;
|
|
63
|
-
writeConfig(config);
|
|
64
|
-
}
|
|
65
|
-
console.log("Logged out. API key cleared.");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function status(): void {
|
|
69
|
-
const envKey = process.env.BLOOM_API_KEY;
|
|
70
|
-
const storedKey = readConfig().api_key;
|
|
71
|
-
if (envKey) {
|
|
72
|
-
console.log(`Authenticated via BLOOM_API_KEY (${maskKey(envKey)})`);
|
|
73
|
-
} else if (storedKey) {
|
|
74
|
-
console.log(`Authenticated via ${CONFIG_FILE} (${maskKey(storedKey)})`);
|
|
75
|
-
} else {
|
|
76
|
-
console.log("Not authenticated. Set BLOOM_API_KEY or run: bloom auth login");
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function maskKey(key: string): string {
|
|
81
|
-
if (key.length <= 8) return "****";
|
|
82
|
-
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
83
|
-
}
|
package/src/commands/auth.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { login, logout, status } from "../auth.js";
|
|
3
|
-
import { createInterface } from "node:readline";
|
|
4
|
-
|
|
5
|
-
export function registerAuthCommand(program: Command): void {
|
|
6
|
-
const auth = program.command("auth").description("Manage API authentication");
|
|
7
|
-
|
|
8
|
-
auth
|
|
9
|
-
.command("login")
|
|
10
|
-
.description("Store API key")
|
|
11
|
-
.argument("[key]", "API key (or pass via stdin)")
|
|
12
|
-
.action(async (key?: string) => {
|
|
13
|
-
if (!key) {
|
|
14
|
-
const rl = createInterface({
|
|
15
|
-
input: process.stdin,
|
|
16
|
-
output: process.stderr,
|
|
17
|
-
});
|
|
18
|
-
key = await new Promise<string>((resolve) => {
|
|
19
|
-
rl.question("API key: ", (answer) => {
|
|
20
|
-
rl.close();
|
|
21
|
-
resolve(answer.trim());
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (!key) {
|
|
26
|
-
console.error("Error: No API key provided");
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
login(key);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
auth
|
|
33
|
-
.command("status")
|
|
34
|
-
.description("Show current auth state")
|
|
35
|
-
.action(() => {
|
|
36
|
-
status();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
auth
|
|
40
|
-
.command("logout")
|
|
41
|
-
.description("Clear stored credentials")
|
|
42
|
-
.action(() => {
|
|
43
|
-
logout();
|
|
44
|
-
});
|
|
45
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
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 registerCatalystsCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("catalysts")
|
|
10
|
-
.description("Forward-looking value catalysts")
|
|
11
|
-
.argument("<symbols...>", "Ticker symbols (up to 5)")
|
|
12
|
-
).action(async (symbols: string[], opts: OutputOptions) => {
|
|
13
|
-
const data = await callTool("generate_catalysts", {
|
|
14
|
-
symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5),
|
|
15
|
-
});
|
|
16
|
-
outputResult(data, opts);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { callTool } from "../api.js";
|
|
3
|
-
import { outputResult, OutputOptions, parseIntSafe } from "../format.js";
|
|
4
|
-
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
-
|
|
6
|
-
export function registerCollectionsCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("collections")
|
|
10
|
-
.description("Curated stock collections by theme")
|
|
11
|
-
.option("--section <s>", "GET_STARTED|VALUE|TRENDS|BEAT_UP|FAMOUS_INVESTORS|HIGH_FLYERS|SECTORS|GEO|LEADERBOARDS", "GET_STARTED")
|
|
12
|
-
.option("--limit <n>", "Max collections", "6")
|
|
13
|
-
).action(async (opts: OutputOptions & { section: string; limit: string }) => {
|
|
14
|
-
const data = await callTool("get_collections", {
|
|
15
|
-
section: opts.section,
|
|
16
|
-
limit: parseIntSafe(opts.limit, 6),
|
|
17
|
-
});
|
|
18
|
-
outputResult(data, opts);
|
|
19
|
-
});
|
|
20
|
-
}
|
package/src/commands/earnings.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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 registerEarningsCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("earnings")
|
|
10
|
-
.description("Earnings history, estimates, surprises")
|
|
11
|
-
.argument("<symbols...>", "Ticker symbols (up to 5)")
|
|
12
|
-
).action(async (symbols: string[], opts: OutputOptions) => {
|
|
13
|
-
const data = await callTool("analyze_earnings", {
|
|
14
|
-
symbols: symbols.map((s) => s.toUpperCase()).slice(0, 5),
|
|
15
|
-
});
|
|
16
|
-
outputResult(data, opts);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
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 CHART_TYPES = [
|
|
7
|
-
"revenue",
|
|
8
|
-
"revenue_growth",
|
|
9
|
-
"operating_margin",
|
|
10
|
-
"net_earnings",
|
|
11
|
-
"net_earnings_growth",
|
|
12
|
-
"free_cash_flow",
|
|
13
|
-
"free_cash_flow_growth",
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
export function registerFinancialsCommand(program: Command): void {
|
|
17
|
-
addGlobalOptions(
|
|
18
|
-
program
|
|
19
|
-
.command("financials")
|
|
20
|
-
.description("Quarterly/annual financial data with YoY growth")
|
|
21
|
-
.argument("<symbol>", "Ticker symbol")
|
|
22
|
-
.option("--type <type>", "Chart type: " + CHART_TYPES.join(", "), "revenue")
|
|
23
|
-
).action(async (symbol: string, opts: OutputOptions & { type: string }) => {
|
|
24
|
-
const data = await callTool("get_financial_charts", {
|
|
25
|
-
symbol: symbol.toUpperCase(),
|
|
26
|
-
chart_type: opts.type,
|
|
27
|
-
});
|
|
28
|
-
outputResult(data, opts);
|
|
29
|
-
});
|
|
30
|
-
}
|
package/src/commands/info.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
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 VALID_FIELDS = [
|
|
7
|
-
"basic_metadata",
|
|
8
|
-
"bottom_line_ratings",
|
|
9
|
-
"embedded_expectations",
|
|
10
|
-
"peers",
|
|
11
|
-
"key_events",
|
|
12
|
-
"holdings",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
export function registerInfoCommand(program: Command): void {
|
|
16
|
-
addGlobalOptions(
|
|
17
|
-
program
|
|
18
|
-
.command("info")
|
|
19
|
-
.description("Company fundamentals, ratings, peers, holdings")
|
|
20
|
-
.argument("<symbols...>", "Ticker symbols (e.g. AAPL MSFT)")
|
|
21
|
-
.option("--fields <fields>", "Comma-separated: " + VALID_FIELDS.join(","))
|
|
22
|
-
).action(async (symbols: string[], opts: OutputOptions & { fields?: string }) => {
|
|
23
|
-
const args: Record<string, unknown> = {
|
|
24
|
-
symbols: symbols.map((s) => s.toUpperCase()),
|
|
25
|
-
};
|
|
26
|
-
if (opts.fields) {
|
|
27
|
-
args.fields = opts.fields.split(",").map((f) => f.trim());
|
|
28
|
-
}
|
|
29
|
-
const data = await callTool("get_symbols_metadata", args);
|
|
30
|
-
outputResult(data, opts, (d) => formatInfo(d));
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function formatInfo(data: unknown): string {
|
|
35
|
-
if (!data || typeof data !== "object") return String(data);
|
|
36
|
-
const obj = data as Record<string, unknown>;
|
|
37
|
-
const sections: string[] = [];
|
|
38
|
-
|
|
39
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
40
|
-
if (key === "timestamp" || key === "source") continue;
|
|
41
|
-
if (val && typeof val === "object") {
|
|
42
|
-
sections.push(`\n=== ${key.toUpperCase()} ===`);
|
|
43
|
-
sections.push(formatSection(val as Record<string, unknown>));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return sections.length > 0 ? sections.join("\n") : JSON.stringify(data, null, 2);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function formatSection(obj: Record<string, unknown>, indent = ""): string {
|
|
51
|
-
const lines: string[] = [];
|
|
52
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
53
|
-
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
54
|
-
lines.push(`${indent}${k}:`);
|
|
55
|
-
lines.push(formatSection(v as Record<string, unknown>, indent + " "));
|
|
56
|
-
} else if (Array.isArray(v)) {
|
|
57
|
-
if (v.length === 0) continue;
|
|
58
|
-
if (typeof v[0] === "string" || typeof v[0] === "number") {
|
|
59
|
-
lines.push(`${indent}${k}: ${v.join(", ")}`);
|
|
60
|
-
} else {
|
|
61
|
-
lines.push(`${indent}${k}:`);
|
|
62
|
-
for (const item of v) {
|
|
63
|
-
if (typeof item === "object" && item) {
|
|
64
|
-
const summary = Object.entries(item as Record<string, unknown>)
|
|
65
|
-
.slice(0, 4)
|
|
66
|
-
.map(([ik, iv]) => `${ik}=${iv}`)
|
|
67
|
-
.join(" ");
|
|
68
|
-
lines.push(`${indent} - ${summary}`);
|
|
69
|
-
} else {
|
|
70
|
-
lines.push(`${indent} - ${item}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
lines.push(`${indent}${k}: ${v}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return lines.join("\n");
|
|
79
|
-
}
|
package/src/commands/market.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { callTool } from "../api.js";
|
|
3
|
-
import { outputResult, OutputOptions, parseIntSafe } from "../format.js";
|
|
4
|
-
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
-
|
|
6
|
-
export function registerMarketCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("market")
|
|
10
|
-
.description("Market performance, sectors, global")
|
|
11
|
-
.option("--type <type>", "top_movers|sector_breakdown|global_markets|major_indexes", "top_movers")
|
|
12
|
-
.option("--period <p>", "1d|1w|1m|3m|6m|ytd|1y|5y", "1d")
|
|
13
|
-
.option("--limit <n>", "Max stocks for top_movers", "24")
|
|
14
|
-
).action(async (opts: OutputOptions & { type: string; period: string; limit: string }) => {
|
|
15
|
-
const data = await callTool("get_market_map", {
|
|
16
|
-
map_type: opts.type,
|
|
17
|
-
period: opts.period,
|
|
18
|
-
limit: parseIntSafe(opts.limit, 24),
|
|
19
|
-
});
|
|
20
|
-
outputResult(data, opts);
|
|
21
|
-
});
|
|
22
|
-
}
|
package/src/commands/news.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
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 registerNewsCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("news")
|
|
10
|
-
.description("Recent news from Benzinga channels")
|
|
11
|
-
.argument("<symbols...>", "Ticker symbols")
|
|
12
|
-
.option("--limit <n>", "Max articles (default: 15)", "15")
|
|
13
|
-
).action(async (symbols: string[], opts: OutputOptions & { limit: string }) => {
|
|
14
|
-
const data = await callTool("get_news_for_symbols", {
|
|
15
|
-
symbols: symbols.map((s) => s.toUpperCase()),
|
|
16
|
-
limit: parseIntSafe(opts.limit, 15),
|
|
17
|
-
});
|
|
18
|
-
outputResult(data, opts, (d, o) => formatNews(d, o));
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function formatNews(data: unknown, opts: OutputOptions): string {
|
|
23
|
-
if (!data || typeof data !== "object") return String(data);
|
|
24
|
-
const obj = data as Record<string, unknown>;
|
|
25
|
-
|
|
26
|
-
let articles: Record<string, unknown>[] = [];
|
|
27
|
-
if (Array.isArray(data)) {
|
|
28
|
-
articles = data as Record<string, unknown>[];
|
|
29
|
-
} else {
|
|
30
|
-
for (const val of Object.values(obj)) {
|
|
31
|
-
if (Array.isArray(val) && val.length > 0) {
|
|
32
|
-
articles = val as Record<string, unknown>[];
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (articles.length === 0) return "(no articles)";
|
|
39
|
-
|
|
40
|
-
const rows = articles.map((a) => [
|
|
41
|
-
String(a.date || a.published || a.created || "").slice(0, 16),
|
|
42
|
-
String(a.title || a.headline || "").slice(0, 80),
|
|
43
|
-
String(a.symbols || a.tickers || a.stocks || "").slice(0, 20),
|
|
44
|
-
String(a.channel || a.source || ""),
|
|
45
|
-
]);
|
|
46
|
-
|
|
47
|
-
return makeTable(["Date", "Title", "Symbols", "Channel"], rows, opts);
|
|
48
|
-
}
|