@clawhub-little-grebe-a78825385c
Access Drillr's financial research capabilities — agentic search over company financials, a high-signal market event feed, published analyst articles, and pe...
---
name: drillr
description: Access Drillr's financial research capabilities — agentic search over company financials, a high-signal market event feed, published analyst articles, and persistent per-user watchlists. Use this whenever the user asks about stock prices, company fundamentals, earnings, SEC filings, market signals, sector trends, or wants to track tickers over time. Requires a user-specific API key obtainable at https://drillr.ai/developer/keys.
version: 1.0.0
license: MIT
homepage: https://drillr.ai
metadata:
openclaw:
homepage: https://drillr.ai
emoji: "📈"
---
# Drillr — Financial Research for Agents
Drillr exposes its AI research agent and financial data pipeline to
external agents through three equivalent channels: **MCP** (Streamable
HTTP), **REST API**, and a **command-line tool**. All three accept the
same `drl_*` API key and expose the same data.
## What you can do
- **search** — Ask natural-language questions about companies, sectors,
tickers, filings, earnings. Runs server-side for 5-15s and returns a
synthesized answer with source attribution.
- **signals** — Browse a curated feed of high-score market events
(news + filings + alerts), filterable by ticker / sector / date.
- **articles** — Read published analyst articles with related-ticker,
sector, and reference metadata.
- **watchlists** — Create, list, and mutate per-user ticker
collections. Persisted across sessions on the user's Drillr account.
---
## Detecting the deployment context
Your onboarding path depends on ONE question:
> Can the user run shell commands on the machine where you are running,
> and see the output directly in the same conversation?
Physical hardware doesn't matter. A Mac mini, MacBook, home NAS,
Raspberry Pi, or cloud VM can all serve either path. What matters is
whether the user's current channel to you is **co-located with your
shell** or **relayed through a chat channel**.
### Path A — Indirect (relayed)
Signals that suggest Path A:
- User is chatting with you via Telegram / WhatsApp / Slack / Discord /
a web chat UI / any IM
- Your input channel is text-only — you have no shared terminal
- User mentions they're on a phone, or away from the host machine
- Your runtime is a bot webhook (e.g. Telegram bot handler)
- The user cannot paste the output of `ls ~/.config/` back to you
→ Use **Onboarding A** below.
### Path B — Direct (co-located)
Signals that suggest Path B:
- You are running as Claude Code, a local Claude Agent SDK session, or
any CLI the user is typing into right now
- You have a Bash / shell tool and anything you run is visible to the
user in the same pane
- The user can copy-paste your suggested `export` or config-edit
commands and run them immediately
→ Use **Onboarding B** below.
### When unsure, ask the user
> Quick setup question: are you talking to me through a terminal on the
> same machine I'm running on — where I can suggest shell commands for
> you to run — or through a separate channel like Telegram / WhatsApp /
> a web chat?
---
## Prerequisites — getting an API key
Do NOT attempt any tool call until you have a `drl_*` key accessible to
your runtime. If it's missing, run the onboarding path that matches
your deployment context.
### Onboarding A — Indirect (IM / web chat / remote host)
The user reaches you through a text-only relay and cannot touch your
filesystem. They will complete the key creation from whatever browser
they have (phone, tablet, or desktop) and paste the key back to you.
**Step 1 — Tell the user (verbatim):**
> To use Drillr I need an API key. From any browser (your phone is
> fine):
>
> 1. Open https://drillr.ai/developer/keys
> 2. Sign in — **Google sign-in is the quickest**; email/password also
> works
> 3. Tap "Create API key" → give it a name (e.g. "my-agent") → copy
> the `drl_...` string
> 4. Paste it back to me here. The key is shown only once.
>
> After I confirm it works, you can delete your message.
**Step 2 — When the key arrives, persist it:**
Write it to `~/.config/drillr/config.json` with file mode `0600`:
```json
{ "api_key": "drl_..." }
```
Equivalent shell:
```bash
mkdir -p ~/.config/drillr
umask 077
cat > ~/.config/drillr/config.json <<EOF
{ "api_key": "$KEY" }
EOF
chmod 600 ~/.config/drillr/config.json
```
**Step 3 — Verify the key works:**
```bash
curl -sS -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer drl_..." \
https://gateway.drillr.ai/api/v1/watchlists
```
Expect HTTP `200`. On `401`, the key is invalid — apologize, ask the
user to regenerate, and rerun Step 1.
**Step 4 — Confirm to the user, masked:**
> Stored `drl_xxxxxxxx_...e9f2`. You can safely delete your message now.
**Rules:**
- Never echo the full key back to the user
- Never write the key into conversation logs, prompts, or scratchpads
- Never commit it to any file other than `~/.config/drillr/config.json`
- If storage fails (permission denied, etc.), tell the user the exact
error — don't silently hold the key in memory
### Onboarding B — Direct (terminal co-located with user)
The user can run shell commands and see their output. Pick ONE of the
three equivalent options and instruct the user accordingly. All three
expect the same `drl_*` key created at
<https://drillr.ai/developer/keys>.
**B1. MCP via Claude Code (recommended for Claude Code users)**
Tell the user:
> 1. Create an API key at <https://drillr.ai/developer/keys> (Google
> sign-in is easiest)
> 2. Add to `~/.claude.json` (or merge into the existing `mcpServers`
> object):
>
> ```json
> {
> "mcpServers": {
> "drillr": {
> "type": "http",
> "url": "https://gateway.drillr.ai/mcp",
> "headers": {
> "Authorization": "Bearer DRILLR_API_KEY"
> }
> }
> }
> }
> ```
>
> 3. Add `export DRILLR_API_KEY=drl_...` to your shell rc
> (`~/.zshrc` / `~/.bashrc`) and restart the shell
> 4. Restart Claude Code. `/mcp` should list `drillr` as connected.
**B2. CLI**
```
npm install -g drillr-cli
drillr auth set-key drl_...
drillr watchlist list # verify: should list (or print "no watchlists yet")
```
**B3. REST with env var**
```
export DRILLR_API_KEY=drl_...
curl -H "Authorization: Bearer $DRILLR_API_KEY" \
https://gateway.drillr.ai/api/v1/watchlists
```
---
## Choosing a channel (after onboarding completes)
Pick based on your runtime's capabilities:
| Runtime characteristic | Preferred channel |
| ----------------------------------- | -------------------- |
| Has native MCP client support | **MCP** |
| HTTP only (no MCP, no shell) | **REST** |
| Shell / subprocess available | **CLI** or **REST** |
All three are equivalent in data and rate limit. MCP tool names
operate by natural names (e.g. watchlist by name); REST uses UUIDs.
---
## Capabilities
### Research & data lookup — `search`
Ask natural-language questions about companies, tickers, sectors,
filings, earnings. Runs 5-15s server-side and returns markdown text
with source references.
| Channel | Call |
| ------- | --------------------------------------------------------------- |
| MCP | `search({ question, session_id?, context? })` |
| REST | `POST /api/v1/search` with `{ question, session_id?, stream? }` |
| CLI | `drillr search "<question>"` |
**Session continuity:** pass the returned `session_id` in the next
call to continue the same research conversation. Use `context` to
pass background info that refines the answer.
**Data coverage:**
- **Market data** — real-time quotes, historical OHLCV, index prices
& composition (S&P 500, Dow, NASDAQ 100)
- **Fundamentals** — income / balance / cash flow statements
(quarterly & annual), valuation ratios, company snapshots
- **Earnings** — call transcripts with AI summaries, calendar with
EPS/revenue estimates vs actuals
- **Analyst research** — ratings & price targets (~550K events from
500+ firms), consensus rollups
- **SEC filings** — semantic search across 10-K, 10-Q, 8-K, 20-F,
6-K, S-1, F-1
- **Corporate events** — M&A, debt issuance, securities offerings
- **People & governance** — executive profiles, compensation,
appointments & departures
- **Ownership** — insider trades (Form 3/4/5), institutional
holdings (13F-HR, 13D/G)
- **News** — aggregated financial news with importance scoring
- **Company discovery** — by industry, product, technology, business
model, supply chain
- **Alternative data** — energy, data centers, semiconductors,
compute & inference pricing, AI model development, platform
adoption, sentiment, macro & trade, patents
**When to use `search`:**
- Stock prices, company financials, market data
- SEC filing content (risk factors, revenue breakdown, MD&A)
- Earnings summaries or analyst consensus
- Insider trading or institutional ownership
- Alternative data (AI value chain, energy, semiconductors)
- Compare companies or sectors
**Example questions that work well:**
- "What is AAPL's current PE ratio, and how does it compare to MSFT?"
- "Summarize NVDA's latest 10-Q earnings"
- "Which semiconductor companies had insider buying this month?"
### Signals — `signals`
A curated investment-event feed. Each signal is **one market event**
(one SUBJECT × one ACTION × one TIME), already aggregated across
outlets — you get one record per event, not one per article.
**Coverage** — sources rolled into the feed:
- News & wires: Finnhub, NewsAPI, GDELT, FMP, Bloomberg, Reuters,
WSJ, FT, CNBC
- Filings: SEC 8-K, 13D/G, 6-K (foreign issuers), structured 8-K
earnings data, earnings-call summaries
- Corporate disclosure: press releases
- Macro & policy: Fed / FOMC / BOJ / ECB / SEC / White House /
Truth Social
- Market microstructure: analyst ratings, insider trading, intraday
price movers
- Social: select financial subreddits
**Freshness**: signals appear within ~3–5 minutes of the originating
event.
| Channel | Call |
| ------- | ---------------------------------------------------------------------------- |
| MCP | `signals({ tickers?, sector?, since?, limit?, offset? })` |
| REST | `GET /api/v1/signals?tickers=AAPL,MSFT§or=Technology&since=...&limit=20` |
| CLI | `drillr signals --tickers AAPL,MSFT --limit 5` |
Response shape: `{ headline, summary, suggested_tickers[], sector[], created_at }`, ordered newest first.
### Articles — `article_list` / `article_get`
Research articles spanning company-specific analysis, event coverage,
and industry trackers.
**What you'll find**:
- **Company & thesis** — focused single-name or small-group analysis
(1–3 tickers), peer comparisons, annual ticker theses, SEC-filing
follow-ups
- **Event coverage** — postmortems on what just happened, watch-pieces
on pending events (policy decisions, upcoming earnings, lawsuits),
follow-up checkpoints on previously covered events, macro-event
analysis
- **Industry & sector** — thematic industry pieces (≥5 names),
recurring sector trackers
| Channel | Call |
| ------- | ------------------------------------------------------------------------ |
| MCP | `article_list({ ticker?, tag?, limit?, offset? })` / `article_get({ article_id })` |
| REST | `GET /api/v1/articles?ticker=NVDA&limit=10` / `GET /api/v1/articles/:id` |
| CLI | `drillr articles list --ticker NVDA` / `drillr articles get <uuid>` |
`article_get` returns the article body (markdown), plus `topics` and
`references` arrays. `article_list` returns 11 public fields per row
(id, title, summary, content, related_tickers, tags, sector, citation,
published_at, created_at, word_count).
### Watchlists
Per-user ticker collections. Owner-isolated (RLS): each key only sees
and mutates its owner's watchlists. Attempting to access another user's
watchlist by UUID returns `404`, not `403`.
| MCP (by name) | REST (by UUID) |
| ---------------------------------------------------- | ------------------------------------------------- |
| `watchlist_list` | `GET /api/v1/watchlists` |
| `watchlist_create({ name, tickers? })` | `POST /api/v1/watchlists` |
| `watchlist_add({ ticker, watchlist_name? })` | `POST /api/v1/watchlists/:id/tickers` |
| `watchlist_remove({ ticker, watchlist_name? })` | `DELETE /api/v1/watchlists/:id/tickers/:ticker` |
| `watchlist_delete({ watchlist_name })` | `DELETE /api/v1/watchlists/:id` |
CLI: `drillr watchlist {list|create|add|remove|delete}` — see
`drillr watchlist --help`.
> MCP tools accept watchlist **names** (chat-friendly). REST uses
> **UUIDs** (URL-friendly). If `watchlist_name` is omitted on add, a
> default "My Watchlist" is used (created on miss).
---
## Typical workflows
### "Daily research briefing"
User: "Can you do a daily morning briefing on my portfolio?"
1. `watchlist_list` — see what tickers the user already tracks
2. If empty, ask the user for tickers; then `watchlist_create`
3. Each morning, execute in order:
- `signals({ tickers: [...watchlist_tickers], since: "<24h-ago ISO>" })`
- For any high-interest signal, `search({ question: "Deeper context on <headline>" })`
- `article_list({ ticker: ... })` for any ticker with fresh activity
4. Synthesize into a chat-sized briefing (headline + 1-2 sentences + links)
### "Quick lookup"
User: "What's Nvidia's market cap?"
1. `search({ question: "What is NVDA's current market cap?" })`
2. Relay the answer and any cited sources
### "Sector scan"
User: "Any interesting biotech moves this week?"
1. `signals({ sector: ["Health Care"], since: "<7d-ago ISO>", limit: 30 })`
2. For each signal the user asks about, `article_list({ ticker })` or
a follow-up `search` with `session_id` from the prior call
---
## Error handling
| HTTP | `code` string | What to do |
| ---- | ---------------------------- | ------------------------------------------------------------------------------------------- |
| 400 | `invalid_body` / `invalid_query` / `invalid_id` | Fix parameter shape and retry (don't pester the user) |
| 401 | `unauthenticated` / `key_invalid` | Re-read the stored key; if still 401, rerun Prerequisites — the key is absent or wrong |
| 401 | `key_revoked` | Tell the user their key was revoked; they need to create a new one at the developer portal |
| 401 | `key_expired` | Tell the user their key expired; same fix |
| 403 | | Key is valid but lacks `external` scope — user needs to issue a different key |
| 404 | `not_found` | Resource doesn't exist, or RLS hides it (someone else's). Do NOT assume just-deleted |
| 429 | | Inspect `retry_after_seconds` in the body; sleep and retry |
| 502 | `upstream_error` | Transient data-source failure; retry once after 2-3s, then surface to user |
**On any 401: re-read `~/.config/drillr/config.json` or the
`DRILLR_API_KEY` env var BEFORE asking the user.** You have the
configuration — diagnose first, then instruct.
**Never** tell the user to "check their configuration."
---
## Rate limits
30 requests per minute per API key. On `429` the response body
includes `retry_after_seconds` (1-60s). For workflows that fan out
(e.g., scanning a 50-ticker watchlist), pace at ≤0.5 req/s or batch
via a single `search` or `signals` call with multiple tickers.
---
## Advanced
Drillr also supports OAuth 2.1 for MCP clients that implement Dynamic
Client Registration (e.g., Claude Code's built-in MCP OAuth). This
skill deliberately does **not** cover that path because:
- OAuth access tokens expire hourly and require client-side refresh
that not all MCP runtimes implement correctly
- The browser callback step assumes the user and agent share a
machine; Path A deployments (remote host / IM-driven) cannot
complete it
For agent automation, prefer the `drl_*` API key flow above. If you
are a human user setting up Claude Code on your own laptop and prefer
the OAuth UX, see the Drillr developer portal
(<https://drillr.ai/developer/docs>).
---
## Reference
- Developer portal: <https://drillr.ai/developer>
- Create / manage API keys: <https://drillr.ai/developer/keys>
- Full API reference: <https://drillr.ai/developer/docs>
- Gateway base URL: `https://gateway.drillr.ai`
- MCP endpoint: `https://gateway.drillr.ai/mcp`
- CLI on npm: `drillr-cli` (`npm install -g drillr-cli`)
Tracks External API **v1** (2026-04). Breaking changes will ship as
`/api/v2/*` alongside `/api/v1/*`.
FILE:CHANGELOG.md
# Changelog
All notable changes to this skill are tracked here. The skill version
tracks the Drillr External API version it was written against.
## [1.0.0] — 2026-04-23
Initial release. Complete rewrite of the earlier repository contents.
### What's covered
- **Three channels**: MCP (Streamable HTTP), REST, CLI — all accept
the same `drl_*` API key
- **Dual onboarding paths**:
- Path A (indirect): IM / web-chat / remote-host agents; user
pastes key into chat, agent persists to
`~/.config/drillr/config.json` (mode `0600`)
- Path B (direct): co-located terminal; three equivalent sub-paths
(MCP via Claude Code, `drillr-cli`, REST + env var)
- **Nine capabilities**: `search`, `signals`, `article_list`,
`article_get`, `watchlist_list`, `watchlist_create`, `watchlist_add`,
`watchlist_remove`, `watchlist_delete`
- **Frontmatter compatible with both** Anthropic Agent Skills and the
clawhub / openclaw skill format (same `SKILL.md`, different consumers)
- **Example MCP configs** for Claude Code, OpenClaw, Hermes
- **Copy-paste user-onboarding prompts** in
`examples/user-onboarding-script.md`
### Deliberately not covered
- OAuth 2.1 flow for MCP (browser callback is incompatible with
remote-host deployments; token TTL is a moving target in the
client ecosystem)
- Helper scripts wrapping the API — the earlier revision shipped a
Python wrapper, but that coupling is what rots first when the API
evolves. Agents should call MCP / REST / CLI directly.
### API version
Tracks Drillr External API **v1** (2026-04). Breaking changes will
ship under `/api/v2/*` alongside `/api/v1/*`; this skill will bump
its minor version when v1 gains non-breaking additions, and bump
major when switching to v2.
FILE:README.md
# drillr-skill
Agent-readable skill for [Drillr](https://drillr.ai) — financial
research, signals, articles, and watchlists, available over **MCP**,
**REST**, and a **CLI**.
This repository is the distribution point for the Drillr agent skill.
Drop it into any Claude Agent Skills-compatible runtime and your
agent will be able to onboard a user, collect an API key, and start
pulling financial data — whether the user is sitting at a terminal or
chatting from their phone.
## Install
### Claude Code / Claude Agent SDK
```bash
git clone https://github.com/Little-Grebe-Inc/drillr-skill ~/.claude/skills/drillr
```
Restart Claude Code. The skill auto-loads based on the `description`
field in `SKILL.md`, so Claude will invoke it when the user asks
about stocks, earnings, signals, etc.
### Clawhub / OpenClaw
The frontmatter in `SKILL.md` includes the `metadata.openclaw.*`
fields required by the [clawhub skill format](https://clawhub.ai).
Install via your clawhub client, or clone into your OpenClaw skills
directory.
### Other agent runtimes
The skill is a single `SKILL.md` with YAML frontmatter and Markdown
body — no executables, no helper scripts, no MCP bundle required.
Any runtime that can read `SKILL.md` files should work.
## What's inside
```
drillr-skill/
├── SKILL.md # the skill itself
├── README.md # this file
├── CHANGELOG.md # version history pinned to API version
└── examples/
├── claude-code-mcp-config.json # drop-in MCP config for Claude Code
├── openclaw-mcp-config.json # drop-in MCP config for OpenClaw
├── hermes-mcp-config.yaml # drop-in MCP config for Hermes
└── user-onboarding-script.md # copy-paste-able onboarding prompts
```
## Prerequisites for use
- A Drillr account (free): <https://drillr.ai>
- An `external` scope API key: <https://drillr.ai/developer/keys>
The skill itself handles teaching the agent how to onboard the
user and collect the key — you don't need to do anything special
beyond dropping the skill in.
## License
MIT — see `LICENSE`.
## Links
- Drillr: <https://drillr.ai>
- API reference: <https://drillr.ai/developer/docs>
- Issues / feedback: <https://github.com/Little-Grebe-Inc/drillr-skill/issues>
FILE:examples/hermes-mcp-config.yaml
mcp_servers:
drillr:
url: 'https://gateway.drillr.ai/mcp'
headers:
Authorization: 'Bearer DRILLR_API_KEY'
timeout: 120
FILE:examples/openclaw-mcp-config.json
{
"mcp": {
"servers": {
"drillr": {
"url": "https://gateway.drillr.ai/mcp",
"headers": {
"Authorization": "Bearer DRILLR_API_KEY"
}
}
}
}
}
FILE:examples/claude-code-mcp-config.json
{
"mcpServers": {
"drillr": {
"type": "http",
"url": "https://gateway.drillr.ai/mcp",
"headers": {
"Authorization": "Bearer DRILLR_API_KEY"
}
}
}
}
FILE:examples/user-onboarding-script.md
# User-onboarding script templates
Copy-paste-able prompts the agent can send to the user during key
onboarding. Adapt freely; they're written to be polite, brief, and
IM-friendly (short paragraphs, numbered steps).
---
## Path A — Indirect (IM / web chat / phone user)
### First-time key request
> To use Drillr I need an API key. From any browser (your phone is
> fine):
>
> 1. Open https://drillr.ai/developer/keys
> 2. Sign in — **Google sign-in is the quickest**; email/password
> also works
> 3. Tap "Create API key" → name it (e.g. "my-agent") → copy the
> `drl_...` string
> 4. Paste it back to me here. The key is shown only once.
>
> After I confirm it works, you can delete your message.
### Confirmation after storing (mask the key!)
> Stored `drl_xxxxxxxx_...e9f2`. You can safely delete your message
> now.
### When the pasted key fails validation (401)
> That key didn't work — I got a 401 from Drillr. It may have a
> typo, or it may have already been revoked. Could you go back to
> https://drillr.ai/developer/keys, create a fresh one, and paste
> it again?
### When the key is revoked mid-session
> Your Drillr API key was just revoked. To keep going, please
> create a new one at https://drillr.ai/developer/keys and paste
> it here.
---
## Path B — Direct (terminal co-located with user)
### Claude Code MCP setup
> 1. Create an API key at https://drillr.ai/developer/keys
> 2. Add this to `~/.claude.json` (or merge it into your existing
> `mcpServers` object):
>
> ```json
> {
> "mcpServers": {
> "drillr": {
> "type": "http",
> "url": "https://gateway.drillr.ai/mcp",
> "headers": {
> "Authorization": "Bearer DRILLR_API_KEY"
> }
> }
> }
> }
> ```
>
> 3. Add `export DRILLR_API_KEY=drl_...` to your `~/.zshrc` or
> `~/.bashrc` and restart your shell
> 4. Restart Claude Code. Run `/mcp` — you should see `drillr`
> listed as connected.
### CLI setup
> ```
> npm install -g drillr-cli
> drillr auth set-key drl_...
> drillr watchlist list # should succeed (empty is fine)
> ```
### REST / env-var setup
> ```
> export DRILLR_API_KEY=drl_...
> ```
>
> Then any HTTP call carries the header
> `Authorization: Bearer $DRILLR_API_KEY`.
Power terminal for deep financial research on US public equities — reason through investment theses, screen for ideas, map supply chains, do forensic account...
---
name: drillr
description: Power terminal for deep financial research on US public equities — reason through investment theses, screen for ideas, map supply chains, do forensic accounting, pull earnings call quotes, model financials, and more in plain English
version: 1.0.0
metadata:
openclaw:
requires:
bins: [python3]
emoji: "📈"
homepage: https://drillr.ai
---
# drillr — Power Terminal for Deep Financial Research
[drillr.ai](https://drillr.ai) is the power terminal for deep financial research on US public equities. Its research agent reasons through multi-step queries the way a buy-side analyst would — pulling real numbers from primary sources, surfacing comparisons, and synthesizing filing language on demand. Coverage spans all US-listed public companies on NYSE, NASDAQ, and OTC markets.
---
## Safety & Guardrails
drillr is a **read-only research skill** — pure question in, markdown answer out. No side effects, no surprises.
**What the skill does, and only does:**
1. Takes the user's question as a plain string
2. Sends a single HTTPS POST to one hardcoded endpoint: `diggr-agent-prod-414559604673.us-east4.run.app/api/public/chat`
3. Streams the Server-Sent Events response
4. Renders the result as markdown text on stdout
**What the skill will never do:**
- Read, write, or delete files — no filesystem access beyond stdout
- Execute shell commands, `eval`, `exec`, or spawn subprocesses
- Make a second network request — exactly one POST per query, nothing chained, nothing retried silently
- Handle credentials, API keys, cookies, or session tokens — the endpoint is unauthenticated
- Persist anything between calls — each invocation is fully stateless
- Access environment variables, git history, `~/.ssh`, or any other local data
- Emit telemetry, analytics, or usage tracking
**Dependencies**: Python stdlib only (`http.client`, `json`, `re`, `io`, `urllib.parse`, `sys`). Zero third-party packages; no `pip install` required; nothing to supply-chain compromise.
**Input hardening**: Queries are capped at 8 KB and sent as a JSON-encoded string field — no string interpolation into shell, URL, or SQL. The API URL is hardcoded, not constructed from input.
**Output trust boundary**: Responses are AI-generated text rendered verbatim as markdown. The script never `exec`s, `eval`s, or otherwise acts on returned content. Treat the numbers as research input, not ground truth — verify material figures against primary SEC filings before acting on them.
---
## What You Can Do
### Thesis-Driven Company Discovery
Find companies that fit a specific investment thesis — across fundamentals, quality, valuation, and momentum signals simultaneously.
- "Screen for mid-cap software companies with revenue growth above 20%, positive free cash flow, and P/FCF under 30x"
- "Find small-cap industrials with improving gross margins, low debt, and insider buying in the last two quarters"
- "Which Russell 2000 companies have had three consecutive quarters of earnings beats and still trade below 15x earnings?"
- "Find healthcare companies where institutional ownership has increased significantly in the last two 13F periods"
### Supply Chain & Sector Mapping
Map competitive dynamics, trace customer/supplier relationships, and understand who wins when a sector theme plays out.
- "Which semiconductor equipment companies have the most revenue exposure to AI infrastructure capex?"
- "Compare gross margin trends across the five largest cloud infrastructure companies over the last three years"
- "Which defense contractors have the highest backlog-to-revenue ratios and how has that changed?"
- "Who are the biggest beneficiaries if US reshoring accelerates — show revenue mix by geography for major industrials"
### Forensic Accounting
Detect earnings quality issues, stress-test reported numbers, and surface divergences between what management says and what the financials show.
- "Compare Palantir's GAAP net income vs operating cash flow vs stock-based compensation over the last 8 quarters — is earnings quality improving?"
- "Show the change in days sales outstanding and inventory levels for Nike over the last six quarters"
- "Flag any companies in the S&P 500 consumer sector where revenue growth is accelerating but cash conversion is declining"
- "What changed in Boeing's 10-K risk factors between 2022 and 2024 — show new additions and deletions"
- "Reconcile Tesla's reported free cash flow against capex and working capital changes — does the cash flow story hold?"
### Cross-Company Data Tabulation
Pull a specific metric across a peer group and lay it out side by side — no manual lookup required.
- "Show revenue, gross margin, operating margin, and FCF margin for the top 10 enterprise software companies — last four quarters"
- "Compare EV/EBITDA, EV/Sales, and P/FCF for the five largest US banks right now"
- "Table out EPS beat/miss percentage and average surprise for the Magnificent 7 over the last 8 quarters"
- "Show capex as a percentage of revenue for major US airlines since 2022 — who is over-investing vs under-investing?"
### Earnings Call Fact Lookup
Extract exactly what management said on a specific topic — across one call or multiple quarters.
- "What did NVIDIA's CEO say about data center demand in the last two earnings calls — pull the exact quotes"
- "Did Salesforce management say anything about price increases or seat expansion in Q4 2025?"
- "How has Meta's tone around AI capex commitments changed from Q1 2024 to Q4 2025 — track the language evolution"
- "What guidance did Microsoft give for Azure growth and did they raise, hold, or lower it versus last quarter?"
- "Which companies in the semiconductor sector flagged inventory destocking as a risk on their most recent calls?"
### SEC Filing Fact Lookup
Pull specific disclosures, track language changes across filings, and surface material events without reading hundreds of pages.
- "What new risk factors did Apple add to their 2024 10-K that weren't in the 2023 filing?"
- "Summarize the liquidity and capital resources section of Tesla's most recent 10-Q"
- "What 8-K events has Boeing filed in the last 60 days — show dates and event types"
- "Pull executive compensation details from the most recent proxy for Meta — base, bonus, equity breakdown"
- "Has any activist investor filed a 13D on a consumer staples company in the last 90 days?"
### Smart Money & Insider Tracking
Track where informed capital is moving — both insiders at the company and major institutional investors.
- "Which insiders at Meta have bought stock on the open market in the last 60 days — dollar amounts and prices"
- "Show how Druckenmiller's family office changed its top 10 positions in the most recent 13F"
- "Which S&P 500 companies have had the highest insider buying-to-selling ratio in the last quarter?"
- "Find small-cap stocks where two or more insiders bought at 52-week lows in the last 90 days"
- "Show the top 20 institutional holders of Palantir and how their positions changed last quarter"
### Financial Modeling Support
Pull the exact historical series you need to build or stress-test a model — formatted and ready to use.
- "Give me Apple's quarterly revenue, gross profit, R&D, SG&A, operating income, and net income for the last 20 quarters"
- "Pull Nvidia's capex, D&A, stock-based comp, and free cash flow for the last 12 quarters"
- "Show Amazon's segment revenue and operating income breakdown — AWS vs North America retail vs International — by quarter since 2022"
- "What is Microsoft's historical effective tax rate by fiscal year for the last 10 years?"
- "Pull the full balance sheet for Berkshire Hathaway — assets, liabilities, equity — for each year-end since 2018"
### Event-Driven & Catalyst Research
Track material events, news catalysts, and price-moving disclosures around specific dates or ongoing situations.
- "What caused the drop in Fastly stock in early 2026 — show news and any 8-K filings around that period"
- "Show all press releases and regulatory filings from Boeing in the last 30 days"
- "Which biotech companies have FDA PDUFA dates coming up in the next 60 days?"
- "What triggered the spike in Palantir's stock in February 2025 — news, earnings, or something else?"
### Valuation & Relative Value
Assess how cheap or expensive something is — in absolute terms, relative to history, or versus peers.
- "What is Nvidia's P/E ratio history over the last three years alongside its revenue growth rate?"
- "Which S&P 500 sectors are trading at the widest discount to their 5-year average EV/EBITDA?"
- "Show Microsoft's current valuation multiples vs its 3-year and 5-year average — is it expensive or cheap historically?"
- "Find the 10 cheapest large-cap technology stocks by P/FCF that still have double-digit revenue growth"
---
## How to Invoke the Agent
When the user asks a financial research question, run the following inline command. Replace `REPLACE_WITH_USER_QUESTION` with the user's exact question inside the triple-quoted string — quotes and special characters in the question are safe.
```bash
python3 - << 'DRILLR_END'
import http.client, io, json, re, sys, urllib.parse
# Force UTF-8 output (avoids encoding errors on Windows)
if hasattr(sys.stdout, "buffer"):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
QUERY = """REPLACE_WITH_USER_QUESTION"""
API_URL = "https://diggr-agent-prod-414559604673.us-east4.run.app/api/public/chat"
payload = json.dumps({"messages": [{"role": "user", "content": QUERY}]}).encode("utf-8")
parsed = urllib.parse.urlparse(API_URL)
conn = http.client.HTTPSConnection(parsed.netloc, timeout=15)
conn.request("POST", parsed.path, body=payload, headers={"Content-Type": "application/json"})
resp = conn.getresponse()
# Remove timeout after connect so the SSE stream runs as long as the query takes
if conn.sock:
conn.sock.settimeout(None)
current_event = None
text_parts = []
artifact_map = {}
reader = io.TextIOWrapper(resp, encoding="utf-8", errors="replace")
for line in reader:
line = line.rstrip("\r\n")
if line.startswith("event: "):
current_event = line[7:]
elif line.startswith("data: "):
try:
d = json.loads(line[6:])
except json.JSONDecodeError:
continue
if current_event == "step.text_delta":
text_parts.append(d.get("content", ""))
elif current_event == "step.artifact":
artifact = d.get("artifact", {})
art_id = artifact.get("id", "")[:8]
if artifact.get("type") == "data_table":
title = artifact.get("title", "Table")
spec = artifact.get("spec", {})
columns = spec.get("columns", [])
rows = spec.get("rows", [])
lines = [f"\n**{title}**\n"]
if columns and rows:
headers = [c["label"] for c in columns]
lines.append("| " + " | ".join(headers) + " |")
lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
for row in rows:
vals = []
for col in columns:
val = row.get(col["key"], "")
fmt = col.get("format", "")
if fmt == "currency" and isinstance(val, (int, float)):
val = (f".1fB" if abs(val) >= 1e9
else f".1fM" if abs(val) >= 1e6
else f",.0f")
elif fmt == "percent" and val not in (None, ""):
val = f"{val}%"
vals.append(str(val) if val is not None else "")
lines.append("| " + " | ".join(vals) + " |")
artifact_map[art_id] = "\n".join(lines)
conn.close()
text = "".join(text_parts)
text = re.sub(r"<!-- artifact:([a-f0-9]+) -->",
lambda m: artifact_map.get(m.group(1)[:8], ""), text)
print(text or "(No response — rephrase the query and try again)")
DRILLR_END
```
Alternatively, use the companion script:
```bash
python3 query.py "your question here"
```
---
## Workflow
1. **Identify the research need** — company, metric, time period, or type of analysis
2. **Clarify if vague** — e.g., "tell me about Apple" → ask: "Revenue trend, recent earnings, insider activity, or valuation?"
3. **Run the agent** — the query can be a full sentence; natural language works better than terse keywords
4. **Present the output** — format is markdown with tables where the agent generates structured data; lead with the key finding, then data
5. **Offer to go deeper** — after answering, suggest one natural follow-up relevant to the result
---
## Example Queries
**Thesis-Driven Discovery**
- `"Screen for mid-cap software companies with revenue growth above 20%, positive FCF, and P/FCF under 30x"`
- `"Find small-cap industrials with improving gross margins, low debt, and insider buying in the last two quarters"`
**Forensic Accounting**
- `"Compare Palantir's GAAP net income vs operating cash flow vs stock-based comp over 8 quarters — is earnings quality improving?"`
- `"Show changes in days sales outstanding and inventory for Nike over the last six quarters"`
- `"What new risk factors did Apple add to their 2024 10-K that weren't in 2023?"`
**Earnings Call Fact Lookup**
- `"What did NVIDIA's CEO say about data center demand in the last two earnings calls — pull the exact quotes"`
- `"How has Meta's language around AI capex commitments changed from Q1 2024 to Q4 2025?"`
- `"What guidance did Salesforce give for FY2026 revenue growth and did they raise or lower it?"`
**Cross-Company Tabulation**
- `"Show revenue, gross margin, operating margin, and FCF margin for the top 10 enterprise software companies — last four quarters"`
- `"Compare EV/EBITDA, EV/Sales, and P/FCF for the five largest US banks right now"`
**Financial Modeling**
- `"Give me Apple's quarterly revenue, gross profit, R&D, SG&A, operating income, and net income for the last 20 quarters"`
- `"Show Amazon's segment revenue and operating income — AWS vs North America vs International — by quarter since 2022"`
**Smart Money Tracking**
- `"Which insiders at Meta have bought stock on the open market in the last 60 days?"`
- `"Find small-cap stocks where two or more insiders bought at 52-week lows in the last 90 days"`
**Event-Driven Research**
- `"What caused the drop in Fastly stock in early 2026 — show news and any 8-K filings around that period"`
- `"Show all press releases and regulatory filings from Boeing in the last 30 days"`
**Valuation**
- `"Show Nvidia's P/E ratio history over the last 3 years alongside revenue growth"`
- `"Which S&P 500 sectors are trading at the widest discount to their 5-year average EV/EBITDA?"`
---
## Technical Notes
- **No authentication required** — the `/api/public/chat` endpoint is open
- **US equities** — covers NYSE, NASDAQ, and OTC-listed US public companies
- **Streaming response** — the API returns Server-Sent Events (SSE); the script handles parsing using Python's stdlib `http.client` (no curl required)
- **Data tables** — the agent returns structured tables for multi-row financial data; the script renders them as markdown
- **Response time** — 10–120 seconds depending on query complexity; multi-step screens and forensic queries take longer; no timeout is enforced on reads so all queries complete fully
- **Connect timeout** — 15 seconds; if the server is unreachable the script exits immediately rather than hanging
FILE:query.py
#!/usr/bin/env python3
"""
drillr financial research agent — CLI wrapper
Usage: python3 query.py "your financial research question"
Calls the drillr.ai public chat API and prints the formatted response.
The API returns Server-Sent Events (SSE); this script streams the response
without a read timeout so long-running research queries complete fully.
Safety summary (see SKILL.md "Safety & Guardrails" for full details):
- Read-only: question in, markdown out. Zero side effects.
- Stdlib only — no third-party packages, no subprocess, no shell.
- Exactly one HTTPS POST per invocation, to a hardcoded endpoint.
- No filesystem access beyond stdout; no env vars read; no state persisted.
- Response text is printed verbatim — never exec'd, eval'd, or interpreted.
"""
import http.client
import io
import json
import re
import sys
import urllib.parse
# Force UTF-8 output on Windows (avoids cp1252 UnicodeDecodeError on rich text)
if hasattr(sys.stdout, "buffer"):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
# Hardcoded — never constructed from user input.
API_URL = "https://diggr-agent-prod-414559604673.us-east4.run.app/api/public/chat"
CONNECT_TIMEOUT = 15 # seconds — fail fast if server is unreachable
MAX_QUERY_BYTES = 8192 # 8 KB cap on the user question — defensive input bound
def fmt_value(val, fmt: str) -> str:
"""Format a cell value based on its declared column format."""
if val is None or val == "":
return ""
if fmt == "currency" and isinstance(val, (int, float)):
if abs(val) >= 1e9:
return f".1fB"
if abs(val) >= 1e6:
return f".1fM"
return f",.0f"
if fmt == "percent" and isinstance(val, (int, float)):
return f"{val}%"
return str(val)
def table_to_markdown(artifact: dict) -> str:
"""Convert a data_table artifact spec to a markdown table string."""
title = artifact.get("title", "Table")
spec = artifact.get("spec", {})
columns = spec.get("columns", [])
rows = spec.get("rows", [])
lines = [f"\n**{title}**\n"]
if not (columns and rows):
return "\n".join(lines)
headers = [c["label"] for c in columns]
lines.append("| " + " | ".join(headers) + " |")
lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
for row in rows:
vals = [fmt_value(row.get(col["key"], ""), col.get("format", "")) for col in columns]
lines.append("| " + " | ".join(vals) + " |")
return "\n".join(lines)
def query(question: str) -> str:
"""Send a question to the drillr agent and return the formatted response."""
if not isinstance(question, str) or not question.strip():
return "(Empty query — provide a research question)"
encoded_question = question.encode("utf-8")
if len(encoded_question) > MAX_QUERY_BYTES:
return f"(Query too large — max {MAX_QUERY_BYTES} bytes, got {len(encoded_question)})"
payload = json.dumps({"messages": [{"role": "user", "content": question}]}).encode("utf-8")
parsed = urllib.parse.urlparse(API_URL)
path = parsed.path + ("?" + parsed.query if parsed.query else "")
conn = http.client.HTTPSConnection(parsed.netloc, timeout=CONNECT_TIMEOUT)
conn.request("POST", path, body=payload, headers={"Content-Type": "application/json"})
resp = conn.getresponse()
# Once connected, remove the timeout so the SSE stream runs as long as needed
if conn.sock:
conn.sock.settimeout(None)
current_event: str | None = None
text_parts: list[str] = []
artifact_map: dict[str, str] = {}
reader = io.TextIOWrapper(resp, encoding="utf-8", errors="replace")
for line in reader:
line = line.rstrip("\r\n")
if line.startswith("event: "):
current_event = line[7:]
elif line.startswith("data: "):
try:
d = json.loads(line[6:])
except json.JSONDecodeError:
continue
if current_event == "step.text_delta":
text_parts.append(d.get("content", ""))
elif current_event == "step.artifact":
artifact = d.get("artifact", {})
art_id = artifact.get("id", "")[:8]
if artifact.get("type") == "data_table":
artifact_map[art_id] = table_to_markdown(artifact)
conn.close()
text = "".join(text_parts)
text = re.sub(
r"<!-- artifact:([a-f0-9]+) -->",
lambda m: artifact_map.get(m.group(1)[:8], ""),
text,
)
return text or "(No response received — check the query or try again)"
def main():
if len(sys.argv) < 2 or not sys.argv[1].strip():
print(__doc__)
print('Example: python3 query.py "What is Apple free cash flow for the last 8 quarters?"')
sys.exit(1)
question = " ".join(sys.argv[1:])
print(query(question))
if __name__ == "__main__":
main()