Changelog
All notable changes to itda are listed here. The format follows Keep a Changelog and versions follow Semantic Versioning. Until 1.0, a minor version may change the definition format or the CLI.
Unreleased
0.7.0 - 2026-10-24
Reads that get past a challenge, a knowledge base per site, and an agent that
prepares a site itself. A read a site answers with a challenge page now
recovers in the browser installed on the computer, and every request in that
mode carries that browser’s user agent; a change is still sent once, over
direct HTTP, after a single-use approval. itda writes down what it found and
tried for each site, itda fsck lists what was never tried or keeps failing,
and itda_shape shows the shape of a page or an answer without its values.
When setup keeps one read or none, itda_prepare lets the agent draft the
site from its docs, seeing shapes and evidence, never what the site holds.
Added
itda_prepare, one MCP tool for an agent to prepare a site thatitda_setupdrafted thinly, as a loop the agent runs:start,recon(the entry page, the API description it links, its GraphQL schema, the routes its docs name and a draft itda made, each with an evidence id),read,search,propose,verify,reviewandinstall. itda calls no model and the agent never sees a value the site holds: only titles, paths, methods, field names, selectors, keys, types, pointers and counts. Each proposed operation is kept only when the evidence carries it (a documented or answering server, the route in the docs or a page at that path, something to check in an HTML answer), a declared read whose request asks for a change becomes a change, a route that needs a sign-in itda cannot use is dropped, and a draft that only reads the home page is refused.verifytries the kept reads through the same path every call takes, fills inputs from earlier answers with$result:<read>:<pointer>(scalars only), addsrequired_pointersfrom the field names at the top of the answer, says in counts where a page read’s selectors miss, and never sends a change. Reads go only to the site’s registrable domain and the hosts it links as documentation. A session has 120 calls, 48 page requests and 24 check requests (browser recovery counted), 6 reads perverifyand two coverage and two repair rounds.installtakes the same path asitda_setup. Whenitda_setupkeeps one read or none, its result carries aremedythat namesitda_prepare.reconwithbundles: truealso reads the scripts the entry page loads and keeps a route string only when one GET answers with data.skills/itda-prepare/SKILL.mdis the loop for skill loaders.- Reads that a site answers with a challenge page can recover through the
browser installed on the computer. itda fetches the page inside a hidden
browser window kept for that one site and account and, for a
GET, lets the page render. Every request in this mode, a change or a sign-in form included, carries the browser’s user agent, and the hidden window keeps the browser’s own client hints, so the site sees one client whichever way a request goes and a clearance the browser earned holds for the direct requests too. itda remembers which way worked for an hour, per site, account and action, so the next call does not pay for the failed try again, and forgets a way that failed after a few minutes. The browser session stays warm in a worker process across CLI calls and closes after five idle minutes; changing an input does not restart it. Everything the page loads passes the same address and read checks as a direct request: other sites get neither the account’s credentials nor a referrer, private addresses stay refused, and a page may send a GraphQL query, never a mutation. A challenge the browser did not get past fails that one read (blocked_by_site,paused: false) without pausing the site. A browser answer may be up to 16 MiB. The worker keeps its files underbrowser-runtime/in the data directory, one directory per itda build; a build’s directory is removed a day after it stopped being used.ITDA_TRANSPORT=httpkeeps direct HTTP only, with the pauses it always had.docs/HONEST_CLIENT.mdhas the rules,docs/TRANSPORT_BENCHMARKS.mdthe measurements. - A local knowledge base per site, in
itda.db. Setup, check, refresh and every call write down what was found and tried: the pages and actions, which list read can supply which detail read’s input, every try with its stage, verdict, HTTP status and time, and the hashes of the definition and the last verdict. It keeps shapes only (selectors with match counts, field names, keys and types), never an answer, input, cookie, header value or record. Nothing that sends a request reads it, anditda removedeletes a site’s rows. A call itda refused before sending (waiting for an approval, a bad input) is not counted as a try. itda fsck [site]runs fixed rules over the knowledge base and lists, worst first, the reads never tried, the reads that need an input (with the list read and pointer that can supply it), the reads that fail every recent try or now and then, and a definition or verdict file changed under itda, each with the command to run. It sends nothing; exit 0, or 1 when a gap is at priority 1. Over MCP,itda_managetakesfsck, and each gap carries the next tool to call with its arguments.itda setupends with the top three.itda_shape, a read-only MCP tool: the shape of a page (selectors with counts, named fields, the keys and types of embedded data) or of an action’s last answer (keys, types and pointers), from the knowledge base or, withfresh, read now the way setup reads a page. At most 24 KB and no values.itda ls --jsonanditda_sitessay per site how many actions answered on their last try (verified), the recentpass_rateand the reads that fail now and then (unstable).itda ls --jsonanditda_siteswith a site add auser_guide(what can be read, what can be changed, how one read feeds another) and two to fournatural_prompts, made by rule in the person’s language;itda describe <site>prints both.
Changed
-
The knowledge base records a preparation loop’s tries under a stage of their own,
prepare, and its notes under thepreparescope; the pass rate, the last success and theitda fsckrules leave both out. -
The honest-client rule changes: itda no longer stops at a challenge. Every request carries the installed browser’s user agent, a read may run inside that browser after a challenge, and a challenged read no longer pauses the site. What stays: a change is sent once, over direct HTTP, after a single-use approval; credentials go only to their own site and account; one request at a time per site, with
Retry-Afterrespected, whichever way the request goes; captchas are not solved.
Fixed
- Opening
itda.dbcould fail withinternal(“Local file operation failed”) when another itda process, the browser worker or the MCP server, closed its connection at that moment: SQLite removeditda.db-walwhile itda was making it private. A file that is gone is now left alone.
0.6.0 - 2026-10-23
Files in and out, a check after a lost answer, sessions kept in browser
storage, a gate that reads the request it is about to send, and twelve
languages. A change can name the read that shows it landed, so after a lost
answer itda checks once before anyone is asked to approve again. Reads can save
an attachment, and a change can send a file whose hash you approved. A
single-page app that keeps its session in localStorage now signs in. Setup,
the landing page and the READMEs speak twelve languages, and the mark is a
cursor striking a site.
Added
-
Twelve languages. What
itda setup,itda login,itda refreshand the first bareitdaprint now comes in English, Korean, Japanese, Simplified and Traditional Chinese, Spanish, French, German, Brazilian Portuguese, Vietnamese, Indonesian and Russian, chosen fromITDA_LANGor the systemLANG(ja_JP.UTF-8,zh-TW,ptall resolve). The landing page picks the browser’s language, has a picker in the header and remembers the choice; the README has one translation per language, linked from a language line at the top of each. What agents act on (tool descriptions, error codes and remedies) stays English. The strings live inapps/cli/text/<tag>.json,site/i18n/<tag>.jsandREADME.<tag>.md;docs/i18n.mdhas the glossary and the rules. -
Outcome check after a lost answer: a change may name the read that shows it landed (
confirm: {read, match: {pointer, from}}in the definition). When the change is sent and its answer is lost (action_outcome_unknown, exit 22), itda runs that read once, as a read, and reportslanded,not_foundorunknownnext to the error, asoutcome_checkin the CLI output,--json, the MCP result and the audit record (outcome_check,confirm_read). The change is never resent. Afterlanded, a new approval request for the same inputs carries anotein the review andprevious_outcome: landedin the answer, so the person can decline.itda auditlists lost answers with what the read said;itda_manage auditreturns them underunknown_outcomewithcheck.itda lsanditda doctorreport aconfirmthat names a missing operation, a change or a read that needs inputs as a definition problem.itda setupanditda draftfillconfirmwhen aPOST’s list read is on the same path prefix and a string input of the change appears in the read’s records; the evidence says so. The lab getslossy(8089), the scripted portal whose post is saved and then the line drops, and every reviewed lab definition names its confirming read. -
Files out: a read may declare
response: {format: file, media_types: [...], max_bytes: N, allow_empty: false}. itda streams the answer to a file underfiles/<site>/in the data directory (oritda call ... --out <dir>), seals it at the end, and returns a manifest{path, bytes, media_type, sha256}, never the bytes; over MCP the result adds aresource_linkto the file and the tool’s description says what comes back. The content type must be one ofmedia_types(an HTML answer only as an attachment), and a 206 orContent-Range, a body shorter than itsContent-Length, an empty body or one over the cap fail by name (wrong_media_type,partial_answer,empty_answer,too_large) and leave nothing on disk. File names come from the site’sContent-Dispositionor the address, reduced to plain characters, and never replace a file already there.itda checkskips file reads. A transfer longer than the gate’s turn renews it (Lease::renew) every 30 seconds; a lost turn stops the transfer (request_lease_lost). -
Files in: a change may declare
body: {multipart: {fields: {...}, file: {name, from}}}. The agent gives the path of a file on this computer in thefrominput; the approval shows the file’s name, size and SHA-256 and is bound to that hash, so other bytes under the same path withdraw it (approval_expired,reason: file_changed). The file is streamed from one open handle in 64 KiB chunks withContent-Lengthset, its size and modification time checked before every chunk and its hash at the end; a file that changes mid-send aborts the send (action_outcome_unknown,reason: file_changed). The audit log records the hash. A body with a file is always a change and goes only to the site’s own origin. -
Drafts: a page form with
enctype=multipart/form-dataand a file control becomes such a change (Upload a file to <page>), and links the page labels as attachments (adownloadattribute, download or attachment words in English or Korean, or an icon named for one) become file reads grouped by shape (download-filewith afile_idinput), withmedia_typesfrom the kinds the links name or the usual document types.itda draft haranditda recorddraft a recorded multipart post from its part names and a recordedContent-Disposition: attachmentanswer as a file read.itda describesays what a file read returns and where it lands, and which input a change sends as a file. -
Storage sessions: a site that keeps no session cookie and instead holds a token in the browser’s
localStorage(orsessionStorage) and sends it asAuthorization: Beareror a custom header now signs in and works. When you set the site up or record it, itda reads that one origin’s storage once, over the DevTools protocol, from the window you signed in to, and recognises the token as the session when the recorded calls actually sent it as a header: a JWT shape, or a long opaque value under a key named liketoken,access_token,id_token,auth,sessionorjwt. A stored value no request sent is reported and never kept. It is drafted as akind: headerauth entry withvalue_from: storage:<key>; the token lives in the keyring, never in the definition, anditda login <site> --browserreads it again at the next sign-in. If the token is a JWT withexp,itda checkreportssigned_out(notsite_changed) once it has passed, anditda_sitesshows when the sign-in expires; itda never signs in again on its own. The token is redacted from results, errors and the audit log like a cookie.value_fromon aheaderentry now acceptsstorage:<key>besidestore:<site>/<key>, and on abrowserentryquery:<key>besidecookie:<name>, with a validation error for anything else. itda reads only the signed-in origin’s storage, never another site’s, never a profile on disk, and never writes anything into a page; seedocs/THREAT_MODEL.md. The definition format is unchanged:spec_versionstays 1. -
The API description a page points at is also found in the first page’s HTTP
Linkheader (RFC 8288) and under two more relations:api-catalog(RFC 9727) anddescribedby, in the header or on<link>/<a>. Learning where costs no request.service-descwins, thenapi-catalog, thendescribedby. An API catalog is a linkset (RFC 9264): itda follows itsservice-desclinks, up to three, and takes the first OpenAPI document. Adescribedbydocument counts only when it is one; otherwise/openapi.jsonis still tried.relis matched as a list of relation types, case-insensitively, sorel="not-service-desc"no longer counts. Pagination’sheader:linkreads the header with the same parser, so a target with a comma in it orrel="Next"is read right. -
itda spec validate <file> [--json]checks a definition the wayitda addwould (Spec::validateand the policy checks), without installing it. A valid file exits 0; otherwise every problem is printed asproblem: <field>: <detail>, the way a draft prints its own, and it exits 2, the definition-problem code.--jsongives{status, file, site, actions, issues}.
Changed
- The mark is a cursor striking a site; the hero shows it. The tile is a
white cursor at the bottom left, a browser window at the top right and a
blue bolt inside the window where the cursor’s tip touches it. The favicon
is a heavier cut of the same tile that reads at 16 px. The landing’s hero
adds the cursor from the mark: it flies in, strikes the terminal’s prompt,
the transcript appears, it strikes the About window, and it holds; with
prefers-reduced-motionit rests at the prompt.assets/logo.svgis the one master;assets/src/mark.pystamps it into the wordmarks and the hero,assets/src/flow.pyreads it, andassets/social.htmlrenders the link preview from it. The README banners (assets/hero.png,assets/hero.ko.png) are rendered again and carry it in the wordmark. - The gate reads the request it is about to send, not only the declared
inputs. After the template is rendered, every query key and body field,
the value of an intent field such as
actionormode, and any GraphQL document inquery(orq/documenton a GraphQL path) are read again. A declared read whose rendered request asks for a change (ajson_templatewhose literal body is a mutation,{"op": "delete"}deep inside it) is treated as a change:approval_requiredcarriesreclassifiedwith the reason, the review shows it, and the audit record sayseffect: writewith the samereclassifiedreason. A read whose effect cannot be told (a GraphQL endpoint sent no document, a document itda cannot read) stops witheffect_unknown(exit 2) and a remedy; nothing is sent. A declared write is unchanged, and so are the 101 reads in the bundled definitions. itda describeshows the read a change names inconfirm, one line under the change in the person’s language (after a lost answer: checks list-notices for the new title);--jsonaddsconfirm: {read, match}. The change’s MCP tool description says the same in English, anditda_describein catalog mode now returns the same description as the tool, the approval note included.- Action descriptions are drafted in the person’s language, any of the
twelve, not only in Korean or English: a Japanese setup now writes
Hacker News の一覧を読む, notList what Hacker News shows. The language isITDA_LANGorLANG, else the page’s<html lang>, else English. The phrases are one table incrates/prep/src/label.rswith the same placeholders in every language (a test checks it); the site’s own words (titles, field and operation names) stay as the site wrote them, and outside Korean so does the kind of record (news articles). Agents read all twelve; tool names, error codes and remedies stay English.
Fixed
- A site whose only reads save files ended
itda setupas if nothing worked: setup skipped every file read (“saves a file”), so no read had answered and the definition was thrown away. Setup now tries a file read once, through the gate, by the answer’s headers and first bytes: the status, media type and size the definition allows count it as working, the rest of the answer is dropped and nothing is saved. A read answered with a page instead of the file is left out (wrong_media_type).itda checkstill skips file reads. - A same-origin redirect or a next-page address of the form
/graphql?query=mutation{...}passed the read check, because only the path and the query keys were read. The check now parses a GraphQL document inquery(andq/documenton a GraphQL path) for its operation type; a mutation or subscription is not followed or turned to. X-Original-URLandX-Rewrite-URL, which ask a server to treat the request as one to another address, are refused like the method override headers.- The SQLite database was created with the umask; only its directory was
private. The file and its
-wal/-shmcompanions are now 0600 on Unix (files left by an earlier run are tightened when the store opens); on Windows the directory’s ACL covers them.
0.5.0 - 2026-10-23
Uglier sites, fewer steps, and a bundle for Claude Desktop. itda setup now
reads pages whose content sits in frames, signs in with a password without a
browser, follows a session carried in the address, turns pages, recognises
known software, and refreshes a definition when the site changed. The landing
page says plainly what works and what does not. Every release now carries an
MCP Bundle per platform, so Claude Desktop installs itda by opening a file.
Added
-
Frames: when
itda draft pageoritda setupfetches a page that is a shell whose content lives in one or more<iframe src>(or an older frameset) on the same origin, and the page has nothing of its own to read, itda fetches each frame page as itself, on the same pacing and with the same session, and drafts from the frame pages as pages of the site, each under the frame’s own address. At most four frames are followed, at most two deep; a cross-origin frame, and a same-origin frame that answered with nothing, are listed in the notes and never fetched. The setup lines and the draft say what was followed (“Followed 1 frame: /frame/board.”).itda refreshfollows them again. -
Query sessions: a site that carries its session in the URL as a query key (
?sid=...) rather than a cookie now gets akind: browserauth entry withvalue_from: query:<key>.itda draft har,itda recordanditda setuprecognise it when a sign-in’s answer hands the key out (in the redirectLocationor on the first signed-in page) and every signed-in call carries it: every operation that sent it getsauth: session, the key is dropped from the operations’ inputs, and the value goes to the keyring, never into the definition.itda login <site>captures it from the sign-in the same way it captures a cookie. At run time the value is appended to the query of each request; it is redacted in results, error text, the audit log and the recording kept for a refresh (addresses,Locationheaders and the links in recorded pages).value_fromon abrowserentry now acceptsquery:<key>besidecookie:<name>, with a validation error for anything else. Because the value rides in URLs,docs/THREAT_MODEL.mdnotes it can reach server and proxy logs. The definition format is unchanged:spec_versionstays 1. -
cargo xtask check-changelog, run by CI: the changelog has one of each section per release, the newest release matches the workspace version, and the landing page and quickstart print the same version. CONTRIBUTING.md says what a minor and a patch version mean before 1.0. -
Sign in with a password, without a browser: a
loginblock on abrowsercredential names the site’s sign-in page and its user and password fields, anditda login <site> --user mereads the page as itda, fills the form in with the password you type (or pipe on stdin), sends it once with the hidden fields and cookies the page gave, and keeps the session the way a browser sign-in is kept. A redirect means the site accepted it; the form coming back is reported, never retried.itda setup <page> --user medoes the same when the page needs a sign-in (a redirect to a sign-in page, 401 or 403, or a “log in to see this” page that links to one), drafts from the page as you see it signed in, gives every actionauth: sessionand writes theloginblock from the form it found. Over MCP,itda_setupnever takes this path.--secretonitda loginnow names the API key path explicitly (api-keystays the name when the site has no sign-in form). -
examples/lab: Gitea, Redmine, WordPress, MediaWiki and a small legacy intranet (intranet/server.py, standard library only) in Docker Compose, pinned by digest, on fixed local ports, with one seeded account and a little content each, plus reviewed definitions inspecs/. WithITDA_LAB=1,apps/cli/tests/lab.rswalks each one fromitda setup --userthroughitda login, a read, a change approved in a terminal and the audit log. CI never runs it. -
examples/lab: three uglier intranets alongside the five, to measure what itda does on pages built the harder ways (intranet/framed.py,scripted.py,urltoken.py, standard library only).framed(8086) keeps the notices table two iframes deep, so setup from the home page finds only the shell; pointed at the frame it signs in and reads and writes the board, andspecs/framed.yamlruns to an approved change.scripted(8087) draws the table fromGET /api/noticesin a script, soitda setup --watch-onlywatches the browser load it and drafts the read, andspecs/scripted.yamlposts with theX-CSRF-Tokenfrom<meta name="csrf-token">.urltoken(8088) carries its session in?sid=with no cookie; with frame following and query sessions in this release, setup signs in, reads thesidfrom the sign-in redirect and keeps it as avalue_from: query:sidsession, andspecs/urltoken.yamlruns to an approved change.README.mdrecords the measured outcome per case; the one itda cannot do yet - signing in from a shell whose frame redirects to a login, soframedfrom the home page still finds only the shell - is a test marked#[ignore]with the reason. -
Sites that run known software are recognised.
itda setupreads the page’s HTML for GitLab, Jira, Confluence, Redmine, Discourse, MediaWiki, WordPress and Gitea or Forgejo (ageneratormeta, well-known asset paths, the Atlassian context-path metas, body classes), confirms the guess with exactly one GET to that software’s own version or info endpoint, and then drafts from a template undersites/software/instead of reading the page like any other: the reads an agent needs most, up to three changes marked as such, models and the software’s own way of signing in, every operation with its documentation inevidence. The template’sbase_urlis filled with the instance’s API base, context path included (/jira,/w, a GitLab relative URL root, a GiteaappSubUrl, the WordPress API root the page links to); the site is named after the host; one line says so (“This looks like GitLab. Using its API at.”). The page draft still runs and its lists, forms and feeds join the template, the same method and path drafted once, and a page that needs a sign-in or scripts still gets the template when the software is confirmed. A fingerprint the API does not confirm is dropped quietly. itda setup --as <software>forces a template,--no-templateskips them,itda checkworks on the result as on any site, anditda_setupover MCP takestemplateand names the template it used in its result. The site’ssources.jsonnames the template and its API base, soitda refreshfills the same template again, with the page read again when it still opens. -
itda refresh <site>: whenitda checksayssite_changed, draft the site again from the sources it was set up from and merge the result with the installed definition. The site’s name, title and address,defaults,authand every action edited by hand are kept; actions the draft changed take the new version, ones it no longer drafts are dropped only when their read also failed, and new ones are tried once and added when they answer. The diff is printed in plain words (added, removed, changed, kept as edited) and written after a yes, or at once with-y; the site is checked right after and the verdict printed. Approvals for the site are withdrawn, as for any replaced definition: what the user reviewed is no longer what would be sent. To make that possible,itda setup,itda draft ... --installanditda record --installnow writespecs/<site>/sources.jsonnext to the definition: the pages, the API description, the GraphQL endpoints asked, the recording when one was kept (copied under the site asrecording.harwith its cookie, token and session values, private fields and sign-in answers blanked), the draft’s options and a hash of every operation as drafted.itda checknow points atitda refresh <site>when a site changed, anditda check --allprints one verdict line per site. -
itda_manageover MCP gainsrefresh: the person confirms the diff in a form (a form nobody answers counts as no), and the site’s tools are reloaded and checked.itda_checkwithout a site already checks every site; the docs now say so. -
itda draft pageanditda setupread a page that is one thing (an article, a package, a product, a bug) as its own content: the headline, the body text, what sits beside the headline, a date, an author and the values the page labels in definition lists and label-value rows, ashtml.pagefields that each match once. Such a page used to be one read that checked the title. A page that is one thing with a named list of records on it (a package with its releases) gets both reads, anditda setupsays “the page’s own content” when it finds one. -
Repeated siblings of one shape become records when they share a title at one place: a link as before, or a heading, a text named as a title, or the record itself when it is a link with something inside. The fields come from what the records share: a summary, a byline, a date, and whatever sits under a class the site named (
score,author,release__versionbecomesversion), with build names left out and a text that reads as a date aspublished_at. Bare paragraphs, bare links and menus never qualify. A list read is described by what its records are (List the projects on Projects) when the page names them. -
A list whose links share one shape (
/item/{id},view.php?id=) gets a read of one record by itsid, with the query keys every link sends as inputs with defaults and no response check yet. -
The
pageinput follows the link markedrel="next"and starts at 0 foroffsetandstart. -
itda setupwatches what a page drawn by scripts loads by itself before asking anyone to record. When the page shows itda nothing but a title, carries scripts that could fetch something (a script file or a preloaded module, or an inline script that imports a module or mentionsfetch,XMLHttpRequest,graphqlorapi) and a browser is installed, it opens the browser on the page in the same fresh profile recording uses, waits until the network has been quiet for two seconds (ten at most), scrolls once to the bottom, waits again, closes the window and drafts from the recording the wayitda draft hardoes, the page’s own address first so what the page itself shows is never lost to a busier API elsewhere. One line says what it saw (Watched the page load: 12 calls, 3 reads drafted., in English or Korean, counting the reads beyond a title check); only when no such read came out does it ask to record as before. Nothing in the window is clicked or typed, nothing it signed in with is kept, and a page whose scripts go to a sign-in page stops with the sameauth_requiredwords a redirect gets.--no-recordskips the window too; a new--watch-onlywatches but never asks to record, for scripts. A site whose software is recognised gets its template instead and is not watched.itda_setupover MCP does the same, and says in its description and its result that a window opens briefly on the user’s screen. -
Reads that list things can turn pages. A definition declares
pagination(style: page|offset|cursor, where the next marker is in the answer as a JSON pointer orheader:link, an optionalmorepointer, and thequeryorbodyinput it goes back in). The answer then carriesmeta.next, an opaque handle such asic_…;itda call ... --next <handle>andnextover MCP (itda_calland the per-action tools, whose schemas list it) read the following page with the same inputs. The site’s own marker stays in the local database for fifteen minutes, bound to the site, action, account, inputs, definition and credential, and is removed from the data; acursorinput is never shown to agents. A handle for another call, or an expired one, is refused withpagination_invalidorpagination_expired(exit 23). Only reads turn pages.itda draft openapi,itda draft har,itda draft pageand the GraphQL schema drafts declare it when the evidence is plain, never by guessing; the bundledgithub(issues, comments, repository search),npm(search) andcrates-io(search) definitions declare it.AGENTS.md, the skill and the server instructions say to read only as many pages as the question needs. -
A preflight step can read a value from the JSON a page embeds for its scripts:
extract: {build_id: "ssr:__NEXT_DATA__#/buildId"}, so a token or build id that only lives there reaches the action’s path or inputs. Only a string, number or boolean counts. -
AGENTS.mdandskills/itda/SKILL.mdtell coding agents how to use itda: discover actions, run reads, treat website text as data, wait foritda approve, never retry a lost change, runitda checkfirst when something stops working. The MCP server’s instructions say the same, shorter. -
itda feedbackprints a link to a new GitHub issue with the version, the platform and the doctor checks filled in (never the data directory path) and opens it;--no-openonly prints. It sends nothing itself. -
itda checkends each site with a verdict and what to do:ok,signed_out,site_changed,blockedorunreachable. The exit code follows the worst verdict, and--jsoncarries it asverdictand per site undersites.
Changed
-
itda draft pageanditda setupon a page that refuses itda say what the gate saw instead oferror[internal]: <url> answered HTTP 403: the host is behind Cloudflare, runs a bot manager or asks for a captcha (blocked_by_site, exit 12), or limits how often it is read, with the wait it asked for when it named one (site_rate_limited, exit 13), in English or Korean. A captcha served with HTTP 200 is reported the same way, and a page that sends itda on to a sign-in page says so (auth_required, exit 10) and points at recording. -
A
Retry-After: 0, or a date already past, no longer counts as a rate limit: a site that sends it with every page is read as itself. -
The landing page on phones: lighter glass, the setup dialog left out of the hero stack, full-width buttons, tighter rhythm, terminal lines wrapped on anything narrower than the two-column layout, and the sky drawn smaller with the cursor effect only while a finger is down.
-
The landing page and both READMEs carry a prompt to hand to an agent: paste it into Claude Code, Cursor or Codex with a site’s address and the agent installs itda, runs
itda setup <address> --client <app> -yand starts using the site. -
The landing page has a living sky behind the whole page: liquid chrome with striped, colour-split reflections and a holographic sheen, a wire grid running to the horizon, and a field of points and threads that gather around the cursor, with a website on the left and an agent on the right (
site/sky.js, three.js from cdnjs). It follows the scroll, draws one still frame under reduced motion, and leaves the plain glow without WebGL. -
The hero lost its badge and chips; the binary, platform and licence line moved to the footer.
-
The landing page is laid out as a desktop. The hero stacks windows that can be dragged: the first run, the actions it found, the setup progress, an about box and a clock. Sections open with a numbered eyebrow and a centred headline, every box is a window with a title bar and a dither texture, and the risk comparison is two windows side by side, the browser-driving agent’s lane next to itda’s.
-
The sky is now a living network: nodes spring home through a slow current, threads join neighbours, packets of light run node to node and along the handoff between the website and agent hubs. The cursor is a node too: nearby nodes gather and orbit it, its motion leaves a wake, threads run from it, and a click sends a ring through the field. Node count follows the viewport, with a lower cap on touch screens.
-
The landing page’s components now speak the same language as the sky: smoked-glass panels with a chrome hairline and faint scanlines, chrome headlines with a holographic accent, glossy pill buttons, Aqua-style window lights on the terminal, and chrome coins for the step numbers.
-
itda <address>sets that site up, anditda github(or npm, crates-io, pypi) adds the ready-made site and connects the agent apps found on this computer; the README’s one line is the one line you type. The tagline and the landing page say so. -
action_outcome_unknownnow says that itda never resends a change on its own, and the runtime tests prove it against a server that drops the connection after taking the request. -
A bare
itdain a terminal says what it is and asks for one address, then runsitda setupon it, so the first run is: typeitda, paste, Enter. -
itda setupfinds the agent apps on this computer (Claude Desktop, Claude Code, Cursor, Codex, or a project with a.vscodefolder) and connects them all after one Enter; the numbered list only appears when none is found. -
itda setupasks whether the site needs a sign-in only when the page it fetched has a password form; a page without one is used as it is. -
The installer ends with “Open a new terminal window and type: itda”.
-
At the first prompt, a ready-made site’s name (
github,npm,crates-io,pypi) installs that site and connects the agent apps found on this computer. -
After adding itda to Claude Desktop on macOS, itda offers to restart the app when it is open, or says that opening it is enough; for Claude Code it runs
claude mcp additself whenclaudeis on the PATH. -
itda doctorlists the agent apps whose settings already start itda. -
itda setupon a page ofpypi.org,github.com,npmjs.com(with or withoutwww.) orcrates.iouses the ready-made definitionitda addinstalls for it, says “This is PyPI. Using its JSON API.” (or the site’s name and API), does not fetch the page and tries the definition’s reads;--no-templatereads the page instead.itda_setupover MCP does the same and puts the line in its notes.
Removed
- The
browser-fetchandbrowsertransports and thederivedcredential kind, left over from the port: no code ran them, and a definition that named them was accepted and then refused at run time.itda addnow refuses them with the field named.
Fixed
- Signing in from a shell whose frame redirects to a login. When a page is a
shell and its same-origin frame answers with a redirect to a sign-in page,
itda setupnow treats it as if the top-level page had: with--userit signs in through that form and reads the frame again with the session, then drafts from it, instead of drafting only the shell’s title. Following the frame’s redirect to read the login form is a read; nothing is posted without the person’s credentials and the usual consent. Without--user, setup names the sign-in the frame leads to, anditda draft pagenotes that the frame leads to a sign-in rather than only that it was not read. - Approving in the client works on MCP protocol 2026-07-28, which Claude
Code 2.1.289 negotiates: that revision took server-to-client requests
away, so the
elicitation/createrequest itda sent was dropped unseen and the call hung until itda gave up. itda now hands the form back as the call’s result (resultType: input_required), the client shows it, and calls again with the answer and the approval id asrequestState; the id only ever names an approval itda stored for this very call, and anything else is asked again. The confirmationsitda_manageanditda_loginput to the person go the same way. Clients on the earlier handshake keep the request they had. The review the form shows no longer starts with a blank line, and leaves “as account” out when the site has no accounts: Claude Code cut a five-line review to its first three and hid the request and the inputs behind “(+2 more lines)”, and shows the four-line one whole. - A change answered with a redirect (post, then redirect, then get, as
Gitea, Redmine and most server-rendered sites do) is reported as landed,
with the address the site pointed at, instead of
request_failed. A redirect to a sign-in page is reported asauth_required, since the site is asking for a session it no longer accepts. - The cookies a preflight step’s answer sets go along with the change, so a Rails site (Redmine) that rewrites its session cookie on every answer accepts the token it just handed out.
- A read redirected to an address itda will not follow, such as a sign-in
page with a
reauthparameter (WordPress), is answered as the redirect it is instead of failing withbad_url, soitda setupcan name the sign-in page. - Two itda processes starting on a fresh data directory at once (the MCP
server and
itda approve, say) no longer race in the database setup:Store::migratereads the schema version and applies the steps in one immediate transaction. - A GraphQL document nested deeper than 32 levels is refused before it is
parsed, with a clear message in a definition’s default and
bad_requestfor an agent’s input, instead of running the parser out of stack. api_denied(exit 14): a 403 that is not a sign-in problem says the site refused that one request as not allowed, withupstream_statusand, when the site’s JSON answer names one of a short list of reasons,provider_reason; nothing else from the answer is passed on. A 403 whose reason says the session was rejected staysauth_required. Every refusal now carriesupstream_status, anditda checkcounts an address answered with 404, 405 or 410 and a query answered with errors (query_failed, exit 11) assite_changed.approval_pending(exit 20): a call with an approval the user has not answered yet is told to wait and call again with the same id, with the command to run incommand; the approval stays. A call with other inputs withdraws the approval (approval_expired,reason: inputs_changed).itda rejectalso withdraws a granted approval, anditda logout,itda remove, a newitda loginand replacing a definition withdraw every approval for the site or account. The reviewitda approveprints, and the form an MCP client shows, name the action, say what it does in the definition’s words, and cut a value longer than 500 characters with its length.- A read is classified again after its preflight steps with the found values
filled in; one that would now be a change stops with
effect_changed(exit 2) and is not sent. Headers and inputs that override the method (X-HTTP-Method-Overrideand the like) are refused at install and before sending. A GraphQL answer that carrieserrorsbehind a 2xx isaction_outcome_unknownfor a change andquery_failedfor a read, never a success. itda auditanditda doctorshow achange_startedrecord with no outcome after it as a change with an unknown outcome (the process stopped after it was sent), anditda_manage auditreturns them underunknown_outcome. A change that failed before anything left the machine (no turn at the gate, a name that did not resolve, a refused connection) keeps its network code and is reported as not sent instead of unknown.- Long secrets are sealed into
vault.sealedunder the data directory (AES-256-GCM, a fresh nonce per write) with one 32-byte master key per data directory kept in the system keyring; every browser session goes there, and so does anything over 1024 bytes or that the keyring refuses as too long (Windows Credential Manager caps a credential atCRED_MAX_CREDENTIAL_BLOB_SIZE, 2560 bytes). API keys stay in the keyring. Existing entries keep working and move on their next write, in both directions;itda doctorsays where secrets are. The cipher comes fromaws-lc-rs, already built throughrustls. itda describe <site>[.<action>]prints each action’s inputs and a command to copy, with a placeholder for every input it cannot run without;itda ls --jsoncarries the same command asexample. An unknown site, action or input names the nearest one (“Did you mean”), in the CLI and over MCP.itda mcp --site <name>serves one site only: its actions are the only tools,itda_setupis refused, and the other management tools stay inside it.- Two more ways to install, from the next release on. Every release carries
one MCP Bundle per platform (
itda-<target>.mcpb): open it and Claude Desktop installs itda with its tools, no terminal needed; approving a change still happens in a terminal withitda approve. The bundle is packed bycargo xtask mcpbfrom the release archives, with a manifest that follows the MCPB spec (manifest_version0.3,server.typebinary,itda mcpas the command). A winget manifest for 0.4.0 (manifests/b/brnyxx/itda/) is ready to submit to winget-pkgs and is not submitted yet. - Five MCP tools so an agent never needs a terminal to set a site up or
look after it:
itda_setup(look at a page as itda, draft, try every read once, keep what works; the site’s actions become tools in the same session, announced withnotifications/tools/list_changed, which the server now declares withtools.listChanged),itda_login(the person signs in in their own browser, after a form in clients that show one; the definition must name the sign-in cookie),itda_check(the verdict per site, asitda check --json),itda_sites(what is set up, with the last check’s time and verdict, now kept inspecs/<site>/check.json) anditda_manage(removeandresumeafter the person confirms in a form,approvals,audit).itda_setupnever asks anyone to record and never keeps a credential; pages that need a sign-in, or still show nothing after the watched load above, are refused with the command to run instead. The server instructions,AGENTS.mdand the skill say when to call them. - A new
itda-setupcrate holds the steps behinditda setup,itda login --browser,itda checkand the installed sites, with the questions answered through a trait and progress reported through a callback, so the command line and the MCP server run the same code. The CLI’s words and questions are unchanged. itda draft pageanditda setupdraft a typed read for each list they find in a page’s embedded data, instead of one read that returns the whole payload: anItemListor a list of one typed thing (NewsArticle,Product…) inld+json, and in__NEXT_DATA__,__NEXT_F__,__APOLLO_STATE__,__PRELOADED_STATE__and the like the list of objects with the most to read. Each read is named after what it holds (list-news-articles), pointsextract_pointerat the list and keeps up to eight plain fields of every record through amodel, ids and titles first and credentials left out.itda checkcounts its records. The whole payload is drafted only when no list is in it.- An Apollo cache (
__APOLLO_STATE__) is also grouped by typename, so a definition can point at/Questionor/Product. defaults.intervalin a site definition asks for more time between requests to that site (2s,500ms, up to 30 s). itda’s own pace, one request at a time per site about 1.2 seconds apart, is the floor; a definition can only slow it down. A value that is not a time is refused byitda add.- A site that answers a read with 429 or 503 and a
Retry-Afterof ten seconds or less gets that wait, once, and the read is tried again. A longer wait stops the read withsite_rate_limited,itda checkreports the site asblocked, and the error, the check result (retry_after_s) and the MCP server instructions say how long the site asked for. A change is never sent twice. - Requests queued in one process now take turns in order instead of polling the shared gate against each other.
- A draft that sees a GraphQL endpoint (a recorded call to it, or a page
naming a same-origin
/graphqlpath) asks it once, with the standard introspection query, what it offers, and drafts one read per rootQueryfield whose arguments are scalars, enums or ids: the field name is the action, the arguments are typed inputs, and the selection is the field’s scalars two levels deep, 24 at most. An optional argument that holds an object or a list is left out and listed; a field that requires one is listed instead.Mutationfields are listed in the notes and never drafted. A site with introspection off is drafted from its pages and calls alone. The query is sent as itda, read-only, with a size cap, byitda draft page,itda draft har,itda recordanditda setup. - An optional input the agent leaves out now leaves the
json_templatebody too, instead of standing in as an empty string where a number or null belongs; GraphQL variables typedIntorBooleanneed that. itda draft pageanditda setupread the API description a site publishes: the one the page links to, else/openapi.jsonon the same origin when it answers with one (one GET, as itda, at most 4 MB). Its reads join the draft the wayitda draft openapimakes them, drafted once when the page already had the same path; its changes become notes, and a read keyed by a credential input is left out and listed. A description on another address is noted, not merged, and one that does not parse leaves the page draft as it is. The header comment and each merged action’sevidencename where it came from, anditda setupsays what it read in one line.- Requests send
Accept-Language: *(no preference) instead ofko-KR,ko;q=0.9,en;q=0.8, a value left over from the browser header block itda was derived from. pypi.org answered a project page asked for with any KoreanAccept-Language(ko,ko-KRor that list) with a 3 KB “Client Challenge” page and kept challenging every request from the same address for a few minutes after, soitda draft page https://pypi.org/project/requests/drafted the challenge instead of the 322 KB page. Leaving the header out is not enough: amazon.com then sends a gzip body itda does not decode. A definition that needs a language can still set one indefaults.headers. - A challenge page served with HTTP 200 (small, titled as a challenge, only
a script and no link to follow, as bugzilla.mozilla.org’s bug list and
pypi.org answer when they challenge) now counts as a block: the site is
paused,
itda draft pageanditda setupsay the site’s bot protection refused itda (blocked_by_site, exit 12), anditda checksaysblocked. Before, it was drafted as a read named after the challenge and checked asok. - Drafts leave ad slots out of a page’s records (an element whose class, id
or label marks it as an ad:
AdHolder,ad-*,sponsored,aria-label="Sponsored"): when ads share their classes with the records beside them, the records’ selector excludes the ad class. On amazon.com the draft had picked the sponsored results, which are not there when the first page is asked for by number. - The page input a draft adds when a page links to its next page (
page,offset,startand the like) has no default any more, so the read anditda checkask for the address the draft saw until an agent or a page handle asks for another page; the next page is still counted from 1, or from 0 for an offset. Sendingpage=1made some sites answer with different markup and the check fail. - A read the site blocks or challenges while
itda setuptries it is left out with “: the site does not give itda this page”, nothing more is sent to the site in that run (the reads after it are kept and shown as not tried, for the next itda check), and the site is not left paused, so a challenge on pypi.org’s search page no longer takes the three feeds after it down too.itda checkand calls on an installed site still pause it.
0.4.0 - 2026-10-22
One address is enough: itda setup looks at a page, or watches you use the
site in your own browser, names what agents will be able to do in plain words,
tries every read, and connects an agent. Any website, not only the ones with an
API description.
Added
itda setup <url>sets a site up in one go: it looks at the page, or opens your browser and records when the page refuses, needs a sign-in or is drawn by scripts; shows what agents will be able to do in plain words; tries every read once and keeps the ones that work; saves the site; and connects an agent. It asks before each step;--record/--no-record,--clientand--yesanswer for scripts.ITDA_LANG=ko(or a KoreanLANG) makes it, and the drafted descriptions, speak Korean.- Drafted actions carry a description a person would say (
List what Notices shows,공지사항 목록 읽기), taken from the page’s title or the call’s name, in English or Korean. itda record <url>opens your own browser in a fresh window and records what the pages ask the server for while you use the site, then drafts a definition the same wayitda draft hardoes. The recording stays in memory;--savekeeps it as a HAR file. itda never clicks, types or navigates in the window.ITDA_DEBUG_CDP=1prints the browser messages.itda record --installoffers to keep the token and the browser session the recording signed in with, each after a yes in the terminal, so a site that sends a bearer token from its scripts works without pasting it.- A recorded GraphQL call becomes one action per operation (
exploreProjectsisexplore-projects): queries are reads, mutations are changes, and the variables are the typed inputs, rendered through ajson_template. itda recordanditda draft haralso draft the pages the recording holds, asitda draft pagedoes, so a site that renders its lists and forms on the server gets actions even when its pages call no API.itda draft page <url>...drafts a site definition from the pages you use, fetched with the honest client: a table or a list of records becomeshtmlfields, data a page embeds for its scripts becomesssr_json, a search form becomes a read, a post form becomes a change with its token read through a preflight step, page links become apageinput, and RSS or Atom feeds come along. Every read gets a response check, soitda checknotices when a page changes. Lists are found by their shape, not only by known class names: any run of three or more siblings outside navigation that each hold a link at the same place, with the title and link read from that link’s own path. When a page embeds its data for its scripts, the draft pointsextract_pointerat the list inside the payload and checks the keys every record shares.itda draft haralso handles a token sent as a header, such asX-CSRF-Token: a preflight step reads it from the page’s<meta name="csrf-token">tag before each call.itda addalso takes an https URL, or the name of a ready-made site (itda add github); the four definitions insites/are built in.
Changed
itda setupnames the agent app it connects (“Adding itda to Claude Desktop…”), shows the settings file it wrote with~for your home folder, says so in Korean too, lines the action names up only as wide as they need, and writes its example request mid-sentence.- The README and the landing page open with a recording of
itda setupas it ran on Hacker News (assets/setup.gif, Korean inassets/setup.ko.gif, rebuilt fromassets/src/setup.*.tapewith vhs). - A new key visual, a hand passing a website card to an agent
(
assets/handoff.png), on the README banners and the link preview (assets/hero.png,assets/hero.ko.png,assets/social.png, rendered fromassets/src/banner.html), and the docs introduction says the same thing the README does.
0.3.0 - 2026-10-20
Install ready-made sites with one command, and work with old intranet forms that need a fresh security token on every change.
Added
- Preflight steps: an action can read a page just before it runs and pass
a fresh value, such as a CSRF token, into its headers, query or form as
{{step.<step>.<name>}}. Agents never see or set those values. - The sandbox has a write form with a one-time CSRF token
(
sandbox.write-notice) to try preflight steps on. itda draft harturns a form token such as_csrfinto a preflight step that reads it from the form page recorded before the post.itda add <file>installs a site definition (or-for stdin) and shows where it sends requests, which actions change the site and how it signs in;itda remove <site>takes it away.- Ready-made site definitions in
sites/: GitHub (public reads without an account, issues and comments with a token), npm, crates.io and PyPI.
Changed
- A
modelnow applies to each record when the answer is a JSON list, instead of returning an empty object.
0.2.0 - 2026-10-20
Sign in through your own browser, approve changes inside the agent app, and check that sites still answer the way their definitions say.
Added
itda accountslists signed-in accounts and the names of their secrets;itda logout <site>removes an account and its saved secrets.itda check [<site>]runs every read that needs no input and reports which ones no longer match the site, with exit code 11 when a site changed.- Browser sign-in for sites without API keys:
itda login <site> --browseropens your own browser in a fresh profile, keeps only that site’s session cookies in the system keyring, andkind: browseractions send them only to the same site. - Approve changes inside the MCP client: with
itda mcp --approve-in-client(oritda mcp install <client> --approve-in-client), clients that show elicitation forms ask you to approve the exact request, and declining returnsapproval_rejected.
0.1.0 - 2026-10-19
The first release: reads and approved changes on plain HTTP sites with API keys, from the command line or over MCP, and definitions drafted from OpenAPI documents or browser recordings.
Added
itdacommand:ls,call,login,resume,doctor, with a stable exit code contract for scripts and agents (2input,10sign in,11page changed,12blocked,13rate limited,20–22approvals and changes,30network).- MCP server (
itda mcp): one tool per action up to 30 actions, three stable catalog tools (itda_search,itda_describe,itda_call) above that. Results are marked untrusted and cut to 24 KB. itda draft openapi: draft a site definition from an OpenAPI 3 or Swagger 2.0 document, with response checks, header auth and a list of what could not be translated.--installputs it in the data directory.itda draft har: draft a site definition from a browser recording of you using the site. Only the shape of each call is kept; recorded tokens, cookies and values never reach the draft.itda mcp installfor Claude Desktop, Cursor, VS Code and Codex, and theclaude mcp addcommand for Claude Code. Existing settings are kept and backed up.- Approvals for changes: a change returns an approval id, the user reviews
the exact request with
itda approvein a terminal, and the change runs once with exactly those inputs.itda approvalsanditda rejectlist and refuse them. - Hash-chained audit log of every call, approval and change, with
itda auditanditda audit --verify. Input values and credentials are never written to it. - Credential vault on the OS keyring, or
ITDA_SECRET__environment variables for CI and containers. Agents only ever see account ids. - Site definitions (
spec.yaml): JSON and server-rendered HTML responses, declared response checks, models, and safety validation with named problems. - Network layer: honest
itda/<version>user agent, per-site pacing and budgets, pauses on blocks and expired sign-ins, private-address guard, credentials bound to their origin. - Sandbox example site for trying everything locally.