mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat(web): idx website and user docs (Astro Starlight)
Landing page plus docs for install, quickstart, config, the stocks and ownership CLI, output and exit codes, the MCP server and per-agent setup, agent usage, and an agent-led install page (agents/install.md) that a coding agent follows from a one-line prompt. - Every docs page is also served as Markdown at <path>.md; llms.txt and llms-full.txt come from starlight-llms-txt; a SKILL.md for skill-based harnesses is under /skills/idx/. - The CLI reference and MCP tools pages are generated from idx --help and the tools golden; a new web-generated flake check fails when they drift. - nix develop .#web provides bun; Cloudflare Pages builds web/ on pushes. - Package homepage metadata points at https://idx.rasyidanaf.com. - .cloudflare/ (the cf CLI's account cache) is git-ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e231061820
commit
e6805474f5
37 changed files with 3629 additions and 4 deletions
1
web/src/assets/wordmark-dark.svg
Normal file
1
web/src/assets/wordmark-dark.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="28" viewBox="0 0 64 28"><text x="0" y="21" font-family="JetBrains Mono, ui-monospace, Menlo, Consolas, monospace" font-weight="700" font-size="23" fill="#e8eaed">idx</text><rect x="45" y="5" width="10" height="19" fill="#CE1126"/></svg>
|
||||
|
After Width: | Height: | Size: 296 B |
1
web/src/assets/wordmark-light.svg
Normal file
1
web/src/assets/wordmark-light.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="28" viewBox="0 0 64 28"><text x="0" y="21" font-family="JetBrains Mono, ui-monospace, Menlo, Consolas, monospace" font-weight="700" font-size="23" fill="#16181c">idx</text><rect x="45" y="5" width="10" height="19" fill="#CE1126"/></svg>
|
||||
|
After Width: | Height: | Size: 296 B |
34
web/src/components/PageTitle.astro
Normal file
34
web/src/components/PageTitle.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
import Default from '@astrojs/starlight/components/PageTitle.astro';
|
||||
|
||||
const { id } = Astro.locals.starlightRoute.entry;
|
||||
const markdownPath = `/${id === 'index' ? 'index' : id}.md`;
|
||||
---
|
||||
|
||||
<div class="title-row">
|
||||
<Default />
|
||||
<a class="md-link" href={markdownPath} title="This page as Markdown, for agents">Markdown</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.title-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem 1rem;
|
||||
}
|
||||
.md-link {
|
||||
font-family: var(--sl-font-mono);
|
||||
font-size: var(--sl-text-xs);
|
||||
color: var(--sl-color-gray-3);
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.md-link:hover {
|
||||
color: var(--sl-color-white);
|
||||
border-color: var(--sl-color-gray-4);
|
||||
}
|
||||
</style>
|
||||
7
web/src/content.config.ts
Normal file
7
web/src/content.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { defineCollection } from 'astro:content';
|
||||
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||
};
|
||||
33
web/src/content/docs/about.md
Normal file
33
web/src/content/docs/about.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: About the data
|
||||
description: Where idx's data comes from, how fresh it is, and the project's terms.
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
| Data | Source | Freshness |
|
||||
| --- | --- | --- |
|
||||
| Quotes, company data, screening | MSN Money (default) or Yahoo Finance | live, cached for 5 minutes (quotes) or 1 hour (fundamentals) |
|
||||
| Price history | Yahoo Finance (default) or MSN | live |
|
||||
| Ownership | the KSEI register of holders above 1% ("Pemegang Saham di Atas 1%"), published monthly by IDX | the maintained snapshot is checked daily and rebuilt when IDX publishes a new month |
|
||||
|
||||
Quotes from these providers can be delayed. The ownership register lists
|
||||
only holders above 1%; everything below is reported as free float.
|
||||
|
||||
## The ownership snapshot
|
||||
|
||||
`idx ownership sync` downloads a SQLite snapshot built from IDX's published
|
||||
reports and attached to the
|
||||
[GitHub release `ownership-snapshot-current`](https://github.com/0xrsydn/idx-cli/releases/tag/ownership-snapshot-current),
|
||||
with a manifest and SHA-256 checksum. You can build the same database
|
||||
yourself from IDX's files with `idx ownership import`.
|
||||
|
||||
## Terms
|
||||
|
||||
idx is open source under the MIT license:
|
||||
[github.com/0xrsydn/idx-cli](https://github.com/0xrsydn/idx-cli).
|
||||
|
||||
It is not affiliated with, endorsed by, or connected to PT Bursa Efek
|
||||
Indonesia (IDX), PT Kustodian Sentral Efek Indonesia (KSEI), Microsoft or
|
||||
Yahoo. Data is provided as is, without warranty. Nothing here is investment
|
||||
advice.
|
||||
60
web/src/content/docs/agents/index.md
Normal file
60
web/src/content/docs/agents/index.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Using idx from an agent
|
||||
description: How an AI agent should call idx, through MCP or the CLI.
|
||||
---
|
||||
|
||||
An agent can reach idx two ways:
|
||||
|
||||
- **MCP** ([setup](/mcp/setup/)): typed tools with input schemas, the best
|
||||
fit for chat-style agents.
|
||||
- **The CLI**: for agents that run shell commands. No setup beyond
|
||||
installing `idx`; output is JSON automatically when captured.
|
||||
|
||||
## Using the CLI
|
||||
|
||||
What an agent needs to know:
|
||||
|
||||
- Output is JSON whenever stdout is not a terminal, so capture it and parse
|
||||
it; no flag is needed. Use `-o jsonl` for long lists.
|
||||
- On failure stdout is empty and stderr holds one JSON object,
|
||||
`{"error": true, "code": "...", "message": "..."}`.
|
||||
- Exit codes say what to do next: `2` fix the input, `3` not found (or no
|
||||
cached data offline), `4` provider unavailable, retry later, `5`
|
||||
unsupported, `1` other.
|
||||
- Tickers are IDX codes (`BBCA`); `.JK` is optional. Several tickers can be
|
||||
passed at once to `stocks quote` and `stocks compare`.
|
||||
- Ownership queries need `idx ownership sync` once; after that they run
|
||||
offline. `idx -o json ownership releases` shows which months exist.
|
||||
|
||||
## Recipes
|
||||
|
||||
```bash
|
||||
# price and day change for several stocks
|
||||
idx stocks quote BBCA BBRI BMRI | jq '.[] | {symbol, price, change_pct}'
|
||||
|
||||
# largest holders and concentration
|
||||
idx ownership ticker BBCA | jq '{holders: [.holders[] | {name, pct: (.percentage_bps / 100)}], concentration}'
|
||||
|
||||
# what one investor holds
|
||||
idx ownership entity "ANTHONI SALIM"
|
||||
|
||||
# holder changes between the two latest months
|
||||
idx -o json ownership releases | jq -r '.[0:2][].as_of_date'
|
||||
idx ownership changes --from 2026-08-31 --to 2026-09-30
|
||||
```
|
||||
|
||||
Percentages in ownership output are basis points (`5494` = 54.94%).
|
||||
|
||||
## Skill file
|
||||
|
||||
For harnesses that load skills (Claude Code, pi, opencode, and others),
|
||||
[`SKILL.md`](/skills/idx/SKILL.md) packages these rules so the agent uses the
|
||||
CLI correctly without MCP. Save it as `idx/SKILL.md` in your harness's skills
|
||||
directory, for example `~/.claude/skills/idx/SKILL.md`.
|
||||
|
||||
## Machine-readable docs
|
||||
|
||||
- [`/llms.txt`](/llms.txt): an index of these docs for language models.
|
||||
- [`/llms-full.txt`](/llms-full.txt): every page in one file.
|
||||
- Any page as Markdown: add `.md` to its path, for example
|
||||
[`/agents/install.md`](/agents/install.md).
|
||||
95
web/src/content/docs/agents/install.md
Normal file
95
web/src/content/docs/agents/install.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
title: Agent-led install
|
||||
description: Instructions for a coding agent to install idx and register idx-mcp with itself.
|
||||
---
|
||||
|
||||
This page is written for a coding agent. A user sent you here to install
|
||||
**idx** (a CLI for Indonesian stock market and KSEI ownership data) and
|
||||
register its MCP server, **idx-mcp**, with the agent harness you are running
|
||||
in. Follow the steps in order and report back at the end.
|
||||
|
||||
Ground rules:
|
||||
|
||||
- Never use `sudo`. Install into the user's home directory.
|
||||
- Before you edit a config file outside the current project (anything under
|
||||
`~`), show the user the exact change and ask for confirmation.
|
||||
- If a step fails, stop and tell the user what failed and the error, rather
|
||||
than trying unrelated workarounds.
|
||||
|
||||
## 1. Check the platform
|
||||
|
||||
Run `uname -sm`. Supported: Linux x86_64/aarch64 and macOS x86_64/arm64. On
|
||||
Windows, stop and tell the user to use WSL.
|
||||
|
||||
## 2. Install idx
|
||||
|
||||
Use the first option that applies:
|
||||
|
||||
1. **Node.js is available** (`node --version` succeeds): nothing to install
|
||||
for the MCP server, which runs through `npx -y idx-mcp`. Install the CLI
|
||||
with `npm install --global idx-cli`; if that fails with a permissions
|
||||
error, skip it and use `npx -y idx-cli` in place of `idx` below.
|
||||
2. **Otherwise**: run the install script, which verifies checksums and
|
||||
installs to `~/.local/bin`:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||
```
|
||||
|
||||
It installs only `idx`. For the MCP server without Node.js, use
|
||||
`cargo install idx-mcp` if `cargo` exists; otherwise tell the user that
|
||||
idx-mcp needs Node.js, Cargo or Nix, and continue with the CLI only.
|
||||
|
||||
Confirm with `idx version` (or `npx -y idx-cli version`).
|
||||
|
||||
## 3. Register idx-mcp with yourself
|
||||
|
||||
Work out which harness you are, then apply its setup. Use `npx` and
|
||||
`["-y", "idx-mcp"]` as command and arguments, or `idx-mcp` with no arguments
|
||||
if you installed the binary.
|
||||
|
||||
| Harness | How |
|
||||
| --- | --- |
|
||||
| Claude Code | run `claude mcp add idx -- npx -y idx-mcp` |
|
||||
| Codex | add to `~/.codex/config.toml`: `[mcp_servers.idx]` with `command = "npx"`, `args = ["-y", "idx-mcp"]` |
|
||||
| Cursor | add to `~/.cursor/mcp.json` under `mcpServers`: `"idx": {"command": "npx", "args": ["-y", "idx-mcp"]}` |
|
||||
| opencode | add to `~/.config/opencode/opencode.json` under `mcp`: `"idx": {"type": "local", "command": ["npx", "-y", "idx-mcp"], "enabled": true}` |
|
||||
| pi | add to `~/.pi/agent/mcp.json` under `mcpServers`: `"idx": {"command": "npx", "args": ["-y", "idx-mcp"], "exposure": "direct"}` |
|
||||
| Hermes | add to `~/.hermes/config.yaml` under `mcp_servers`: `idx` with `command: npx`, `args: ["-y", "idx-mcp"]`, `enabled: true` |
|
||||
| anything else | find how your harness registers a stdio MCP server, and ask the user if unsure |
|
||||
|
||||
Merge into existing files; never overwrite other servers or settings. Full
|
||||
examples: [MCP setup](/mcp/setup/).
|
||||
|
||||
## 4. Download ownership data
|
||||
|
||||
```bash
|
||||
idx ownership sync
|
||||
```
|
||||
|
||||
This downloads a few MB into `~/.local/share/idx/ownership.db`. It is safe to
|
||||
rerun.
|
||||
|
||||
## 5. Verify
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
idx -o json ownership ticker BBCA
|
||||
```
|
||||
|
||||
It should list holders, the first being DWIMURIA INVESTAMA ANDALAN. Most
|
||||
harnesses load MCP servers at startup, so the `idx` tools usually appear only
|
||||
after a restart. If yours already shows them, call `get_shareholders` with
|
||||
`{"ticker": "BBCA"}` as well.
|
||||
|
||||
## 6. Report to the user
|
||||
|
||||
Tell the user, briefly:
|
||||
|
||||
- what you installed and how (npm, install script or Cargo), and the version;
|
||||
- which config file you changed for idx-mcp, or why you didn't;
|
||||
- the result of the verification (largest BBCA holder and its percentage);
|
||||
- whether they need to restart the agent to use the idx tools;
|
||||
- a next thing to try, such as asking "Who are BBCA's largest holders, and
|
||||
what changed last month?"
|
||||
53
web/src/content/docs/cli/output.md
Normal file
53
web/src/content/docs/cli/output.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: Output and exit codes
|
||||
description: Tables, JSON, errors and exit codes; the contract scripts and agents rely on.
|
||||
---
|
||||
|
||||
## Formats
|
||||
|
||||
| `-o` | Prints |
|
||||
| --- | --- |
|
||||
| `auto` (default) | a table when stdout is a terminal, compact JSON otherwise |
|
||||
| `table` | a table |
|
||||
| `json` | one JSON document |
|
||||
| `jsonl` | one JSON value per line (one per array item) |
|
||||
|
||||
Set the default with `IDX_OUTPUT` or `general.output`. Colour is off whenever
|
||||
stdout is not a terminal, and with `--no-color`.
|
||||
|
||||
## The contract
|
||||
|
||||
- stdout carries only data; warnings, progress and errors go to stderr.
|
||||
- On failure stdout is empty.
|
||||
- In JSON modes an error is one JSON object on stderr:
|
||||
|
||||
```json
|
||||
{"error":true,"code":"INVALIDINPUT","message":"invalid input: invalid ticker symbol '../x'"}
|
||||
```
|
||||
|
||||
## Exit codes
|
||||
|
||||
| Exit | Meaning | What to do |
|
||||
| --- | --- | --- |
|
||||
| `0` | success | |
|
||||
| `2` | invalid input or config | fix the command |
|
||||
| `3` | not found, no data, or no cached data offline | check the ticker or name; go online |
|
||||
| `4` | upstream unavailable | retry later |
|
||||
| `5` | unsupported | use another command or provider |
|
||||
| `1` | anything else | read the message |
|
||||
|
||||
## Pipes
|
||||
|
||||
```bash
|
||||
# names and prices
|
||||
idx stocks quote BBCA BBRI | jq -r '.[] | "\(.symbol) \(.price)"'
|
||||
|
||||
# the last five daily closes
|
||||
idx -o jsonl stocks history BBCA --period 1y | tail -5
|
||||
|
||||
# symbols from stdin
|
||||
idx stocks screen --filter high-dividend | jq -r '.[].symbol' | idx stocks compare -
|
||||
```
|
||||
|
||||
`stocks quote` and `stocks compare` read symbols from stdin when given `-`,
|
||||
separated by whitespace or commas.
|
||||
57
web/src/content/docs/cli/ownership.md
Normal file
57
web/src/content/docs/cli/ownership.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
title: Ownership
|
||||
description: KSEI shareholder data, synced once and queried offline.
|
||||
---
|
||||
|
||||
The ownership commands work on the monthly KSEI register of holders above 1%
|
||||
("Pemegang Saham di Atas 1%") that IDX publishes for every listed company.
|
||||
idx keeps it in a local SQLite database, so queries are instant and work
|
||||
offline.
|
||||
|
||||
## Get the data
|
||||
|
||||
```bash
|
||||
idx ownership sync
|
||||
```
|
||||
|
||||
This downloads a maintained snapshot (a few MB, currently the latest three
|
||||
months) and installs it. Run it again any time: it does nothing when you are
|
||||
already current, and it never replaces a database holding more history than
|
||||
the snapshot. `idx ownership releases` lists the months you have.
|
||||
|
||||
## Queries
|
||||
|
||||
```bash
|
||||
idx ownership ticker BBCA # holders of one stock, with concentration
|
||||
idx ownership entity "ANTHONI SALIM" # every holding of one investor
|
||||
idx ownership search salim # find investor names
|
||||
idx ownership changes --from 2026-08-31 --to 2026-09-30
|
||||
idx ownership concentration --top 20 # most concentrated stocks (by HHI)
|
||||
idx ownership cross-holders --min-tickers 3
|
||||
idx ownership graph BBCA --depth 2 # ownership network; --format dot for Graphviz
|
||||
```
|
||||
|
||||
`changes` lists holders that are new, exited, increased or decreased between
|
||||
two releases. `ticker` also reports concentration: the top holder's share,
|
||||
the top three, the Herfindahl index (HHI) and the free float (shares held by
|
||||
holders below 1%).
|
||||
|
||||
## Import reports yourself
|
||||
|
||||
The snapshot covers most needs. To load a month it lacks, or older history,
|
||||
import IDX's reports directly:
|
||||
|
||||
```bash
|
||||
idx ownership discover # find the latest report URLs on IDX
|
||||
idx ownership import --url <xlsx-or-pdf> # needs curl-impersonate
|
||||
idx ownership import --file report.xlsx # or a local XLSX, PDF, ZIP or TXT
|
||||
```
|
||||
|
||||
Since June 2026 IDX publishes the register as XLSX; earlier months were PDF
|
||||
announcements, which need `mutool`. See [helper tools](/start/install/#helper-tools).
|
||||
|
||||
## Entity names
|
||||
|
||||
The register spells some investors differently from month to month.
|
||||
`idx ownership resolve` lists unresolved names and lets you map an alias to
|
||||
an entity or merge two entities.
|
||||
1103
web/src/content/docs/cli/reference.md
Normal file
1103
web/src/content/docs/cli/reference.md
Normal file
File diff suppressed because it is too large
Load diff
67
web/src/content/docs/cli/stocks.md
Normal file
67
web/src/content/docs/cli/stocks.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: Stocks
|
||||
description: Quotes, history, company data, analysis and screening.
|
||||
---
|
||||
|
||||
All `stocks` commands take IDX tickers (`BBCA`, or `BBCA.JK`). Every flag is
|
||||
listed in the [command reference](/cli/reference/).
|
||||
|
||||
## Quotes and history
|
||||
|
||||
```bash
|
||||
idx stocks quote BBCA BBRI,BMRI
|
||||
idx stocks history BBCA --period 1y --interval 1wk
|
||||
```
|
||||
|
||||
`quote` takes several tickers, separated by spaces or commas, or `-` to read
|
||||
them from stdin. `history` takes `--period` (`1d` to `5y`) and `--interval`
|
||||
(`1d`, `1wk`, `1mo`).
|
||||
|
||||
## Company data
|
||||
|
||||
| Command | What it shows |
|
||||
| --- | --- |
|
||||
| `profile` | sector, industry, description, contacts and officers |
|
||||
| `financials` | income, balance sheet and cash flow statements (`--statement income,balance`) |
|
||||
| `earnings` | reported and estimated earnings |
|
||||
| `sentiment` | crowd sentiment |
|
||||
| `insights` | AI insights from the provider |
|
||||
| `news` | recent news |
|
||||
|
||||
## Analysis
|
||||
|
||||
| Command | What it computes |
|
||||
| --- | --- |
|
||||
| `technical` | trend, RSI, MACD, moving averages and signals |
|
||||
| `growth` | revenue and earnings growth rating |
|
||||
| `valuation` | P/E, P/B, ROE, net margin and EV/EBITDA, with a rating |
|
||||
| `risk` | debt to equity, current ratio and ROA, with a rating |
|
||||
| `fundamental` | growth, valuation and risk in one report |
|
||||
| `compare` | `fundamental` for several tickers side by side |
|
||||
|
||||
```bash
|
||||
idx stocks fundamental BBRI
|
||||
idx stocks compare BBCA BBRI BMRI
|
||||
```
|
||||
|
||||
## Screening
|
||||
|
||||
```bash
|
||||
idx stocks screen --filter high-dividend --limit 20
|
||||
```
|
||||
|
||||
Filters: `top-performers` (default), `worst-performers`, `high-dividend`,
|
||||
`low-pe`, `52w-high`, `52w-low`, `high-volume`, `large-cap`.
|
||||
|
||||
Screen results pipe straight into `compare`:
|
||||
|
||||
```bash
|
||||
idx stocks screen --filter high-dividend | jq -r '.[].symbol' | idx stocks compare -
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
MSN is the default for quotes, company data and screening; Yahoo serves
|
||||
price history. Switch with `IDX_PROVIDER=msn|yahoo` and
|
||||
`IDX_HISTORY_PROVIDER=auto|yahoo|msn`, or the matching
|
||||
[config keys](/start/config/).
|
||||
56
web/src/content/docs/mcp/index.md
Normal file
56
web/src/content/docs/mcp/index.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: MCP server
|
||||
description: idx-mcp serves idx's stock and ownership data to AI agents over MCP.
|
||||
---
|
||||
|
||||
`idx-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io)
|
||||
server over stdio. It gives an agent the same data as the `idx` CLI, as
|
||||
[11 tools](/mcp/tools/): quotes, price history, screening, company data,
|
||||
analysis, comparison, and the KSEI ownership queries.
|
||||
|
||||
It shares idx's [config, cache and ownership database](/start/config/#files-idx-keeps),
|
||||
so `idx ownership sync` in a terminal also feeds the server.
|
||||
|
||||
[Set it up in your agent →](/mcp/setup/)
|
||||
|
||||
## Results
|
||||
|
||||
Tools that fetch live data return:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { "...": "the same shape as idx -o json" },
|
||||
"source": "msn",
|
||||
"fetched_at": "2026-10-15T03:20:11Z",
|
||||
"stale": false
|
||||
}
|
||||
```
|
||||
|
||||
`source` is the provider (`msn` or `yahoo`). Results are cached on disk;
|
||||
`stale` is true when an expired cache entry was served because a fresh fetch
|
||||
failed. Ownership tools return their data directly, from the local
|
||||
database.
|
||||
|
||||
Errors come back as a tool result with `isError` set and the CLI's error
|
||||
body, so the agent can decide whether to retry:
|
||||
|
||||
```json
|
||||
{"error": true, "code": "PROVIDERUNAVAILABLE", "message": "..."}
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
| Input | Limit |
|
||||
| --- | --- |
|
||||
| tickers per call (`get_quotes`, `compare_stocks`) | 20, duplicates removed |
|
||||
| `screen_stocks` `limit` | 200 |
|
||||
| `search_investors` `limit` | 100 |
|
||||
| `get_ownership_changes` `limit` | 2000 (default 200) |
|
||||
| concurrent tool calls | 4 |
|
||||
|
||||
## Ownership data
|
||||
|
||||
Ownership tools need a populated database. Either run `idx ownership sync`
|
||||
once, or start the server with `--allow-sync`, which adds an
|
||||
`ownership_sync` tool so the agent can download the snapshot itself (several
|
||||
MB).
|
||||
142
web/src/content/docs/mcp/setup.mdx
Normal file
142
web/src/content/docs/mcp/setup.mdx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
title: Set up idx-mcp
|
||||
description: Add idx-mcp to Claude Code, Codex, Cursor, opencode, pi, Hermes or any MCP client.
|
||||
sidebar:
|
||||
label: Setup
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
:::tip[Quickest: let your agent do it]
|
||||
Paste this into your coding agent. It follows
|
||||
[agents/install.md](/agents/install/): installs idx, registers idx-mcp with
|
||||
itself, syncs ownership data and runs a test call. It asks before changing
|
||||
global config.
|
||||
|
||||
```text wrap
|
||||
Install idx-cli and its MCP server for me by following https://idx.rasyidanaf.com/agents/install.md, then show me that it works.
|
||||
```
|
||||
:::
|
||||
|
||||
Or set it up yourself:
|
||||
|
||||
## 1. Get the server
|
||||
|
||||
Nothing to install if your client can run `npx`: the configs below use
|
||||
`npx -y idx-mcp`. Otherwise install the binary once and use `idx-mcp` as the
|
||||
command:
|
||||
|
||||
```bash
|
||||
cargo install idx-mcp # Rust 1.89+
|
||||
nix profile install github:0xrsydn/idx-cli#idx-mcp
|
||||
```
|
||||
|
||||
## 2. Add it to your client
|
||||
|
||||
<Tabs syncKey="agent">
|
||||
<TabItem label="Claude Code">
|
||||
|
||||
```bash
|
||||
claude mcp add idx -- npx -y idx-mcp
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Codex">
|
||||
|
||||
`~/.codex/config.toml`:
|
||||
|
||||
```toml
|
||||
[mcp_servers.idx]
|
||||
command = "npx"
|
||||
args = ["-y", "idx-mcp"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Cursor">
|
||||
|
||||
`~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"idx": { "command": "npx", "args": ["-y", "idx-mcp"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="opencode">
|
||||
|
||||
`opencode.json` in a project, or `~/.config/opencode/opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"idx": { "type": "local", "command": ["npx", "-y", "idx-mcp"], "enabled": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="pi">
|
||||
|
||||
`~/.pi/agent/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"idx": { "command": "npx", "args": ["-y", "idx-mcp"], "exposure": "direct" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`"exposure": "direct"` gives the agent each tool by name.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Hermes">
|
||||
|
||||
`~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
mcp_servers:
|
||||
idx:
|
||||
command: npx
|
||||
args: ["-y", "idx-mcp"]
|
||||
enabled: true
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Other">
|
||||
|
||||
Any MCP client that runs stdio servers: command `npx`, arguments
|
||||
`-y idx-mcp` (or the command `idx-mcp` if installed). Add `--allow-sync` to
|
||||
the arguments to expose `ownership_sync`.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
These are the setups that `tests/agents/agent-smoke.sh` drives with real
|
||||
agents.
|
||||
|
||||
## 3. Get ownership data
|
||||
|
||||
Ownership tools read a local database. Fill it once:
|
||||
|
||||
```bash
|
||||
npx -y idx-cli ownership sync # or: idx ownership sync
|
||||
```
|
||||
|
||||
Or start the server with `--allow-sync` so the agent can call
|
||||
`ownership_sync` itself.
|
||||
|
||||
## 4. Check it works
|
||||
|
||||
Ask your agent:
|
||||
|
||||
> What is BBCA's free float, and who is its largest holder?
|
||||
|
||||
It should call `get_shareholders` and answer with the free float and
|
||||
DWIMURIA INVESTAMA ANDALAN, the largest holder.
|
||||
|
||||
See [all tools](/mcp/tools/).
|
||||
115
web/src/content/docs/mcp/tools.md
Normal file
115
web/src/content/docs/mcp/tools.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: Tools
|
||||
description: Every idx-mcp tool and its inputs, generated from the server's tool list.
|
||||
---
|
||||
|
||||
<!-- Generated by web/scripts/gen-mcp-tools.sh; do not edit by hand. -->
|
||||
|
||||
idx-mcp serves these tools. `ownership_sync` is listed only when the server
|
||||
starts with `--allow-sync`. Results that come from a live provider are wrapped
|
||||
as `{data, source, fetched_at, stale}`; failures return `{error, code,
|
||||
message}` with `isError` set.
|
||||
|
||||
## `analyze_stock`
|
||||
|
||||
Analyze one IDX ticker: technical signals (trend, RSI, MACD, moving averages) and fundamental growth, valuation, and risk ratings. Analyses that fail are listed under `errors`; the call fails only if every analysis fails.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `kinds` | array of `technical` / `growth` / `valuation` / `risk` | no | Analyses to run (default: all). |
|
||||
| `symbol` | string | yes | IDX ticker, e.g. "BBCA". |
|
||||
|
||||
## `compare_stocks`
|
||||
|
||||
Compare fundamental reports (growth, valuation, risk) across IDX tickers. Tickers that fail are listed under `failures`.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `symbols` | array of string | yes | IDX tickers, e.g. ["BBCA", "BBRI"]; the `.JK` suffix is optional. At most 20. |
|
||||
|
||||
## `get_company`
|
||||
|
||||
Company data for one IDX ticker: profile, financial statements, earnings, analyst sentiment, insights, and news. Sections that fail are listed under `errors`; the call fails only if every section fails.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `sections` | array of `profile` / `financials` / `earnings` / `sentiment` / `insights` / `news` | no | Sections to fetch (default: all). |
|
||||
| `symbol` | string | yes | IDX ticker, e.g. "BBCA". |
|
||||
|
||||
## `get_investor_holdings`
|
||||
|
||||
Every IDX holding (above 1%) of one investor, matched by name.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `entity` | string | yes | Investor name or part of it, e.g. "DWIMURIA"; the closest match is used. |
|
||||
|
||||
## `get_ownership_changes`
|
||||
|
||||
Holder changes (new, exit, increase, decrease) between two KSEI release dates, optionally for one ticker.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `from` | string | yes | Earlier release date, YYYY-MM-DD (see ownership_status). |
|
||||
| `limit` | integer | no | Maximum rows returned (default 200, at most 2000); `total` reports the full count. |
|
||||
| `ticker` | string | no | Only this ticker's changes. |
|
||||
| `to` | string | yes | Later release date, YYYY-MM-DD. |
|
||||
|
||||
## `get_price_history`
|
||||
|
||||
OHLCV price history for one IDX ticker.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `interval` | `1d` / `1wk` / `1mo` | no | Candle size (default "1d"). |
|
||||
| `period` | `1d` / `5d` / `1mo` / `3mo` / `6mo` / `1y` / `2y` / `5y` | no | Lookback window (default "3mo"). |
|
||||
| `symbol` | string | yes | IDX ticker, e.g. "BBCA". |
|
||||
|
||||
## `get_quotes`
|
||||
|
||||
Latest quotes (price, change, volume, 52-week range, market cap) for one or more IDX tickers.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `symbols` | array of string | yes | IDX tickers, e.g. ["BBCA", "BBRI"]; the `.JK` suffix is optional. At most 20. |
|
||||
|
||||
## `get_shareholders`
|
||||
|
||||
Shareholders above 1% of an IDX ticker from the latest KSEI release, with concentration metrics (top1, top3, HHI, free float).
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `ticker` | string | yes | IDX ticker, e.g. "BBCA". |
|
||||
|
||||
## `ownership_status`
|
||||
|
||||
Ownership database status: whether it has data, and the imported KSEI release dates.
|
||||
|
||||
No inputs.
|
||||
|
||||
## `ownership_sync`
|
||||
|
||||
Download or refresh the KSEI ownership snapshot (several MB) into the local database.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `force` | boolean | no | Re-download even when the local snapshot is current. |
|
||||
|
||||
## `screen_stocks`
|
||||
|
||||
Run a stock screen (top/worst performers, high dividend, low P/E, 52-week highs/lows, high volume, large cap).
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `filter` | `top-performers` / `worst-performers` / `high-dividend` / `low-pe` / `52w-high` / `52w-low` / `high-volume` / `large-cap` | no | Screen to run (default "top-performers"). |
|
||||
| `limit` | integer | no | Maximum results (default 50, at most 200). |
|
||||
| `region` | `id` / `us` / `sg` / `hk` / `jp` | no | Market region (default "id"). |
|
||||
|
||||
## `search_investors`
|
||||
|
||||
Search investor names in the KSEI ownership database.
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `limit` | integer | no | Maximum results (default 20, at most 100). |
|
||||
| `query` | string | yes | Full-text query over investor names. |
|
||||
75
web/src/content/docs/start/config.md
Normal file
75
web/src/content/docs/start/config.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
title: Configuration
|
||||
description: Config file, environment variables and their precedence.
|
||||
---
|
||||
|
||||
idx works without configuration. To change defaults, use the config file,
|
||||
environment variables or flags. When they disagree, the first one wins:
|
||||
|
||||
1. command-line flags
|
||||
2. environment variables
|
||||
3. the config file
|
||||
4. built-in defaults
|
||||
|
||||
## Config file
|
||||
|
||||
`~/.config/idx/config.toml` (or `$XDG_CONFIG_HOME/idx/config.toml`):
|
||||
|
||||
```bash
|
||||
idx config init # write the defaults below
|
||||
idx config set general.output json
|
||||
idx config get cache.quote_ttl
|
||||
idx config path
|
||||
```
|
||||
|
||||
```toml
|
||||
[general]
|
||||
provider = "msn" # msn | yahoo
|
||||
history_provider = "auto" # auto | yahoo | msn
|
||||
exchange = "JK"
|
||||
output = "auto" # auto | table | json | jsonl
|
||||
color = true
|
||||
|
||||
[cache]
|
||||
quote_ttl = 300 # seconds
|
||||
fundamental_ttl = 3600
|
||||
|
||||
[ownership]
|
||||
# db_path = "/path/to/ownership.db"
|
||||
# snapshot_manifest = "https://..."
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Overrides |
|
||||
| --- | --- |
|
||||
| `IDX_PROVIDER` | `general.provider` |
|
||||
| `IDX_HISTORY_PROVIDER` | `general.history_provider` |
|
||||
| `IDX_EXCHANGE` | `general.exchange` |
|
||||
| `IDX_OUTPUT` | `general.output` |
|
||||
| `IDX_NO_COLOR` | `general.color` (`1` or `true` disables colour) |
|
||||
| `IDX_CACHE_QUOTE_TTL` | `cache.quote_ttl` |
|
||||
| `IDX_CACHE_FUNDAMENTAL_TTL` | `cache.fundamental_ttl` |
|
||||
| `IDX_OWNERSHIP_SNAPSHOT_MANIFEST` | `ownership.snapshot_manifest` |
|
||||
| `IDX_CURL_IMPERSONATE_BIN` | path to a `curl_chrome*` binary |
|
||||
|
||||
## Files idx keeps
|
||||
|
||||
| What | Where |
|
||||
| --- | --- |
|
||||
| Config | `~/.config/idx/config.toml` |
|
||||
| Cache | `~/.cache/idx/` (`idx cache info`, `idx cache clear`) |
|
||||
| Ownership database | `~/.local/share/idx/ownership.db` |
|
||||
|
||||
The MCP server reads the same config, cache and database.
|
||||
|
||||
## Offline and caching
|
||||
|
||||
Quotes are cached for `quote_ttl` seconds and fundamentals for
|
||||
`fundamental_ttl`. `--offline` makes no network requests and serves cached
|
||||
data, even when stale; `--no-cache` always fetches.
|
||||
|
||||
:::note
|
||||
A config created by `idx config init` before v0.3 pins `output = "table"`.
|
||||
Run `idx config set general.output auto` to get JSON when piped.
|
||||
:::
|
||||
98
web/src/content/docs/start/install.mdx
Normal file
98
web/src/content/docs/start/install.mdx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
title: Install
|
||||
description: Install idx with npm, the install script, Cargo or Nix.
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
Prebuilt binaries cover Linux x64/arm64 (static) and macOS arm64/x64. On
|
||||
Windows, use WSL.
|
||||
|
||||
:::tip[Let your agent do it]
|
||||
Paste this into Claude Code, Codex, Cursor or another coding agent:
|
||||
|
||||
```text wrap
|
||||
Install idx-cli and its MCP server for me by following https://idx.rasyidanaf.com/agents/install.md, then show me that it works.
|
||||
```
|
||||
:::
|
||||
|
||||
<Tabs syncKey="install">
|
||||
<TabItem label="npm">
|
||||
|
||||
```bash
|
||||
npm install --global idx-cli
|
||||
npx idx-cli --help # or run without installing
|
||||
```
|
||||
|
||||
The package downloads the checksum-verified release binary during
|
||||
`postinstall`. pnpm 10+ and bun skip install scripts by default; allow them
|
||||
with `pnpm add --global --allow-build=idx-cli idx-cli` or
|
||||
`bun add --global --trust idx-cli`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Install script">
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||
```
|
||||
|
||||
Downloads the binary for your platform from GitHub Releases, verifies it
|
||||
against the release `SHA256SUMS`, and installs it into `~/.local/bin` without
|
||||
sudo. Pin a version or change the directory:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh -s -- --version v0.3.1 --dir ~/bin
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Cargo">
|
||||
|
||||
```bash
|
||||
cargo install idx-cli
|
||||
```
|
||||
|
||||
Builds from source. Requires Rust 1.89 or newer.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Nix">
|
||||
|
||||
```bash
|
||||
nix profile install github:0xrsydn/idx-cli
|
||||
nix run github:0xrsydn/idx-cli -- version # or run without installing
|
||||
```
|
||||
|
||||
The Nix package bundles the helper tools below.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Check it works:
|
||||
|
||||
```bash
|
||||
idx version
|
||||
```
|
||||
|
||||
## Helper tools
|
||||
|
||||
Everyday use needs nothing else: stock data with the default MSN provider,
|
||||
price history, `ownership sync` and every local ownership query. A few
|
||||
commands call external tools:
|
||||
|
||||
| Tool | Needed for |
|
||||
| --- | --- |
|
||||
| `curl_chrome*` from [curl-impersonate](https://github.com/lexiforest/curl-impersonate), or `IDX_CURL_IMPERSONATE_BIN` | `ownership discover` and `ownership import --url` (IDX sits behind Cloudflare), and Yahoo fundamentals with `IDX_PROVIDER=yahoo` |
|
||||
| `mutool` from MuPDF | importing legacy PDF ownership reports |
|
||||
|
||||
## The MCP server
|
||||
|
||||
`idx-mcp` installs the same ways (`npx -y idx-mcp`, `cargo install idx-mcp`,
|
||||
or `nix profile install github:0xrsydn/idx-cli#idx-mcp`). See
|
||||
[MCP setup](/mcp/setup/).
|
||||
|
||||
## Shell completions
|
||||
|
||||
```bash
|
||||
idx completions bash > ~/.local/share/bash-completion/completions/idx
|
||||
idx completions zsh > "${fpath[1]}/_idx"
|
||||
idx completions fish > ~/.config/fish/completions/idx.fish
|
||||
```
|
||||
50
web/src/content/docs/start/quickstart.md
Normal file
50
web/src/content/docs/start/quickstart.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: Quickstart
|
||||
description: First stock and ownership commands.
|
||||
---
|
||||
|
||||
## Stocks
|
||||
|
||||
Tickers are IDX codes; the `.JK` suffix is optional.
|
||||
|
||||
```bash
|
||||
idx stocks quote BBCA # one quote
|
||||
idx stocks quote BBCA BBRI BMRI # several (spaces or commas)
|
||||
idx stocks history BBCA --period 1y # daily OHLCV
|
||||
idx stocks technical BBCA # trend, RSI, MACD, moving averages
|
||||
idx stocks fundamental BBCA # growth, valuation and risk together
|
||||
idx stocks compare BBCA BBRI BMRI # fundamentals side by side
|
||||
idx stocks screen --filter high-dividend
|
||||
```
|
||||
|
||||
## Ownership
|
||||
|
||||
Ownership data comes from the monthly KSEI register of holders above 1%.
|
||||
Download the maintained snapshot once; after that every query runs offline.
|
||||
|
||||
```bash
|
||||
idx ownership sync # a few MB, safe to rerun
|
||||
idx ownership ticker BBCA # who holds BBCA
|
||||
idx ownership entity "ANTHONI SALIM" # everything one investor holds
|
||||
idx ownership search salim # find investor names
|
||||
idx ownership releases # which months you have
|
||||
idx ownership changes --from 2026-08-31 --to 2026-09-30
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
In a terminal idx prints tables. When its output is piped or captured it
|
||||
prints JSON, so scripts and agents need no flag:
|
||||
|
||||
```bash
|
||||
idx stocks quote BBCA BBRI | jq -r '.[] | "\(.symbol) \(.price)"'
|
||||
idx -o json stocks quote BBCA # force JSON in a terminal
|
||||
```
|
||||
|
||||
See [Output and exit codes](/cli/output/) for the full contract.
|
||||
|
||||
## Next
|
||||
|
||||
- [Stocks guide](/cli/stocks/) and [ownership guide](/cli/ownership/)
|
||||
- [Configuration](/start/config/)
|
||||
- [Use idx from an AI agent](/mcp/setup/)
|
||||
15
web/src/pages/[...slug].md.ts
Normal file
15
web/src/pages/[...slug].md.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Serves every docs page as raw Markdown at `<path>.md`, so agents can read
|
||||
// the docs without HTML (for example /agents/install.md).
|
||||
import type { APIRoute, GetStaticPaths } from 'astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const docs = await getCollection('docs');
|
||||
return docs.map((entry) => ({ params: { slug: entry.id }, props: { entry } }));
|
||||
};
|
||||
|
||||
export const GET: APIRoute = ({ props }) => {
|
||||
const { entry } = props;
|
||||
const body = `# ${entry.data.title}\n\n${entry.body ?? ''}`.replace(/<!--[\s\S]*?-->\n*/g, '').replace(/^import .*\n+/gm, '');
|
||||
return new Response(body, { headers: { 'Content-Type': 'text/markdown; charset=utf-8' } });
|
||||
};
|
||||
427
web/src/pages/index.astro
Normal file
427
web/src/pages/index.astro
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
---
|
||||
import '@fontsource/ibm-plex-sans/400.css';
|
||||
import '@fontsource/ibm-plex-sans/600.css';
|
||||
import '@fontsource/ibm-plex-sans/700.css';
|
||||
import '@fontsource/jetbrains-mono/400.css';
|
||||
import '@fontsource/jetbrains-mono/700.css';
|
||||
|
||||
const agentPrompt =
|
||||
'Install idx-cli and its MCP server for me by following https://idx.rasyidanaf.com/agents/install.md, then show me that it works.';
|
||||
|
||||
const installs = [
|
||||
{ id: 'npm', label: 'npm', cmd: 'npm install -g idx-cli', note: 'Downloads the checksum-verified release binary. Or run once with npx idx-cli.' },
|
||||
{ id: 'curl', label: 'curl', cmd: 'curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh', note: 'Linux and macOS. Installs to ~/.local/bin without sudo.' },
|
||||
{ id: 'cargo', label: 'cargo', cmd: 'cargo install idx-cli', note: 'Builds from source. Requires Rust 1.89+.' },
|
||||
{ id: 'nix', label: 'nix', cmd: 'nix profile install github:0xrsydn/idx-cli', note: 'Bundles curl-impersonate and MuPDF for the ownership importers.' },
|
||||
{ id: 'agent', label: 'Ask your agent', cmd: agentPrompt, prompt: true, note: 'Paste into Claude Code, Codex, Cursor or any coding agent. It installs idx, registers idx-mcp with itself, and asks before changing global config.' },
|
||||
];
|
||||
|
||||
const agents = [
|
||||
{ id: 'claude', label: 'Claude Code', file: 'terminal', code: 'claude mcp add idx -- npx -y idx-mcp' },
|
||||
{ id: 'codex', label: 'Codex', file: '~/.codex/config.toml', code: '[mcp_servers.idx]\ncommand = "npx"\nargs = ["-y", "idx-mcp"]' },
|
||||
{ id: 'cursor', label: 'Cursor', file: '~/.cursor/mcp.json', code: '{\n "mcpServers": {\n "idx": { "command": "npx", "args": ["-y", "idx-mcp"] }\n }\n}' },
|
||||
{ id: 'opencode', label: 'opencode', file: 'opencode.json', code: '{\n "mcp": {\n "idx": { "type": "local", "command": ["npx", "-y", "idx-mcp"] }\n }\n}' },
|
||||
{ id: 'pi', label: 'pi', file: '~/.pi/agent/mcp.json', code: '{\n "mcpServers": {\n "idx": { "command": "npx", "args": ["-y", "idx-mcp"], "exposure": "direct" }\n }\n}' },
|
||||
{ id: 'hermes', label: 'Hermes', file: '~/.hermes/config.yaml', code: 'mcp_servers:\n idx:\n command: npx\n args: ["-y", "idx-mcp"]\n enabled: true' },
|
||||
{ id: 'other', label: 'Other', file: 'any MCP client (stdio)', code: 'command: npx\nargs: -y idx-mcp\n# add --allow-sync to expose ownership_sync' },
|
||||
];
|
||||
|
||||
// Captured from `idx ownership ticker BBCA` on the 2026-09-30 snapshot.
|
||||
const holders = [
|
||||
{ name: 'DWIMURIA INVESTAMA ANDALAN', type: 'Corporate', shares: '67,729,950,000', pct: 54.94 },
|
||||
{ name: 'ANTHONI SALIM', type: 'Individual', shares: '1,416,306,835', pct: 1.15 },
|
||||
{ name: 'TRICIPTA MANDHALA GUMILANG', type: 'Corporate', shares: '1,313,250,000', pct: 1.07 },
|
||||
{ name: 'CATURGUWIRATNA SUMAPALA', type: 'Corporate', shares: '1,261,750,000', pct: 1.02 },
|
||||
];
|
||||
|
||||
const copyIcon =
|
||||
'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15V5a2 2 0 0 1 2-2h10"></path></svg>';
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>idx: Indonesian stocks and ownership, in your terminal and your agent</title>
|
||||
<meta name="description" content="Quotes, fundamentals, technicals and KSEI shareholder data for every IDX stock. Tables for you, JSON for scripts, and an MCP server for AI agents." />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="canonical" href="https://idx.rasyidanaf.com/" />
|
||||
<link rel="alternate" type="text/plain" href="/llms.txt" title="llms.txt" />
|
||||
<meta property="og:title" content="idx: Indonesian stocks and ownership, in your terminal and your agent" />
|
||||
<meta property="og:description" content="A Rust CLI and MCP server for IDX market data and KSEI shareholder ownership." />
|
||||
<meta property="og:url" content="https://idx.rasyidanaf.com/" />
|
||||
<meta property="og:type" content="website" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="bar">
|
||||
<nav class="wrap nav">
|
||||
<a href="/" class="mark" aria-label="idx home">idx<span class="cursor"></span></a>
|
||||
<div class="links">
|
||||
<a href="/start/install/">Docs</a>
|
||||
<a href="/mcp/">MCP</a>
|
||||
<a href="/agents/">Agents</a>
|
||||
<a href="https://github.com/0xrsydn/idx-cli">GitHub</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="wrap hero">
|
||||
<div class="hero-text">
|
||||
<div class="eyebrow">v0.3.1 · Rust · MIT</div>
|
||||
<h1>Indonesian stocks and ownership, in your terminal and your agent.</h1>
|
||||
<p class="lede">Quotes, fundamentals, technicals and KSEI shareholder data for every IDX stock. Tables for you, JSON for scripts, and an MCP server for AI agents.</p>
|
||||
|
||||
<div class="tabs" data-tabs>
|
||||
<div role="tablist" aria-label="Install method" class="tablist">
|
||||
{installs.map((t, i) => (
|
||||
<button type="button" role="tab" id={`tab-${t.id}`} aria-controls={`panel-${t.id}`} aria-selected={i === 0 ? 'true' : 'false'} tabindex={i === 0 ? 0 : -1}>{t.label}</button>
|
||||
))}
|
||||
</div>
|
||||
{installs.map((t, i) => (
|
||||
<div role="tabpanel" id={`panel-${t.id}`} aria-labelledby={`tab-${t.id}`} hidden={i !== 0}>
|
||||
<div class="cmd">
|
||||
<code class={t.prompt ? 'prompt' : ''}>{!t.prompt && <span class="dim">$ </span>}{t.cmd}</code>
|
||||
<button type="button" class="copy" data-copy={t.cmd} aria-label="Copy" set:html={copyIcon} />
|
||||
</div>
|
||||
<p class="note">{t.note}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="ctas">
|
||||
<a class="btn primary" href="/start/quickstart/">Get started</a>
|
||||
<a class="btn" href="#agents">Add to your agent</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="term" aria-label="Example idx session">
|
||||
<div class="term-bar"><span></span><span></span><span></span><em>real data · 15 Oct 2026</em></div>
|
||||
<pre><span class="dim">$</span> <b>idx stocks quote BBCA BBRI BMRI TLKM</b>
|
||||
<span class="muted">SYMBOL PRICE CHG% VOLUME</span>
|
||||
BBCA.JK 6,125 <span class="up">+2.94%</span> 136,303,100
|
||||
BBRI.JK 3,090 <span class="up">+0.32%</span> 109,152,100
|
||||
BMRI.JK 3,960 <span class="down">-0.75%</span> 106,870,300
|
||||
TLKM.JK 2,250 <span class="up">+0.45%</span> 87,159,900
|
||||
|
||||
<span class="dim">$</span> <b>idx stocks quote BBCA | jq -c '.[0] | {symbol, price, change_pct}'</b>
|
||||
{"symbol":"BBCA.JK","price":6125,"change_pct":2.9412}
|
||||
|
||||
<span class="dim">$</span> <b>idx ownership ticker BBCA</b>
|
||||
<span class="muted">Ownership: BBCA KSEI=2026-09-30</span>
|
||||
1 DWIMURIA INVESTAMA ANDALAN <b>54.94%</b>
|
||||
2 ANTHONI SALIM 1.15%
|
||||
3 TRICIPTA MANDHALA GUMILANG 1.07%
|
||||
<span class="muted">Concentration: top1=54.94% free_float=41.82%</span></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="band">
|
||||
<div class="wrap features">
|
||||
<div>
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M3 17l6-6 4 4 8-8"></path><path d="M15 7h6v6"></path></svg>
|
||||
<h2>Stocks</h2>
|
||||
<p>Quotes, OHLC history, financial statements and a screener. Technical, growth, valuation and risk analysis, and side-by-side compare.</p>
|
||||
<code>idx stocks fundamental BBRI</code>
|
||||
</div>
|
||||
<div>
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M12 3v9l6.5 6"></path></svg>
|
||||
<h2>Ownership</h2>
|
||||
<p>Who holds more than 1% of every listed company, from the monthly KSEI register. Month-over-month changes, entity search, cross-holdings. Sync once, query offline.</p>
|
||||
<code>idx ownership entity "ANTHONI SALIM"</code>
|
||||
</div>
|
||||
<div>
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="4" y="7" width="16" height="12" rx="3"></rect><path d="M12 3v4M9 13h.01M15 13h.01"></path></svg>
|
||||
<h2>Agents</h2>
|
||||
<p>JSON whenever output is piped, errors as one JSON object, meaningful exit codes. Plus idx-mcp: 11 tools for any MCP client.</p>
|
||||
<code>npx -y idx-mcp</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="agents" class="wrap split">
|
||||
<div class="side">
|
||||
<h2 class="h2">Add idx to your agent</h2>
|
||||
<p class="muted-p">idx-mcp serves the same data over stdio. Each setup below is the one our agent smoke test runs against real agents.</p>
|
||||
<div class="card">
|
||||
<div class="label">Ask it</div>
|
||||
<div class="ask">"Who are BBCA's largest holders, and what changed last month?"</div>
|
||||
<div class="chips"><span>get_shareholders</span><span>get_ownership_changes</span></div>
|
||||
</div>
|
||||
<div class="card hot">
|
||||
<div class="label strong">Or let your agent set it up</div>
|
||||
<div class="cmd bare">
|
||||
<code class="prompt">{agentPrompt}</code>
|
||||
<button type="button" class="copy" data-copy={agentPrompt} aria-label="Copy agent prompt" set:html={copyIcon} />
|
||||
</div>
|
||||
</div>
|
||||
<a class="more" href="/mcp/tools/">See all 11 tools →</a>
|
||||
</div>
|
||||
|
||||
<div class="main-col tabs" data-tabs>
|
||||
<div role="tablist" aria-label="Agent" class="tablist">
|
||||
{agents.map((a, i) => (
|
||||
<button type="button" role="tab" id={`tab-${a.id}`} aria-controls={`panel-${a.id}`} aria-selected={i === 0 ? 'true' : 'false'} tabindex={i === 0 ? 0 : -1}>{a.label}</button>
|
||||
))}
|
||||
</div>
|
||||
{agents.map((a, i) => (
|
||||
<div role="tabpanel" id={`panel-${a.id}`} aria-labelledby={`tab-${a.id}`} hidden={i !== 0} class="codebox">
|
||||
<div class="codebar"><span>{a.file}</span><button type="button" class="copy flat" data-copy={a.code} aria-label="Copy configuration" set:html={copyIcon} /></div>
|
||||
<pre>{a.code}</pre>
|
||||
</div>
|
||||
))}
|
||||
<p class="note">Ownership tools need data: run <code>idx ownership sync</code> once, or start the server with <code>--allow-sync</code>. <a href="/mcp/setup/">Full setup guide</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="band">
|
||||
<div class="wrap stack">
|
||||
<div class="head-row">
|
||||
<h2 class="h2">Who owns BBCA?</h2>
|
||||
<span class="mono muted">KSEI register, holders above 1% · as of 30 Sep 2026</span>
|
||||
</div>
|
||||
<div class="table-box">
|
||||
<table>
|
||||
<thead><tr><th>#</th><th>Investor</th><th>Type</th><th class="r">Shares</th><th class="stake">Stake</th></tr></thead>
|
||||
<tbody>
|
||||
{holders.map((h, i) => (
|
||||
<tr>
|
||||
<td class="muted">{i + 1}</td>
|
||||
<td class="name">{h.name}</td>
|
||||
<td class="soft">{h.type}</td>
|
||||
<td class="r mono">{h.shares}</td>
|
||||
<td><div class="bar-row"><div class="track"><div class="fill" style={`width: ${h.pct}%`}></div></div><span class="mono pct">{h.pct.toFixed(2)}%</span></div></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div><span>Top holder</span><b>54.94%</b></div>
|
||||
<div><span>Top 3</span><b>57.16%</b></div>
|
||||
<div><span>Free float (below 1%)</span><b>41.82%</b></div>
|
||||
<div><span>HHI</span><b>3,021</b></div>
|
||||
</div>
|
||||
<p class="muted-p small">Rendered from <code>idx ownership ticker BBCA</code>. The snapshot is rebuilt when IDX publishes a new month.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wrap split center">
|
||||
<div class="side">
|
||||
<h2 class="h2">Built for pipes</h2>
|
||||
<p class="muted-p">stdout carries only data. Errors go to stderr as one JSON object, with exit codes an agent can act on.</p>
|
||||
<dl class="codes">
|
||||
<dt>2</dt><dd>invalid input or config</dd>
|
||||
<dt>3</dt><dd>not found, or no cached data offline</dd>
|
||||
<dt>4</dt><dd>upstream unavailable, retry later</dd>
|
||||
<dt>5</dt><dd>unsupported</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="main-col term">
|
||||
<pre><span class="dim"># screen, then compare the hits</span>
|
||||
idx stocks screen --filter high-dividend \
|
||||
| jq -r '.[].symbol' \
|
||||
| idx stocks compare -
|
||||
|
||||
<span class="dim"># a year of daily closes, one JSON object per line</span>
|
||||
idx -o jsonl stocks history BBCA --period 1y | tail -5
|
||||
|
||||
<span class="dim"># bad input fails fast, as JSON on stderr</span>
|
||||
idx stocks quote ../x
|
||||
<span class="down">{"error":true,"code":"INVALIDINPUT","message":"invalid input: invalid ticker symbol '../x'"}</span>
|
||||
<span class="dim">exit 2</span></pre>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="bar top">
|
||||
<div class="wrap foot">
|
||||
<span class="mark small">idx</span>
|
||||
<a href="https://github.com/0xrsydn/idx-cli">GitHub</a>
|
||||
<a href="https://www.npmjs.com/package/idx-cli">npm</a>
|
||||
<a href="https://crates.io/crates/idx-cli">crates.io</a>
|
||||
<a href="/llms.txt">llms.txt</a>
|
||||
<a href="/about/">About the data</a>
|
||||
<span class="grow"></span>
|
||||
<span>MIT licensed. Not affiliated with IDX or KSEI. Not investment advice.</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
for (const group of document.querySelectorAll<HTMLElement>('[data-tabs]')) {
|
||||
const tabs = [...group.querySelectorAll<HTMLButtonElement>(':scope > [role=tablist] > [role=tab]')];
|
||||
const select = (tab: HTMLButtonElement) => {
|
||||
for (const t of tabs) {
|
||||
const on = t === tab;
|
||||
t.setAttribute('aria-selected', String(on));
|
||||
t.tabIndex = on ? 0 : -1;
|
||||
const panel = document.getElementById(t.getAttribute('aria-controls')!);
|
||||
if (panel) panel.hidden = !on;
|
||||
}
|
||||
};
|
||||
tabs.forEach((tab, i) => {
|
||||
tab.addEventListener('click', () => select(tab));
|
||||
tab.addEventListener('keydown', (e) => {
|
||||
const step = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
|
||||
if (!step) return;
|
||||
const next = tabs[(i + step + tabs.length) % tabs.length];
|
||||
select(next);
|
||||
next.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
for (const button of document.querySelectorAll<HTMLButtonElement>('[data-copy]')) {
|
||||
button.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(button.dataset.copy ?? '');
|
||||
button.classList.add('done');
|
||||
setTimeout(() => button.classList.remove('done'), 1200);
|
||||
} catch {
|
||||
/* clipboard unavailable: the text stays selectable */
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
:root {
|
||||
--bg: #0d0e10;
|
||||
--band: #111316;
|
||||
--surface: #16181c;
|
||||
--term: #121417;
|
||||
--line: #2c3036;
|
||||
--line-soft: #1d2024;
|
||||
--text: #e8eaed;
|
||||
--soft: #b8bec6;
|
||||
--muted: #9aa1ab;
|
||||
--dim: #6f7782;
|
||||
--accent: #ce1126;
|
||||
--link: #ff6b6f;
|
||||
--up: #4ac26b;
|
||||
--down: #ff7b72;
|
||||
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
color-scheme: dark;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text); font-family: 'IBM Plex Sans', system-ui, sans-serif; font-size: 16px; line-height: 1.55; }
|
||||
a { color: var(--link); text-decoration: none; }
|
||||
a:hover { color: #ff9a9c; }
|
||||
code, pre { font-family: var(--mono); }
|
||||
pre { margin: 0; }
|
||||
.wrap { max-width: 1200px; margin: 0 auto; padding-left: 24px; padding-right: 24px; }
|
||||
.bar { border-bottom: 1px solid #23262b; }
|
||||
.bar.top { border-bottom: 0; border-top: 1px solid var(--line-soft); }
|
||||
.nav { display: flex; flex-wrap: wrap; align-items: center; gap: 24px; padding-top: 16px; padding-bottom: 16px; }
|
||||
.mark { display: inline-flex; align-items: center; font-family: var(--mono); font-weight: 700; font-size: 22px; color: var(--text); }
|
||||
.mark.small { font-size: 16px; }
|
||||
.cursor { display: inline-block; width: 10px; height: 20px; background: var(--accent); margin-left: 3px; }
|
||||
.links { display: flex; flex-wrap: wrap; gap: 20px; font-size: 15px; }
|
||||
.links a { color: #c3c8cf; }
|
||||
.links a:hover { color: var(--text); }
|
||||
|
||||
.hero { display: flex; flex-wrap: wrap; gap: 56px; align-items: center; padding-top: 88px; padding-bottom: 72px; }
|
||||
.hero-text { flex: 1 1 440px; min-width: 0; display: flex; flex-direction: column; gap: 24px; }
|
||||
.eyebrow { font-family: var(--mono); font-size: 13px; color: var(--muted); letter-spacing: 0.04em; }
|
||||
h1 { margin: 0; font-size: clamp(36px, 5vw, 52px); line-height: 1.08; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.lede { margin: 0; font-size: 19px; color: var(--soft); max-width: 540px; }
|
||||
|
||||
.tabs { display: flex; flex-direction: column; max-width: 560px; }
|
||||
.main-col.tabs { max-width: none; }
|
||||
.tablist { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
[role=tab] { min-height: 44px; padding: 0 16px; border-radius: 8px 8px 0 0; font: inherit; font-size: 14px; cursor: pointer; background: transparent; color: var(--muted); border: 1px solid transparent; border-bottom: 0; }
|
||||
[role=tab]:hover { color: var(--text); }
|
||||
[role=tab][aria-selected=true] { background: var(--surface); color: var(--text); font-weight: 600; border-color: var(--line); box-shadow: inset 0 2px 0 var(--accent); }
|
||||
.codebox [role=tab][aria-selected=true], .main-col [role=tab][aria-selected=true] { background: var(--term); }
|
||||
.cmd { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--line); border-radius: 0 10px 10px 10px; padding: 14px 16px; }
|
||||
.cmd.bare { background: transparent; border: 0; padding: 0; align-items: flex-start; }
|
||||
.cmd code { flex-grow: 1; min-width: 0; overflow-x: auto; white-space: nowrap; font-size: 14px; }
|
||||
.cmd code.prompt { white-space: normal; line-height: 1.55; }
|
||||
.dim { color: var(--dim); }
|
||||
.note { margin: 8px 0 0; font-size: 13px; color: var(--muted); }
|
||||
.note code { font-size: 12px; }
|
||||
.copy { flex-shrink: 0; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--line); border-radius: 8px; color: #c3c8cf; cursor: pointer; }
|
||||
.copy:hover { color: var(--text); border-color: #3a3f46; }
|
||||
.copy.flat { border: 0; }
|
||||
.copy.done { color: var(--up); border-color: var(--up); }
|
||||
|
||||
.ctas { display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
.btn { display: inline-flex; align-items: center; min-height: 44px; padding: 0 22px; border-radius: 8px; border: 1px solid #3a3f46; color: var(--text); font-weight: 600; }
|
||||
.btn:hover { color: var(--text); border-color: var(--muted); }
|
||||
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.btn.primary:hover { background: #b30f21; color: #fff; }
|
||||
|
||||
.term { flex: 1 1 520px; min-width: 0; background: var(--term); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45); }
|
||||
.term-bar { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid #23262b; }
|
||||
.term-bar span { width: 11px; height: 11px; border-radius: 50%; background: #3a3f46; }
|
||||
.term-bar em { flex-grow: 1; text-align: center; font-style: normal; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.term pre { overflow-x: auto; padding: 20px; font-size: 13px; line-height: 1.5; color: #c3c8cf; }
|
||||
.split .term pre { font-size: 14px; line-height: 1.6; color: var(--text); }
|
||||
.term b { color: var(--text); font-weight: 400; }
|
||||
.up { color: var(--up); }
|
||||
.down { color: var(--down); }
|
||||
.muted { color: var(--muted); }
|
||||
.soft { color: var(--soft); }
|
||||
|
||||
.band { border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); background: var(--band); }
|
||||
.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 32px; padding-top: 64px; padding-bottom: 64px; }
|
||||
.features > div { display: flex; flex-direction: column; gap: 12px; }
|
||||
.features svg { color: var(--link); }
|
||||
.features h2 { margin: 0; font-size: 22px; font-weight: 600; }
|
||||
.features p { margin: 0; color: var(--soft); }
|
||||
.features code { font-size: 13px; color: var(--muted); }
|
||||
|
||||
.split { display: flex; flex-wrap: wrap; gap: 56px; padding-top: 88px; padding-bottom: 88px; }
|
||||
.split.center { align-items: center; }
|
||||
.side { flex: 1 1 360px; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
|
||||
.main-col { flex: 1.4 1 520px; min-width: 0; }
|
||||
.h2 { margin: 0; font-size: 36px; line-height: 1.15; font-weight: 700; letter-spacing: -0.01em; }
|
||||
.muted-p { margin: 0; color: var(--soft); }
|
||||
.muted-p.small { font-size: 14px; color: var(--muted); }
|
||||
.muted-p code { color: #c3c8cf; }
|
||||
.card { display: flex; flex-direction: column; gap: 10px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 18px 20px; }
|
||||
.card.hot { background: #1a1416; border-color: #4a2a2f; }
|
||||
.card.hot code { font-size: 13px; color: #fff; }
|
||||
.card.hot .copy { border-color: #4a2a2f; color: #e3d5d6; }
|
||||
.label { font-size: 13px; color: var(--muted); }
|
||||
.label.strong { color: #e3d5d6; font-weight: 600; }
|
||||
.ask { font-size: 17px; }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
|
||||
.chips span { font-family: var(--mono); font-size: 12px; padding: 4px 8px; border-radius: 6px; background: #22252a; color: #c3c8cf; }
|
||||
.more { font-weight: 600; }
|
||||
.codebox { background: var(--term); border: 1px solid var(--line); border-radius: 0 10px 10px 10px; overflow: hidden; }
|
||||
.codebar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 4px 0 16px; border-bottom: 1px solid #23262b; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.codebox pre { overflow-x: auto; padding: 20px; font-size: 14px; line-height: 1.6; }
|
||||
|
||||
.stack { display: flex; flex-direction: column; gap: 28px; padding-top: 88px; padding-bottom: 88px; }
|
||||
.head-row { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 12px; }
|
||||
.mono { font-family: var(--mono); font-size: 13px; }
|
||||
.table-box { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }
|
||||
table { width: 100%; min-width: 640px; border-collapse: collapse; font-size: 15px; }
|
||||
thead tr { background: var(--surface); color: var(--muted); font-size: 13px; text-align: left; }
|
||||
th { padding: 12px 16px; font-weight: 500; }
|
||||
td { padding: 14px 16px; border-top: 1px solid #23262b; }
|
||||
td.mono { font-size: 14px; }
|
||||
.r { text-align: right; }
|
||||
.stake { width: 36%; }
|
||||
.name { font-weight: 600; }
|
||||
.bar-row { display: flex; align-items: center; gap: 12px; }
|
||||
.track { flex-grow: 1; height: 8px; background: #23262b; border-radius: 4px; }
|
||||
.fill { height: 8px; min-width: 4px; background: var(--accent); border-radius: 4px; }
|
||||
.pct { width: 64px; text-align: right; font-size: 14px; }
|
||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
|
||||
.stats div { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; }
|
||||
.stats span { font-size: 13px; color: var(--muted); }
|
||||
.stats b { font-family: var(--mono); font-size: 24px; font-weight: 400; }
|
||||
|
||||
.codes { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin: 0; font-size: 15px; }
|
||||
.codes dt { font-family: var(--mono); }
|
||||
.codes dd { margin: 0; color: var(--soft); }
|
||||
|
||||
.foot { display: flex; flex-wrap: wrap; gap: 16px 28px; align-items: center; padding-top: 32px; padding-bottom: 32px; font-size: 14px; color: var(--muted); }
|
||||
.foot a { color: var(--soft); }
|
||||
.grow { flex-grow: 1; }
|
||||
:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
42
web/src/styles/theme.css
Normal file
42
web/src/styles/theme.css
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* idx theme: neutral greys, one red accent (Indonesian flag red), Plex + JetBrains Mono. */
|
||||
:root {
|
||||
--sl-font: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
--sl-font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
--sl-color-accent-low: #2a0a0e;
|
||||
--sl-color-accent: #ce1126;
|
||||
--sl-color-accent-high: #ff8a8d;
|
||||
/* Starlight's "tip" aside uses purple; keep it on the accent. */
|
||||
--sl-color-purple-low: #2a1216;
|
||||
--sl-color-purple: #ff6b6f;
|
||||
--sl-color-purple-high: #ffd6d7;
|
||||
--sl-color-white: #ffffff;
|
||||
--sl-color-gray-1: #e8eaed;
|
||||
--sl-color-gray-2: #c3c8cf;
|
||||
--sl-color-gray-3: #9aa1ab;
|
||||
--sl-color-gray-4: #5f6670;
|
||||
--sl-color-gray-5: #2c3036;
|
||||
--sl-color-gray-6: #16181c;
|
||||
--sl-color-black: #0d0e10;
|
||||
}
|
||||
|
||||
:root[data-theme='light'] {
|
||||
--sl-color-accent-low: #fde3e4;
|
||||
--sl-color-accent: #b30f21;
|
||||
--sl-color-accent-high: #6e0a15;
|
||||
--sl-color-purple-low: #fde3e4;
|
||||
--sl-color-purple: #b30f21;
|
||||
--sl-color-purple-high: #6e0a15;
|
||||
--sl-color-white: #0d0e10;
|
||||
--sl-color-gray-1: #16181c;
|
||||
--sl-color-gray-2: #2c3036;
|
||||
--sl-color-gray-3: #4b525c;
|
||||
--sl-color-gray-4: #7c838d;
|
||||
--sl-color-gray-5: #c9ced5;
|
||||
--sl-color-gray-6: #eef0f2;
|
||||
--sl-color-gray-7: #f7f8f9;
|
||||
--sl-color-black: #ffffff;
|
||||
}
|
||||
|
||||
.sl-markdown-content code:not(:where(.not-content *)) {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue