gdelt
Global multilingual news event stream with tone scoring via GDELT 2.0 Doc API.
Install
npx skills add https://github.com/kansoku-trade/kansoku/tree/main/.claude/skills/gdelt
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kansoku-trade-kansoku@llmmart
git clone https://github.com/kansoku-trade/kansoku.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kansoku-trade/kansoku collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
gdelt
Response language: match user input.
⚠️ GDELT is a rolling recent-window API, not a historical event archive. Time windows are anchored to absolute timestamps (UTC) for reproducibility — the same query asked tomorrow will return different results.
⚠️ 5-second throttle between requests (enforced). Plan batches accordingly.
When to use
Trigger phrases:
- 全球新闻 / 全球事件 / 多语种新闻
- 媒体 tone / sentiment trend / 国际关系
- geopolitical / event tone
- GDELT
Useful for "what is the world saying about X right now" — i.e. retrieving articles from non-English / non-financial sources that don't surface in Longbridge's curated newsfeed.
Workflow
- Build a query in GDELT DSL (the user's term, optionally with operators like
domain:bloomberg.com,sourcelang:eng). - Pick a mode:
artlist— list of articles (default).timelinetone— per-15-min tone time series (-10 = very negative, +10 = very positive).timelinevol/timelinevolinfo— article volume over time.tonechart— tone histogram.
- Specify the window — prefer
--start/--end(absolute), fall back to--timespan. The script converts relative timespans to absolute timestamps before the call and echoes them inmeta.windowso the journal can be re-run.
CLI examples
# Articles about Nvidia in the last 24h
python3 .claude/skills/gdelt/scripts/doc.py "Nvidia"
# 7-day window, English + Chinese articles about TSMC
python3 .claude/skills/gdelt/scripts/doc.py "TSMC OR \"Taiwan Semiconductor\"" --timespan 7d --lang eng,zho
# Tone timeline for Federal Reserve over 30 days
python3 .claude/skills/gdelt/scripts/doc.py "Federal Reserve" --mode timelinetone --timespan 30d
# Absolute window
python3 .claude/skills/gdelt/scripts/doc.py "AI chips" --start 20260501000000 --end 20260528000000
Output shape (artlist)
{
"data": [
{
"url": "https://...",
"title": "...",
"seendate": "20260527T161500Z",
"domain": "...",
"language": "English",
"sourcecountry": "United States",
"socialimage": "..."
}
],
"meta": {
"mode": "artlist",
"query": "Nvidia",
"window": { "start": "20260527071804", "end": "20260528071804" },
"max_records": 75
},
"ok": true
}
Output shape (timelinetone)
{
"ok": true,
"data": [
{"date": "20260520T000000Z", "value": 1.42},
{"date": "20260520T001500Z", "value": 1.05},
...
],
"meta": {"mode": "timelinetone", ...}
}
Error handling
| Exit code | Meaning | LLM action |
|---|---|---|
| 0 | Success | Parse data. |
| 1 | Invalid args (e.g. bad timespan / lang) | Read hint. |
| 3 | HTTP 4xx / non-JSON response | If body contains "Please limit requests", the throttle was tripped — wait and retry. |
| 4 | Network | Suggest retry. |
Known limitations
- 5-second minimum between requests; batch tone + artlist queries must be sequenced.
--max-recordscap is 250.- GDELT's tone metric is a heuristic — useful for direction-of-narrative, not ground truth.
- Results are not cached (window-sensitive).
Related skills
longbridge-newsfor curated equity-specific newsfeed (Chinese-language UX).sec-edgarfor primary-source filings as the contrast to media narrative.fredfor macro data referenced in the narrative.
Files (kansoku)
-
scripts
-
doc.py 4.6 KB
#!/usr/bin/env python3 """GDELT 2.0 Doc API — global multilingual news with tone. GDELT is a rolling recent-window API, not a historical archive. Time windows must be specified absolutely (YYYYMMDDHHMMSS) to keep journal entries reproducible — relative `--timespan` is converted to absolute timestamps before the call. Throttle: ≥ 5 seconds between requests (enforced by _shared/client.py). Output: always JSON (we append format=json; the API defaults to HTML). """ from __future__ import annotations import argparse import re import sys from datetime import datetime, timedelta, timezone from pathlib import Path from urllib.parse import urlencode ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from _shared import client # noqa: E402 GDELT_BASE = "https://api.gdeltproject.org/api/v2/doc/doc" LANG_ALLOWED = { "eng", "zho", "spa", "fra", "deu", "rus", "jpn", "kor", "ara", "por", "ita", "tur", "vie", "ind", "tha", "hin", "nld", "pol", "swe", "fin", } LANG_ALIAS = { "en": "eng", "zh": "zho", "ja": "jpn", "ko": "kor", "fr": "fra", "de": "deu", "es": "spa", "ru": "rus", "pt": "por", "it": "ita", "ar": "ara", } _TIMESPAN_RE = re.compile(r"^(\d+)([hHdDmM])$") def parse_timespan(spec: str) -> timedelta: m = _TIMESPAN_RE.match(spec) if not m: raise client.ClientError( f"Invalid --timespan: {spec}", exit_code=1, hint="Use e.g. 24h, 7d, 1m (=30d)", ) n, unit = int(m.group(1)), m.group(2).lower() if unit == "h": return timedelta(hours=n) if unit == "d": return timedelta(days=n) if unit == "m": return timedelta(days=30 * n) raise client.ClientError(f"Unknown unit: {unit}", exit_code=1) def fmt_gdelt_ts(d: datetime) -> str: return d.strftime("%Y%m%d%H%M%S") def normalise_langs(spec: str) -> list[str]: out = [] for raw in spec.split(","): code = raw.strip().lower() code = LANG_ALIAS.get(code, code) if code not in LANG_ALLOWED: raise client.ClientError( f"Unsupported language code: {code}", exit_code=1, hint=f"Allowed: {', '.join(sorted(LANG_ALLOWED))}", ) out.append(code) return out def main() -> dict: p = argparse.ArgumentParser(description="GDELT 2.0 Doc API.") p.add_argument("query", help="Search query (GDELT DSL accepted).") p.add_argument( "--mode", choices=["artlist", "timelinetone", "timelinevol", "timelinevolinfo", "tonechart"], default="artlist", ) p.add_argument("--start", help="Absolute start YYYYMMDDHHMMSS.") p.add_argument("--end", help="Absolute end YYYYMMDDHHMMSS.") p.add_argument( "--timespan", help="Relative window, e.g. 24h, 7d, 1m. Converted to abs start/end at call time.", ) p.add_argument( "--lang", help="Comma-separated lang codes (eng, zho, jpn, ...). Mapped to sourcelang: query operator.", ) p.add_argument("--max-records", type=int, default=75) p.add_argument("--smoke", action="store_true") args = p.parse_args() if args.smoke: return client.success({"status": "ok"}, smoke=True) query = args.query.strip() if args.lang: codes = normalise_langs(args.lang) if len(codes) == 1: query = f"({query}) sourcelang:{codes[0]}" else: joined = " OR ".join(f"sourcelang:{c}" for c in codes) query = f"({query}) ({joined})" now = datetime.now(timezone.utc) if args.start and args.end: start_ts, end_ts = args.start, args.end elif args.timespan: delta = parse_timespan(args.timespan) start_ts = fmt_gdelt_ts(now - delta) end_ts = fmt_gdelt_ts(now) else: start_ts = fmt_gdelt_ts(now - timedelta(hours=24)) end_ts = fmt_gdelt_ts(now) params = { "query": query, "mode": args.mode, "format": "json", "startdatetime": start_ts, "enddatetime": end_ts, "maxrecords": args.max_records, } url = f"{GDELT_BASE}?{urlencode(params)}" resp = client.fetch(url, source="gdelt", ttl=0) if args.mode == "artlist": items = resp.get("articles", []) if isinstance(resp, dict) else [] elif args.mode in ("timelinetone", "timelinevol", "timelinevolinfo", "tonechart"): items = resp.get("timeline", resp) if isinstance(resp, dict) else resp else: items = resp return client.success( items, mode=args.mode, query=query, window={"start": start_ts, "end": end_ts}, max_records=args.max_records, ) if __name__ == "__main__": client.run(main)
-
-
SKILL.md 4 KB
--- name: gdelt description: Global multilingual news event stream with tone scoring via GDELT 2.0 Doc API. --- # gdelt > Response language: match user input. > ⚠️ **GDELT is a rolling recent-window API**, not a historical event archive. > Time windows are anchored to absolute timestamps (UTC) for reproducibility — > the same query asked tomorrow will return different results. > > ⚠️ **5-second throttle** between requests (enforced). Plan batches accordingly. ## When to use Trigger phrases: - 全球新闻 / 全球事件 / 多语种新闻 - 媒体 tone / sentiment trend / 国际关系 - geopolitical / event tone - GDELT Useful for "what is the world saying about X right now" — i.e. retrieving articles from non-English / non-financial sources that don't surface in Longbridge's curated newsfeed. ## Workflow 1. Build a query in GDELT DSL (the user's term, optionally with operators like `domain:bloomberg.com`, `sourcelang:eng`). 2. Pick a mode: - `artlist` — list of articles (default). - `timelinetone` — per-15-min tone time series (-10 = very negative, +10 = very positive). - `timelinevol` / `timelinevolinfo` — article volume over time. - `tonechart` — tone histogram. 3. Specify the window — prefer `--start`/`--end` (absolute), fall back to `--timespan`. The script converts relative timespans to absolute timestamps before the call and echoes them in `meta.window` so the journal can be re-run. ## CLI examples ```bash # Articles about Nvidia in the last 24h python3 .claude/skills/gdelt/scripts/doc.py "Nvidia" # 7-day window, English + Chinese articles about TSMC python3 .claude/skills/gdelt/scripts/doc.py "TSMC OR \"Taiwan Semiconductor\"" --timespan 7d --lang eng,zho # Tone timeline for Federal Reserve over 30 days python3 .claude/skills/gdelt/scripts/doc.py "Federal Reserve" --mode timelinetone --timespan 30d # Absolute window python3 .claude/skills/gdelt/scripts/doc.py "AI chips" --start 20260501000000 --end 20260528000000 ``` ## Output shape (artlist) ```json { "data": [ { "url": "https://...", "title": "...", "seendate": "20260527T161500Z", "domain": "...", "language": "English", "sourcecountry": "United States", "socialimage": "..." } ], "meta": { "mode": "artlist", "query": "Nvidia", "window": { "start": "20260527071804", "end": "20260528071804" }, "max_records": 75 }, "ok": true } ``` ## Output shape (timelinetone) ```json { "ok": true, "data": [ {"date": "20260520T000000Z", "value": 1.42}, {"date": "20260520T001500Z", "value": 1.05}, ... ], "meta": {"mode": "timelinetone", ...} } ``` ## Error handling | Exit code | Meaning | LLM action | | --------- | --------------------------------------- | ------------------------------------------------------------------------------------ | | 0 | Success | Parse `data`. | | 1 | Invalid args (e.g. bad timespan / lang) | Read `hint`. | | 3 | HTTP 4xx / non-JSON response | If body contains "Please limit requests", the throttle was tripped — wait and retry. | | 4 | Network | Suggest retry. | ## Known limitations - 5-second minimum between requests; batch tone + artlist queries must be sequenced. - `--max-records` cap is 250. - GDELT's tone metric is a heuristic — useful for direction-of-narrative, not ground truth. - Results are not cached (window-sensitive). ## Related skills - `longbridge-news` for curated equity-specific newsfeed (Chinese-language UX). - `sec-edgar` for primary-source filings as the contrast to media narrative. - `fred` for macro data referenced in the narrative.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.