@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/src/commands/options.ts
DELETED
|
@@ -1,35 +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 registerOptionsCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("options-history")
|
|
10
|
-
.description("Options sentiment evolution over time")
|
|
11
|
-
.argument("<symbol>", "Ticker symbol")
|
|
12
|
-
.option("--days <n>", "Lookback days (7-90)", "30")
|
|
13
|
-
.option("--weekly", "Use weekly sampling (default)")
|
|
14
|
-
.option("--daily", "Use daily data")
|
|
15
|
-
.option("--greeks", "Include advanced Greeks analysis")
|
|
16
|
-
).action(
|
|
17
|
-
async (
|
|
18
|
-
symbol: string,
|
|
19
|
-
opts: OutputOptions & {
|
|
20
|
-
days: string;
|
|
21
|
-
weekly?: boolean;
|
|
22
|
-
daily?: boolean;
|
|
23
|
-
greeks?: boolean;
|
|
24
|
-
}
|
|
25
|
-
) => {
|
|
26
|
-
const data = await callTool("analyze_options_history", {
|
|
27
|
-
symbol: symbol.toUpperCase(),
|
|
28
|
-
lookback_days: parseIntSafe(opts.days, 30),
|
|
29
|
-
weekly_sampling: opts.weekly ?? !opts.daily,
|
|
30
|
-
include_greeks_analysis: !!opts.greeks,
|
|
31
|
-
});
|
|
32
|
-
outputResult(data, opts);
|
|
33
|
-
}
|
|
34
|
-
);
|
|
35
|
-
}
|
|
@@ -1,28 +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 registerPortfolioCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("portfolio")
|
|
10
|
-
.description("Portfolio risk-return analysis")
|
|
11
|
-
.argument("<holdings>", 'JSON holdings, e.g. \'{"AAPL":0.3,"MSFT":0.7}\'')
|
|
12
|
-
.option("--strategy <s>", "Investment strategy context")
|
|
13
|
-
).action(async (holdingsStr: string, opts: OutputOptions & { strategy?: string }) => {
|
|
14
|
-
let holdings: Record<string, number>;
|
|
15
|
-
try {
|
|
16
|
-
holdings = JSON.parse(holdingsStr);
|
|
17
|
-
} catch {
|
|
18
|
-
console.error('Error: Invalid JSON for holdings. Use format: \'{"AAPL":0.3,"MSFT":0.7}\'');
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const args: Record<string, unknown> = { holdings };
|
|
23
|
-
if (opts.strategy) args.strategy = opts.strategy;
|
|
24
|
-
|
|
25
|
-
const data = await callTool("analyze_portfolio", args);
|
|
26
|
-
outputResult(data, opts);
|
|
27
|
-
});
|
|
28
|
-
}
|
package/src/commands/position.ts
DELETED
|
@@ -1,44 +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 registerPositionCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("position-size")
|
|
10
|
-
.description("Kelly criterion position sizing")
|
|
11
|
-
.requiredOption("--bull <n>", "Expected bull return (decimal, e.g. 0.6)")
|
|
12
|
-
.requiredOption("--bear <n>", "Expected bear return (decimal, e.g. -0.15)")
|
|
13
|
-
.requiredOption("--conviction <n>", "Conviction level 1-10")
|
|
14
|
-
.requiredOption("--value <n>", "Portfolio value in dollars")
|
|
15
|
-
.option("--symbol <s>", "Optional symbol for valuation context")
|
|
16
|
-
).action(
|
|
17
|
-
async (
|
|
18
|
-
opts: OutputOptions & {
|
|
19
|
-
bull: string;
|
|
20
|
-
bear: string;
|
|
21
|
-
conviction: string;
|
|
22
|
-
value: string;
|
|
23
|
-
symbol?: string;
|
|
24
|
-
}
|
|
25
|
-
) => {
|
|
26
|
-
const bullReturn = parseFloat(opts.bull);
|
|
27
|
-
const bearReturn = parseFloat(opts.bear);
|
|
28
|
-
const portfolioValue = parseFloat(opts.value);
|
|
29
|
-
if (isNaN(bullReturn)) { console.error("Error: --bull must be a decimal number (e.g. 0.6)"); process.exit(1); }
|
|
30
|
-
if (isNaN(bearReturn)) { console.error("Error: --bear must be a decimal number (e.g. -0.15)"); process.exit(1); }
|
|
31
|
-
if (isNaN(portfolioValue)) { console.error("Error: --value must be a number"); process.exit(1); }
|
|
32
|
-
const args: Record<string, unknown> = {
|
|
33
|
-
bull_return: bullReturn,
|
|
34
|
-
bear_return: bearReturn,
|
|
35
|
-
conviction: parseIntSafe(opts.conviction, 5),
|
|
36
|
-
portfolio_value: portfolioValue,
|
|
37
|
-
};
|
|
38
|
-
if (opts.symbol) args.symbol = opts.symbol.toUpperCase();
|
|
39
|
-
|
|
40
|
-
const data = await callTool("calculate_position_size", args);
|
|
41
|
-
outputResult(data, opts);
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
}
|
package/src/commands/price.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { callTool } from "../api.js";
|
|
3
|
-
import { outputResult, OutputOptions, makeTable } from "../format.js";
|
|
4
|
-
import { addGlobalOptions } from "../global-opts.js";
|
|
5
|
-
|
|
6
|
-
export function registerPriceCommand(program: Command): void {
|
|
7
|
-
addGlobalOptions(
|
|
8
|
-
program
|
|
9
|
-
.command("price")
|
|
10
|
-
.description("Historical price data")
|
|
11
|
-
.argument("<symbol>", "Ticker symbol")
|
|
12
|
-
.option("--timeframes <tf>", "Comma-separated: 1d,1w,1m,3m,6m,ytd,1y,2y,5y", "1d,1m,1y")
|
|
13
|
-
.option("--ohlc", "Include full OHLC data")
|
|
14
|
-
).action(async (symbol: string, opts: OutputOptions & { timeframes: string; ohlc?: boolean }) => {
|
|
15
|
-
const data = await callTool("get_price_charts", {
|
|
16
|
-
symbol: symbol.toUpperCase(),
|
|
17
|
-
timeframes: opts.timeframes.split(",").map((t) => t.trim()),
|
|
18
|
-
include_ohlc: !!opts.ohlc,
|
|
19
|
-
});
|
|
20
|
-
outputResult(data, opts, (d, o) => formatPrices(d, o));
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function formatPrices(data: unknown, opts: OutputOptions): string {
|
|
25
|
-
if (!data || typeof data !== "object") return String(data);
|
|
26
|
-
const obj = data as Record<string, unknown>;
|
|
27
|
-
const sections: string[] = [];
|
|
28
|
-
|
|
29
|
-
for (const [tf, val] of Object.entries(obj)) {
|
|
30
|
-
if (tf === "symbol" || tf === "timestamp" || tf === "source" || tf === "type") continue;
|
|
31
|
-
if (!val || typeof val !== "object") continue;
|
|
32
|
-
|
|
33
|
-
const tfData = val as Record<string, unknown>;
|
|
34
|
-
let points: Record<string, unknown>[] = [];
|
|
35
|
-
|
|
36
|
-
if (Array.isArray(tfData)) {
|
|
37
|
-
points = tfData as Record<string, unknown>[];
|
|
38
|
-
} else if (tfData.data && Array.isArray(tfData.data)) {
|
|
39
|
-
points = tfData.data as Record<string, unknown>[];
|
|
40
|
-
} else if (tfData.prices && Array.isArray(tfData.prices)) {
|
|
41
|
-
points = tfData.prices as Record<string, unknown>[];
|
|
42
|
-
} else {
|
|
43
|
-
// Fallback: find the first array of objects (skip primitive arrays)
|
|
44
|
-
for (const v of Object.values(tfData)) {
|
|
45
|
-
if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null) {
|
|
46
|
-
points = v as Record<string, unknown>[];
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (points.length === 0) continue;
|
|
53
|
-
|
|
54
|
-
sections.push(`\n--- ${tf.toUpperCase()} (${points.length} points) ---`);
|
|
55
|
-
|
|
56
|
-
const show = points.length <= 12 ? points : [
|
|
57
|
-
...points.slice(0, 5),
|
|
58
|
-
{ date: "...", price: "..." } as Record<string, unknown>,
|
|
59
|
-
...points.slice(-5),
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
const rows = show.map((p) => [
|
|
63
|
-
String(p.date ?? p.datetime ?? p.timestamp ?? ""),
|
|
64
|
-
String(p.price ?? p.close ?? ""),
|
|
65
|
-
p.open != null ? String(p.open) : "",
|
|
66
|
-
p.high != null ? String(p.high) : "",
|
|
67
|
-
p.low != null ? String(p.low) : "",
|
|
68
|
-
p.volume != null ? String(p.volume) : "",
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
const headers = ["Date", "Price", "Open", "High", "Low", "Volume"];
|
|
72
|
-
sections.push(makeTable(headers, rows, opts));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return sections.join("\n") || JSON.stringify(data, null, 2);
|
|
76
|
-
}
|
package/src/commands/screen.ts
DELETED
|
@@ -1,77 +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 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
|
-
}
|
|
@@ -1,51 +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 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
|
-
}
|
|
@@ -1,25 +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 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
|
-
}
|
package/src/commands/trades.ts
DELETED
|
@@ -1,41 +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 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
|
-
}
|
|
@@ -1,69 +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 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
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
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
|
-
}
|
package/src/global-opts.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
}
|