@tudovu/cli
v0.4.0
Published
Tudovu CLI — AIMEE (the harness) from the terminal: ask, propose, approve, close out checks and findings; Architect drafts the compliant CloudFormation.
Readme
Tudovu CLI
AIMEE, the Tudovu harness, from your terminal. The same commands the Slack
bot answers (status, queue, propose …, run access review) work here,
plus the things a terminal is better at: listing failing checks, picking one,
and closing it out resource by resource. Architect is still in the box, as the
tool AIMEE uses to draft compliant CloudFormation (tudovu iac …).
Same contract as always: every change is a pull request a person approves. The CLI never merges and never applies to AWS.
Primary entry: bare tudovu opens the shell.
Install
Local (monorepo)
npm install
npm run cli:build
npm link -w @tudovu/cli
tudovu authenticate # saves ~/.tudovu/config.json
tudovu # the shellRe-run npm run cli:build after CLI source changes. Without linking:
npm run cli:dev -- queue.
From npm
npm i -g @tudovu/cli
tudovu authenticate
tudovuOne-shot: npx -y @tudovu/cli queue. Windows PATH notes are at the bottom.
Try the pre-release
The in-development build ships under the beta tag, so it never replaces the
stable @tudovu/cli:
npm i -g @tudovu/cli@beta
tudovu --version # a -beta versionRun it without installing
For a quick trial on a machine you don't want to install anything on, run the
single-file bundle (Node 18+). Copy tudovu.mjs over and:
node tudovu.mjs help
node tudovu.mjs discover > discover.sh # the read-only discovery script
TUDOVU_BASE_URL=https://your-app node tudovu.mjs statusNo npm, no registry, no dependencies. Build it yourself from the monorepo with:
npm run cli:build
npx esbuild apps/cli/dist/index.js --bundle --platform=node --format=esm \
--banner:js="import{createRequire as __cr}from'module';const require=__cr(import.meta.url);" \
--outfile=tudovu.mjsYou need an org API key (app → Config → Connect Agents) and the harness
switched on for the org (Config → Harness). Proposals need a connected GitHub
repo, or there is nowhere to open the PR; tudovu whoami tells you.
The shell
aimee> what needs me?
aimee> is anything in this account public?
aimee> propose block public access on the uploads bucketPlain sentences go to AIMEE. Ones it recognises are its own verbs (queue,
brief, duties, run access review, propose …, control …, tune …,
alert when …, monitor …, deploy …, score, never); anything else is a
read-only question Architect answers from the live account. A proposal comes
back with the plan, the cost line, the guardrail verdict, and a question:
Open this as a pull request? (case 5f05eae8, decide by 2026-09-02 16:00 UTC)
[y] Open the PR [n] Drop it [s] decide later ›y hands it to Architect and waits for the PR link. s leaves it in the
queue; tudovu approve 5f05eae8 later does the same thing.
| Slash command | What it does |
|---|---|
| /queue · /case <id> | What is waiting; one case file (first 8 characters of an id are enough) |
| /approve <id> · /decline <id> · /attest <id> [--note …] | Answer a case |
| /checks [--status …] [--severity …] [--service …] [--search …] | Open checks from the latest run |
| /check <id> · /check <id> close … | One check with its resources; walk the failing ones |
| /findings [text] [--severity …] [--resource …] · /finding <id> close … | Security Hub, live |
| /status /brief /duties /run <duty> /propose … /ask … | Any AIMEE verb |
| /iac <request> · /pr · /expand | Architect conversation; open a PR from its last validated template |
| /prs [repo] · /review · /exception … | PR list and deploy runs; posture snapshot; accepted-risk exception |
| /whoami /authenticate /thread [id] /clear /help /exit | Housekeeping |
Ctrl+C clears a non-empty line; on an empty line it leaves. /iac threads are
saved under ~/.tudovu/threads/<id>.json and resume with --thread.
Closing out a check
The case this was built for: a check fails on several resources and they do not all want the same answer. One bucket is public on purpose (a marketing site); the other is a mistake. Whole-check exceptions could not say that.
tudovu checks --service s3 # what's failing
tudovu check AC-S3-003 # the buckets it judged, one row each
tudovu check AC-S3-003 close # walk themclose asks, per failing resource: [x] accept as an exception, [f] fix
via a pull request, [s] leave it. Then, in this order:
- Exceptions are recorded first, resource-scoped: the disposition lands on
AC-S3-003@<bucket>, not on the check. That bucket reads ACCEPTED; the check stays FAIL until every other failing resource is fixed or accepted. - The fix goes to AIMEE as one proposal that names the excepted resources as off limits, so Architect drafts a change for the misconfigured bucket and leaves the accepted one alone. The plan, the cost, and the guardrail verdict (mapped to the checks the template would fail) come back. Approving commits exactly that template as the pull request. Only when the draft still has blocking guardrail issues does Architect get a second turn to clear them before the PR opens.
--evaluatere-runs just that check afterwards so the register reflects the exception now. The check reads ACCEPTED once the PR merges, deploys and the check re-runs.
Non-interactive, for a script or a ticket:
tudovu check AC-S3-003 close \
--except marketing-site-bucket --fix uploads-bucket \
--reason "Marketing site must be publicly readable" --expires 2026-12-31 \
--evaluate -ySecurity Hub findings work the same way. A finding is one title across many
resources; tudovu finding <id|title> close walks them with the same
exception-then-PR order, keyed the way finding exceptions already are
(source:control:resource).
tudovu findings --search "block public"
tudovu finding 326feeff closeReclaimer: what nothing manages
tudovu reclaim # Reclaimer's inventory, sorted by what it can import today
tudovu adopt /app/my-log-group # import one into CloudFormation as a PRreclaim reads Reclaimer's own inventory (ownership from stack enumeration,
deleted-stack history and repository evidence, never from a tag) and says
what can happen to each row: ready to import, needs an owner to confirm no
other tool manages it, stack-owned so fix the template instead, or owned by
Terraform and friends. Reclaimer imports fourteen shapes in place today: log
groups, standalone EC2 instances, security groups, S3 buckets, RDS instances,
ECR repositories, container-image Lambda functions, DynamoDB tables, IAM roles,
EFS file systems, ECS services, SNS topics, SQS queues, and the application
load balancer trio (load balancer, listener, target group). Every plan carries
CloudFormation's own change set, planned in your account before the merge;
Lambda is the one shape that gets no pre-merge plan, and the pull request says
why. Everything else is inventory-only and the CLI says so.
The same five calls the CLI makes are plain HTTP with the org API key as a
Bearer token: POST /api/reclaimer/inventory, /resource-checks,
/ownership-confirmations, /previews, /adoption-plans/draft-pr. adopt
is an owner-only write (it opens a pull request), so a key minted by a
non-owner gets the scan and the previews and is refused at the pull request,
which is the same boundary the app draws.
adopt runs Reclaimer's own steps behind one approval: the focused checks,
the ownership confirmation when machine evidence found no manager (recorded
under the approver's name for 30 days), the retained-import preview, and the
pull request. A resource that fails a check is refused, and an accepted
exception on that check doesn't change that: the import keeps the live
configuration exactly as it is, so an exception never counts as a pass here.
Fix the setting first (the resource's page in the app builds that fix as its
own plan), then run adopt again. An app owner has
to approve, because that is Reclaimer's boundary too. Nothing in AWS is
stopped, modified or recreated: a person merges, and the customer's own
deploy role runs the import.
Deploying a repo
tudovu deploy acme/shop --via eks # the plan: source, images, what the PR adds
tudovu deploy acme/shop --via eks --open # open the pull request; merging it deploys
tudovu repos # what runs from each repo, where, and its CI/CD--via takes ecs, eks, ec2 or lambda, and the usual synonyms
(kubernetes, k8s, helm, fargate, serverless). The repo has to be
connected to the Tudovu GitHub App. For EKS the plan picks the repo's best
Kubernetes source (Kustomize, Helm, plain manifests or Skaffold) and --source
overrides it; a repo with only a Dockerfile gets a Deployment and Service
scaffolded. --stage names the environment and --port overrides the port the
plan read from the repo.
Nothing deploys until a person merges the pull request, and the security gate
runs first. A repo has one deploy workflow, so pointing it at a different target
replaces the old pipeline; the plan warns you before it does. The Repositories
page in the app, the MCP pipeline tool and deploy owner/repo via eks to AIMEE
all make the same plan.
Subcommands
tudovu [--json] [--base-url URL] [--api-key KEY] [--thread ID] [<command> …]| Command | |
|---|---|
| aimee "…" (-y / -n; snippy still works) | One command to AIMEE, wait for the answer, approve or decline what it proposes |
| status · brief · duties · run <duty> | AIMEE's status, the briefing, the calendar, a duty now |
| queue · case <id> · approve · decline · attest | The inbox and decisions (--no-wait skips waiting for the PR) |
| checks · check <id> [close] | Compliance checks and the closeout walk |
| findings [text] · finding <id> [close] | Security Hub findings and their closeout |
| iac draft "…" [--out f.yaml] · iac pr --template f.yaml | Architect as a tool: plan, then open a PR (never merges) |
| deploy <repo> --via <target> [--open] · repos [search] | Deploy a repo to ECS, EKS, EC2 or Lambda; what's deployed where |
| prs [repo] · review · exception add · preflight | PR list + deploy runs; posture; accepted risk; CI container smoke test |
| authenticate · whoami · help | |
--json prints the same payloads the API returns, for scripting. ask and
apply from 0.3 are now iac draft and iac pr; status now means AIMEE's
status, the PR list is prs.
Auth & environment
Credentials resolve in this order (highest wins): CLI flags → env vars
→ ~/.tudovu/config.json → default base URL https://app.tudovu.com.
| Source | Notes |
|---|---|
| tudovu authenticate | Guided flow; saves baseUrl + apiKey |
| TUDOVU_API_KEY | Org API key (Bearer). Alias: INFRANITUM_API_KEY |
| TUDOVU_BASE_URL | App origin. Alias: INFRANITUM_BASE_URL |
| --base-url / --api-key | One invocation |
| TUDOVU_NOTIFY=1 / --notify | OS notification when AIMEE or Architect finishes |
| TUDOVU_BELL=0 | No terminal bell |
An API key acts as the user who minted it, in their org. Approving a case
needs the harness approve.pr permission (or the app owner role when no
roster is set up), same as in Slack.
Under the hood
POST /api/harness/commandruns the same dispatcher the Slack bot uses withsurface: "cli"; the ack and the outcome come back in the response instead of being posted. A proposal's decision lives on a case, approved throughPOST /api/harness/situations/{id}/decision.- A resource-scoped check exception is a disposition on
check/AC-XXX-NNN@<resourceId>;/api/checks/latest?overlay=1returns the run with those applied, the way the Checks tab shows it. - Shared HTTP client:
@tudovu/api-client(also used by the MCP servers).
Terminal tables
Architect answers sometimes include GitHub-flavored markdown pipe tables.
The CLI re-renders them with space-padded columns sized to content / terminal
width (long cells truncate with …). review uses the same helper.
Releasing to npm
Bump versions in apps/cli/package.json and packages/api-client/package.json,
commit, then tag and push:
git tag cli-vX.Y.Z
git push origin cli-vX.Y.ZThat runs .github/workflows/publish-cli.yml. You can also trigger it via
Actions → Publish CLI → Run workflow.
Windows (PATH)
npm i -g @tudovu/cli installs a tudovu.cmd shim into the npm global
prefix, which is often missing from PATH.
npm i -g @tudovu/cli
npm prefix -g
# Add that folder to your user PATH (commonly %APPDATA%\npm), then open a new terminal.One-shot without relying on PATH: npx -y @tudovu/cli whoami.
Development
npm run cli:build
npm link -w @tudovu/cli # once
npm run cli:typecheck
npm run cli:test