Skip to content

declick 0.6.0

Latest

Choose a tag to compare

@ucsandman ucsandman released this 04 Sep 14:31
· 20 commits to main since this release

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=v on any verb filters a list before --fields and --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, and k=* 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.where is {matched, of} and meta.count is what matched. A condition alone unwraps the rows inside a response object the way --fields does. 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 by DECLICK_MAX_BYTES or per call by --max-bytes N, and turned off with 0. It applies after --where, --rows, --fields and --limit, and never to --dry-run, --help or describe. 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 --fields survives any cap. meta.truncated is true and meta.capped is {bytes, max, hint}; the exit code stays 0. A --each batch 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 own capped.
  • --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 reports meta.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. --cache on a mutating verb is exit 1 naming the verb, --dry-run neither reads nor writes, --cache 0 and DECLICK_CACHE=off bypass it, and declick build and declick remove clear it. A defaults file may set it per verb.
  • declick audit --sum adds the run log up instead of listing it: one row per adapter and one total, sorted by bytes read. Every audit line now carries bytes, the size of the envelope that run wrote to stdout, beside ms, so 212 calls, 340.2 KB read through adapters, 3 failed answers what the adapters actually cost. --adapter, --since and --failed narrow the sum the way they narrow the lines; a line written before bytes existed counts as 0 rather than dropping the run. A run that asked for a cache records cache: hit or cache: miss.
  • where, max-bytes and cache are reserved flag names now: a verb parameter called any of them is renamed param-<name> on the next build.
  • The DashClaw guard has a contract test: test/fixtures/dashclaw-guard-schema.json is DashClaw's guard input schema, test/guard-contract.test.mjs validates what guardBody() sends against it, and npm run qa posts one real body to $DASHCLAW_URL when 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|status runs a detached per-user process that keeps stdio MCP servers alive between calls, so only the first declick 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 carries meta.daemon: true. Servers are pooled per adapter and per what it was spawned from, dropped after DECLICK_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.sock at 0600 elsewhere), every message carries the token in ~/.declick/daemon.json (0600), and a daemon.json whose pid is gone counts as no daemon. declick doctor reports 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