mysql-rw-axi
v0.1.5
Published
WRITE ACCESS - loopback hosts only. MySQL CLI for agents that CAN MODIFY AND DROP DATA on local databases - exec, import, create/drop databases, plus every read-only mysql-axi command. Use mysql-axi for anything that is not a local dev database.
Readme
mysql-rw-axi
[!CAUTION] WRITE ACCESS. This tool can modify and DROP data. It only connects to loopback hosts (127.0.0.0/8, ::1) and unix sockets, and nothing can override that. However, an SSH tunnel to 127.0.0.1 also counts as loopback: never tunnel it to a shared or production database. For reading, or for any database that is not a local dev copy, use the read-only
mysql-axi.
MySQL CLI for agents that need to change a local development database. It follows the AXI principles and prints token-efficient TOON.
npm install -g mysql-rw-axi
mysql-rw-axi auth login --profile local --user root --no-password -d shop_dev
mysql-rw-axi exec "UPDATE orders SET status = 'paid' WHERE id = 42"
mysql-rw-axi exec "TRUNCATE TABLE sessions" # previews; nothing runs
mysql-rw-axi exec "TRUNCATE TABLE sessions" --confirm # runs
mysql-rw-axi import dump.sql --confirm
mysql-rw-axi db create shop_test
mysql-rw-axi "SELECT COUNT(*) FROM orders" # bare SQL runs as a read-only queryWhat it does
exec "<sql>": runs one statement of any kind. Routine definitions withBEGIN … ENDbodies count as one statement.- Needs
--confirm:DROP,TRUNCATE,ALTER … DROP,REPLACE(it deletes the conflicting row, andON DELETE CASCADEchildren with it), andDELETE/UPDATEwithoutWHERE. These show a preview until you pass it. - Everything else runs directly.
affected_rowscounts rows actually changed. AnUPDATEalso reportsmatched_rows, so a rerun showsaffected_rows: 0.
- Needs
import <file.sql|->: runs a script such as a mysqldump file, statement by statement, honouringDELIMITERlines. It previews until--confirmwhen the script contains irreversible statements. Statements autocommit: if one fails, the import stops and reports how many statements were already applied.- Dumps made with
--databasescontainUSE/CREATE DATABASE. With a target database they are refused unless you pass--skip-use(load everything into the target) or--follow-use(write where the script says). Scripts that switch to a system schema are always refused. - The preview and the summary name every database written (
writes_to). - The file is streamed, so dumps larger than memory work;
-(stdin) is spooled to a temp file first. - The summary counts server warnings and shows the first five (silent truncation, zero dates). DEFINER accounts that do not exist on this server are flagged, because their views, triggers and routines fail later with MySQL 1449.
- Dumps made with
db create|drop <name>: idempotent.droppreviews the table count and size until--confirm, and system schemas are always refused.- Every
mysql-axicommand (query,sample,describe,tables, …). These still run read-only, so writes always go throughexec. Bare SQL (mysql-rw-axi "SELECT …") runs asquery; a bare write is refused with a pointer toexec. Reads get the same protection asmysql-axi, shown in its safety table. - Rows from
queryor from anexecthat returns rows are capped at 50 (--limit, max 1000). A cut result reports the total, such asrows: 50 of 72 shown, and the--limitthat shows them all.
The irreversible-statement detection is a best-effort check against mistakes, not a sandbox. The loopback rule is the real boundary.
Recipes
These are the flows in test/integration/scenarios.test.ts.
Restore a dump into a scratch database and compare it with the source.
mysql-rw-axi db create shop_restore
mysql-rw-axi import shop.sql -d shop_restore # preview: irreversible statements, writes_to
mysql-rw-axi import shop.sql -d shop_restore --confirm # add --skip-use for a --databases dump
mysql-rw-axi query "SELECT (SELECT COUNT(*) FROM shop.orders) AS src, (SELECT COUNT(*) FROM shop_restore.orders) AS copy"
mysql-rw-axi db drop shop_restore --confirmA unique index fails on duplicates: find them, keep the oldest, add the index.
mysql-rw-axi query "SELECT email, COUNT(*) AS n, GROUP_CONCAT(id ORDER BY id SEPARATOR ' ') AS ids FROM subscribers GROUP BY email HAVING n > 1"
mysql-rw-axi exec "WITH ranked AS (SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM subscribers) DELETE FROM subscribers WHERE id IN (SELECT id FROM ranked WHERE rn > 1)"
mysql-rw-axi exec "ALTER TABLE subscribers ADD UNIQUE KEY uq_email (email)"Upsert without losing child rows. Prefer this to REPLACE, which deletes first:
mysql-rw-axi exec "INSERT INTO customers (id, email, name) VALUES (1, '[email protected]', 'Ada') AS new ON DUPLICATE KEY UPDATE name = new.name"A one-off data fix that is safe to rerun.
mysql-rw-axi exec "UPDATE orders SET status = 'paid' WHERE id = 42" # affected_rows: 1
mysql-rw-axi exec "UPDATE orders SET status = 'paid' WHERE id = 42" # affected_rows: 0, matched_rows: 1Live overview
mysql-rw-axi with no command starts with the WRITE-CAPABLE warning and the loopback scope. It then connects with short limits and shows the server and the default database's tables, like mysql-axi, reading through a read-only session. A profile outside the loopback rule shows as live: unavailable without connecting.
Connections
- Profiles:
auth loginconnects first, then stores the profile. The password goes in the OS keychain and the rest goes in~/.config/mysql-rw-axi/config.json. - Separate from
mysql-axi: the profiles, keychain entries and env vars are not shared, so read-write credentials are never visible to the read-only tool. - Env override:
MYSQL_RW_AXI_URL=mysql://user:[email protected]:3306/database.
Scripts
Output is strict TOON apart from raw text (plans, DDL) between --- fences. For scripts, --json anywhere on the line prints the same payload as JSON, with raw text as a string field (plan, create_table, explain); errors keep their exit code:
mysql-rw-axi exec "UPDATE orders SET status = 'paid' WHERE id = 42" --json | jq '.exec.affected_rows'Agent integration
There are two ways to tell an agent about mysql-rw-axi, and you only need one. Install either only where writing is wanted:
- Session hook:
mysql-rw-axi setup hooks --localcovers one repository, for you only. At session start the agent getsmysql-rw-axi context, which starts with theWRITE-CAPABLEwarning and the loopback scope and never connects. At session end Claude Code records which commands ran, by name and count. It works likemysql-axi's hook, including--project,setup statusandsetup uninstall. With both tools installed, a common setup ismysql-axi setup hooksfor every session andmysql-rw-axi setup hooks --localin the repositories whose local database the agent may change. - Skill:
npx skills add cmosgh/mysql-axi --skill mysql-rw-axiinstallsskills/mysql-rw-axi/SKILL.md. It loads only when a task must change a local database, and costs nothing per session.
Allow Bash(mysql-rw-axi:*) only in projects where an agent should change a local database.
MySQL 8.x only. MIT licensed.
Other AXI tools
atl-axi: Atlassian Jira Cloud CLI for agentsbb-axi: Bitbucket Cloud CLI for agentshetzner-axi: Hetzner Cloud CLI for agentsmongo-axi: Read-only MongoDB CLI for agentsmongo-rw-axi: Write-capable MongoDB CLI for agents, loopback hosts onlymysql-axi: Read-only MySQL CLI for agentsts-axi: TypeScript symbol CLI for agents
