Five answers to the same problem, that an API sends more than the agent asked for and the whole of it lands in the context, plus warm MCP servers so the second call stops paying the first one's startup. The savings are measured: scripts/bench-tokens.mjs against nine real MCP servers (258 tools) puts the raw tool listing at 236,818 bytes and declick describe at 58,309, a 4.1x reduction (docs/bench.md). 631 tests pass.
--where k=von any verb filters a list before--fieldsand--limit, so rows are narrowed on the machine that has them. Repeatable, comma-separable, dotted paths, and eight operators:k=v,k!=v,k~re(case-insensitive regex),k>n,k>=n,k<n,k<=n, andk=*for present and not null. A number compares as a number and a bool as a bool; everything else is an exact string match, and a row with no value at the path fails the condition instead of stopping the run.meta.whereis{matched, of}andmeta.countis what matched. A condition alone unwraps the rows inside a response object the way--fieldsdoes. A condition on a verb that answers with one object is exit 1 naming the verb.- A default ceiling on what one envelope carries: 8192 bytes of
data, moved byDECLICK_MAX_BYTESor per call by--max-bytes N, and turned off with 0. It applies after--where,--rows,--fieldsand--limit, and never to--dry-run,--helpordescribe. A list drops tail rows and keeps at least one, a string is sliced, and an object keeps every key with its biggest values replaced by<N bytes; add --fields or --limit>, so the shape needed to write a--fieldssurvives any cap.meta.truncatedis true andmeta.cappedis{bytes, max, hint}; the exit code stays 0. A--eachbatch is held to the cap per item and never as a whole, since every entry is the record of a run that happened; a capped item carries its owncapped. --cache <seconds>on a read-only verb answers from the response the wire already gave. Entries live in~/.declick/<name>/cache/keyed on the adapter, the verb, its positional args and its own flags, so the same call shaped two ways reads one stored response. A hit skips the engine and reportsmeta.cache {hit: true, age}; a miss stores only a result that worked and reports{hit: false, stored: true}. The local policy still decides on a hit.--cacheon a mutating verb is exit 1 naming the verb,--dry-runneither reads nor writes,--cache 0andDECLICK_CACHE=offbypass it, anddeclick buildanddeclick removeclear it. A defaults file may set it per verb.declick audit --sumadds the run log up instead of listing it: one row per adapter and one total, sorted by bytes read. Every audit line now carriesbytes, the size of the envelope that run wrote to stdout, besidems, so212 calls, 340.2 KB read through adapters, 3 failedanswers what the adapters actually cost.--adapter,--sinceand--failednarrow the sum the way they narrow the lines; a line written beforebytesexisted counts as 0 rather than dropping the run. A run that asked for a cache recordscache: hitorcache: miss.where,max-bytesandcacheare reserved flag names now: a verb parameter called any of them is renamedparam-<name>on the next build.- The DashClaw guard has a contract test:
test/fixtures/dashclaw-guard-schema.jsonis DashClaw's guard input schema,test/guard-contract.test.mjsvalidates whatguardBody()sends against it, andnpm run qaposts one real body to$DASHCLAW_URLwhen a key is set. The 0.5.1 wire-format bug survived 566 tests because every guard test was a mock. - Warm MCP servers.
declick daemon start|stop|statusruns a detached per-user process that keeps stdio MCP servers alive between calls, so only the firstdeclick run <mcp adapter> <verb>pays the server's startup instead of every one of them. A run tries the daemon with a 300 ms connect budget and falls back to spawning its own server when nothing answers, so nothing breaks when it is not up; a served run carriesmeta.daemon: true. Servers are pooled per adapter and per what it was spawned from, dropped afterDECLICK_DAEMON_IDLE_MS(default 600000) idle, and the daemon exits once it has had none for the same window. The endpoint is per user (a named pipe on Windows,~/.declick/daemon.sockat 0600 elsewhere), every message carries the token in~/.declick/daemon.json(0600), and adaemon.jsonwhose pid is gone counts as no daemon.declick doctorreports it. HTTP MCP adapters are untouched, and the guard, the local policy and the audit line stay on the client side of the socket.
Install: npm i -g declick@0.6.0 · npm · declick.dev