# ebman

A k9s-style terminal UI for AWS Elastic Beanstalk.

Browse environments, drill into events / instances / metrics / queue / config, stream CloudWatch logs, edit env vars / option settings, deploy new versions — all without leaving the terminal.

<!-- screenshot here once the maintainer can capture a real frame -->

## Highlights

- **Live env table** with sort, filter, group-by-app, health sparkline (trend window auto-labelled), severity tints, mouse support. `Tab` cycles between Envs and Apps scope.
- **Drill-down per env** — opens to a **Health rollup tab** (drillable: `j`/`k` walks items, `Enter` jumps to source). Other tabs: Events (regex-searchable), Instances (with health causes + embedded SSM shell), Metrics (CloudWatch line charts; custom metrics with arbitrary dimensions), Queue (Worker tier; main + DLQ stats with viewer), Logs (one-shot snapshot or real `tail -f` from CW Logs), Config (tags + env vars read-only, plus cost estimate).
- **Red-env triage** — `:why` (or `!` on the selected env) opens a four-section diagnostic overlay: recent events, alarms, instance health, recent deploys. Worker envs also get a main + DLQ peek. Updating envs are labelled with the *kind* of update in flight (deploy / config / scale); an env with active CW alarms no longer renders as plain green "Ready". Worker envs with a non-empty DLQ flip Red even when EB calls them healthy.
- **Daily-driver write surface** — env vars (`:env set/unset`), tags (`:tag`/`:untag`), version deploy from existing label or local zip / S3 (`:deploy --from`, with `--preview` for a side-by-side current-vs-candidate overlay), saved-config CRUD, CloudWatch alarms CRUD, log streaming toggle, notifications endpoint, managed-update window, ALB scheme, instance type, key pair, IAM roles, deployment policy, rolling-update settings, health-check URL, capacity (`:capacity` — Min / Max / Instance type / Cooldown in one modal), plus a generic `:set-option NAMESPACE OPTION VALUE` escape hatch.
- **Worker / SQS workflow** — DLQ viewer with per-message resend (`r`), strict-typed purge (`p`), bulk delete (`x`), peek-and-tail with long-polling.
- **Multi-region / multi-account** — `:region all` fans across every configured region in parallel; `:account NAME` switches via `sts:AssumeRole` (configure `accounts.NAME` in `config.toml`); `:accounts` lists AWS-Organizations child accounts; `:find-env` and `:org-health` both fan across `~/.aws` profiles **and** configured AssumeRole accounts.
- **Bulk ops** — `space` multi-selects; `:batch-rebuild`, `:batch-restart`, `:batch-deploy LABEL`, `:batch-tag KEY VALUE`, `:batch-untag KEY`, `:batch-set-option NAMESPACE OPTION VALUE` all fan out in parallel with per-env audit + pending pill rows.
- **Safety** — `--read-only` CLI flag or `:readonly on` disables every write surface; destructive actions (Terminate, DLQ purge) require typed-name confirmation; pre-flight dry-run shows impact (N instances across M AZs) + last 3 events before authorising; recent-change / mid-deploy traffic warnings in the confirm modal.
- **Audit log** — every dispatched action and its outcome are appended to `~/.cache/ebman/audit.log`; rotates at 1 MiB.
- **Power-user ergonomics** — fuzzy command palette (`Ctrl-K`) across commands / envs / saved views / plugins, named filters + saved views, custom keybindings (`F1-F12` and uppercase letters via `~/.config/ebman/keys.toml`), plugin commands (`~/.config/ebman/commands.toml`), in-app `:loglevel` reload, `:diff` between envs.
- **Headless / scriptable** — `--control-socket PATH` exposes a Unix-socket interface; `ebman ctl <op>` is a one-shot client (`screen` / `state` / `key <spec>` / `cmd <:cmd>`).
- **Non-interactive CLI** — `ebman envs [--json]` / `ebman action rebuild --env NAME` / `ebman ctl ...` for scripts and CI.

## Install

**Homebrew (macOS / Linux):**

```bash
brew tap tombaldwin/tap
brew install ebman
```

**Pre-built binary:**

Download the tarball for your platform from the [GitHub Releases page](https://github.com/tombaldwin/ebman/releases), verify the `*.sha256` next to it, extract, and put `ebman` on your `PATH`.

**Cargo:**

```bash
cargo install ebman
```

Tested on Rust 1.91+. macOS (Apple Silicon + Intel) and Linux x86_64. AWS SDK uses the standard credentials chain (`AWS_PROFILE` / `AWS_REGION` env, `~/.aws/credentials`, instance role, etc.).

### Fonts (optional, for the prettier glyph set)

Ebman runs fine in any terminal with the default `icons = "unicode"` config. For the Powerline-style pill chain, tab ribbon, and per-tab MDI icons (`icons = "powerline"` or `icons = "auto"`), your terminal needs a Nerd Font installed — vanilla Powerline fonts give you the triangles but tofu/boxes where the tab icons should be.

**1. Install a Nerd Font:**

```bash
brew install font-meslo-lg-nerd-font           # Powerlevel10k crowd; safe default
brew install font-jetbrains-mono-nerd-font     # modern monospace, no ligature surprises
```

**2. Set your terminal's font** to one of the `Mono` variants — they're sized for fixed-width TUIs (e.g. `MesloLGS Nerd Font Mono`, `JetBrainsMono Nerd Font Mono`):

- iTerm2: Preferences → Profiles → Text → Font
- Terminal.app: Preferences → Profiles → Font → Change
- Ghostty / Alacritty / WezTerm: `font-family` in the relevant config file
- VS Code / Cursor terminal: `terminal.integrated.fontFamily` in settings

**3. Tell ebman to use the new glyphs** — either run `:settings` in ebman and pick `auto` (or `powerline`) from the Icons field, or add this to `~/.config/ebman/config.toml`:

```toml
icons = "auto"   # probes the terminal at startup; falls back to "unicode"
```

Restart ebman (or use `ebman ctl reload` if you're driving via the control socket) so the startup probe runs against your new font. `icons = "powerline"` skips the probe and forces the Nerd glyph set unconditionally.

Without a Nerd Font, stick to `icons = "unicode"` (the default) — everything still works, you just don't get the per-tab MDI icons.

## Quickstart

```bash
ebman                                  # launch the TUI
ebman --read-only                      # disable all write surfaces (audit-friendly)
ebman --control-socket ~/.cache/ebman/control.sock   # expose the ctl interface
ebman envs --json                      # non-interactive: print env list as JSON
ebman action rebuild --env myenv --yes # non-interactive: dispatch a rebuild
ebman ctl screen                       # dump the current frame from a running instance
ebman --version
ebman --help
```

Once running, press `?` for a per-context keymap (Detail, DLQ, Action menu, Saved-configs overlay all have scoped help).

## Keys

### Normal mode (env table)

| Key | Action |
|---|---|
| `j` / `k` / wheel | Move selection |
| `g` / `G` | Top / bottom |
| `1` – `9` | Jump to position |
| `'` | Name-jump (type prefix) |
| `Enter` | Drill into env |
| `Tab` / `Shift-Tab` | Switch scope (Envs ↔ Apps) |
| `a` | Actions menu |
| `b` | Open env in AWS console |
| `D` | Describe overlay (raw env JSON) |
| `space` | Multi-select |
| `*` | Pin / unpin |
| `!` | `:why` overlay (Red-env diagnostic) |
| `/` | Filter |
| `:` | Command bar |
| `^K` | Command palette |
| `s` / `S` | Cycle sort key / reverse |
| `^G` | Toggle group-by-application |
| `^E` | Toggle events panel |
| `^]` | Cycle focus (table ↔ events panel) |
| `^D` | Cycle view mode (default / compact / spacious) |
| `^X` | Toggle redact mode |
| `^Y` | Yank filtered table as TSV |
| `^W` | Yank equivalent `aws elasticbeanstalk describe-environments` |
| `y` / `Y` | Yank CNAME / name |
| `f` | Freeze / unfreeze auto-refresh |
| `r` / `p` | Switch region / profile |
| `^R` / `F5` | Force refresh |
| `?` | Help |
| `q` / `^C` | Quit |

### Detail view

Tabs cycle with `Tab` / `Shift-Tab` (or `l` / `h`). `^R` re-fetches the active tab; `R` toggles per-tab auto-refresh; `a` opens the env actions menu; `b` opens the env in the AWS console.

| Tab | Per-tab keys |
|---|---|
| Health (default) | `j`/`k` walk items, `Enter` drill into source tab (Events / Instances / Queue) |
| Events | `/` filter, `n`/`N` next/prev match |
| Instances | `Enter` / `i` info overlay, `b` EC2 console, `s` embedded SSM shell, `y` yank id, `x` terminate (Y/N) |
| Metrics | `[` / `]` cycle range (15m → 24h), mouse hover for value-at-cursor |
| Queue | `j`/`k` Main ↔ DLQ, `Enter` opens viewer, `d` quick-open DLQ |
| Logs | `^R` one-shot snapshot, `s` open live CW Logs streaming overlay, `/` filter |
| Config | scrollable; tags + env vars + cost estimate read-only |

### DLQ viewer

`j`/`k` move, `Enter` view body, `r` resend (DLQ → main), `x` delete, `p` purge (typed-name confirm), `m` toggle Main ↔ DLQ, `^R` re-peek.

## Command reference

Type `:` to open the command bar. Tab-completion is not implemented, but `Ctrl-K` fuzzy-searches every command + env + view + plugin.

### Navigation / inspection

- `:region NAME` / `:region all` — switch region, or fan out across every configured region.
- `:profile NAME` — switch AWS profile.
- `:account NAME` — switch to a configured AssumeRole account (`accounts.NAME` in `config.toml`). Falls back to `:profile NAME` aliasing when no `accounts.` entry exists.
- `:accounts` — list child accounts in the active AWS organization; rows matching a configured `accounts.NAME` get a `:account NAME` switch hint.
- `:sort KEY [desc]` — set sort (name/app/status/health/version/age).
- `:group on|off` — toggle group-by-application.
- `:redact on|off` — toggle redact mode.
- `:events on|off` — toggle events panel.
- `:filter NAME` / `:f NAME` — load a saved filter.
- `:save NAME` / `:drop NAME` / `:filters` — manage named filters.
- `:save-view NAME` / `:view NAME` / `:views` / `:view-drop NAME` — saved views (filter + sort + grouping + scope).
- `:cols list|hide NAME|show NAME|reset` — manage columns.
- `:pin` — pin / unpin selected env.
- `:alias NAME LABEL` / `:alias-drop NAME` — local env aliases.
- `:minimap on|off` — corner mini-map of env health.
- `:loglevel LEVEL` — live-reload the tracing filter.

### Per-env inspection

- `:why` — Red-env diagnostic overlay (recent events / alarms / instance health / recent deploys; main + DLQ peek for Worker envs). Bound to `!` on the env table.
- `:diff NAME` — side-by-side env comparison.
- `:resources` / `:res` — `DescribeEnvironmentResources` dump.
- `:alarms` — CloudWatch alarms referencing the env.
- `:versions` — application versions (deployed marker, total count, deploy hint).
- `:saved-configs` / `:configs` — saved configuration templates (interactive: `a` apply, `i` inspect, `x` delete, `c` create).
- `:custom-platforms` / `:platforms` — custom EB platforms.
- `:plugins` — list user plugin commands.
- `:history` — recent status / error log.
- `:pending` / `:in-flight` — overlay of dispatched actions + outcomes.
- `:whatsnew` — embedded changelog.
- `:about` / `:credits` — version, license, attributions.
- `:update` — show (and yank to clipboard) the upgrade command for whichever install channel (Homebrew / cargo-bin / tarball) ebman was installed from.
- `:settings` — interactive form to edit `~/.config/ebman/config.toml`; writes back on submit and live-applies theme / icons / refresh interval.

### Write — env state

- `:rebuild` / `:restart` / `:terminate` — action menu shortcuts (Terminate requires typed-name confirm).
- `:deploy LABEL` — ship an existing application version to the selected env.
- `:deploy LABEL --preview` — open a side-by-side overlay of the currently-deployed version vs the candidate (label, description, S3 source, timestamp + rollback / traffic warnings) without dispatching.
- `:deploy --from PATH [--label L] [--describe D] [--no-deploy]` — upload a local `.zip` (or `--from s3://bucket/key`), register a new version, optionally deploy.
- `:upgrade [ARN]` — list compatible platforms; with ARN, dispatch the migration.
- `:clone NEWNAME` — clone the selected env.
- `:scale N` / `:stop` / `:start` — set ASG min=max=N / 0 / 1.
- `:capacity` — modal form to edit Min / Max / Instance type / Cooldown in one shot (pre-filled from `DescribeConfigurationSettings`).
- `:swap TARGET` — swap CNAMEs (Y/N confirm).
- `:abort` — abort an in-flight env update.

### Write — env config

- `:env list | set KEY VAL | unset KEY` — application env-var editor.
- `:tag KEY VALUE` / `:untag KEY` — env tag editor.
- `:set-option NAMESPACE OPTION VALUE` / `:unset-option NAMESPACE OPTION` — generic option-settings escape hatch.
- `:instance-type TYPE` — EC2 instance type (e.g. `t3.medium`).
- `:keypair NAME` / `:service-role ARN` / `:instance-profile NAME` — security tab.
- `:public-ip on|off` / `:elb-scheme public|internal` — network tab.
- `:subnets` / `:elb-subnets` / `:security-groups` — MultiSelect picker forms pre-filled with the env's current selection (lists available subnets / SGs from the VPC).
- `:deployment-policy AllAtOnce|Rolling|RollingWithAdditionalBatch|Immutable|TrafficSplitting` — deploy policy.
- `:rolling-update on|off` — ASG rolling-update policy.
- `:health-check-url /path` — HTTP health-check path.
- `:logs-stream on|off [--retention DAYS]` — toggle CW Logs streaming.
- `:logs-tail [LOG_GROUP]` — open a live streaming overlay for a CW Logs group (auto-picks `web.stdout.log`).
- `:notify EMAIL_OR_SNS_ARN | off` — notification endpoint.
- `:managed-window DAY HOUR | off` — managed-platform-updates window.

### Write — application versions / configs / alarms / platforms

- `:delete-version LABEL [--force]` — delete an application version (with optional source-bundle removal).
- `:config-save NAME` / `:config-apply NAME` / `:config-delete APP NAME` / `:config-inspect NAME` — saved-configuration templates.
- `:alarm-create NAME KIND THRESHOLD [OP]` — CloudWatch alarm (KIND: `health`, `4xx`, `5xx`, `latency`).
- `:alarm-delete NAME` — remove a CW alarm.
- `:custom-platform-delete ARN` — delete a custom EB platform.
- `:metric add LABEL NAMESPACE NAME [STAT] [DIM=VAL,...]` / `:metric remove LABEL` / `:metric list` — custom Metrics-tab charts.

### Multi-account / multi-region

- `:region all` — fan across `extra_regions` + current.
- `:account NAME` — switch to a configured AssumeRole account (see Configuration).
- `:accounts` — list AWS-Organizations child accounts; switch hints rendered for those with a configured `accounts.NAME`.
- `:find-env SUBSTRING` — scan every profile in `~/.aws/{config,credentials}` **and** every configured AssumeRole account in REGION.
- `:org-health` — aggregate env / red counts per profile + per configured AssumeRole account.

### Multi-env

- `space` — toggle multi-select.
- `:batch-rebuild` / `:batch-restart` — dispatch a non-destructive action across the selection.
- `:batch-deploy LABEL` — deploy the same version to every selected env in parallel.
- `:batch-tag KEY VALUE` / `:batch-untag KEY` — fan a tag write across the selection.
- `:batch-set-option NAMESPACE OPTION VALUE` — fan an option-settings write across the selection.
- `:deselect` / `:select-clear` — clear selection.

### Output / yank

- `:export` — yank filtered view as TSV.
- `:json` — yank filtered view as JSON array.
- `:report` / `:markdown` — yank filtered view as a Markdown table.

### Read-only mode

- `:readonly on|off` — toggle. `--read-only` on the CLI also locks every write surface.

## Configuration

`~/.config/ebman/config.toml`:

```toml
# Refresh interval in seconds (default 15).
refresh_interval_secs = 15

# Extra regions to expose in the region picker, comma-separated.
extra_regions = ""

# Theme: "dark" (default), "light", or "high-contrast".
theme = "dark"

# Glyph set: "unicode" (default), "ascii" for low-feature terminals,
# "powerline" (alias "nerd") for Powerline-patched / Nerd Fonts, or
# "auto" to probe the terminal at startup and pick powerline if its
# support is detected (one-cell U+E0B0 advance), unicode otherwise.
icons = "unicode"

# Per-profile theme override — pin a theme per AWS profile so the screen
# itself says "you're in prod" without reading the breadcrumb. Format:
# "PROFILE:THEME,PROFILE:THEME". Theme names match the `theme = ...` key.
profile_themes = "prod:high-contrast,staging:dark"

# Start with these toggles on (state.toml takes precedence after first run).
redact_default = false
grouped_default = false

# Notification bell on increase in Red-env count.
notify_bell = false

# Tag policy — flag envs missing any of these tags in the Config tab.
required_tags = "Owner,Project"

# Webhook URL to POST to when an env transitions to Red.
webhook_url = ""

# AssumeRole targets reachable via `:account NAME`. One stanza per
# account. `source_profile` carries the base creds for the
# sts:AssumeRole call. `external_id` and `region` are optional.
# The temporary credentials build a fresh SdkConfig carrying only the
# assumed-role identity — source-profile creds never leak into request
# signing once the switch lands.
accounts.prod.role_arn = "arn:aws:iam::111122223333:role/EbmanReadOnly"
accounts.prod.source_profile = "default"
accounts.prod.region = "eu-west-2"
# accounts.prod.external_id = "..."
```

`~/.config/ebman/keys.toml` (optional) — custom keybindings:

```toml
# Aliases must be F1-F12 or an uppercase A-Z; map to a :command.
F1 = "refresh"
F2 = "region us-east-1"
Q = "history"
```

`~/.config/ebman/commands.toml` (optional) — user plugin commands. Each `:NAME` substitutes `{name}` / `{cname}` / `{application}` / `{tier}` / `{region}` / `{profile}` placeholders and yanks the rendered command to the clipboard.

```toml
[commands.tunnel]
template = "aws ssm start-session --target $(aws ec2 describe-instances --filters Name=tag:elasticbeanstalk:environment-name,Values={name} --query 'Reservations[].Instances[].InstanceId' --output text) --profile {profile}"
description = "Yank a tunnel command into clipboard"
```

`~/.config/ebman/state.toml` is managed by the app — filter / sort / cursor position / named filters / saved views / pinned envs / custom metrics live there.

## Headless interface (`--control-socket`)

Launch ebman with `--control-socket PATH` to expose a Unix-socket interface. A second binary, `ebman ctl <op>`, is the one-shot client (defaults to `~/.cache/ebman/control.sock`).

```bash
ebman ctl state                   # JSON: mode, profile, region, account, envs, selected, ...
ebman ctl screen                  # plain-text dump of the current frame
ebman ctl key Down                # synthesise a keypress
ebman ctl key Ctrl+R              # … or a combo
ebman ctl cmd ':region eu-west-2' # run a : command
```

Useful for integration tests, screenshot capture, scripted workflows.

## What's stored locally

- `~/.config/ebman/config.toml` — user configuration (see above).
- `~/.config/ebman/keys.toml` — optional custom keybindings.
- `~/.config/ebman/commands.toml` — optional plugin commands.
- `~/.config/ebman/state.toml` — persisted UI state: profile, region, filter, sort, grouping, redact, selected env, named filters, saved views, pinned envs, aliases, hidden columns, custom metrics. No credentials.
- `~/.cache/ebman/ebman.log` — application log; rotates as needed. Set `RUST_LOG=debug` for verbose output.
- `~/.cache/ebman/audit.log` — every dispatched action and outcome (account, profile, region, action, target). Rotates at 1 MiB to `audit.log.1`.
- `~/.cache/ebman/crash-*.log` — panic backtraces (10 most recent kept; 30-day TTL).
- Clipboard — `y` / `Y` / `^Y` / `^W` write via `arboard`.

## Safety model

- **Read-only mode** (`--read-only` or `:readonly on`) disables every write surface: action menu, DLQ resend / purge, all `:`-commands that mutate state. A green `READ-ONLY` pill in the header makes it visible.
- **Strict-typed confirm** for irreversible actions: typing the env name is required to Terminate; typing the literal string to Purge.
- **Pre-flight checks** in the confirm modal: `DescribeInstancesHealth` impact count, last 3 events, traffic warnings for env-in-deploy / recently-changed / currently-Red.
- **Audit log** records dispatch + outcome of every action.

## Distribution

- **Cargo**: `cargo install ebman` from crates.io; `cargo install --path .` from a checkout.
- **GitHub Releases**: tagging `v<X.Y.Z>` triggers `.github/workflows/release.yml`, which builds release binaries for `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, and `x86_64-apple-darwin` and attaches tarballs + SHA-256 checksums to a draft release.
- **Homebrew**: tap lives at [`tombaldwin/homebrew-tap`](https://github.com/tombaldwin/homebrew-tap). Per-release: bump the version + 3 platform SHAs in `Formula/ebman.rb` in both this repo (for `brew install --formula PATH`) and the tap.

## Development

```bash
cargo build
cargo test
cargo fmt --all
cargo clippy --all-targets -- -D warnings
```

See `BACKLOG.md` for in-flight and planned work. See `CLAUDE.md` for the AI-assisted-contributor rules (the project has been developed heavily with Claude Code).

## License

Dual-licensed under MIT or Apache-2.0. See `LICENSE-MIT` and `LICENSE-APACHE`.
