#!/bin/bash # fdq — quick FinData OS client for shell sessions (Claude Code or human). # Reads the API key from ~/Desktop/Cluster/secrets/findata/api-key-claude-code.txt (or $FINDATA_API_KEY). # fdq health # status + alerts # fdq datasets # catalog (id, status, rows) # fdq bars AAPL 1m 2024-03-01 2024-03-31 [raw|split|split_div] # JSON page (<= 10 000 rows); use export for more # fdq sql "SELECT ... FROM market_bars_stock_1m_raw WHERE symbol='AAPL' AND year=2024 ..." [max_rows] # fdq fund AAPL [as_of_iso] [metrics,comma] # point-in-time fundamentals # fdq export "AAPL,MSFT" 2024-01-01 2024-12-31 [dataset_id] [out.parquet] # async Parquet export -> file # fdq jobs | fdq enqueue '{"kind":"incremental","params":{"type":"stock","period":"day"}}' # admin # fdq get /api/v1/... | fdq post /api/v1/... '{json}' # raw set -euo pipefail BASE="${FINDATA_URL:-https://findata.spboucher.ai}" KEY="${FINDATA_API_KEY:-$(cat "$HOME/Desktop/Cluster/secrets/findata/api-key-claude-code.txt")}" export BASE KEY j() { python3 -c 'import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=1, ensure_ascii=False))'; } cmd="${1:-help}"; shift || true case "$cmd" in health) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/health" | j ;; datasets) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/catalog/datasets" | python3 -c 'import sys,json for d in json.load(sys.stdin)["data"]: print(f"{d[\"id\"]:<44} {d[\"status\"]:<10} rows={(d.get(\"row_count\") or 0):>14,} {d.get(\"first_session\") or \"\"} -> {d.get(\"last_session\") or \"\"}")' ;; bars) curl -s -H "Authorization: Bearer $KEY" --get "$BASE/api/v1/market/bars" --data-urlencode "symbol=$1" --data-urlencode "interval=$2" --data-urlencode "start=$3" --data-urlencode "end=$4" --data-urlencode "adjustment=${5:-raw}" --data-urlencode "limit=${LIMIT:-10000}" | j ;; sql) SQL="$1" MAXROWS="${2:-1000}" python3 -c ' import os, sys, json, urllib.request req = urllib.request.Request(os.environ["BASE"] + "/api/v1/query", data=json.dumps({"sql": os.environ["SQL"], "max_rows": int(os.environ["MAXROWS"])}).encode(), headers={"Authorization": "Bearer " + os.environ["KEY"], "Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=180) as r: d = json.load(r) except urllib.error.HTTPError as e: sys.exit(e.read().decode()[:800]) print(json.dumps({"meta": d["meta"], "columns": [c["name"] for c in d["columns"]]}, indent=1)) for row in d["rows"]: print(json.dumps(row, ensure_ascii=False))' ;; fund) curl -s -H "Authorization: Bearer $KEY" --get "$BASE/api/v1/fundamentals/$1" ${2:+--data-urlencode "as_of=$2"} ${3:+--data-urlencode "metrics=$3"} | j ;; export) SYMS="$1" START="$2" END="$3" DS="${4:-market_bars.stock.1m.raw}" OUT="${5:-findata-export.parquet}" python3 -c ' import os, sys, json, time, urllib.request base, key = os.environ["BASE"], os.environ["KEY"] hdr = {"Authorization": "Bearer " + key, "Content-Type": "application/json"} body = {"dataset_id": os.environ["DS"], "symbols": os.environ["SYMS"].split(","), "start": os.environ["START"], "end": os.environ["END"]} r = json.load(urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports", data=json.dumps(body).encode(), headers=hdr))) eid = r["id"] while True: st = json.load(urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports/" + eid, headers=hdr))) if st["status"] == "ready": break if st["status"] in ("failed", "expired"): sys.exit("export " + st["status"] + ": " + str(st.get("error"))) time.sleep(2) with urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports/" + eid + "/download", headers=hdr)) as resp, open(os.environ["OUT"], "wb") as f: f.write(resp.read()) print(os.environ["OUT"] + ": " + str(st["row_count"]) + " rows, " + str(st["bytes"]) + " bytes, dataset v" + str(st["snapshot"].get("version")))' ;; jobs) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/admin/jobs?limit=${LIMIT:-30}" | python3 -c 'import sys,json for j in json.load(sys.stdin)["data"]: print(f"{j[\"id\"][:8]} {j[\"kind\"]:<12} {str(j[\"dataset_id\"]):<36} {j[\"status\"]:<9} {str(j[\"created_at\"])[:16]} {str(j.get(\"last_error\") or \"\")[:60]}")' ;; enqueue) curl -s -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d "$1" "$BASE/api/v1/admin/jobs" | j ;; get) curl -s -H "Authorization: Bearer $KEY" "$BASE$1" | j ;; post) curl -s -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d "${2:-{}}" "$BASE$1" | j ;; *) sed -n '2,12p' "$0" ;; esac