Note: this codebase is AI-generated, under active development and testing, and should be used at your own peril.
a1s is a k9s-style terminal UI for AWS: browse, edit, create, and observe 1000+ resources
across accounts and regions, powered by the CloudControl API.
- Highlights
- Install
- First Run
- Conventions
- Views
- Writing Resources
- Observability
- Service-Specific Features
- Read-Only Mode
- Theming
- Resource Type Aliases
- SDK Profile Discovery
- SSO Credentials
- Configuration
- Development
- License
- Browse any CloudControl resource across credentials, regions, and scopes —
filter, sort, mark rows, pick columns (including
tag:Keytag columns), and view or edit resource details as YAML. - Multiple credential types: SDK profiles, assumed roles (with chaining), and self-managed IAM Identity Center SSO registrations.
- Scopes: one credential in one region, or a named group of region scopes.
- Customizable views: persisted, per-resource type views with custom columns (including tag columns) and sorting.
- Export resource information: Export individual or bulk resource details from your custom views to JSON(L)
- Related-resource jump: press
oon any row to see jumpable references;enteron a reference peeks that resource read-only, and peeks chain. References that use a secondary key (one that is not the target type's primary identifier) are resolved on demand by a bounded, cancellable scan of the target type before the peek opens. The reference catalog is user-extendable. - Write resources:
+creates from a schema-derived YAML skeleton,eedits the live YAML with a diff/confirm step, bulk-delete via marked rows. - Type catalogue:
:typesopens a sorted catalogue of every known CloudFormation type — description, status, mutability, and operations — withenterto activate one. - Read-only mode:
--readonly(orreadOnly: true) disables all mutations; header shows[RO]. - Theming: built-in skins plus user
themes/directory; per-credential accent colors as a visual guard rail for production accounts. - CloudWatch metrics:
Mopens a sparkline grid for catalogued types (Lambda, EC2, DynamoDB, RDS, SQS, SNS, …), with configurable named views. - Logs and history:
ltails CloudWatch logs (live-follow withf);Hopens CloudTrail event history with configurable time windows. - Scope change watch: background CloudTrail polling raises a
● N changesbadge;:changesopens the scope-wide feed; stale rows are marked·. - CloudWatch alarms: a
▲ N alarmsbadge counts alarms currently in ALARM state (all group-member regions summed);:alarmsopens the alarm state-transition history feed, the companion to:changes. The badge clears itself when alarms recover. - Service-specific surfaces: S3 object browser, DynamoDB query-first viewer,
Kinesis shard tailer, SQS poll modal, SNS publish, Step Functions executions,
Secrets Manager reveal, ECS exec, Lambda invoke, SSM sessions, CloudFormation
stack events, EKS → k9s handoff.
Note: the service-specific interfaces are the most likely place you will find bugs or broken functionality..
Pre-release: build from source with Go 1.26+.
go install github.com/chucklehead-dev/a1s/cmd/a1s@latestOr build a local binary:
go build -o ./a1s ./cmd/a1sTagged releases will ship archives for Linux (amd64/arm64), macOS (arm64), and Windows (amd64/arm64) on GitHub Releases, plus Homebrew, Scoop, and WinGet packages.
Start the TUI:
a1sIf no credentials or scopes exist, the app opens the credentials table.
a1s uses the AWS SDK for Go and the credentials available to it: SDK profile
credentials can come from the normal AWS config files, environment variables,
SSO cache, instance metadata, or any other SDK-supported provider. The principal
you browse with needs permissions for the AWS services and CloudControl calls
used by the selected resource type; some types are region-specific, some are
global, and CloudControl availability varies by type and region.
Create a credential:
- Press
sfor an SDK profile credential — a picker lists the profiles discovered in your AWS config/credentials files (see SDK Profile Discovery). - Press
afor an assume-role credential. - Press
rfor an SSO registration. - Mark one or more SSO registrations and press
oto create SSO role credentials.
Create a scope:
- Open the command palette with
:. - Run
scopeorscopes. - Press
rto create region scopes from one or more credentials. - Press
gto group marked region scopes. - Press
/inside the role and region-scope pickers to filter the list live (same substring /re:/~/!grammar as the table filter).
Browse resources:
- Open the command palette with
:. - Enter a resource type such as
AWS::S3::Bucket, or use an alias such ass3. - Move with the arrows or
j/k;h/lpan wide tables horizontally. - Press
yto open the selected resource as YAML;/inside the detail view searches the YAML (n/Njump between matches). - Press
Yto copy the selected row's identifier to the clipboard. - Press
/to filter the active resource table. Filters have modes: plain text is a case-insensitive substring,re:is a regex,~is a fuzzy subsequence match, and a leading!inverts any of them. - Press digits
1–9(and0for the 10th column) to sort by the addressed visible column; repeat to cycle ascending → descending → off. The sort persists per resource type. - Press
cto choose columns, includingtag:Keytag columns. - Press
spaceto mark rows (ctrl+spacemarks a range,ctrl+\clears marks); bulk actions like delete and tagging operate on marked rows. - Press
shift+pto prefetch resource details. - Press
ctrl+sto export the table to JSONL (visible columns or full properties). - Press
ctrl+rto refresh the active screen. - Press
Eto open the grouped listing-error table when a multi-region or group browse hits per-scope failures. - Recall earlier palette commands and filter queries with up/down — both prompts keep a per-session history.
- Press
?to open the fullscreen help screen (grouped by General, Resource Table, and service-specific sections). - Run
q!from the command palette to quit.
Press o to open the selected resource's related references; enter on a
jumpable row peeks the referenced resource read-only, and peeks chain.
Run :types from the command palette to open the type catalogue: a sorted table
of every known CloudFormation type with Type, Description, Status, Mutability,
and Ops columns. The Ops column is a fixed LRCUD encoding — List, Read, Create,
Update, Delete — with - for an operation the type does not support (a
read-only type reads LR---, an immutable type LRC-D). When a scope is active,
types that are not enabled anywhere in it render faint. Press / to filter the
catalogue, and enter to activate the selected type: with a scope active it
opens that scope's resource list on the type; with no scope active it drops into
the scope picker so you can choose one first.
The same verbs work everywhere they make sense:
| Key | Verb | Where |
|---|---|---|
enter |
Drill into the type's primary surface | S3 objects, DynamoDB viewer, Kinesis shards, SFN executions, Secrets reveal, CFN stack events, EKS → k9s |
+ |
Create / produce / publish | Resource create, Kinesis record, SQS message, SNS publish, SFN execution, S3 object, DynamoDB item |
ctrl+s |
Save / export to a file | Table export (JSONL), S3 object download, SQS message body, Kinesis record body, DynamoDB item JSON |
U |
Produce / upload from a file | S3 upload, SQS message, Kinesis record, DynamoDB item |
/ |
Filter the current table or picker | Resource table, type catalogue, S3 browser, pickers, YAML search |
[ / ] |
Widen / narrow the time range | Logs, CloudTrail, metrics |
esc |
Back / close one level | Everywhere |
All mutating verbs are blocked in read-only mode. Most hotkeys are rebindable via
a keybindings: section in config.yaml — see
docs/CONFIGURATION.md.
Per-type table layout lives in a k9s-style views: section of config.yaml:
an ordered columns list (property names, AccountID/Region origin
columns, or tag:Key entries) plus a persisted sortColumn: Name:asc.
Column-picker and digit-sort changes write back to the active type's view.
Press T over marked rows to bulk-set or remove a tag, then use c → t in
the column picker to add a tag:Key column that shows the tag values inline.
+ opens a schema-derived YAML skeleton in $EDITOR; fill in the required
fields, save, and confirm to send the create through CloudControl. Status polls
until the operation reaches SUCCESS and the new row appears in the list.
e opens the selected resource's live YAML in $EDITOR. After saving, a1s
shows a diff and asks for confirmation before patching via CloudControl. Press
esc to cancel without writing.
Press l on a log-capable resource to tail its CloudWatch logs — f follows
live; press H on any resource for its CloudTrail event history. [/] widen
and narrow the time window.
A resource is log-capable either by convention — the group is derived from
the row's identifier: log groups, Lambda functions (/aws/lambda/<name>),
CodeBuild projects (/aws/codebuild/<name>), EKS clusters
(/aws/eks/<name>/cluster), AppSync APIs — or by configuration: any
resource whose loaded detail carries a log-group reference (Step Functions
logging, ECS task awslogs-group, API Gateway access logs, custom Lambda/
CodeBuild groups). The configured path resolves once the row's detail is
loaded.
On the CloudTrail tables (H and :changes), c opens a column picker for the
event columns, just like the resource table.
While a scope is active, a1s watches its CloudTrail event history in the
background and raises a ● N changes badge when something in the scope
changes; run :changes to open the scope-wide change feed. Rows that were
touched by a recent change event are marked with a · staleness dot in the
table until they are refreshed.
While a scope is active, the same background watch polls each member region for
alarms currently in ALARM state and renders a ▲ N alarms badge beside the
● N changes badge. The count is the live sum across all member regions; it
clears itself automatically when alarms recover — it is not an unread-items
counter and is not reset by opening :alarms.
Run :alarms to open the alarm state-transition history feed: every member
region's DescribeAlarmHistory StateUpdate items merged into the shared
event-stream pane (Timestamp | Alarm | Summary columns), windowed and
navigable with [/] like the other history feeds. The peek shows the raw
HistoryData JSON for each transition. Requires an active scope.
Press M on any row whose resource type has catalogued metrics to open a 2×2
sparkline grid for that resource. The grid shows up to four preset metrics
(Invocations/Errors/Duration/Throttles for Lambda, CPU/network for EC2, and so
on) loaded in one batched call. Use [ to widen the time window (1h → 3h →
24h → 7d), ] to narrow it, and r to refresh. Press esc to close.
Press tab to cycle between a type's metric views when more than one is
configured. The preset catalog can be extended in config.yaml: a
metrics.views section adds named per-type views, and metrics.custom declares
free-standing CloudWatch views (literal dimensions, not tied to a row) that open
with :metrics <name> from the command palette — bare :metrics lists the
configured names. See docs/CONFIGURATION.md.
Press enter on an AWS::S3::Bucket row to open the S3 object browser for
that bucket. The browser is prefix-aware: CommonPrefixes render as folder rows
(pinned above object rows), and enter on a folder drills into that prefix. Press
esc to pop back one level; at the root it closes the browser.
| Key | Action |
|---|---|
enter |
Drill into a prefix folder |
y |
Object metadata detail (HeadObject) |
o |
Open object for viewing — text verbatim, JSON pretty-printed, binary as hex |
Y |
Copy a presigned GET URL to the clipboard |
/ |
Filter objects and prefixes in the current listing |
ctrl+s |
Export the selected object to a local path |
+ |
New object via $EDITOR then a key prompt |
U |
Upload a local file (multipart for large files) |
ctrl+d |
Delete marked objects (batch, y/n confirm) |
esc |
Pop prefix / close browser |
The breadcrumb s3://bucket › folder › subfolder › reflects the current prefix
stack.
Press enter on an AWS::DynamoDB::Table row to open the query-first viewer.
DescribeTable resolves the key schema (PK, SK, GSIs) before the form opens; the
schema is cached so switching back skips the round-trip.
The query form shows: PK value (required), optional SK condition (=,
begins_with, between, <, >), and an index selector (base table or any GSI
by name). Submitting runs a Query; results render in a table with PK and SK
pinned as the first columns.
Scan is the explicit fallback: shift+s shows a cost warning. Confirming
runs a Scan with a 1-page cap; m loads the next page.
| Key | Phase | Action |
|---|---|---|
enter |
Resource list | Open viewer |
tab |
Query form | Cycle field focus |
enter |
Query form | Run query |
shift+s |
Viewer | Scan (cost-gated); m loads the next page |
y |
Results | Item JSON detail |
e |
Results | Edit item in $EDITOR → diff → PutItem |
+ |
Results | Create item via $EDITOR |
ctrl+s |
Results | Save selected item JSON to a file |
U |
Results | Create item from a file |
esc |
Results / detail | Back to query form / results |
Press enter on an AWS::Kinesis::Stream row to open the shard table. The
table lists open shards; mark shards with space (or leave all unmarked to tail
the cursor shard), choose an iterator type, then press enter to start tailing.
Iterator types: L sets LATEST (default — only new records); t sets
TRIM_HORIZON (all seeded records from the beginning).
| Key | Context | Action |
|---|---|---|
enter |
Resource list | Open shard table |
space |
Shard table | Toggle shard mark |
L |
Shard table | Set iterator to LATEST |
t |
Shard table | Set iterator to TRIM_HORIZON |
enter |
Shard table | Start tail on marked (or cursor) shard(s) |
d |
Stream pane | Decode record body (JSON pretty-print or plaintext) |
f |
Stream pane | Toggle live follow (poll-based, 2s–30s backoff) |
+ |
Stream pane | Produce a record via $EDITOR + partition key prompt |
ctrl+s |
Stream pane | Save record body to a file |
U |
Stream pane | Produce a record from a file |
esc |
Any | Back / close |
Press p on an AWS::SQS::Queue row to open the poll modal. Fill in the
bounded poll form (number of messages, wait seconds, visibility timeout) and
submit to receive messages.
Important: SQS has no side-effect-free peek. Every ReceiveMessage call
increments ApproximateReceiveCount on each returned message, even in peek mode.
When receiveCount exceeds the queue's maxReceiveCount, the message is
automatically moved to the DLQ — without any explicit consume action. The modal
warns of this prominently in the results header.
| Key | Context | Action |
|---|---|---|
p |
Resource list | Open poll modal |
enter |
Poll form | Submit poll |
x |
Results | Consume (delete) selected message — confirm-gated |
+ |
Results | Produce a message via $EDITOR |
ctrl+s |
Results | Save message body to a file |
U |
Results | Send a message from a file |
r |
Results | Redrive DLQ messages to source (StartMessageMoveTask) |
esc |
Any | Close modal |
Polling is allowed in read-only mode; consume (x), produce (+), and redrive
(r) are blocked.
Press + on an AWS::SNS::Topic row to publish. a1s opens a parameter form
(subject; FIFO topics also require a message group ID), then $EDITOR for the
message body, then a confirm step that shows the topic's subscriber count before
sending.
| Key | Context | Action |
|---|---|---|
+ |
Resource list | Publish to topic (form → $EDITOR body → confirm) |
Press enter on an AWS::StepFunctions::StateMachine row to open its
executions in the event-stream pane; press + to start a new execution
(input JSON via $EDITOR).
| Key | Context | Action |
|---|---|---|
enter |
Resource list | Open executions stream |
+ |
Executions pane | Start execution via $EDITOR |
Press enter on an AWS::SecretsManager::Secret row to reveal its value
behind a confirm gate. Revealed values are never logged, cached, or written to
disk.
| Key | Context | Action |
|---|---|---|
enter |
Resource list | Reveal secret value (confirm-gated) |
Press x on an AWS::ECS::Service row to exec into a running container
(confirm-gated; hands off to the session-manager plugin like SSM sessions).
| Key | Context | Action |
|---|---|---|
x |
Resource list | Exec into a container (confirm-gated) |
Press i on an AWS::Lambda::Function row to invoke the function. a1s opens
the payload in $EDITOR as a JSON file (defaulting to {}); save and quit to
advance to the confirmation modal, which shows the function name, payload
size, and invocation type (RequestResponse or Event, toggled with t).
Press y to invoke. The result view shows the HTTP status, any function error,
the response payload, and the CloudWatch log tail. Press l from the result
view to jump directly to the function's CloudWatch logs.
Press ! on an AWS::EC2::Instance row to start an SSM Session Manager
session for that instance. a1s verifies that the instance is SSM-managed, then
lets you choose between an interactive shell or a port-forwarding session. A
port-forwarding session prompts for local and remote ports. Press y on the
confirmation modal to start the session; a1s calls ssm:StartSession and
hands the terminal over to the session-manager-plugin binary for the duration
of the session.
SSM sessions require the session-manager-plugin binary on PATH. Install it
from the AWS documentation.
SSM sessions are blocked in read-only mode.
Press enter on an AWS::CloudFormation::Stack row to open the stack's
event history in the event-stream pane. The pane shows each stack event as
a row (Timestamp | Logical ID | Status); the peek detail carries the full event
including the status reason, which is too long to fit in a table cell but is
what operators look for when diagnosing a failed deployment. Window keys
([/]) widen and narrow the time range; r refreshes.
| Key | Context | Action |
|---|---|---|
enter |
Resource list | Open stack event history |
[ / ] |
Event pane | Widen / narrow time window |
r |
Event pane | Refresh |
enter |
Event pane | Peek event detail (status reason) |
esc |
Event pane | Close |
Press enter on an AWS::EKS::Cluster row to hand the terminal to
k9s. Before any AWS call fires, a confirmation modal
names the cluster, scope, and role. Confirming calls eks:DescribeCluster
through the row's origin-scope client, writes an ephemeral kubeconfig to a
0600 temp file (never ~/.kube/config), and launches k9s with KUBECONFIG
pointing at it. The kubeconfig uses an exec credential plugin
(aws eks get-token) so tokens are minted on demand for the whole k9s session.
The temp file is deleted on every k9s exit.
Prerequisites: k9s and aws CLI must be on PATH. If either is missing
a1s shows an install hint and does not open the modal.
Read-only mode: the action is read-only-gated. In read-only mode enter
falls through silently to the standard open-detail behavior rather than showing
a blocked-action notice, because enter has a non-mutating meaning on any row.
| Key | Context | Action |
|---|---|---|
enter |
Resource list | Open k9s confirm modal |
y / enter |
Confirm modal | Describe cluster + write kubeconfig + launch k9s |
n / esc |
Confirm modal | Cancel |
esc |
Preparing | Cancel kubeconfig build |
Start with a1s --readonly (or set readOnly: true in config.yaml) to
disable resource create, edit, and delete for the session. The header shows
an [RO] marker; --readonly=false lifts a read-only config for one run.
skin: <name> in config.yaml selects a color skin, k9s-style: built-ins
plus user skins from a themes/ directory next to the config file. A
theme: block overrides individual roles on top of the skin. Credentials
and scopes accept an accent: color used while that scope is active — a
visual guard rail for production accounts. :accent <color> sets the active
scope's accent live from the command palette (:accent none clears it),
persisting to the same config field.
New configs include common aliases for resource types such as S3 buckets, EC2 instances, EBS volumes and snapshots, RDS instances, IAM roles and policies, Lambda functions, SQS queues, SNS topics, and VPC resources.
Use the command palette to add an alias:
alias buckets AWS::S3::Bucket
Run aliases from the command palette to open the alias table: + adds an
alias, enter edits the selected one, and ctrl+d deletes it (with
confirmation). Aliases are also stored in config.yaml under
resourceTypeAliases, where they can be edited directly.
Press s on the credentials table to open the SDK profile picker. The AWS SDK
for Go has no ListProfiles API, so a1s discovers profiles by scanning the
section headers of the shared config file ([default], [profile NAME] —
[sso-session ...] and [services ...] sections are excluded) and the shared
credentials file ([NAME]), merged config-file order first and deduplicated.
AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE override the default
~/.aws/config / ~/.aws/credentials paths. Each profile is enriched
best-effort with its credential-source type (sso / assume-role / web-identity /
process / static) and region; a profile that fails to load still appears with
blank columns.
Selecting rows creates sdkProfile credentials with the profile name as the
default alias (suffixed for uniqueness). Profiles that already back a
credential show as exists and cannot be re-created. If discovery finds
nothing — or the profile you want is not in the files — press m for the
original manual two-field form; esc there returns to the picker.
| Key | Context | Action |
|---|---|---|
s |
Credentials table | Open the profile picker (starts discovery) |
enter |
Picker | Create credentials for marked rows (or the cursor row) |
space / ctrl+space / ctrl+\ |
Picker | Mark / range-mark / clear marks |
/ |
Picker | Filter rows live (same grammar as the table filter) |
m |
Picker | Manual entry (the original alias + profile form) |
esc |
Picker | Cancel back to the credentials table |
esc |
Manual form | Return to the picker |
In addition to typical SDK-managed credentials, A1s can also make IAM Identity Center SSO registrations directly. SSO registrations are treated as a type of credential. Creating one opens a registration modal and starts OIDC client registration and device authorization. The modal shows the client ID, registration expiration, authorization status, and authorization expiration.
The first SSO flow that writes sensitive material prompts for a passphrase. That
passphrase encrypts SSO client secrets and refresh tokens with
age before they are written to
credentials.yaml. If encrypted credentials already exist, startup prompts for
the passphrase before launching the TUI.
Configuration is split by sensitivity:
config.yamlstores non-sensitive settings, credentials definitions, scopes, aliases, views (columns and sort), fetch settings, keybindings, read-only mode, and skin/theme values.credentials.yamlstores sensitive SSO registration material and is encrypted when it contains non-empty credentials.
By default these files live under the platform user config directory in an a1s
subdirectory. On Linux this is usually ~/.config/a1s.
Caches live under the platform user cache directory in an a1s subdirectory. On
Linux this is usually ~/.cache/a1s.
Useful flags:
a1s --config-dir ./config --cache-dir ./cache
a1s --reset-config
a1s --version--reset-config clears the resolved config, credentials, and cache locations,
then writes current minimal defaults without launching the TUI.
See docs/CONFIGURATION.md for the full config,
keybinding, theme, and cache reference.
go build -o ./a1s ./cmd/a1s
go test ./...See docs/DEVELOPMENT.md for the full maintainer
workflow and docs/ARCHITECTURE.md for design notes.
a1s is licensed under the Apache License 2.0. See
LICENSE.


























