1#!/bin/bash2# fdq — quick FinData OS client for shell sessions (Claude Code or human).3# Reads the API key from ~/Desktop/Cluster/secrets/findata/api-key-claude-code.txt (or $FINDATA_API_KEY).4# fdq health # status + alerts5# fdq datasets # catalog (id, status, rows)6# fdq bars AAPL 1m 2024-03-01 2024-03-31 [raw|split|split_div] # JSON page (<= 10 000 rows); use export for more7# fdq sql "SELECT ... FROM market_bars_stock_1m_raw WHERE symbol='AAPL' AND year=2024 ..." [max_rows]8# fdq fund AAPL [as_of_iso] [metrics,comma] # point-in-time fundamentals9# fdq export "AAPL,MSFT" 2024-01-01 2024-12-31 [dataset_id] [out.parquet] # async Parquet export -> file10# fdq jobs | fdq enqueue '{"kind":"incremental","params":{"type":"stock","period":"day"}}' # admin11# fdq get /api/v1/... | fdq post /api/v1/... '{json}' # raw12set -euo pipefail13BASE="${FINDATA_URL:-https://findata.spboucher.ai}"14KEY="${FINDATA_API_KEY:-$(cat "$HOME/Desktop/Cluster/secrets/findata/api-key-claude-code.txt")}"15export BASE KEY16j() { python3 -c 'import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=1, ensure_ascii=False))'; }17cmd="${1:-help}"; shift || true18case "$cmd" in19 health) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/health" | j ;;20 datasets) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/catalog/datasets" | python3 -c 'import sys,json21for d in json.load(sys.stdin)["data"]:22 i, st, rows, a, b = d["id"], d["status"], d.get("row_count") or 0, d.get("first_session") or "", d.get("last_session") or ""23 print("%-44s %-10s rows=%14s %s -> %s" % (i, st, format(rows, ","), a, b))' ;;24 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 ;;25 sql) SQL="$1" MAXROWS="${2:-1000}" python3 -c '26import os, sys, json, urllib.request27req = urllib.request.Request(os.environ["BASE"] + "/api/v1/query", data=json.dumps({"sql": os.environ["SQL"], "max_rows": int(os.environ["MAXROWS"])}).encode(),28 headers={"Authorization": "Bearer " + os.environ["KEY"], "Content-Type": "application/json"})29try:30 with urllib.request.urlopen(req, timeout=180) as r: d = json.load(r)31except urllib.error.HTTPError as e:32 sys.exit(e.read().decode()[:800])33print(json.dumps({"meta": d["meta"], "columns": [c["name"] for c in d["columns"]]}, indent=1))34for row in d["rows"]: print(json.dumps(row, ensure_ascii=False))' ;;35 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 ;;36 export) SYMS="$1" START="$2" END="$3" DS="${4:-market_bars.stock.1m.raw}" OUT="${5:-findata-export.parquet}" python3 -c '37import os, sys, json, time, urllib.request38base, key = os.environ["BASE"], os.environ["KEY"]39hdr = {"Authorization": "Bearer " + key, "Content-Type": "application/json"}40body = {"dataset_id": os.environ["DS"], "symbols": os.environ["SYMS"].split(","), "start": os.environ["START"], "end": os.environ["END"]}41r = json.load(urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports", data=json.dumps(body).encode(), headers=hdr)))42eid = r["id"]43while True:44 st = json.load(urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports/" + eid, headers=hdr)))45 if st["status"] == "ready": break46 if st["status"] in ("failed", "expired"): sys.exit("export " + st["status"] + ": " + str(st.get("error")))47 time.sleep(2)48with urllib.request.urlopen(urllib.request.Request(base + "/api/v1/exports/" + eid + "/download", headers=hdr)) as resp, open(os.environ["OUT"], "wb") as f:49 f.write(resp.read())50print(os.environ["OUT"] + ": " + str(st["row_count"]) + " rows, " + str(st["bytes"]) + " bytes, dataset v" + str(st["snapshot"].get("version")))' ;;51 jobs) curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/admin/jobs?limit=${LIMIT:-30}" | python3 -c 'import sys,json52for j in json.load(sys.stdin)["data"]:53 print("%s %-12s %-36s %-9s %s %s" % (j["id"][:8], j["kind"], str(j["dataset_id"]), j["status"], str(j["created_at"])[:16], str(j.get("last_error") or "")[:60]))' ;;54 enqueue) curl -s -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d "$1" "$BASE/api/v1/admin/jobs" | j ;;55 get) curl -s -H "Authorization: Bearer $KEY" "$BASE$1" | j ;;56 post) curl -s -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d "${2:-{}}" "$BASE$1" | j ;;57 *) sed -n '2,12p' "$0" ;;58esac59