Guide / Scripts and agents

Scripts and agents

Subcommands run once and print instead of opening the TUI. They make the same read-only calls, so they’re safe to hand to a script or an AI agent: nothing neboto can call changes an account. With no subcommand, neboto is the TUI as before.

neboto services                                   # every @prefix it knows
neboto ls @ec2 -t volume --state available        # unattached EBS volumes
neboto ls @iam -t role -f 'deploy'                # IAM roles, fuzzy-matched
neboto ls @ec2 -t instance --regions              # across your config `regions`
neboto get @lambda orders-api                     # every detail-pane section
neboto get @iam deploy-role --section policies -o json
neboto audit                                      # security findings; exit 3 if any

Try any of them with --demo, which answers from a built-in fake account and needs no credentials.

ls: list and filter

ls <@service> lists a service’s resources, filtered the way the TUI’s search filters them.

FlagEffect
-t, --type <TYPE>Only this resource type, by its name or last word(s): "Security Group", role, role|policy
-f, --filter <QUERY>Fuzzy text plus exact tag:key[=value] terms
--state <STATE>Only rows in this state (running, available, …)
--hide-noiseDrop AWS-managed defaults and other noise rows (the TUI’s a)
--limit <N>At most N rows, across every region with --regions
--regions[=LIST]List across several regions, with a region column (see below)

Across regions

--regions lists a regional service in several regions at once. Bare, it uses your config regions plus the current region; --regions=us-east-1,eu-west-1 names them for one run (the = is required).

neboto ls @ec2 -t instance --regions
neboto ls @rds --regions=us-east-1,eu-west-1 -o json

Tables, Markdown and CSV gain a REGION column. In JSON, "region" becomes "regions": [{"region", "status", "count", "message"}] and every row carries its own "region". A region an SCP denies is denied: a line on stderr, then the next region. The command fails only when no region could be listed. Global services such as IAM have nothing to list per region, so they’re a usage error.

get: everything the detail pane shows

get <@service> <ID|NAME>... prints every section of a resource’s detail pane, including the ones the TUI loads when you open them: IAM policy documents, CloudFormation events, template and drift, ECS deployments, and so on. It takes up to 50 resources, matched by exact id (or ARN, where that’s the id), then exact name.

FlagEffect
-t, --type <TYPE>Pick between resources that share a name (an ECS service and its task definition)
--section <NAME>Only this section, any case; repeatable. Section 1 is overview (details on a pane with only one section). The other sections’ data isn’t fetched
--wait <SECS>How long to wait for slow sections (default 60); anything still loading prints as not loaded

audit: security findings

audit loads EC2, RDS, S3, EKS, IAM and KMS, reads the account settings and per-resource settings the lists don’t carry, runs the same checks as the TUI’s @audit, and prints the findings. Each resource finding names its service and resource_id, so neboto get on that pair gives the full detail. Account-level findings, such as GuardDuty being off or no multi-region trail, have service: null and an empty resource_id.

neboto audit                                      # every finding
neboto audit --fail-on high -o json               # gate CI on high severity only
FlagEffect
--fail-on <SEVERITY>high, medium, low (the default) or never: exit 3 when a finding is at or above it

A check that couldn’t run, usually because a call was denied, is printed too, with the IAM action it needed, and -o json puts it under not_checked. An empty findings with a non-empty not_checked is not a clean account.

Compare two resources

Diff two gets. Both print the same sections in the same order, so the diff lines up:

diff <(neboto get @lambda orders-api-prod -o md) <(neboto get @lambda orders-api-staging -o md)

--section narrows it to what you care about, and any diff tool works (delta, vimdiff, git diff --no-index).

Across accounts or regions, give each side its own -p / -r, and swap out the account id and region first, or every ARN row differs:

norm() { sed -e "s/$1/ACCOUNT/g" -e "s/$2/REGION/g"; }
neboto get @lambda orders-api -p prod    -o md | norm 111111111111 ap-southeast-2 > prod.md
neboto get @lambda orders-api -p staging -o md | norm 222222222222 ap-southeast-2 > staging.md
diff prod.md staging.md

These run one after the other, so two profiles with a prompting credential_process don’t both ask at once. For an Organizations member account, use a profile that assumes into it (role_arn + source_profile); the CLI doesn’t assume org roles itself.

Output

-o table is the default on a terminal and -o json the default when piped; -o md and -o csv work too. JSON is one document carrying "schema": "neboto/v1". Section rows are the pane’s readable key: value rows, not raw API fields, and a section that didn’t load is null. Warnings go to stderr, so | jq keeps working.

Exit codeMeaning
0Success, including an empty list
1AWS error (permissions, throttling, network)
2A bad service, region or section, or a resource that isn’t there or matches more than one. The message says what to pass instead
3audit only: it ran and found something at or above --fail-on. stdout still has the full result

-p, -r, --endpoint-url and --demo work on every subcommand. Unlike the TUI, an unknown profile or region is an error rather than a fallback, so a script never gets another account’s answer. Secret values are never fetched: the TUI’s x / Y have no CLI equivalent.

What scripts can rely on

  • Stable: the flags; the JSON envelopes; each ls row’s type, id, name, state and tags; get’s resource object and tags; section names (.sections.Permissions); resource type names; service prefixes; the first four CSV columns; the exit codes. Removing or renaming any of these is a breaking change: the schema moves to neboto/v2 and the release notes say so. New sections, types, services and flags can arrive in any release. A test holds this, so it can’t change by accident.
  • Descriptive: everything inside a section, and the extra per-type columns on an ls row ("Runtime", "Memory", …). These are the detail pane, serialized: labels, units and grouping follow the TUI and can change in any release. Read them; don’t hard-code paths into them. When a script needs one field to stay put, the AWS API is that contract (aws …).
  • For people only: -o table and -o md. Don’t parse them.

For AI agents

skills/neboto/SKILL.md teaches an agent the commands, the output shapes and the exit codes. For Claude Code:

mkdir -p ~/.claude/skills/neboto
curl -fsSL https://raw.githubusercontent.com/neboto/neboto-tui/main/skills/neboto/SKILL.md \
  -o ~/.claude/skills/neboto/SKILL.md