riskmodels 2.0.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.
Files changed (72) hide show
  1. package/README.md +104 -0
  2. package/dist/commands/agent.d.ts +2 -0
  3. package/dist/commands/agent.js +76 -0
  4. package/dist/commands/balance.d.ts +2 -0
  5. package/dist/commands/balance.js +25 -0
  6. package/dist/commands/batch.d.ts +2 -0
  7. package/dist/commands/batch.js +76 -0
  8. package/dist/commands/config.d.ts +2 -0
  9. package/dist/commands/config.js +152 -0
  10. package/dist/commands/correlation.d.ts +2 -0
  11. package/dist/commands/correlation.js +80 -0
  12. package/dist/commands/doctor.d.ts +2 -0
  13. package/dist/commands/doctor.js +51 -0
  14. package/dist/commands/estimate.d.ts +2 -0
  15. package/dist/commands/estimate.js +40 -0
  16. package/dist/commands/health.d.ts +2 -0
  17. package/dist/commands/health.js +60 -0
  18. package/dist/commands/install.d.ts +2 -0
  19. package/dist/commands/install.js +136 -0
  20. package/dist/commands/l3.d.ts +2 -0
  21. package/dist/commands/l3.js +33 -0
  22. package/dist/commands/macro-factors.d.ts +2 -0
  23. package/dist/commands/macro-factors.js +39 -0
  24. package/dist/commands/manifest.d.ts +2 -0
  25. package/dist/commands/manifest.js +270 -0
  26. package/dist/commands/mcp-config.d.ts +2 -0
  27. package/dist/commands/mcp-config.js +95 -0
  28. package/dist/commands/mcp.d.ts +12 -0
  29. package/dist/commands/mcp.js +59 -0
  30. package/dist/commands/metrics.d.ts +2 -0
  31. package/dist/commands/metrics.js +29 -0
  32. package/dist/commands/portfolio.d.ts +2 -0
  33. package/dist/commands/portfolio.js +80 -0
  34. package/dist/commands/query.d.ts +2 -0
  35. package/dist/commands/query.js +90 -0
  36. package/dist/commands/rankings.d.ts +2 -0
  37. package/dist/commands/rankings.js +108 -0
  38. package/dist/commands/returns.d.ts +2 -0
  39. package/dist/commands/returns.js +77 -0
  40. package/dist/commands/schema.d.ts +2 -0
  41. package/dist/commands/schema.js +77 -0
  42. package/dist/commands/status.d.ts +2 -0
  43. package/dist/commands/status.js +25 -0
  44. package/dist/commands/tickers.d.ts +2 -0
  45. package/dist/commands/tickers.js +36 -0
  46. package/dist/commands/uninstall.d.ts +2 -0
  47. package/dist/commands/uninstall.js +45 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +67 -0
  50. package/dist/lib/api-client.d.ts +22 -0
  51. package/dist/lib/api-client.js +100 -0
  52. package/dist/lib/api-url.d.ts +2 -0
  53. package/dist/lib/api-url.js +8 -0
  54. package/dist/lib/config.d.ts +22 -0
  55. package/dist/lib/config.js +46 -0
  56. package/dist/lib/credentials.d.ts +26 -0
  57. package/dist/lib/credentials.js +70 -0
  58. package/dist/lib/display.d.ts +1 -0
  59. package/dist/lib/display.js +19 -0
  60. package/dist/lib/mcp-config-paths.d.ts +16 -0
  61. package/dist/lib/mcp-config-paths.js +100 -0
  62. package/dist/lib/mcp-config-writer.d.ts +36 -0
  63. package/dist/lib/mcp-config-writer.js +265 -0
  64. package/dist/lib/mcp-install-plan.d.ts +16 -0
  65. package/dist/lib/mcp-install-plan.js +28 -0
  66. package/dist/lib/oauth.d.ts +2 -0
  67. package/dist/lib/oauth.js +47 -0
  68. package/dist/lib/redact.d.ts +2 -0
  69. package/dist/lib/redact.js +28 -0
  70. package/dist/lib/sql-validation.d.ts +9 -0
  71. package/dist/lib/sql-validation.js +21 -0
  72. package/package.json +41 -0
@@ -0,0 +1,59 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ /**
9
+ * Resolve path to the built MCP server entry (mcp/dist/index.js).
10
+ *
11
+ * Order:
12
+ * 1. RISKMODELS_MCP_SERVER_PATH
13
+ * 2. --mcp-server-path CLI option
14
+ * 3. ./mcp/dist/index.js relative to cwd (repo root when running from RiskModels_API)
15
+ * 4. ../../mcp/dist/index.js relative to cli/dist (monorepo dev: npm link / npm run from repo)
16
+ */
17
+ export function resolveMcpServerPath(explicit) {
18
+ if (process.env.RISKMODELS_MCP_SERVER_PATH?.trim()) {
19
+ return resolve(process.env.RISKMODELS_MCP_SERVER_PATH.trim());
20
+ }
21
+ if (explicit?.trim()) {
22
+ return resolve(explicit.trim());
23
+ }
24
+ const cwdCandidate = join(process.cwd(), "mcp", "dist", "index.js");
25
+ if (existsSync(cwdCandidate)) {
26
+ return cwdCandidate;
27
+ }
28
+ // cli/dist/commands → ../../../ = RiskModels_API repo root (sibling of cli/)
29
+ const fromCliDist = join(__dirname, "..", "..", "..", "mcp", "dist", "index.js");
30
+ if (existsSync(fromCliDist)) {
31
+ return fromCliDist;
32
+ }
33
+ return null;
34
+ }
35
+ export function mcpServeCommand() {
36
+ return new Command("mcp")
37
+ .description("Run the RiskModels MCP server over stdio (for Claude Desktop, Cursor, etc.). Same as: node /path/to/mcp/dist/index.js")
38
+ .option("-p, --mcp-server-path <path>", "Absolute path to mcp/dist/index.js (overrides env RISKMODELS_MCP_SERVER_PATH)")
39
+ .action((opts) => {
40
+ const path = resolveMcpServerPath(opts.mcpServerPath);
41
+ if (!path) {
42
+ console.error(chalk.red("Could not find MCP server build at mcp/dist/index.js.\n"));
43
+ console.error(chalk.yellow("Build it: cd mcp && npm ci && npm run build\n" +
44
+ "Or set RISKMODELS_MCP_SERVER_PATH to the absolute path to mcp/dist/index.js\n" +
45
+ "Or run this command from the RiskModels_API repo root after building mcp/."));
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ const child = spawn(process.execPath, [path], {
50
+ stdio: "inherit",
51
+ env: { ...process.env },
52
+ });
53
+ child.on("exit", (code, signal) => {
54
+ if (signal)
55
+ process.exit(1);
56
+ process.exit(code ?? 1);
57
+ });
58
+ });
59
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function metricsCommand(): Command;
@@ -0,0 +1,29 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../lib/config.js";
4
+ import { requireResolvedAuth } from "../lib/credentials.js";
5
+ import { apiFetchJson } from "../lib/api-client.js";
6
+ import { printResults } from "../lib/display.js";
7
+ export function metricsCommand() {
8
+ return new Command("metrics")
9
+ .description("Latest risk metrics snapshot for one ticker (GET /metrics/{ticker})")
10
+ .argument("<ticker>", "Ticker symbol, e.g. NVDA")
11
+ .action(async (ticker, _opts, cmd) => {
12
+ const json = cmd.optsWithGlobals().json ?? false;
13
+ const cfg = await loadConfig();
14
+ const auth = requireResolvedAuth(cfg, chalk.yellow);
15
+ if (!auth)
16
+ return;
17
+ const enc = encodeURIComponent(ticker.trim());
18
+ try {
19
+ const { body, costUsd } = await apiFetchJson(auth, "GET", `/metrics/${enc}`);
20
+ printResults(body, json);
21
+ if (!json && costUsd)
22
+ console.log(chalk.dim(`Cost: $${costUsd}`));
23
+ }
24
+ catch (e) {
25
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
26
+ process.exitCode = 1;
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function portfolioCommand(): Command;
@@ -0,0 +1,80 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { Command } from "commander";
3
+ import chalk from "chalk";
4
+ import { loadConfig } from "../lib/config.js";
5
+ import { requireResolvedAuth } from "../lib/credentials.js";
6
+ import { apiFetchJson } from "../lib/api-client.js";
7
+ import { printResults } from "../lib/display.js";
8
+ export function portfolioCommand() {
9
+ const portfolio = new Command("portfolio").description("Portfolio-level API (POST /portfolio/risk-index)");
10
+ portfolio
11
+ .command("risk-index")
12
+ .description("Holdings-weighted L3 variance decomposition and optional ER time series")
13
+ .option("--file <path>", "JSON file: { positions: [{ ticker, weight }, ...] }")
14
+ .option("--stdin", "Read positions JSON from stdin")
15
+ .option("--time-series", "Include daily portfolio ER time series")
16
+ .option("--years <n>", "Years when --time-series (1–15)", "1")
17
+ .action(async (opts, cmd) => {
18
+ const json = cmd.optsWithGlobals().json ?? false;
19
+ const cfg = await loadConfig();
20
+ const auth = requireResolvedAuth(cfg, chalk.yellow);
21
+ if (!auth)
22
+ return;
23
+ let raw;
24
+ try {
25
+ if (opts.stdin) {
26
+ raw = await readStdin();
27
+ }
28
+ else if (opts.file) {
29
+ raw = await readFile(opts.file, "utf8");
30
+ }
31
+ else {
32
+ console.error(chalk.red("Provide --file <path> or --stdin with JSON body."));
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ }
37
+ catch (e) {
38
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ let bodyIn;
43
+ try {
44
+ bodyIn = JSON.parse(raw);
45
+ }
46
+ catch {
47
+ console.error(chalk.red("Invalid JSON."));
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+ const positions = bodyIn.positions;
52
+ if (!Array.isArray(positions)) {
53
+ console.error(chalk.red('JSON must include a "positions" array: { ticker, weight }.'));
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+ const years = parseInt(String(opts.years ?? bodyIn.years ?? "1"), 10) || 1;
58
+ const timeSeries = opts.timeSeries ?? bodyIn.timeSeries ?? false;
59
+ try {
60
+ const { body, costUsd } = await apiFetchJson(auth, "POST", "/portfolio/risk-index", {
61
+ jsonBody: { positions, timeSeries, years },
62
+ });
63
+ printResults(body, json);
64
+ if (!json && costUsd)
65
+ console.log(chalk.dim(`Cost: $${costUsd}`));
66
+ }
67
+ catch (e) {
68
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
69
+ process.exitCode = 1;
70
+ }
71
+ });
72
+ return portfolio;
73
+ }
74
+ async function readStdin() {
75
+ const chunks = [];
76
+ for await (const chunk of process.stdin) {
77
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
78
+ }
79
+ return Buffer.concat(chunks).toString("utf8");
80
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function queryCommand(): Command;
@@ -0,0 +1,90 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { createClient } from "@supabase/supabase-js";
4
+ import ora from "ora";
5
+ import { loadConfig, isDirectReady, needsConfigMessage, needsApiKeyMessage } from "../lib/config.js";
6
+ import { resolveApiAuth } from "../lib/credentials.js";
7
+ import { apiFetchJson } from "../lib/api-client.js";
8
+ import { validateQuery, ensureLimitClause } from "../lib/sql-validation.js";
9
+ import { printResults } from "../lib/display.js";
10
+ async function runBilledQuery(auth, sql, limit) {
11
+ const finalSql = ensureLimitClause(sql, limit);
12
+ const { body, costUsd } = await apiFetchJson(auth, "POST", "/cli/query", {
13
+ jsonBody: { sql: finalSql, limit },
14
+ });
15
+ return { body, costUsd };
16
+ }
17
+ async function runDirectQuery(supabaseUrl, serviceKey, sql, limit) {
18
+ const validation = validateQuery(sql);
19
+ if (!validation.valid) {
20
+ throw new Error(validation.error);
21
+ }
22
+ const finalSql = ensureLimitClause(validation.sanitized, limit);
23
+ const supabase = createClient(supabaseUrl, serviceKey, {
24
+ auth: { persistSession: false, autoRefreshToken: false },
25
+ });
26
+ const { data, error } = await supabase.rpc("exec_sql", { query: finalSql });
27
+ if (error) {
28
+ throw new Error(error.message);
29
+ }
30
+ return { results: data ?? [], count: Array.isArray(data) ? data.length : 0, sql: finalSql };
31
+ }
32
+ export function queryCommand() {
33
+ return new Command("query")
34
+ .description("Run a read-only SQL query (billed: HTTP API, direct: Supabase exec_sql)")
35
+ .argument("<sql>", "SELECT statement")
36
+ .option("-l, --limit <n>", "Max rows when query has no LIMIT", "100")
37
+ .action(async (sqlArg, opts, cmd) => {
38
+ const json = cmd.optsWithGlobals().json ?? false;
39
+ const limit = parseInt(String(opts.limit ?? "100"), 10) || 100;
40
+ const cfg = await loadConfig();
41
+ if (!isDirectReady(cfg) && !resolveApiAuth(cfg)) {
42
+ if (cfg?.mode === "direct") {
43
+ console.error(chalk.yellow(needsConfigMessage()));
44
+ }
45
+ else {
46
+ console.error(chalk.yellow(needsApiKeyMessage()));
47
+ }
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+ const validation = validateQuery(sqlArg);
52
+ if (!validation.valid) {
53
+ console.error(chalk.red(validation.error));
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+ const auth = resolveApiAuth(cfg);
58
+ if (auth && !isDirectReady(cfg)) {
59
+ const spinner = json ? null : ora("Running query…").start();
60
+ try {
61
+ const { body, costUsd } = await runBilledQuery(auth, validation.sanitized, limit);
62
+ spinner?.succeed("Done");
63
+ printResults(body, json);
64
+ if (!json && costUsd) {
65
+ console.log(chalk.dim(`Cost: $${costUsd}`));
66
+ }
67
+ }
68
+ catch (e) {
69
+ spinner?.fail("Query failed");
70
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
71
+ process.exitCode = 1;
72
+ }
73
+ return;
74
+ }
75
+ if (isDirectReady(cfg)) {
76
+ const spinner = json ? null : ora("Running query…").start();
77
+ try {
78
+ const body = await runDirectQuery(cfg.supabaseUrl, cfg.serviceRoleKey, validation.sanitized, limit);
79
+ spinner?.succeed("Done");
80
+ printResults(body, json);
81
+ }
82
+ catch (e) {
83
+ spinner?.fail("Query failed");
84
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
85
+ process.exitCode = 1;
86
+ }
87
+ return;
88
+ }
89
+ });
90
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function rankingsCommand(): Command;
@@ -0,0 +1,108 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../lib/config.js";
4
+ import { requireResolvedAuth, resolveApiAuth } from "../lib/credentials.js";
5
+ import { apiFetchJson, apiFetchOptionalAuth } from "../lib/api-client.js";
6
+ import { apiRootFromUserBase } from "../lib/api-url.js";
7
+ import { printResults } from "../lib/display.js";
8
+ export function rankingsCommand() {
9
+ const rank = new Command("rankings").description("Cross-sectional rankings (GET /rankings/...)");
10
+ rank
11
+ .command("snapshot")
12
+ .description("Full or filtered ranking grid for one ticker (GET /rankings/{ticker})")
13
+ .argument("<ticker>", "Symbol")
14
+ .option("--metric <m>", "mkt_cap | gross_return | sector_residual | ...")
15
+ .option("--cohort <c>", "universe | sector | subsector")
16
+ .option("--window <w>", "1d | 21d | 63d | 252d")
17
+ .action(async (ticker, opts, cmd) => {
18
+ const json = cmd.optsWithGlobals().json ?? false;
19
+ const cfg = await loadConfig();
20
+ const auth = requireResolvedAuth(cfg, chalk.yellow);
21
+ if (!auth)
22
+ return;
23
+ const enc = encodeURIComponent(ticker.trim());
24
+ const query = {};
25
+ if (opts.metric)
26
+ query.metric = opts.metric;
27
+ if (opts.cohort)
28
+ query.cohort = opts.cohort;
29
+ if (opts.window)
30
+ query.window = opts.window;
31
+ try {
32
+ const { body, costUsd } = await apiFetchJson(auth, "GET", `/rankings/${enc}`, { query });
33
+ printResults(body, json);
34
+ if (!json && costUsd)
35
+ console.log(chalk.dim(`Cost: $${costUsd}`));
36
+ }
37
+ catch (e) {
38
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
39
+ process.exitCode = 1;
40
+ }
41
+ });
42
+ rank
43
+ .command("badge")
44
+ .description("Shields.io-style badge JSON (GET /rankings/{ticker}/badge; Bearer optional)")
45
+ .argument("<ticker>", "Symbol")
46
+ .option("--token <t>", "When server requires RANKINGS_BADGE_TOKEN")
47
+ .option("--metric <m>", "Ranking metric")
48
+ .option("--cohort <c>", "universe | sector | subsector")
49
+ .option("--window <w>", "1d | 21d | 63d | 252d")
50
+ .action(async (ticker, opts, cmd) => {
51
+ const json = cmd.optsWithGlobals().json ?? false;
52
+ const cfg = await loadConfig();
53
+ const apiRoot = apiRootFromUserBase(cfg?.apiBaseUrl);
54
+ const auth = resolveApiAuth(cfg);
55
+ const enc = encodeURIComponent(ticker.trim());
56
+ const query = {};
57
+ if (opts.token)
58
+ query.token = opts.token;
59
+ if (opts.metric)
60
+ query.metric = opts.metric;
61
+ if (opts.cohort)
62
+ query.cohort = opts.cohort;
63
+ if (opts.window)
64
+ query.window = opts.window;
65
+ try {
66
+ const { body } = await apiFetchOptionalAuth(apiRoot, "GET", `/rankings/${enc}/badge`, {
67
+ query,
68
+ auth: auth ?? undefined,
69
+ });
70
+ printResults(body, json);
71
+ }
72
+ catch (e) {
73
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
74
+ process.exitCode = 1;
75
+ }
76
+ });
77
+ rank
78
+ .command("top")
79
+ .description("Leaderboard: best names for metric × cohort × window (GET /rankings/top)")
80
+ .requiredOption("--metric <m>", "mkt_cap | gross_return | ...")
81
+ .requiredOption("--cohort <c>", "universe | sector | subsector")
82
+ .requiredOption("--window <w>", "1d | 21d | 63d | 252d")
83
+ .option("--limit <n>", "Rows (1–100)", "10")
84
+ .action(async (opts, cmd) => {
85
+ const json = cmd.optsWithGlobals().json ?? false;
86
+ const cfg = await loadConfig();
87
+ const auth = requireResolvedAuth(cfg, chalk.yellow);
88
+ if (!auth)
89
+ return;
90
+ const query = {
91
+ metric: opts.metric,
92
+ cohort: opts.cohort,
93
+ window: opts.window,
94
+ limit: parseInt(String(opts.limit ?? "10"), 10) || 10,
95
+ };
96
+ try {
97
+ const { body, costUsd } = await apiFetchJson(auth, "GET", "/rankings/top", { query });
98
+ printResults(body, json);
99
+ if (!json && costUsd)
100
+ console.log(chalk.dim(`Cost: $${costUsd}`));
101
+ }
102
+ catch (e) {
103
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
104
+ process.exitCode = 1;
105
+ }
106
+ });
107
+ return rank;
108
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function returnsCommand(): Command;
@@ -0,0 +1,77 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../lib/config.js";
4
+ import { requireResolvedAuth } from "../lib/credentials.js";
5
+ import { apiFetchJson } from "../lib/api-client.js";
6
+ import { printResults } from "../lib/display.js";
7
+ export function returnsCommand() {
8
+ const ret = new Command("returns").description("Return time series for stocks and ETFs (GET /ticker-returns)");
9
+ ret
10
+ .command("ticker")
11
+ .description("Daily returns for a stock or ETF (GET /ticker-returns). Stocks include L3 hedge ratios; ETFs return date/returns_gross/price_close only.")
12
+ .argument("<ticker>", "Symbol, e.g. NVDA or SPY")
13
+ .option("--years <n>", "Years of history (1–15)", "1")
14
+ .option("--limit <n>", "Max rows")
15
+ .option("--nocache", "Bypass cache")
16
+ .action(async (ticker, opts, cmd) => {
17
+ const years = parseInt(String(opts.years ?? "1"), 10) || 1;
18
+ const query = {
19
+ ticker: ticker.trim(),
20
+ years,
21
+ format: "json",
22
+ };
23
+ if (opts.limit)
24
+ query.limit = parseInt(opts.limit, 10);
25
+ if (opts.nocache)
26
+ query.nocache = true;
27
+ await runReturns(cmd, "/ticker-returns", query);
28
+ });
29
+ // Deprecated aliases: /returns and /etf-returns were removed. Forward to
30
+ // /ticker-returns (which now accepts both stocks and ETFs) and print a notice.
31
+ ret
32
+ .command("stock")
33
+ .description("DEPRECATED: alias for 'returns ticker'. Forwards to GET /ticker-returns.")
34
+ .argument("<ticker>", "Symbol")
35
+ .option("--years <n>", "Years of history (1–15)", "1")
36
+ .action(async (ticker, opts, cmd) => {
37
+ console.error(chalk.yellow("[deprecated] 'returns stock' is now an alias for 'returns ticker'. /returns was removed."));
38
+ const years = parseInt(String(opts.years ?? "1"), 10) || 1;
39
+ await runReturns(cmd, "/ticker-returns", {
40
+ ticker: ticker.trim(),
41
+ years,
42
+ format: "json",
43
+ });
44
+ });
45
+ ret
46
+ .command("etf")
47
+ .description("DEPRECATED: alias for 'returns ticker'. Forwards to GET /ticker-returns (ETFs now flow through /ticker-returns).")
48
+ .argument("<etf>", "ETF symbol, e.g. SPY")
49
+ .option("--years <n>", "Years of history (1–15)", "1")
50
+ .action(async (etf, opts, cmd) => {
51
+ console.error(chalk.yellow("[deprecated] 'returns etf' is now an alias for 'returns ticker'. /etf-returns was removed; ETFs are served via /ticker-returns."));
52
+ const years = parseInt(String(opts.years ?? "1"), 10) || 1;
53
+ await runReturns(cmd, "/ticker-returns", {
54
+ ticker: etf.trim(),
55
+ years,
56
+ format: "json",
57
+ });
58
+ });
59
+ return ret;
60
+ }
61
+ async function runReturns(cmd, path, query) {
62
+ const json = cmd.optsWithGlobals().json ?? false;
63
+ const cfg = await loadConfig();
64
+ const auth = requireResolvedAuth(cfg, chalk.yellow);
65
+ if (!auth)
66
+ return;
67
+ try {
68
+ const { body, costUsd } = await apiFetchJson(auth, "GET", path, { query });
69
+ printResults(body, json);
70
+ if (!json && costUsd)
71
+ console.log(chalk.dim(`Cost: $${costUsd}`));
72
+ }
73
+ catch (e) {
74
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
75
+ process.exitCode = 1;
76
+ }
77
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function schemaCommand(): Command;
@@ -0,0 +1,77 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { loadConfig, isDirectReady, needsConfigMessage } from "../lib/config.js";
4
+ import { printResults } from "../lib/display.js";
5
+ function sanitizeTableName(name) {
6
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
7
+ return null;
8
+ }
9
+ return name;
10
+ }
11
+ export function schemaCommand() {
12
+ return new Command("schema")
13
+ .description("Inspect PostgREST schema (direct / Supabase mode only)")
14
+ .option("-t, --table <name>", "Show a single table definition")
15
+ .action(async (opts, cmd) => {
16
+ const json = cmd.optsWithGlobals().json ?? false;
17
+ const cfg = await loadConfig();
18
+ if (!isDirectReady(cfg)) {
19
+ if (cfg?.mode === "billed") {
20
+ console.error(chalk.yellow("Schema introspection is only available in direct (Supabase) mode. Use the Supabase dashboard or run: riskmodels config init and choose Service Role mode."));
21
+ }
22
+ else {
23
+ console.error(chalk.yellow(needsConfigMessage()));
24
+ }
25
+ process.exitCode = 1;
26
+ return;
27
+ }
28
+ if (opts.table) {
29
+ const safe = sanitizeTableName(opts.table);
30
+ if (!safe) {
31
+ console.error(chalk.red("Invalid table name"));
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ }
36
+ const base = cfg.supabaseUrl.replace(/\/$/, "");
37
+ const url = `${base}/rest/v1/`;
38
+ const key = cfg.serviceRoleKey;
39
+ const res = await fetch(url, {
40
+ headers: {
41
+ Accept: "application/openapi+json",
42
+ apikey: key,
43
+ Authorization: `Bearer ${key}`,
44
+ },
45
+ });
46
+ if (!res.ok) {
47
+ const t = await res.text();
48
+ console.error(chalk.red(`OpenAPI fetch failed: ${res.status} ${t.slice(0, 300)}`));
49
+ process.exitCode = 1;
50
+ return;
51
+ }
52
+ const spec = (await res.json());
53
+ const schemas = spec.definitions ?? spec.components?.schemas ?? {};
54
+ if (opts.table) {
55
+ const safe = sanitizeTableName(opts.table);
56
+ const tableKey = Object.keys(schemas).find((k) => k.toLowerCase() === safe.toLowerCase());
57
+ if (!tableKey) {
58
+ console.error(chalk.red(`Table not found in OpenAPI schemas: ${safe}`));
59
+ process.exitCode = 1;
60
+ return;
61
+ }
62
+ printResults({ table: tableKey, schema: schemas[tableKey] }, json);
63
+ return;
64
+ }
65
+ const names = Object.keys(schemas).sort();
66
+ if (json) {
67
+ printResults({ tables: names, openapi_version: spec.openapi }, true);
68
+ }
69
+ else {
70
+ console.log(chalk.bold("Tables (from OpenAPI schemas):"));
71
+ for (const n of names) {
72
+ console.log(` ${n}`);
73
+ }
74
+ console.log(chalk.dim(`\nDetail: riskmodels schema --table <name>`));
75
+ }
76
+ });
77
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function statusCommand(): Command;
@@ -0,0 +1,25 @@
1
+ import { Command } from "commander";
2
+ import { configPath, loadConfig, maskSecret } from "../lib/config.js";
3
+ import { detectClients, selectedClients } from "../lib/mcp-config-paths.js";
4
+ import { printResults } from "../lib/display.js";
5
+ export function statusCommand() {
6
+ return new Command("status")
7
+ .description("Show RiskModels CLI and MCP client configuration status")
8
+ .option("--client <name>", "claude | cursor | codex | vscode")
9
+ .option("--all", "Check all supported clients")
10
+ .option("--json", "JSON output")
11
+ .action(async (opts, cmd) => {
12
+ const json = opts.json || cmd.optsWithGlobals().json || false;
13
+ const cfg = await loadConfig();
14
+ const clients = selectedClients({ client: opts.client, all: opts.all });
15
+ const detections = await detectClients(clients);
16
+ printResults({
17
+ configPath: configPath(),
18
+ configFound: !!cfg,
19
+ apiBaseUrl: cfg?.apiBaseUrl ?? "https://riskmodels.app",
20
+ apiKey: maskSecret(cfg?.apiKey),
21
+ oauthConfigured: !!cfg?.clientId && !!cfg?.clientSecret,
22
+ mcpClients: detections,
23
+ }, json);
24
+ });
25
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function tickersCommand(): Command;
@@ -0,0 +1,36 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../lib/config.js";
4
+ import { apiRootFromUserBase } from "../lib/api-url.js";
5
+ import { apiFetchOptionalAuth } from "../lib/api-client.js";
6
+ import { printResults } from "../lib/display.js";
7
+ export function tickersCommand() {
8
+ return new Command("tickers")
9
+ .description("Ticker universe search (GET /tickers, no auth required)")
10
+ .option("--search <q>", "Match symbol or company name")
11
+ .option("--mag7", "MAG7 names only")
12
+ .option("--metadata", "Include sector / ETF metadata")
13
+ .option("--array <name>", "ticker | teo (full symbol list or trading dates)")
14
+ .action(async (opts, cmd) => {
15
+ const json = cmd.optsWithGlobals().json ?? false;
16
+ const cfg = await loadConfig();
17
+ const apiRoot = apiRootFromUserBase(cfg?.apiBaseUrl);
18
+ const query = {};
19
+ if (opts.search)
20
+ query.search = opts.search;
21
+ if (opts.mag7)
22
+ query.mag7 = true;
23
+ if (opts.metadata)
24
+ query.include_metadata = true;
25
+ if (opts.array)
26
+ query.array = opts.array;
27
+ try {
28
+ const { body } = await apiFetchOptionalAuth(apiRoot, "GET", "/tickers", { query });
29
+ printResults(body, json);
30
+ }
31
+ catch (e) {
32
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
33
+ process.exitCode = 1;
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function uninstallCommand(): Command;