@sayuthisobri/awan
v0.1.0
Published
Multi-cloud resource management TUI inspired by k9s
Readme
awan
A k9s-inspired terminal UI for browsing AWS resources. Bun + TypeScript +
React on OpenTUI, with both a real AWS
SDK-backed data path and a deterministic mock data path behind one
ICloudService interface.
:ec2 :vpc :sg :lb :nacl :tgw :rds :eks :ecs :secrets
:route53 :s3 :iam :acm :tg :volume :rds-subnet-groups …(: opens the command prompt; : again cancels. See
src/actions/commands.ts for the full list.)
Quick start
bun install
bun run dev # launches the TUI (src/main.tsx)
bun test # bun's runner, vitest-compatible API
bun run typecheck # tsc --noEmit, strict
bun run lint # eslint flat config
bun run format # prettier --write src/ tests/Override the data source at runtime:
| Env var | Effect |
| ---------------- | ----------------------------------------------------------------- |
| AWAN_DATA=mock | Force mock data even for SSO profiles (useful for demos / tests). |
| AWAN_DATA=sso | Require a real SSO profile; throws otherwise. |
| AWANAN_CONFIG | Point at a single YAML file that replaces the merged config. |
By default awan reads ~/.aws/config for profiles and merges
config.yaml with ~/.config/awan/config.yaml.
What ships today
| Module family | What you can browse | | --------------- | ---------------------------------------------------------------- | | EC2 / Storage | Instances, EBS volumes | | VPC | VPCs, subnets, security groups + rules, NACLs + rules, ENIs, LBs | | ELBv2 | Load balancers, listeners, listener rules, target groups | | RDS | DB instances, subnet groups, parameter groups | | EKS | Clusters, node groups | | ECS | Clusters, services | | Route53 | Hosted zones | | S3 | Buckets | | Secrets Manager | Secrets | | ACM | TLS certificates | | IAM | Roles | | Transit Gateway | TGWs, attachments, route tables |
SSO profiles are detected automatically; awan opens a modal prompt to
refresh an expired token and re-runs the failed request once
aws sso login succeeds.
Architecture in one screen
┌────────────────────────┐
keypress ──▶ │ CommandPrompt / keys │ runAction(path, args)
└──────────┬─────────────┘
▼
┌────────────────────────┐
│ Action registry │ src/actions/registry.ts
│ (path → handler) │
└──────────┬─────────────┘
▼
┌─────────────────────────────────────────────┐
│ pushView / popView / replaceView / … │ src/actions/navigation.ts
│ (mutates useAppStore.stack — only place) │
└──────────────────────┬──────────────────────┘
▼
┌────────────────────────┐
│ View stack: BaseView │ src/components/Base/
│ → ListView / Detail │
└──────────┬─────────────┘
▼
┌─────────────────────────────────────────────┐
│ useResourceList / useParallelFields │ src/hooks/
│ + getService(profile) → ICloudService │
└──────────┬──────────────────┬───────────────┘
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ MockAwsEc2Service│ │ SdkAwsService │ src/services/aws/{ec2,sdk}/
│ (deterministic) │ │ (@aws-sdk/*) │
└──────────────────┘ └────────────────────┘Two important invariants:
- The UI never imports
@aws-sdk/*directly. All cloud calls go throughgetService(profile)(src/services/factory.ts) which picks the mock or the SDK implementation. Non-AWS providers getUnsupportedService, which throws on every method. - Navigation goes through the helpers in
@/actions, not the store directly. CallinguseAppStore.pushView()from a view bypassespruneFiltersand breaks the single-source-of-truth invariant thattests/contracts/enforces.
The full set of architectural rules — keybinds, refresh, breadcrumbs,
SSO recovery, tests, hook inventory, service-layer invariants — is in
CONSISTENCY.md. Read it before adding a view, a
hook, or a service method.
Project conventions (imports, formatting, types, naming, error
handling, commits) are in AGENTS.md.
How to add a new module
A "module" is one AWS service — e.g. ACM, RDS, Transit Gateway. The end-to-end checklist:
1. Define the resource types
Add interfaces to src/types/cloud.ts (or the appropriate sub-file).
Mirror the SDK shape but only what the views need.
2. Add the ICloudService method
src/services/base.ts is the contract. Add the method signatures —
both list and get — even if you only need one to start. Every method
takes profileId so the implementation can route per-profile if it
needs to later.
3. Wire the mock
MockAwsEc2Service in src/services/aws/ec2.ts implements the full
ICloudService surface (name notwithstanding). Add your methods there
plus a fixture array. Tests rely on this data path — never make real
AWS calls in bun test.
4. Wire the SDK
Create src/services/aws/sdk/<your-service>.ts. Use
buildCredentials(profile) and wrapSdkError(err, profile) from
sdk/client.ts so SSO expiry surfaces as SsoExpiredError. Then add
the methods to the composition in src/services/aws/sdk/index.ts.
5. Add the UnsupportedService stub
src/services/factory.ts exposes UnsupportedService for non-AWS
providers. Add the same method names returning this.fail() so the
interface compiles.
6. Build the views
Each module is a folder under src/components/Modules/AWS/<Name>/:
index.ts— re-export everything<Name>ListView.tsx— list page (usesuseResourceList)<Name>DetailView.tsx— detail page (usesuseParallelFields)
Every view must be wrapped in BaseView, declare its
metadata.breadcrumb via the typed crumbs.<provider>.<module>.*()
helpers in src/utils/breadcrumbs.ts, and register handlers for every
non-local keybind. findUnregisteredKeybinds() (called from
BaseView) logs drift warnings at mount.
7. Add breadcrumbs
src/utils/breadcrumbs.ts is the single source of truth for the
strings used as view-state keys. Add list(), detail(id), plus any
context-scoped helpers you need (e.g. targetGroups(lbArn)).
8. Surface it
Two entry points, both required:
ProviderView(src/components/Modules/Profile/ProviderView.tsx) — add a row so the user can navigate from the service menu.commands.ts(src/actions/commands.ts) — add aregisterAction('command/<short>', …)so the user can jump from anywhere with:<short>. Use thepushList({…})helper so the navigation resets to the provider view first.
Update the example hint at the bottom of CommandPrompt.tsx to include
your new short name.
9. Tests
- Mock path: a
tests/services/<svc>-mock.test.tsexercising the mock methods. - SDK path: stub
@aws-sdk/client-<svc>viavi.mock(...)(see the existing SDK tests for the pattern). - Contract: add a
tests/contracts/test if you introduce a new convention. Existing contract tests catch things like directuseAppStore.subscribeusage, unregistered global/nav actions, and unregisteredcommand/*paths.
10. Verify
bun run typecheck && bun run lint && bun testKeep commits scoped per module: feat(acm): add certificate list view
is the kind of granularity the project uses. Conventional commits, one
module per commit.
Common gotchas
A non-exhaustive list; full table in AGENTS.md.
DescribeTargetGroupsCommanddoes not return target-group attributes (stickiness, deregistration delay). UseDescribeTargetGroupAttributesCommandfor those.DescribeRulesCommanddoes not return the parentListenerArnon a rule; passlistenerArnthrough props if a view needs it.DescribeNodegroupCommandparameter isnodegroupName(all lowercase) — the SDK key is case-sensitive.- SDK mappers must never throw on missing optional fields. Use
?? defaultsand filterundefinedids before constructing rows. - Detail-row activation logic must guard on
state.kind === 'ready'before reading data; ignore activation on rows still inloadingorerror.
Layout
src/
actions/ # action registry + core actions (navigation, global, commands)
components/
Base/ # BaseView, ListView, DetailTable, ScrollBox, …
Layout/ # AppShell, BreadcrumbBar, HelpBar, CommandPrompt, …
Modules/
Profile/ # profile list + provider service menu
AWS/ # one folder per AWS service family
hooks/ # useResourceList, useParallelFields, useRefreshTick, …
logs/ # file-only logger with size-based rotation
rendererRef.ts # singleton handle to the OpenTUI CliRenderer
services/
base.ts # ICloudService contract
factory.ts # getService(profile) + UnsupportedService
aws/
ec2.ts # MockAwsEc2Service (full mock implementation)
sdk/ # one file per AWS service, composed by sdk/index.ts
discovery/ # ~/.aws/config parsing, SSO detection
store/ # zustand stores (useAppStore, useActionStore, useCommandHistory)
types/ # CloudProfile, ViewMetadata, per-service resource types
utils/ # breadcrumbs, viewState, sso-recovery helpers, …
tests/
services/ # mock data path coverage
components/ # OpenTUI render tests (use tests/helpers/testRender.ts)
store/ # zustand slice tests
hooks/ # hook contract tests
utils/ # breadcrumb + viewState + logger helpers
actions/ # command + ctx action tests
config/ # loadConfig tests
logs/ # logger rotation tests
types/ # type-guard and helper tests
contracts/ # cross-cutting invariants (run in CI to catch drift)
helpers/ # shared test utilities (testRender, destroyRenderer)