A Claude Code plugin for building, testing, and deploying Lua AI agents from inside your Claude Code session. It wraps the lua-cli toolchain (a TypeScript SDK/CLI — unrelated to the Lua programming language) with single-permission slash commands, hook-enforced production gates, a source-verified knowledge base, and two MCP servers.
Verified against lua-cli 3.33.0 (September 2026): every command shape, SDK type and API endpoint the plugin uses was read from the lua-cli / lua-api source, not from the public docs. 1.3.0 added the per-step model classes (taskClass, class/fast|balanced|strong, --apply-effort, lua workflows policy models, clear-gate, recompose, lua models list --workflows) and the workflow autonomy envelope (lua workflows policy autonomy, Consent: auto (policy)) from lua-cli 3.36.0. 1.4.0 corrects what a workflow run COSTS — a Job-tier step is billed per model reply (a credit per reply on a legacy plan, actions = price band × the model's multiplier with cached tokens at the provider's fraction on a seat plan), a Job step runs on its own model and not the agent's, and raise-budget --credits <n> raises a cap counted in the run's own unit — and describes the lua-cli 3.37.0 read-outs that show it (Tokens:, the Uncached/Cached/Output columns, the ⚙ Job-model line, finished past the cap, the budget events in workflows logs, the job-model-default deploy advisory), read from lua-cli main and marked ⏳. The pin is 3.37.0 since 1.4.0; 3.37.0 adds no command and no option, so every shape the plugin emits still runs on 3.36.0.
/plugin marketplace add lua-ai-global/claude-code-lua-plugin
/plugin install lua-agent-builder@claude-code-lua-plugin
/reload-plugins
/lua-doctor # Node, npm, lua-cli ≥ 3.38.0, auth, permission rules
New login runs in your own terminal (lua auth configure) — the plugin never handles your email, one-time code or credential. /lua-auth guides it.
plugins/lua-agent-builder/
├── .claude-plugin/plugin.json # plugin manifest
├── .mcp.json # lua-platform (local stdio) + lua-docs (https://docs.heylua.ai/mcp)
├── commands/ # 22 slash commands
├── agents/ # 10 subagents (architect, skill-builder, debug, deploy-pilot, qa, qa-cartographer, qa-player, qa-grader, qa-analyst, qa-reporter)
├── hooks/ # 12 Node ESM hooks + hooks.json
├── lib/
│ ├── knowledge/ # primitives, workflows, cli-reference, integrations, decision-trees, log-drains, devices
│ │ └── qa/ # /lua-qa full prompts: rubric, ICP cards, player, red team, graders, analyst, fix-locus, mechanics, qualifying questions
│ ├── qa/ # /lua-qa full helpers behind one entry, lib/qa/cli.mjs (recorder, checks, discovery + SVG, report, Workflow script)
│ ├── permissions-template.json# allow/ask/deny rules /lua-doctor merges into .claude/settings.json
│ ├── tokenizer.mjs # production-verb classifier for the deploy gate
│ └── credentials.mjs, hook-runtime.mjs, lua-cli.mjs, platform.mjs
├── mcp/lua-platform/ # vendored MCP server (src/, tests/, dist/server.js committed)
├── scripts/ # 17 lint scripts + 2 check scripts + pack
└── test/ # jest tests for hooks + lib
| Slash | What it does |
|---|---|
/lua-doctor |
5-step environment diagnostic with consent-gated fixes |
/lua-auth |
Keep a working credential or guide a private lua auth configure login |
/lua-update |
npm install -g lua-cli@latest |
/lua-docs <topic> |
Search the docs via the lua-docs MCP (WebFetch fallback) |
/lua-status |
lua status --json — auth, project, per-primitive local-vs-deployed sync |
/lua-init |
lua init --ci — new agent, existing agent, or duplicate; model from the live catalog |
/lua-architect <goal> |
Plan an agent end-to-end (subagent) |
/lua-new <type> [name] |
Scaffold + register + compile + test a tool, skill, webhook, trigger, job, preprocessor, postprocessor, mcp, device, device-trigger, voice, workflow, workflow-script or web app (subagent) |
/lua-test [type] |
lua test --ci skill|webhook|job|preprocessor|postprocessor|workflow|webapp; a web-app route that writes (POST/PUT/PATCH/DELETE) runs against live Data only after your one confirmation; failures go to the debug subagent |
/lua-workflow <verb> |
Offline workflow runs with scripted approvals/signals; list/status/watch; start/approve/signal/resume/cancel with one confirmation |
/lua-chat |
One-shot lua chat --ci -e <env> -m … -t on an isolated thread |
/lua-logs |
lua logs --ci --json with the real 18-source --type list and ⏳ the 3.38.0 read window (--since / --until / --environment / --follow) |
/lua-drains |
⏳ 3.38.0 log drains: lua drains list|status|deliveries|create|update|delete|test|verify|pause|resume|rotate-secret; reads run at once, the seven config verbs confirm once, the signing secret is never stored |
/lua-devices |
Hardware and local machines: self-describing vs defineDevice, ⏳ 3.45.0 lua devices credential --out (the secret goes to a git-ignored mode-600 file, never the conversation), Node / Python / Pico W client scaffolds, push device / device-trigger, status, ⏳ 3.45.0 test --command / test-trigger --trigger, lua logs --type device|device-trigger, troubleshooting — every 3.45-only verb has a 3.44 fallback |
/lua-env |
lua env <sandbox|production> --list | -k KEY -v VALUE | -k KEY --delete; the Bash prompt is the confirmation, values never echoed |
/lua-integrations |
lua integrations available|list|info|webhooks …|mcp …; read-only verbs run at once, mutations confirm once, OAuth connects go to your terminal |
/lua-sync |
Drift report from lua status --json + lua sync --check; --pull / --push |
/lua-push |
lua push <type> --ci --force for every push type incl. trigger/device/voice/workflow/webapp; never --auto-deploy |
/lua-deploy |
Gated ship sequence for any production change — primitive versions, persona, workflow versions, web apps, MCP activation, agent-version promote/rollback (subagent) |
/lua-version |
Agent versions: list/show/diff/status/create; promote routes to /lua-deploy |
/lua-template |
Marketplace agent templates: view/versions/status/health/installed/create/draft/install; publish and apply are prefixed production verbs |
/lua-qa [quick|full] |
Quick: conversational QA + offline workflow scenarios + log scan → triage report (subagent), as before. Full: a tier (smoke 30-min cap from plan approval, grader A only, no stress test; medium default 1–2 h, production-ready 3–5 h with a release gate) and four gates (discovery diagrams, qualifying answers and metrics inferred from the code unless --interview, environment, the plan), then personas × runs sized to the tier (medium: ≥10 × 3; production-ready: ≥12 × 5, 4 must pass), a red team, offline workflow flow tests, direct tool tests and (outside smoke) a stress test run in parallel, graded by two independent Opus graders (one in smoke), clustered by root cause → PDF + HTML + results.json report |
- Production gate — every verb that changes what runs in production is blocked by the
confirm-deployhook (on every Bash call) unless it carries theLUA_DEPLOY_CONFIRMED=1prefix, which only the deploy flow emits after your single confirmation:lua deploy,lua skills|webhooks|jobs|preprocessors|postprocessors deploy,lua skills production deploy(non-interactive since lua-cli 3.44.0; gated since 1.7.0),lua persona production deploy,lua workflows deploy|activate,lua version promote,lua mcp activate,lua marketplace template publish|apply— in every spelling lua-cli accepts (itspublish/on/enable/submit/rollout/prodaliases and theheylua/lua-aibinaries). A hook block wins over any allow rule. The permission template allows the literal prefixed forms and carries no deny/ask rule for the bare verbs, because Claude Code evaluates deny/ask past a leading env assignment and such a rule would block the confirmed form too.lib/tokenizer.mjsis the one classifier;test/lib/permissions-mirror.test.mjsfails if the layers drift. Since 1.6.0 the classifier reads the whole command — chains, groups, substitutions,bash -cstrings, env prefixes,npx/pnpm exec/nodelaunchers and binary paths — and honours the prefix only on the simple command that runs the verb. - Headless mode —
LUA_PLUGIN_HEADLESS=1(forclaude -pin the Lua Job tier or CI): no hook message points at a slash command, theLUA_DEPLOY_CONFIRMED=1prefix is void (every production verb is blocked) and the post-deploy smoke ping is skipped. See Running in the Lua Job tier. --auto-deployis denied and blocked unconditionally.- Log drains (⏳ lua-cli 3.38.0) are ORGANIZATION configuration, not a deploy:
lua drains list|status|deliveries|testare allowed, andcreate|update|delete|verify|pause|resume|rotate-secretsit in theasktier — that prompt is/lua-drains's single confirmation. They are deliberately not inlib/tokenizer.mjs: none of them changes what runs in production, andLUA_DEPLOY_CONFIRMED=1would be the wrong sentence for a log-shipping change. No secret ever reaches a command line — a header value is prompted or read from an environment variable (--header-from-env NAME=ENV_VAR), and the HMAC signing secret is minted server-side and printed exactly once. - Devices are not part of agent versions and are not deploy verbs: a pushed
defineDeviceis live on the next turn, solua push device/device-triggersit in theasktier (above the generic push allow rule), as dolua devices enable|disable|remove|test|test-trigger|credentialand their aliases;list/statusare allowed. A device-trigger is published only by--auto-deploy, which stays denied —/lua-devicesprints that line for your own terminal. - Web apps (lua-cli 3.42.0+) run their routes as you against the agent's live
Data, locally too — there is no test database. The plugin runsGETroutes freely and asks once before any route that writes; it never startslua apps dev(it prints that for your own terminal). Onlylua apps new <name>and four npm commands run without a prompt —npm --prefix src/apps/<name>/web install --ignore-scripts [@lua-ai-global/app-client],run typecheck,run build— and only in that exact form: theapprove-web-apphook matches them with anchored regexes rather than permission globs (a glob's*would also match an extra package, a second--prefix, or anything after the app name). A web-app push is staged only;/lua-deploy webapppromotes a new agent version and stops before promoting if that version would also make other staged primitives live. - Credential isolation —
lua auth configure|key|logoutare denied for the model; login happens in your terminal. A device credential is only ever issued with--out <file>(theblock-device-secrethook blocks the printing form), into a git-ignored mode-600 file the plugin never reads back. - Single permission per slash — each slash asks at most one question (
x-lua-multi-step: truemarks the diagnostic exceptions).
lua-platform(local,mcp/lua-platform/dist/server.js, one self-contained bundle — nonode_modulesneeded at runtime): five read-only tools —list_agents,get_agent,list_primitive_versions,get_deployment_status,tail_logs— over the same lua-api routes lua-cli uses, with lua-cli's credential resolution (env →.env→ renewable session → credentials file).lua-docs(remote,https://docs.heylua.ai/mcp): the public docs search (search_lua_cli,query_docs_filesystem_lua_cli).- Claude Code names a plugin's MCP tools
mcp__plugin_lua-agent-builder_<server>__<tool>; the subagents, slashes and the permission template use that form (the plainmcp__lua-platform__…spelling only exists for a server you register yourself withclaude mcp add).
npm ci && npm run lint && npm run test:coverage # plugin: 17 lints, hooks 100% / lib ≥ 90% coverage
cd mcp/lua-platform && npm ci && npm test && npm run build # MCP server → dist/server.js (committed)dist/server.js must be committed: marketplace installs copy the repo verbatim with no build step.
ci.yml runs lint + tests on macOS/Ubuntu/Windows × Node 18/20 and builds the MCP bundle; release-beta.yml cuts a prerelease on every push to staging; release-prod.yml cuts v<package.json version> on every push to main and refuses an existing tag. Versions in package.json, .claude-plugin/plugin.json, ../../.claude-plugin/marketplace.json, mcp/lua-platform/package.json and the MCP source must match (lint-release-version).