Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
746fdf0
Add AWS config popup
azlyth Aug 17, 2025
04c212b
Interactive UI improvements
azlyth Aug 17, 2025
06dfb7d
Add new declarative terminal component system
azlyth Aug 17, 2025
7f739fe
Finish migration to terminal UI components
azlyth Aug 19, 2025
f5cace3
Copy wizard updates
azlyth Aug 19, 2025
4e27a27
Simplify colors
azlyth Aug 19, 2025
e794e0c
Fix popup rendering
azlyth Aug 19, 2025
0574715
Interactive updates
azlyth Aug 20, 2025
793fc9b
Fix ctrl-b/f paging
azlyth Aug 20, 2025
aeba33e
Fix interactive bugs
azlyth Aug 20, 2025
0577744
Fix interactive UX
azlyth Aug 20, 2025
c26a5c3
Update CLAUDE.md
azlyth Aug 20, 2025
60ac375
Help popup render fixes
azlyth Aug 20, 2025
f51a029
More help popup updates
azlyth Aug 20, 2025
0e5d4fa
Add k8s context selector
azlyth Aug 20, 2025
389cd1b
Fix AWS region handling in interactive
azlyth Aug 20, 2025
4f5f95d
Add key deletion
azlyth Aug 20, 2025
6b34951
Use multiselect for copy key ux
azlyth Aug 20, 2025
adc99a8
Simplify help popup content
azlyth Aug 20, 2025
805f815
Fix error width calculation
azlyth Aug 20, 2025
80af5a9
Add breadcrumb to screen selection
azlyth Aug 20, 2025
ed03a15
Update Makefile commands that contain docker commands
azlyth Aug 20, 2025
437bd96
Use secondary color for key values
azlyth Aug 20, 2025
bd36e3e
Have edit selection work like deleting
azlyth Aug 20, 2025
68b6684
Update ctrl-e/b/f handling
azlyth Aug 20, 2025
a06bf41
Fix test GHA
azlyth Aug 20, 2025
3642ca7
Fix claude settings syntax issue
azlyth Aug 22, 2025
343b900
Drop coverage threshold
azlyth Aug 22, 2025
99aef63
Drop the function coverage threshold too
azlyth Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
"Bash(curl:*)",
"Bash(LOCALSTACK_ENDPOINT=http://localhost:4566 AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 npm test tests/integration/copy-matrix.test.js)",
"Bash(LOCALSTACK_ENDPOINT=http://localhost:4566 AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 node debug-test.js)",
"Bash(git commit:*)"
"Bash(nvm install:*)",
"Bash(nvm use:*)",
"Bash(echo \"Exit code: $?\")",
"WebFetch(domain:productionresultssa0.blob.core.windows.net)"
],
"deny": [],
"additionalDirectories": [
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,5 @@ jobs:
- name: Test Docker build
run: |
echo "🐳 Testing Docker build..."
make build
make run ARGS="--version"
make docker-build
make docker-run-version
118 changes: 110 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,34 @@ The `lib/` directory is now organized into focused modules:
#### Interactive Key Bindings
- `↑↓` or `j/k` - Navigate items
- `/` - Enter search mode (shows `█` cursor)
- `Space` - **Multi-select keys** (toggle individual key selection)
- `e` - Edit mode (only for env/json/AWS secrets)
- `Ctrl+S` - **Copy secrets** (launches copy wizard from Key Browser)
- `Ctrl+S` - **Copy secrets** (selected keys or all if none selected)
- `Ctrl+D` - **Delete keys** (selected keys or current key)
- `Ctrl+V` - Toggle value visibility
- `Enter` - Select/confirm
- `Esc` - Go back or exit search
- `Esc` - Clear multi-selection, exit search, or go back
- `Ctrl+C` - Exit application

### Multi-Select Key Management (`Space` from Key Browser)
- **Individual key selection** - Select specific keys with spacebar toggle
- **Visual indicators** - Checkmark prefix shows selected keys
- **Unified operations** - Both copy (Ctrl+S) and delete (Ctrl+D) respect selections
- **Smart escape behavior** - Esc clears selections → exits search → goes back
- **Selection state display** - Header shows "Selected: X keys" when active
- **Copy behavior**: Selected keys OR all keys if none selected
- **Delete behavior**: Selected keys OR current focused key if none selected

### Copy Wizard (`Ctrl+S` from Key Browser)
- **Smart filtering** - Copies filtered keys if search is active, all keys otherwise
- **Multi-select aware** - Copies selected keys or all keys if none selected
- **Multi-step wizard** - Preview → Format Selection → File/Namespace Selection → Confirmation
- **Context preservation** - Always shows keys being copied and current selections
- **Merge behavior** - Adds new keys to existing files instead of overwriting
- **Context preservation** - Shows exactly which keys will be copied
- **Format support** - Export to .env, .json, or Kubernetes secrets
- **Kubernetes integration** - Full namespace selection, secret listing, and inline secret creation
- **File management** - Choose existing files or create new ones with guided naming
- **Visual feedback** - Inline status updates (copying → success/error) without losing context
- **Automatic backup** - Creates .bak files before overwriting existing files
- **Visual feedback** - Clear explanation of merge behavior in confirmation
- **Automatic backup** - Creates .bak files before modifying existing files
- **Auto-navigation** - Automatically navigates to newly created secrets/files after successful copy

### Editing Features
Expand Down Expand Up @@ -179,7 +191,8 @@ The interactive system now uses a **screen-based architecture** with individual
### File Operations
- **Env files**: Regex parsing with quote handling and escape sequences
- **JSON files**: Validation for flat object structure (no nested objects/arrays)
- **Backup functionality**: Creates `.bak` files before overwriting
- **Merge functionality**: New keys added to existing files, existing keys overwritten
- **Backup functionality**: Creates `.bak` files before modifying existing files
- **Smart exclusions**: Filters out standard config files (configurable via constants)

## Error Handling Strategy
Expand Down Expand Up @@ -358,6 +371,63 @@ This **layered architecture** provides:

## Recent Major Features (2025)

### Multi-Select Key Management System
- **Spacebar selection**: Toggle individual keys in Key Browser screen
- **Visual indicators**: Checkmark prefix (✓) shows selected keys
- **Unified operations**: Both copy (Ctrl+S) and delete (Ctrl+D) use same selection system
- **Smart escape**: Esc clears selections → exits search → goes back
- **Header feedback**: Shows "Selected: X keys" when in multi-select mode
- **Atomic operations**: All operations (copy/delete) work on exact selection
- **Merge behavior**: Copy operations add/overwrite keys instead of replacing files

### Enhanced Copy System
- **Selection-aware**: Copies selected keys OR all keys if none selected
- **File merging**: New keys added to existing files, preserving other content
- **Visual confirmation**: Shows merge behavior explanation in confirmation screen
- **Backup safety**: Creates .bak files before modifying existing files
- **Success feedback**: Shows "merged X keys (Y total)" vs "wrote X keys"

### Delete Operations System
- **Multi-select support**: Delete selected keys or current focused key
- **Ctrl+D hotkey**: Delete keys from Key Browser or secrets from selection screens
- **Type-to-confirm safety**: Must type confirmation text exactly
- **Popup modal**: Overlay preserves context while confirming
- **All secret types**: Supports env, json, AWS Secrets Manager, Kubernetes
- **Clipboard support**: Ctrl+V (or Cmd+V on macOS) to paste confirmation text
- **Visual feedback**: Progressive states (input → deleting → success)
- **Error handling**: Clear messages for failed deletions
- **Auto-refresh**: Lists update after successful operations

### Popup System Architecture
- **PopupManager**: Singleton manager for modal overlays
- **BasePopup**: Foundation class for popup components
- **Overlay rendering**: Intelligent content positioning with ANSI preservation
- **Key routing**: Popup receives key events while preserving base screen
- **Current popups**:
- Delete confirmation (Ctrl+D)
- AWS profile selector (Ctrl+A)

### Declarative Component System
- **40+ UI Components**: Text, Title, List, Box, Modal, Table, ProgressBar, etc.
- **Factory functions**: Consistent API across all components
- **Zone-based rendering**: Header, body, footer zones
- **ComponentScreen**: Base class for declarative screens
- **Automatic features**: Pagination, scrolling, truncation
- **Component examples**:
```javascript
Title('Select a secret'),
List(items, selectedIndex, { paginate: true }),
InstructionsFromOptions({ hasSearch: true, hasDelete: true })
```

### AWS Profile Management (Ctrl+A)
- **Global popup**: Available from any screen
- **Three modes**: Compact → profile-list → region-list
- **Real-time switching**: Updates AWS configuration immediately
- **Visual indicators**: Shows current profile/region in header
- **Search functionality**: Filter profiles and regions
- **Environment persistence**: Updates AWS_PROFILE and AWS_REGION

### Copy Wizard System
- **Ctrl+S hotkey**: Accessible from Key Browser screen
- **Context-aware copying**: Respects current search filters
Expand Down Expand Up @@ -542,6 +612,36 @@ node --test --experimental-test-coverage \
tests/**/*.test.js
```

## Best Practices & Patterns

### Popup Development Pattern
When creating a new popup:
1. Extend `BasePopup` class
2. Implement `render()` to return string content
3. Implement `handleKey()` for input handling
4. Use `PopupManager.showPopup()` to display
5. Consider multi-character paste handling for text input

### Component Screen Pattern
For new declarative screens:
1. Extend `ComponentScreen` class
2. Override `getComponents()` to return component array
3. Use factory functions from `component-system.js`
4. Let the renderer handle layout and pagination
5. Keep business logic separate from rendering

### File Exclusion Pattern
When listing files:
1. Use `listJsonFiles()` and `listEnvFiles()` from `files.js`
2. These automatically exclude system files (package.json, etc.)
3. Exclusion list is configurable in `constants.js`

### Cross-Platform Considerations
- **Clipboard**: Use pbpaste (macOS) / xclip (Linux)
- **Key bindings**: Show Cmd+V on macOS, Ctrl+V elsewhere
- **Terminal detection**: Handle both iTerm2 and standard terminals
- **Paste handling**: Multi-character input comes as single event on macOS

## Testing Guidelines

### When to Add Tests
Expand Down Expand Up @@ -701,4 +801,6 @@ Add more tests
- whenever i say "add and commit", i want you to add the updated files and commit using my standard commit messaging
- remember that you can't run lowkey in interactive mode because it nees TTY
- "test add commit" should use make test to make sure it tests localstack and k3d too
- use a simpler oneliner for git commit messages
- use a simpler oneliner for git commit messages
- we shouldn't use console.logs as debug logging, we have a debuglogger setup for that writes to a file as we go, you should use that when adding logs for debugging
- you put console logs, they should write to the debug logger
89 changes: 47 additions & 42 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,34 @@ help: ## Show this help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'

# Docker build commands
.PHONY: build
build: ## Build Docker image locally
.PHONY: docker-build
docker-build: ## Build Docker image locally
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) .

.PHONY: build-full
build-full: ## Build Docker image with full registry path
.PHONY: docker-build-full
docker-build-full: ## Build Docker image with full registry path
docker build -t $(FULL_IMAGE) .

# Docker run commands
.PHONY: run
run: ## Run Docker container with help command
.PHONY: docker-run
docker-run: ## Run Docker container with help command
docker run --rm $(IMAGE_NAME):$(IMAGE_TAG)

.PHONY: run-version
run-version: ## Show version using Docker container
.PHONY: docker-run-version
docker-run-version: ## Show version using Docker container
docker run --rm $(IMAGE_NAME):$(IMAGE_TAG) --version

.PHONY: run-help
run-help: ## Show help using Docker container
.PHONY: docker-run-help
docker-run-help: ## Show help using Docker container
docker run --rm $(IMAGE_NAME):$(IMAGE_TAG) --help

# Interactive development commands
.PHONY: run-shell
run-shell: ## Run container with shell for debugging
.PHONY: docker-run-shell
docker-run-shell: ## Run container with shell for debugging
docker run --rm -it --entrypoint /bin/sh $(IMAGE_NAME):$(IMAGE_TAG)

.PHONY: run-aws
run-aws: ## Run container with AWS environment variables mounted
.PHONY: docker-run-aws
docker-run-aws: ## Run container with AWS environment variables mounted
docker run --rm \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
Expand All @@ -50,29 +50,29 @@ run-aws: ## Run container with AWS environment variables mounted
$(IMAGE_NAME):$(IMAGE_TAG) $(ARGS)

# Example usage commands
.PHONY: example-copy-env
example-copy-env: ## Example: Copy secrets to env format (requires AWS credentials)
$(MAKE) run-aws ARGS="copy --input-type aws-secrets-manager --input-name example-secret --output-type env"
.PHONY: docker-example-copy-env
docker-example-copy-env: ## Example: Copy secrets to env format (requires AWS credentials)
$(MAKE) docker-run-aws ARGS="copy --input-type aws-secrets-manager --input-name example-secret --output-type env"

.PHONY: example-copy-json
example-copy-json: ## Example: Copy secrets to JSON format (requires AWS credentials)
$(MAKE) run-aws ARGS="copy --input-type aws-secrets-manager --input-name example-secret --output-type json"
.PHONY: docker-example-copy-json
docker-example-copy-json: ## Example: Copy secrets to JSON format (requires AWS credentials)
$(MAKE) docker-run-aws ARGS="copy --input-type aws-secrets-manager --input-name example-secret --output-type json"

.PHONY: example-list-aws
example-list-aws: ## Example: List AWS secrets (requires AWS credentials)
$(MAKE) run-aws ARGS="list --type aws-secrets-manager --region us-east-1"
.PHONY: docker-example-list-aws
docker-example-list-aws: ## Example: List AWS secrets (requires AWS credentials)
$(MAKE) docker-run-aws ARGS="list --type aws-secrets-manager --region us-east-1"

.PHONY: example-list-env
example-list-env: ## Example: List .env files in current directory
.PHONY: docker-example-list-env
docker-example-list-env: ## Example: List .env files in current directory
docker run --rm -v $(PWD):/workspace $(IMAGE_NAME):$(IMAGE_TAG) list --type env --path /workspace

.PHONY: example-list-json
example-list-json: ## Example: List JSON files in current directory
.PHONY: docker-example-list-json
docker-example-list-json: ## Example: List JSON files in current directory
docker run --rm -v $(PWD):/workspace $(IMAGE_NAME):$(IMAGE_TAG) list --type json --path /workspace

# File output commands
.PHONY: run-output
run-output: ## Run container with volume mount for file output
.PHONY: docker-run-output
docker-run-output: ## Run container with volume mount for file output
docker run --rm \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
Expand All @@ -83,24 +83,28 @@ run-output: ## Run container with volume mount for file output
$(IMAGE_NAME):$(IMAGE_TAG) $(ARGS)

# Testing commands
.PHONY: test-build
test-build: build run-version ## Build and test that the container works
.PHONY: docker-test-build
docker-test-build: docker-build docker-run-version ## Build and test that the container works
@echo "✅ Docker build and basic functionality test passed"

.PHONY: test-all
test-all: build run run-version run-help ## Run all basic tests
.PHONY: docker-test-all
docker-test-all: docker-build docker-run docker-run-version docker-run-help ## Run all basic tests
@echo "✅ All basic tests passed"

# Cleanup commands
.PHONY: clean
clean: ## Remove locally built images
.PHONY: docker-clean
docker-clean: ## Remove locally built Docker images
docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true
docker rmi $(FULL_IMAGE) 2>/dev/null || true

.PHONY: clean-all
clean-all: clean ## Remove all related Docker images and containers
.PHONY: docker-clean-all
docker-clean-all: docker-clean ## Remove all Docker images and containers
docker system prune -f

.PHONY: clean-all
clean-all: docker-clean-all k3d-clean localstack-clean log-clean ## Clean everything: Docker, k3d, LocalStack, and logs
@echo "✅ All environments and resources cleaned up"

# Development commands
.PHONY: dev-install
dev-install: ## Install dependencies locally for development
Expand Down Expand Up @@ -204,7 +208,8 @@ k3d-status: ## Show status of k3d clusters
@kubectl config current-context

.PHONY: k3d-clean
k3d-clean: k3d-delete ## Clean up k3d cluster and all resources
k3d-clean: ## Clean up k3d cluster and all resources (safe to run even if cluster doesn't exist)
@k3d cluster delete lowkey-test 2>/dev/null || true
@echo "✅ k3d cluster and resources cleaned up"

.PHONY: k3d-restart
Expand All @@ -222,7 +227,7 @@ debug-run: ## Run lowkey in debug mode with logging

.PHONY: debug-interactive
debug-interactive: ## Run interactive mode with debug logging
LOWKEY_DEBUG=true node cli.js interactive
LOWKEY_DEBUG=true LOCALSTACK_ENDPOINT=http://localhost:4566 AWS_REGION=us-east-1 node cli.js interactive

.PHONY: log
log: ## View the latest debug log
Expand Down Expand Up @@ -357,9 +362,9 @@ localstack-status: ## Check LocalStack health status
@curl -s http://localhost:4566/_localstack/health | jq . 2>/dev/null || curl -s http://localhost:4566/_localstack/health

.PHONY: localstack-clean
localstack-clean: localstack-stop ## Stop LocalStack and clean up volumes
docker compose -f docker-compose.localstack.yml down -v
rm -rf tmp/localstack
localstack-clean: ## Stop LocalStack and clean up volumes (safe to run even if not running)
@docker compose -f docker-compose.localstack.yml down -v 2>/dev/null || true
@rm -rf tmp/localstack 2>/dev/null || true
@echo "✅ LocalStack cleaned up"

.PHONY: localstack-test-setup
Expand Down
6 changes: 6 additions & 0 deletions asdf.env.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DATABASE_HOST="db.example.com"
DATABASE_NAME="myapp_staging"
DATABASE_PASSWORD="super_secure_db_password_123"
DATABASE_PORT="5442"
DATABASE_URL="postgresql://user:password@localhost:5432/myapp_production"
DATABASE_USER="app_user123"
3 changes: 3 additions & 0 deletions commands/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ async function handleCopyCommand(options) {
outputName: options.outputName,
region: options.region,
namespace: options.namespace,
outputNamespace: options.outputNamespace, // Pass through outputNamespace if provided
stage: options.stage,
autoYes: options.autoYes,
secretData: options.secretData, // Pass through pre-fetched secret data for interactive mode
filteredKeys: options.filteredKeys, // Pass through filtered keys for selective copying
onProgress: (message) => {
// Send progress messages to stderr so they don't interfere with stdout output
console.error(colorize(message, 'gray'));
Expand Down
1 change: 1 addition & 0 deletions commands/interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async function handleInteractiveCommand(options, searchState = {}) {
console.error(colorize('This command cannot be run in piped or non-interactive contexts', 'yellow'));
process.exit(1);
}


try {
const terminalManager = TerminalManager.getInstance();
Expand Down
Loading