Note
This project was written by AI (Claude Code).
A Git-like CLI for AWS Parameter Store and Secrets Manager. Familiar commands like show, log, diff, and a staging workflow for safe, reviewable changes.
- Git-like commands:
show,log,diff,ls,set,rm - Staging workflow:
edit→status→diff→apply(review changes before applying) - Version navigation:
#VERSION,~SHIFT,:LABELsyntax - Colored diff output: Easy-to-read unified diff format
- Both services: SSM Parameter Store and Secrets Manager
- GUI mode: Desktop application via
--guiflag (built with Wails)
Using Homebrew (macOS/Linux)
brew install mpyw/tap/suveThis installs three binaries:
| Binary | Description |
|---|---|
suve |
CLI + GUI integrated (use suve --gui to launch GUI) |
suve-cli |
CLI only (lightweight, no GUI dependencies) |
suve-gui |
GUI only (standalone desktop app) |
Using go install
# CLI only (recommended)
go install github.com/mpyw/suve/cmd/suve-cli@latest
# CLI + GUI integrated (requires CGO)
CGO_ENABLED=1 go install -tags production github.com/mpyw/suve/cmd/suve@latestNote
The --gui flag in suve requires building with -tags production and CGO. For CLI-only usage, suve-cli is recommended as it has no CGO dependencies.
Using go tool (Go 1.24+)
# Add to go.mod as a tool dependency
go get -tool github.com/mpyw/suve/cmd/suve-cli@latest
# Run via go tool
go tool suve-cli param show /my/paramTip
Using with aws-vault: Wrap commands with aws-vault exec for temporary credentials:
aws-vault exec my-profile -- suve param show /my/paramuser@host:~$ suve param show /app/config/database-url
Name: /app/config/database-url
Version: 3
Type: SecureString
Modified: 2024-01-15T10:30:45Z
postgres://db.example.com:5432/myapp
user@host:~$ suve param show --raw /app/config/database-url
postgres://db.example.com:5432/myappThe show command displays value with metadata; --raw outputs raw value for piping:
# Use in scripts
DB_URL=$(suve param show --raw /app/config/database-url)
# Pipe to file
suve param show --raw /app/config/ssl-cert > cert.pemView version history, just like git log:
user@host:~$ suve param log /app/config/database-url
Version 3 (current)
Date: 2024-01-15T10:30:45Z
postgres://db.example.com:5432/myapp...
Version 2
Date: 2024-01-14T09:20:30Z
postgres://old-db.example.com:5432/myapp...
Version 1
Date: 2024-01-13T08:10:00Z
postgres://localhost:5432/myapp...Use --patch to see what changed in each version:
user@host:~$ suve param log --patch /app/config/database-urlOutput will look like:
Version 3 (current)
Date: 2024-01-15T10:30:45Z
--- /app/config/database-url#2
+++ /app/config/database-url#3
@@ -1 +1 @@
-postgres://old-db.example.com:5432/myapp
+postgres://db.example.com:5432/myapp
Version 2
Date: 2024-01-14T09:20:30Z
--- /app/config/database-url#1
+++ /app/config/database-url#2
@@ -1 +1 @@
-postgres://localhost:5432/myapp
+postgres://old-db.example.com:5432/myappTip
Add --parse-json to pretty-print JSON values before diffing. This normalizes formatting and sorts keys alphabetically, so you can focus on the actual content changes rather than formatting differences:
suve param log --patch --parse-json /app/config/credentialsCompare previous version with latest (most common use case):
user@host:~$ suve param diff /app/config/database-url~Output will look like:
--- /app/config/database-url#2
+++ /app/config/database-url#3
@@ -1 +1 @@
-postgres://old-db.example.com:5432/myapp
+postgres://db.example.com:5432/myappCompare any two specific versions:
user@host:~$ suve param diff /app/config/database-url#1 /app/config/database-url#3Output will look like:
--- /app/config/database-url#1
+++ /app/config/database-url#3
@@ -1 +1 @@
-postgres://localhost:5432/myapp
+postgres://db.example.com:5432/myappNote
The staging workflow lets you prepare changes locally, review them, and apply when ready—just like git add → git diff --staged → git commit.
Caution
Staged values are stored in plain text at ~/.suve/stage.json. If you no longer need pending changes, run suve stage reset --all to clear them.
1. Stage changes (opens editor or accepts value directly):
Tip
To use VSCode or Cursor as your editor, set export VISUAL='code --wait' or export VISUAL='cursor --wait' in your shell profile.
user@host:~$ suve stage param add /app/config/new-param "my-value"
✓ Staged for creation: /app/config/new-param
user@host:~$ suve stage param edit /app/config/database-url
✓ Staged: /app/config/database-url
user@host:~$ suve stage param delete /app/config/old-param
✓ Staged for deletion: /app/config/old-param2. Review staged changes:
user@host:~$ suve stage status
Staged SSM Parameter Store changes (3):
A /app/config/new-param
M /app/config/database-url
D /app/config/old-param
user@host:~$ suve stage diffOutput will look like:
--- /app/config/database-url#3 (AWS)
+++ /app/config/database-url (staged)
@@ -1 +1 @@
-postgres://db.example.com:5432/myapp
+postgres://new-db.example.com:5432/myapp
--- /app/config/new-param (not in AWS)
+++ /app/config/new-param (staged for creation)
@@ -0,0 +1 @@
+my-value3. Apply changes:
user@host:~$ suve stage apply
Applying SSM Parameter Store parameters...
✓ Created /app/config/new-param
✓ Updated /app/config/database-url
✓ Deleted /app/config/old-paramReset if needed:
# Unstage specific parameter
suve stage param reset /app/config/database-url
# Unstage all
suve stage reset --allTip
suve stage apply prompts for confirmation before applying. Use --yes to skip the prompt.
Navigate versions with Git-like syntax.
Note
SSM Parameter Store uses numeric version numbers (1, 2, 3, ...) that auto-increment on each update.
<name>[#VERSION][~SHIFT]*
where ~SHIFT = ~ | ~N (repeatable, cumulative)
| Syntax | Description |
|---|---|
/my/param |
Latest version |
/my/param#3 |
Version 3 |
/my/param~1 |
1 version ago |
/my/param#5~2 |
Version 5 minus 2 = Version 3 |
/my/param~~ |
2 versions ago (~1~1) |
Note
Secrets Manager uses UUID version IDs and staging labels instead of numeric versions.
AWSCURRENT and AWSPREVIOUS are special labels automatically managed by AWS—AWSCURRENT always points to the latest version.
<name>[#VERSION | :LABEL][~SHIFT]*
where ~SHIFT = ~ | ~N (repeatable, cumulative)
| Syntax | Description |
|---|---|
my-secret |
Current (AWSCURRENT) |
my-secret:AWSPREVIOUS |
Previous staging label |
my-secret#abc123 |
Specific version ID |
my-secret~1 |
1 version ago |
Important
When specifying version-only syntax like '#3' or ':AWSPREVIOUS', you must use quotes to prevent shell interpretation of the # (comment) or : characters.
Tip
~ without a number means ~1. You can chain them: ~~ = ~1~1 = ~2
| Service | Aliases |
|---|---|
| SSM Parameter Store | param, ssm, ps |
| Secrets Manager | secret, sm |
| Command | Options | Description |
|---|---|---|
suve param show |
--raw--parse-json (-j)--no-pager--output=<FORMAT> |
Display parameter with metadata |
suve param log |
--number=<N> (-n)--patch (-p)--parse-json (-j)--oneline--reverse--since=<DATE>--until=<DATE>--no-pager--output=<FORMAT> |
Show version history |
suve param diff |
--parse-json (-j)--no-pager--output=<FORMAT> |
Compare versions |
suve param list |
--recursive (-R)--filter=<REGEX>--show--output=<FORMAT> |
List parameters |
suve param set |
--type=<TYPE>--secure--description=<TEXT>--yes |
Create or update parameter |
suve param delete |
--yes |
Delete parameter |
suve param tag |
<KEY>=<VALUE>... |
Add or update tags |
suve param untag |
<KEY>... |
Remove tags |
Staging commands (under suve stage param):
| Command | Options | Description |
|---|---|---|
suve stage param add |
--description=<TEXT> |
Stage new parameter |
suve stage param edit |
--description=<TEXT> |
Stage modification |
suve stage param delete |
Stage deletion | |
suve stage param status |
--verbose (-v) |
Show staged changes |
suve stage param diff |
--parse-json (-j)--no-pager |
Compare staged vs AWS |
suve stage param apply |
--yes--ignore-conflicts |
Apply staged changes |
suve stage param reset |
--all |
Unstage changes |
suve stage param tag |
<KEY>=<VALUE>... |
Stage tag additions |
suve stage param untag |
<KEY>... |
Stage tag removals |
| Command | Options | Description |
|---|---|---|
suve secret show |
--raw--parse-json (-j)--no-pager--output=<FORMAT> |
Display secret with metadata |
suve secret log |
--number=<N> (-n)--patch (-p)--parse-json (-j)--oneline--reverse--since=<DATE>--until=<DATE>--no-pager--output=<FORMAT> |
Show version history |
suve secret diff |
--parse-json (-j)--no-pager--output=<FORMAT> |
Compare versions |
suve secret list |
--filter=<REGEX>--show--output=<FORMAT> |
List secrets |
suve secret create |
--description=<TEXT> |
Create new secret |
suve secret update |
--description=<TEXT>--yes |
Update existing secret |
suve secret delete |
--force--recovery-window=<DAYS>--yes |
Delete secret |
suve secret restore |
Restore deleted secret | |
suve secret tag |
<KEY>=<VALUE>... |
Add or update tags |
suve secret untag |
<KEY>... |
Remove tags |
Staging commands (under suve stage secret):
| Command | Options | Description |
|---|---|---|
suve stage secret add |
--description=<TEXT> |
Stage new secret |
suve stage secret edit |
--description=<TEXT> |
Stage modification |
suve stage secret delete |
--force--recovery-window=<DAYS> |
Stage deletion |
suve stage secret status |
--verbose (-v) |
Show staged changes |
suve stage secret diff |
--parse-json (-j)--no-pager |
Compare staged vs AWS |
suve stage secret apply |
--yes--ignore-conflicts |
Apply staged changes |
suve stage secret reset |
--all |
Unstage changes |
suve stage secret tag |
<KEY>=<VALUE>... |
Stage tag additions |
suve stage secret untag |
<KEY>... |
Stage tag removals |
| Command | Options | Description |
|---|---|---|
suve stage status |
--verbose (-v) |
Show all staged changes |
suve stage diff |
--parse-json (-j)--no-pager |
Compare all staged vs AWS |
suve stage apply |
--yes--ignore-conflicts |
Apply all staged changes |
suve stage reset |
--all |
Unstage all changes |
suve respects the TZ environment variable for date/time formatting:
# Show times in UTC
TZ=UTC suve param show /app/config
# Show times in Japan Standard Time
TZ=Asia/Tokyo suve param show /app/configAll timestamps are formatted in RFC3339 format with the local timezone offset applied. If TZ is not set, the system's local timezone is used. Invalid timezone values fall back to UTC.
suve uses standard AWS SDK configuration:
Authentication (in order of precedence):
- Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN) - Shared credentials file (
~/.aws/credentials) - IAM role (EC2, ECS, Lambda)
Region:
AWS_REGIONorAWS_DEFAULT_REGIONenvironment variable~/.aws/configfile
Warning
Ensure your IAM role/user has appropriate permissions:
- SSM:
ssm:GetParameter,ssm:GetParameterHistory,ssm:PutParameter,ssm:DeleteParameter,ssm:DescribeParameters,ssm:AddTagsToResource,ssm:RemoveTagsFromResource - SM:
secretsmanager:GetSecretValue,secretsmanager:ListSecretVersionIds,secretsmanager:ListSecrets,secretsmanager:CreateSecret,secretsmanager:PutSecretValue,secretsmanager:UpdateSecret,secretsmanager:DeleteSecret,secretsmanager:RestoreSecret,secretsmanager:TagResource,secretsmanager:UntagResource
# Run tests
make test
# Run linter
make lint
# Build CLI (without GUI)
make build
# Build with GUI support
go build -tags production -o bin/suve ./cmd/suve
# Run E2E tests (requires Docker)
make e2e
# Coverage (unit + E2E combined)
make coverage-allTo test against localstack instead of real AWS:
# Start localstack
SUVE_LOCALSTACK_EXTERNAL_PORT=4566 docker compose up -d
# Run commands with localstack
AWS_ENDPOINT_URL=http://127.0.0.1:4566 \
AWS_ACCESS_KEY_ID=dummy \
AWS_SECRET_ACCESS_KEY=dummy \
AWS_DEFAULT_REGION=us-east-1 \
suve param ls
# GUI with localstack
AWS_ENDPOINT_URL=http://127.0.0.1:4566 \
AWS_ACCESS_KEY_ID=dummy \
AWS_SECRET_ACCESS_KEY=dummy \
AWS_DEFAULT_REGION=us-east-1 \
suve --gui
# Stop localstack
docker compose downImportant
Dummy credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) are required to prevent the SDK from attempting IAM role credential fetching.
MIT License