Back to the network

Tooling

tool-kit

A personal toolbox behind one command: safe publishing of private repos, script vetting, checksummed backups, machine diagnosis and AI helpers.

tool-kit calls models from the shell, to explain a script, vet one, write one or draft a commit message. Every one of those calls follows the overview's rule: the AI may advise, and it never owns an exit code. This page is about the plumbing that keeps the rule true: one engine the scripts share, a filter in front of it, a fallback ladder for commit messages, and a measured try at running the work on a local model.

One engine behind the AI scripts

ai-call.sh takes a system prompt and some input, calls an OpenAI-compatible chat endpoint and prints the answer. The caller owns the prompt. The engine owns the transport, the list of endpoints, the failover and the reading of the reply, and it never runs anything it sends or receives. A new script does not write a chat request of its own: it calls the engine.

Four scripts call ai-call.sh: explain-script.sh, vet-script.sh, write-script.sh, prep-mess.sh. The first two send the script through strip-comments.sh first, a local filter that drops the comments and talks to nothing; the other two send what they have as it is. ai-call.sh takes one prompt in and gives one answer out: the caller owns the prompt, and ai-call.sh owns the transport, the endpoint list and failover. It reads the endpoints file one endpoint per line, label, url, model and key, and tries them top to bottom; the first usable answer wins, and if every endpoint fails it exits 6. Three checks sit beside it: it reads the HTTP status because curl exits 0 on a 500, the key column is data and never code, and a named config that is missing exits 2.CallersChecks it makesexplain-script.shexplains a scriptvet-script.shadvisory risk reportwrite-script.shwrites a new scriptprep-mess.shcommit messagesno strip step:sent as they arestrip-comments.shdrops the # comments;a local filter thattalks to nothingai-call.shone prompt in, oneanswer out. The callerowns the prompt; thisowns the transport,the endpoint list andfailoverreads the HTTP status:curl exits 0 on a 500the key column isdata, not codea named config thatis missing exits 2The endpoints filelabel | url | model | key, one endpoint per lineendpoint 1endpoint 2and so ontried top to bottom; the first usable answer winsif every endpoint fails it exits 6
The engine and the four callers its doc names. explain and vet send code through strip-comments first; write sends a description and the commit-message script a diff, as they are. The engine tries the endpoints in its file from the top until one answers.

The rule came from a copy. The commit-message script carried its own copy of the call code until 2026-08-12, and by then the copy had fallen behind the engine on six fixes. One of them: a 401 whose body happened to parse came back as the commit message. The engine's rules, each with the failure it rules out:

The explain script with a misspelled endpoints file: no endpoint is tried
$ ai/explain-script.sh --config no-such.conf ai/ai.sh
note: comments removed before sending -- the explanation describes the code (--keep-comments to include them)
Error: endpoints file 'no-such.conf' does not exist.
$ echo $?
2

The model never sees the comments

A comment can lie about the line beside it. A model that reads both will often repeat the comment, because the comment is the most confident sentence in the file. Telling the model to ignore comments does not help: that is an instruction, and an instruction is exactly what a manipulated model stops following. So the explain and vet scripts send a copy with the comments taken out, and vet prints the comments back to you, for a person to compare with the code:

strip-comments on the doc's own example, the comments it took out, and the fuzzer
$ cat cleanup.sh
#!/usr/bin/env bash
# cleanup: removes only temporary files
rm -rf "$HOME"
$ ai/strip-comments.sh cleanup.sh
#!/usr/bin/env bash
rm -rf "$HOME"
$ ai/strip-comments.sh cleanup.sh --comments-only
2: # cleanup: removes only temporary files
$ ai/tests/fuzz.sh
fuzz: 400 scripts, 0 mismatches (seed 1)

The stripper is deliberately conservative. A # opens a comment only at the start of a line or after a space or a tab, and only outside every quote, heredoc and arithmetic expression, with the quoting carried across lines. Where a case is ambiguous, the text stays. A missed comment is harmless, but a mangled line sends the model code nobody wrote.

The last line of that capture is the real check. bash -n parsed the stripped output cleanly for all four revisions of the tokenizer, including the three that rewrote code. So a fuzzer builds scripts from a grammar of awkward fragments, strips each one, runs both copies and compares the exit code, stdout and stderr. It earns its place because it can fail: given an older revision, it reports mismatches within a hundred scripts.

Commit messages, with a last rung that never fails

The commit-message script sends the diff of the uncommitted changes to a model and prints a message. It never commits. A git-cola hook fills the GUI's commit box with it, and on failure the box is left as it was. On a machine that opts in, the push sweeps that commit across my repositories draft their messages through a ladder, tried from the top: an OpenAI-compatible endpoint, or the local Claude Code CLI, and last a static rung that never fails. A rung whose key, transport or endpoint fails falls to the next one, so a sweep gets its commit message with or without a model.

A local model, measured

Another tool reads my exported Claude Code sessions and reports on them. Its cost line is always API-equivalent, what the tokens would cost at list price, because that is the one measure that compares two models or two tools. In September 2026 I tried its model pass on a local model, so that the transcripts would never leave the machine. It needed only configuration, no change to the code. On a clean re-run of the same four jobs, on 2026-09-27:

So the runner stays claude. The small model finds what a transcript states outright, and misses what has to be read between turns: an approval gate skipped, a scope widened, a constraint that came late.

The probe also found a bug that was not the model's. When Claude Code loads a skill, it injects the skill's text as a user-role record, and the exporter wrote that record as the human's own turn. Both small models read it as the human pasting documentation. The fix went into the exporter, in 0-libraries, and since then no finding blames the human for a skill's text.

Status lines, briefly

The status lines I run in Claude Code and Qwen Code are generated: one settings file and a template for each line produce the scripts, which are committed, and nothing reads the settings at run time. A check exits 1 when a generated file has drifted. Qwen kills a status command after 5 s and blanks the line, so the script gives all its git calls one 3 s budget together. A git that hangs costs the branch and the repository name, never the line.

Stack

  • Python
  • Shell

Connected to