- JSON named kinds by their Rust names (public_expose, tender_offer,
stock_dividend, reverse_split) while --kind and MCP take expose, tender,
stock-dividend, reverse-split, so an output kind could not be passed
back. JSON now uses the input names; old cache entries still read, and
--kind also accepts underscores.
- {"dividend": null, "today": "..."} failed to parse and aborted the
whole command; a null list beside scalars now reads as none.
- An extreme --from panicked on the default 30 days; it is an error now.
The 366-day limit let 367 days through; it counts both ends now.
- An entitlement without a cum date sat on its ex date under a note that
calls DATE the cum date. The table marks it (ex) and says what that
means; JSON leaves cum_date out, as before, and the docs say so.
- A dividend's currency was ignored; one not in IDR now carries currency
and the table shows it instead of Rp.
- The live check no longer fails a quiet month's empty calendar.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Logged-in quotes passed Stockbit's session state through verbatim
(STATE_NAME_SESSION_2), while public-page quotes say open or close, so a
consumer checking for "open" broke after login. Quotes now use the two
words either way; the order book keeps the state verbatim.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
stocks intraday never set quote.behind_session, so on a holiday or when
Stockbit lags, a past session's chart read as current. Mark it as quotes
do and print the same note under the header.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
`idx stocks actions SYM [--kind ...]`: one stock's cash and stock
dividends, bonus shares, rights, warrants, splits and reverse splits,
meetings, public exposes and tender offers (`--kind ipo` adds its IPO),
newest first. `idx stocks calendar [--from] [--to] [--kind ...]`: the
market's actions dated in the range (default today and 30 days ahead),
oldest first. MCP: get_corporate_actions, get_corporate_calendar.
Stockbit's `/corpaction/{type}` serves one kind per request; each type has
its own field prefix and list key, so entitlements are read by field
suffix. An action's `date` is the cum date for entitlements, else the
event's first day. Dividends keep their decimals. One request per kind,
cached 6 hours. AUTHERROR without a session.
Fixtures are the 2026-11-03 recordings (market-wide ones trimmed to
October-December). Goldens added; help-stocks and completions-bash gain
the two subcommands, MCP tools.json the two tools (regenerated with
UPDATE_GOLDEN=1). The MCP docs counted 17 tools without stockbit_login;
now 20.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Observed in session on 2026-11-03:
- The regular board trades as STATE_NAME_SESSION_2, not the assumed
STATE_NAME_MARKET_OPEN, so session quotes hid bid/offer all day. Any
numbered STATE_NAME_SESSION_<n> now counts as trading; the lunch break
is STATE_NAME_BREAK.
- In session the order book's fbuy/fsell/fnet were the previous session's
(equal to IPOT's 2026-11-02 net for BBCA, BBRI and TLKM) and
has_foreign_bs was false. Foreign figures are kept only when that flag
is true.
Fixtures for the break, the trading state and an in-session book; the
table names the session ("market open (session 2)").
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
`stockbit::signin` is now the single sign-in flow. `idx stockbit login`
waits on it in the foreground; the `stockbit_login` MCP tool runs it on a
thread and `stockbit_status` reports `sign_in.state`, so an agent knows when
to stop polling. Auto uses a browser unless the shell is over SSH or has no
display, and falls back to the local page only when the browser cannot start.
The browser way opens Stockbit's sign-in page in a profile only idx uses and
reads the session cookie over DevTools. What a real sign-in showed:
- The cookie is host-only on stockbit.com, so deleting by the assumed
.stockbit.com removed nothing and left live tokens in the profile. It is
now deleted by its own domain and path, with every other tab closed first,
and read back to confirm.
- A signed-out page writes the cookie without tokens; it no longer counts.
- A profile belongs to one browser, so it records the executable that made it.
- On macOS the config directory depends on XDG_CONFIG_HOME, so a shell and an
app started from the Dock disagreed; both default locations are now checked.
- A thread does not outlive main, so idx-mcp closes a waiting browser when
stdin closes or on SIGTERM or Ctrl-C.
Progress goes to stderr, which keeps `-o json` to one document on stdout.
Google refused its own sign-in in this window on 2026-10-31, most likely over
the debugging flag; password sign-in works, and the docs and roadmap say so.
Claude-Session: https://claude.ai/code/session_01RqPPVaoFxLToGAXXBEoCtH
Opt-in, with the user's own Stockbit session from a pasted
credentialStorage cookie (or IDX_STOCKBIT_REFRESH_TOKEN):
- idx stockbit login/status/logout; session file 0600, tokens never shown
- rotating refresh under a cross-process lock; one refresh+retry on 401
- stocks orderbook: depth with lots and orders, OHLC, value, foreign flow
- --provider stockbit quotes from the session API when logged in
- --history-provider stockbit, and auto under the stockbit provider for
5d-1y daily windows
- MCP get_orderbook and stockbit_status (17 tools)
Endpoints, units and recorded responses from fzn0x/idx (MIT).
Read Stockbit's public symbol page (__NEXT_DATA__) with a plain ureq
request and an honest user agent: `stocks intraday` (today by the
minute, any provider), `--provider stockbit` quotes with the best
bid/offer only while the market is open, and MCP `get_intraday`.
- UPSTREAMBLOCKED error (exit 4) for a CDN 403; never retried
- requests paced 1 s apart across idx processes, 5 s snapshot cache
- global --provider flag (yahoo, msn, stockbit)
- roadmap: L1 ships from Stockbit; depth stays a login follow-on
Review findings on #71:
- The IDX session rule flagged non-IDX data: US screen rows on Monday
evening WIB, and JP/HK/SG rows on their own holidays. Only `.JK`
quotes and `--region id` screens are marked now.
- Nothing tested the marking call sites. A screen test checks IDX rows
online and offline, and US rows unmarked. A quote test reads a cache
entry in 0.3.3's format (no field) offline.
- The get_quotes description now says the flag also means the stock did
not trade, or an exchange holiday; screen_stocks mentions the field.
- latest_session is pub(crate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
MSN can serve the previous session's close for hours after IDX closes;
on 2026-10-16 at 16:53 WIB it still returned the 15th. Quotes and
screener rows now carry `behind_session`, true when `as_of` is before
the latest weekday whose session has ended (16:15 WIB). It is computed
on return, not cached. The quote table notes such quotes, and the
get_quotes description explains the field.
Closes#50
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
`idx stocks flow` now reads IPOT by default, which needs nothing installed:
per session, the all-investor broker summary gives the value traded and
the foreign one gives foreign buying (its footer), the foreign net and
selling (buying less the net), across all boards, through the broker
summary cache. IPOT's foreign net matched IDX's estimate within 0.3 B on
BBCA 2026-10-12 to 16.
`--source idx` (and `source` on get_foreign_flow) keeps IDX's trading
summary: exact share counts, close and the non-regular share, behind
curl-impersonate and Cloudflare.
- ForeignFlowDay gains foreign_buy_value, foreign_sell_value and
foreign_share_bps for both sources (estimated at the session average
for IDX); ForeignFlow gains source, the value totals and rounded.
- The table lays out each source's figures; the live smoke runs both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
IPOT's investor filter splits each broker row by client domicile, but its
footer then gives only that side's buying (BBCA 2026-10-16: foreign 1.7 T,
domestic 4.9 T of 6.6 T). The foreign net gives the selling on all boards:
foreign 1.7 + 2.3 = 4.0 T (IDX: about 3.95 T regular), domestic
4.9 - 2.3 = 2.6 T.
- BrokerSummary gains buy_value and sell_value: the total for all
investors, the footer and the derived selling for one side (selling is
null with a board filter, where the net covers other boards).
- Bandar weighs the top N buyers' share of the value bought against the
top N sellers' share of the value sold, so `--investor foreign|domestic`
reads that side alone; for all investors the readings are unchanged.
One side on one board is refused, as there is no selling to weigh.
- Tables print what the side bought and sold; fixtures for foreign and
domestic on all boards were recorded from IPOT.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
IDX's Value is regular-board turnover, so NonRegularValue / Value gave
non-regular over regular (BBRI 2026-10-14: 50.91%) instead of the share of
all trading (33.74%). non_regular_bps now divides by regular plus
non-regular value, with a regression test on that fixture. The docs now say
volume, value and the foreign figures are the regular board: IPOT's
regular total matches Value, and in a week of every stock no foreign figure
exceeded regular volume, even with non-regular volume above twice it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
`idx stocks flow SYMBOL` shows what foreign investors bought and sold of a
stock, session by session, from IDX's own daily trading summary: foreign
buy, sell and net volume, net value at the session's average price, and
the share of value traded outside the regular board, with range totals.
Latest 10 sessions by default (--days, at most 60) or --from/--to. The MCP
server exposes it as get_foreign_flow.
- api/idx: pure parser for GetStockSummary (rows dated another session
are dropped) and a provider over curl-impersonate's profile fallback
with a 30 s limit; Cloudflare passes only some fingerprints.
- flow: each session is cached whole by date, so other stocks' flows for
the same sessions need no request. Weekends are never requested.
- Idx::idx_code is shared by broker and flow for the .JK-only check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
- Basis points truncate toward zero instead of flooring, so a reading of
-4.9952% stays neutral like its mirror image and -14.9952% stays
distribution; tests cover both signs at both thresholds through
bandar_tier. Goldens move by the rounding (BBCA top 5: -2426 -> -2425).
- With one investor side selected the summary has no total to weigh
brokers against, so bandar and analyze_bandar reject it (INVALIDINPUT)
instead of reading every tier as missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
`idx stocks bandar SYMBOL...` reads the broker summary: for the top 1, 3,
5 and 10 brokers per side, buy value minus sell value in basis points of
value traded, with a signal from big accumulation to big distribution on
the headline tier (--top, default 5). Several symbols give a screen ranked
by that figure (at most 100, one request each), which pipes from
`stocks screen`. The MCP server exposes it as analyze_bandar (20 symbols).
- analysis/bandar.rs is pure and versioned (`top_concentration_v1`):
integer basis points, no figure missing counted as zero, buy/sell VWAP
from exact averages, and the brokers on both sides listed because their
gross figures overstate their direction.
- Thresholds (±5%/±15%) follow fzn0x/idx (MIT); docs call them heuristics.
- Invalid input fails a screen at once; other errors fail one symbol and
are warned about on stderr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
`idx stocks broker SYMBOL` shows the top 10 buyers and sellers of a stock
by lots for the latest session with trades, one `--date`, or a
`--from`/`--to` range, filtered by `--investor` and `--board`. The MCP
server exposes the same as get_broker_summary.
- Range and filter parsing lives in idx-core (BrokerRange::from_args,
FromStr for the filters), so both frontends accept the same input.
- Without dates, the latest session is found by walking back over
weekdays; offline, uncached days are skipped.
- Table output lists foreign and state-owned brokers, and says when the
source rounded figures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
From a full-repo Codex audit, each reproduced on the synced ownership DB:
- `changes` paired an investor's accounts across releases instead of
comparing totals: KREN's KRESNA PRIMA INVEST went 9.44% -> 13.45% (a
second 4.01% account) but was reported as decreased 944 -> 401 bps.
Sum a holder's accounts per ticker, and read one release per date (the
latest imported, as other queries do), so a corrected file supersedes
the original instead of mixing with it.
- `changes` / get_ownership_changes accepted any dates: `from: ""`
reported all 7,154 holders as new, unknown dates returned no changes.
Dates must be YYYY-MM-DD imported releases with `from` before `to`.
- get_investor_holdings / `entity` with an empty name matched every
investor and returned an arbitrary one ("AMY"); `%` and `_` acted as
LIKE wildcards. Reject empty names; match wildcards literally.
- Alias search never worked: the FTS table declared an `aliases` column
its content table lacks (`no such column: T.aliases`), so searches
fell back to canonical names and a raw KSEI name such as
"PT ASTRA INTERNATIONAL TBK" found nothing. Search names and aliases
directly, ranked exact name, exact alias, prefix, then shorter names.
- `graph` enumerated every path (exponential in depth) and its cycle
check matched substrings, dropping entity:1 when entity:10 was on the
path. Traverse breadth-first with an exact visited set.
- Name normalization removed leading PT tokens from the front of a Vec,
quadratic in hostile names; narrow a slice instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
From the Pullfrog review of #48: an offset at or past `total` returns an
empty page with `next_offset: null`, and every page carries the same
`summary` (it covers all rows). Assert both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
Between the two latest releases there are 1,568 holder changes. With only
`limit` (up to 2000, ~200 KB), agents that wanted them all overflowed
their tool output: Hermes then paged ticker by ticker (32 calls), and
opencode 1 and 2 did the same and timed out.
- `offset` pages through the rows; responses carry `offset` and
`next_offset` (null after the last page). Pages cap at 300 rows
(~40 KB); the over-limit error points to `offset` and `summary`.
- Rows come largest |delta| first, so the first page holds the moves
that matter rather than tickers starting with A.
- `summary` covers every row: counts by change type, the number of
tickers, and the ten tickers with the most changes.
The CLI's `ownership changes` is unchanged. tools.json and the website's
MCP pages are regenerated for the new description and parameter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
MSN's feed for a stock is often mostly general market news: of 10 items,
ADRO, BREN and CUAN had none about the company, BBRI one, yet they were
presented as the company's news. Fetch at least 50 items, label each
`relevance: company` (tagged by MSN with the stock's instrument id, or
naming the ticker or the company) or `market`, and return company items
first, newest first, trimmed to --limit.
Company names come from the quote's short and display names, best
effort: MSN tags Telkom articles with its NYSE ADR, not TLKM, so names
catch what tags miss. Classification stays a pure function in parse_news.
Output change, intentional: news JSON gains `relevance`, the table an
ABOUT column (goldens regenerated), and the get_company tool description
explains the field (tools.json and the website tools page regenerated).
News cache keys change so entries without the field are not reused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
Findings from a Codex (gpt-6.1-sol, xhigh) review of v0.2.4..HEAD, each
verified in the code before fixing:
- high: ticker symbols reached cache file paths unchecked, so a symbol
like `../../REPORT` read (and deleted, if unparseable) .json files
outside the cache, reachable through MCP get_quotes. resolve_symbol now
accepts only ticker characters, and cache keys are encoded as single
safe path components.
- high: sync decided before downloading and replaced the database
without rechecking; an import that landed meanwhile was erased. It now
rechecks the local state before installing, and idx-mcp runs
ownership_sync exclusively with ownership reads.
- replaces_sqlite_sha256 never downgrades history: the client requires
the rebuild to keep at least the local release count, and the
publisher lists a replaced build only then.
- cache writes are atomic (temp file + rename), so concurrent readers
never see, and delete, a partial entry.
- idx-mcp caps symbols (20, deduplicated) and limits, and runs at most 4
tool calls at once.
- each Yahoo client gets its own cookie jar, paired with its crumb.
- ZIP entries (XLSX, KSEI archives) are read with a 256 MiB bound;
non-ASCII archive dates and huge percentages are parse errors instead
of panics or overflow.
- agent-smoke --keep deletes every copied agent config, not only auth
files.
CLI goldens unchanged; the MCP tool golden changes only to state the
new caps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- tests/agents/agent-smoke.sh runs tests/agents/cases.json through
Claude Code, Codex, pi, Hermes Agent, and opencode headless against a
local idx-mcp build, each with an isolated config, and checks the
tools called, their key arguments, and (mock data) facts in the
answer. Opt-in; never part of the gate.
- crates/idx-mcp/tests/golden/tools.json pins tool names, descriptions,
and input schemas, so changes to what agents see are reviewed.
- nix develop .#agents adds Claude Code, Codex, and opencode from
nixpkgs after the host PATH.
First run: 30/30 (5 agents x 6 cases, mock data).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sync hashed only the main database file, but local edits can sit in
the -wal sidecar, so an edited database could match an earlier build
and be replaced. Checkpoint the WAL before hashing; if changes remain,
never take the rebuilt-snapshot path.
- MCP tools rejected nothing for empty symbols/sections/kinds and
returned an empty success; they now fail with INVALIDINPUT, as the CLI
requires input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 4 of docs/DESIGN.md. Eleven grouped tools (quotes, history, screen,
company, analysis, compare, shareholders, investor holdings, search,
ownership changes, status); ownership_sync is listed only with
--allow-sync. Live results keep the Fetched envelope, errors use the CLI's
{error, code, message} body, and investor tools on an empty database
point at the sync path.
idx-core gains Period/Interval FromStr, Investor::status, a ticker
filter on changes, and Display for ErrorCode (now used by the CLI too).
CLI output and goldens are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>