inderes-cli
Unofficial CLI for the Inderes MCP server. A thin terminal-friendly wrapper around the hosted MCP endpoint, designed to pair with an on-demand agent skill (OpenClaw, Hermes, or ptrclaw) so an agent can reach Inderes data without loading every MCP tool schema into its context on every turn.
Disclaimer. This project is a community tool. It is not affiliated with Inderes Oyj and the authors have no relationship with Inderes beyond being subscribers. You need your own Inderes Premium subscription — the CLI never bypasses authentication.
Why this exists
MCP is a clean protocol for "give an agent a data source" and it's the right answer for most cases. But when a server exposes many tools that an agent uses only occasionally, the default integration has real costs worth surfacing before you adopt it.
1. Tool schemas are always resident in model context. Inderes exposes 16 tools. Registering its MCP server directly with an agent host means roughly 1.5–3k tokens of tool descriptions sit in the system prompt of every single turn, regardless of what the user is asking. Over a 40-turn conversation that's ~80k tokens of dead weight — paid both in input cost and in the model's attention budget. The MCP spec has no "lazy load" for schemas; they come down at session start and stay.
2. Most MCP client hosts don't handle OAuth. They accept a static bearer token you paste into config. Inderes's Keycloak issues access tokens that expire in minutes, so the status-quo workflow is "copy fresh token → paste → restart agent → repeat." This CLI does a full PKCE browser login once, stores a refresh token in the OS-appropriate config dir with 0600 perms, and auto-rotates. The agent host never sees credentials.
3. MCP is agent-only.
Useful for agents. Not useful if you want to grep analyst comments from a shell pipeline, cron-schedule earnings-transcript downloads, or pipe results through jq. This crate is the missing CLI; every subcommand has --json so agent-adjacent scripting works too.
4. On-demand skills are cheaper than always-on schemas.
OpenClaw, Hermes, and ptrclaw all load skill markdown lazily — only when the model decides the skill is relevant to the current turn. A ~400-token skill that teaches the model to shell out to inderes <subcommand> is roughly 4–8× cheaper per conversation than keeping 16 full tool schemas permanently resident, and the skill doesn't re-read on every turn the way a system prompt does.
When you should NOT use this
If your agent is dedicated to Inderes (you use its tools on most turns) or you're running a headless service that talks to one specific MCP server, direct MCP registration is simpler — one line in the host config, and the per-turn context cost is paid on turns where you'd want the schemas loaded anyway. This CLI's win is the occasional-use case: Inderes queries interspersed with other work.
Install
macOS / Linux — install script (recommended)
curl -sSL https://raw.githubusercontent.com/heikki-laitala/inderes-cli/main/install.sh | bash
Downloads the latest release binary for your OS+arch into ~/.local/bin/inderes, verifies SHA-256 against the release's SHAxxxxSUMS sidecar, and prints a PATH reminder if needed. No authentication needed — release assets are public.
If you hit GitHub's anonymous rate limit (60/hr) on a shared network, export GH_TOKEN=$(gh auth token) before running to use your authenticated 5000/hr quota.
Any platform — cargo
cargo install --git https://github.com/heikki-laitala/inderes-cli
Requires Rust 1.82+.
Windows — PowerShell (recommended)
iwr -useb https://raw.githubusercontent.com/heikki-laitala/inderes-cli/main/install.ps1 | iex
Installs inderes.exe into %LOCALAPPDATA%\Programs\inderes\bin and prints a PATH reminder if needed. Verifies SHA-256 before installing. Set $env:GH_TOKEN first only if you're hitting GitHub's anonymous rate limit.
Or download inderes-x86_64-pc-windows-msvc.zip from the latest release manually.
First run
inderes login
Opens your default browser and signs you in with your Inderes account via OAuth 2.0 (authorization code + PKCE) against Inderes's Keycloak (sso.inderes.fi). Tokens are stored as a JSON file in the platform config directory (0600 on Unix, per-user %APPDATA% ACLs on Windows).
If the redirect fails with "Invalid redirect URI", follow the guidance in the Inderes MCP setup docs and email [email protected] with the client name inderes-mcp and the exact error URL.
Headless / SSH / agent flow
The default inderes login binds a loopback HTTP listener for the OAuth callback. That fails when you're on a machine without a browser and the actual user is elsewhere — typical scenarios:
- SSH'd into a remote box; browser is on your laptop
- Running inside Docker without port forwarding
- Driven by an agent (OpenClaw, Hermes, ptrclaw) that doesn't have a graphical session
Use --paste-callback for those:
inderes login --paste-callback
The CLI prints the auth URL, you open it in any browser on any machine, sign in, and your browser tries to redirect to a localhost URL — which will show "unable to connect" because no listener is running. That's expected. Copy the full URL from your browser's address bar (it looks like http://127.0.0.1:46233/callback?state=…&code=…) and paste it back to the CLI's prompt. The CLI extracts the code, validates state, and exchanges it for tokens just like the loopback flow.
For agent integrations: the CLI prints the auth URL to stderr and reads the pasted URL from stdin, so an agent driving inderes login --paste-callback as a subprocess can:
- Read stderr to surface the auth URL to the user.
- Capture the user's pasted URL through whatever UI the agent uses.
- Write that URL plus a newline to the subprocess's stdin.
- Wait for the subprocess to exit successfully — tokens are now stored.
Usage
inderes search "Nokia"
inderes fundamentals COMPANY:200 --field revenue --field ebitda --from-year 2020
inderes estimates COMPANY:200 --field revenue --field eps --quarters --years 3
inderes content list --company-id COMPANY:200 --type COMPANY_REPORT --first 10
inderes content get ANALYST_COMMENT:directus-1234
inderes documents list COMPANY:200
inderes documents read <documentId> -s 1,2,5
The 16-tool escape hatch
Friendly subcommands cover 10 of Inderes's 16 MCP tools. For the rest:
inderes call --list # see all tools
inderes call list-transcripts --arg companyId=COMPANY:200
inderes call list-insider-transactions --arg companyId=COMPANY:200
inderes call list-calendar-events --arg 'regions=["FINLAND","SWEDEN"]'
inderes call get-model-portfolio-content
--arg KEY=VALUE parses VALUE as JSON when possible (numbers, booleans, arrays, objects, quoted strings); otherwise it's a plain string. For a full object, use --json-args '{"key":"value"}'.
Machine-readable output
Every tool-calling subcommand accepts --json to emit raw MCP output:
inderes --json search "Nokia" | jq '.content[0].text'
Forum (via the MCP server)
inderes forum reads the Inderes forum through the hosted MCP server's search-forum-topics and get-forum-posts tools, so it needs inderes login like every other subcommand. Use it for community/retail sentiment and discussion, as distinct from analyst research.
inderes forum search "Nokia" # search topic titles (up to 10 matches)
inderes forum topic 74 # full thread, cached locally (see below)
inderes forum topic 74 --refresh # re-fetch from the start, updating edited posts
--json emits the structured fields (cooked, username, created_at, plus url/score/reply_count in raw), handy for scripting or downstream analysis:
inderes --json forum topic 74 | jq '.post_stream.posts[].cooked'
Read-through cache.
forum topic <id>is backed by a local SQLite cache: each call fetches only the posts added since last time (resuming from a stored pagination cursor), stores them, and serves the whole thread from disk — so re-reads are instant and a long thread is downloaded only once. The first call on a very long thread walks every page; it's resumable, so if it's interrupted or rate-limited, just re-run to continue.--refreshre-walks from the start to pick up edits to older posts — it upserts rather than wiping, so an interrupted refresh never loses the cached copy. The DB lives at the platform data dir; override withINDERES_FORUM_DB. Beyond avoiding re-downloads, the cache is a queryable corpus — common fields land in real columns (username,created_at,post_number,cooked) for local analysis, and each post's full raw object is kept too, so--jsonstays faithful.forum searchresults are always live, never cached.Titles.
get-forum-postsreturns posts but no thread title, so a thread opened directly by id lists as(untitled); the title is shown byforum search. Post bodies arrive as markdown (not Discourse HTML).
Analyzing the cache. Once topics are cached, query them with SQL straight from the CLI, or point your own tools at the database file:
inderes forum activity 74 # posting volume over time + a momentum read
inderes forum momentum # rank ALL cached topics by momentum
inderes forum db-path # path to the SQLite file
inderes forum query "SELECT username, COUNT(*) n FROM posts GROUP BY username ORDER BY n DESC LIMIT 10"
inderes --json forum query "SELECT date(created_at) d, COUNT(*) c FROM posts GROUP BY d ORDER BY d"
datasette "$(inderes forum db-path)" # or duckdb / sqlite3 / pandas — it's a plain SQLite file
forum activity <id> buckets posts by day/week/month (--bucket, --periods) and prints a small bar chart plus a momentum read — whether the thread is heating up or cooling off (latest bucket vs the average of the rest). It's an attention signal over your cached data, so refresh the topic first for a current picture.
Two tiers of analysis sit on the cache:
- Deterministic (the CLI computes it): activity/momentum, top posters, posting volume — via
forum activityandforum query. Exact, reproducible, no model. - Model-judgment (an agent does it): thread summary / catch-me-up and sentiment / bull-vs-bear, by reasoning over the clean
textcolumn using the recipes in the installed skills. The CLI never calls a model — it just serves the data.
Managing the cache.
inderes forum topics # list cached topics (id, post count, last synced)
inderes forum refresh-all # pull new posts for every cached topic (incremental)
inderes forum clear 74 # drop one topic from the cache
inderes forum clear --all # wipe the cache (prompts; --yes to skip)
refresh-all keeps a whole watchlist current in one go, and forum momentum then ranks every cached topic by how much it's heating up — the "which company is the crowd suddenly talking about?" view across your watchlist. (An attention signal over cached data; refresh-all first for a current picture.)
forum query opens the cache read-only, so a query can never modify it; table output by default, --json emits rows as an array of objects. The posts table has typed columns (id, topic_id, post_number, username, created_at, cooked, …), a text column with the clean post body, and a raw column with each post's full JSON (reach any other field — url, score, reply_count — via json_extract(raw, '$.score') etc.).
Model-judgment analysis (summary, sentiment) is done by the agent, not the CLI. The CLI just serves clean, sliceable data; an agent does the reasoning with its own model — e.g. pull the latest 40 posts' text to be caught up, chunk a long thread by post_number ranges to summarize it (map-reduce), or classify a high-score slice for bull-vs-bear. The installed skills include these query recipes.
Upgrade
inderes upgrade --check-only # just print current vs latest
inderes upgrade # install the latest release in place
inderes upgrade --force # re-install even if already on latest
The CLI queries GitHub for the latest tag, compares to the running version, and (if newer or --force) shells out to the same install.sh / install.ps1 you'd curl | bash. The new binary lands in the same directory the running binary was launched from, so an upgrade preserves the install location regardless of where you originally put it.
After a successful upgrade, any skill files already installed at their default paths (~/.<host>/skills/inderes/SKILL.md) are automatically refreshed — the new binary rewrites them — so an agent's on-disk guidance stays in sync with the upgraded CLI's capabilities. (A skill installed to a custom --dest isn't tracked; re-run install-skill there yourself.)
inderes upgrade --check-only is safe to run from cron or an agent.
Uninstall
inderes uninstall # confirms, clears tokens, prints rm hint
inderes uninstall --yes # skip the confirmation prompt
inderes uninstall --yes --remove-skills # also delete ~/.<host>/skills/inderes/
uninstall clears stored tokens, optionally removes installed skill files, and prints the platform-appropriate command to delete the binary itself. The CLI doesn't self-delete its running executable — that step is left to you because it's the only sane cross-platform answer (Unix permits it; Windows requires a workaround that has its own footguns).
Install the skill
The inderes binary is designed to be invoked by an agent through an on-demand skill — keeps per-turn context small. Three hosts are supported out of the box:
inderes install-skill openclaw # -> ~/.openclaw/skills/inderes/SKILL.md
inderes install-skill hermes # -> ~/.hermes/skills/inderes/SKILL.md
inderes install-skill ptrclaw # -> ~/.ptrclaw/skills/inderes/SKILL.md
Pass --force to overwrite an existing skill, --dest <path> to write somewhere else. The skill content is shipped inside the binary, and inderes upgrade automatically refreshes skills installed at their default paths — so an agent's guidance stays up to date without a manual reinstall (only re-run install-skill for custom --dest locations).
All three skills teach the model to shell out to inderes <subcommand> via the host's terminal/bash/shell tool — no MCP server registration, no tool-schema bloat.
Shell completions
inderes completions bash > /etc/bash_completion.d/inderes
inderes completions zsh > "${fpath[1]}/_inderes"
inderes completions fish > ~/.config/fish/completions/inderes.fish
inderes completions powershell | Out-String | Invoke-Expression
Configuration
Token file paths:
- macOS:
~/Library/Application Support/com.inderes.inderes-cli/tokens.json - Linux:
~/.config/inderes-cli/tokens.json - Windows:
%APPDATA%\inderes\inderes-cli\config\tokens.json
Written atomically (tempfile + rename) so a crash can't leave a half-written file. Unix enforces 0600; Windows relies on per-user AppData ACLs.
Architecture
agent ─┐ ┌─ tokens.json (0600)
│ │
▼ ▼
SKILL.md ──shells──▶ inderes ──OAuth──▶ sso.inderes.fi (Keycloak)
│ │
forum-cache.db ◀───┘ │ Bearer + JSON-RPC
(SQLite, local ▼
read-through) mcp.inderes.com (Streamable-HTTP MCP)
inderes forum reads through mcp.inderes.com like every other command.
get-forum-posts thread posts are persisted to the local forum-cache.db
so a thread is downloaded once and then served and queried from disk;
search-forum-topics results stay live and are never cached.
src/oauth.rs— PKCE S256, loopback redirect on ephemeral port, refresh grant.src/storage.rs— atomic-rename JSON file at the platform config dir (0600 on Unix).src/mcp.rs— MCP 2025-03-26 Streamable HTTP client, handles bothapplication/jsonandtext/event-streamresponses.src/commands.rs— subcommand implementations and output formatting.src/forum.rs— maps and renders forum data read via the MCPget-forum-posts/search-forum-topicstools.src/cache.rs— local SQLite read-through cache + queryable corpus for forum topic posts.src/skill/{openclaw,hermes,ptrclaw}.md— embedded at compile time;inderes install-skill <host>writes the right one to disk.
Development
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --all
CI runs the same checks on ubuntu-latest, macos-latest, and windows-latest.
License
MIT. See LICENSE.