Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 5 additions & 15 deletions packages/cli/internal/cmd/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

"github.com/fatih/color"
"github.com/keywaysh/cli/internal/analytics"
"github.com/keywaysh/cli/internal/env"
envpkg "github.com/keywaysh/cli/internal/env"
"github.com/keywaysh/cli/internal/ui"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -186,14 +186,14 @@ func runDiffWithDeps(opts DiffOptions, deps *Dependencies) error {
if err != nil {
pullErr1 = err
} else {
secrets1 = env.Parse(resp1.Content)
secrets1 = envpkg.Parse(resp1.Content)
}

resp2, err := client.PullSecrets(ctx, repo, env2)
if err != nil {
pullErr2 = err
} else {
secrets2 = env.Parse(resp2.Content)
secrets2 = envpkg.Parse(resp2.Content)
}

return nil
Expand Down Expand Up @@ -241,18 +241,8 @@ func runDiffWithDeps(opts DiffOptions, deps *Dependencies) error {
return nil
}

func normalizeEnvName(env string) string {
env = strings.ToLower(strings.TrimSpace(env))
switch env {
case "prod":
return "production"
case "dev":
return "development"
case "stg":
return "staging"
default:
return env
}
func normalizeEnvName(name string) string {
return envpkg.NormalizeEnvName(name)
}

func compareSecrets(env1, env2 string, secrets1, secrets2 map[string]string, includeValues bool) *DiffResult {
Expand Down
31 changes: 30 additions & 1 deletion packages/cli/internal/env/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,37 @@ func Discover() []Candidate {
return candidates
}

// envAliases maps common framework-specific env file suffixes and shorthand
// names to standard vault environment names.
var envAliases = map[string]string{
"local": "development",
"dev": "development",
"prod": "production",
"stage": "staging",
"stg": "staging",
"development.local": "development",
"production.local": "production",
"staging.local": "staging",
"test.local": "development",
"dev.local": "development",
"prod.local": "production",
"stage.local": "staging",
}

// NormalizeEnvName maps shorthand or framework-specific environment names
// to their canonical vault environment names.
func NormalizeEnvName(name string) string {
name = strings.ToLower(strings.TrimSpace(name))
if mapped, ok := envAliases[name]; ok {
return mapped
}
return name
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// DeriveEnvFromFile derives the environment name from a filename.
// Examples:
// - ".env" -> "development"
// - ".env.local" -> "development"
// - ".env.production" -> "production"
// - ".env.staging" -> "staging"
func DeriveEnvFromFile(file string) string {
Expand All @@ -51,7 +79,8 @@ func DeriveEnvFromFile(file string) string {
return "development"
}
if strings.HasPrefix(base, ".env.") {
return strings.TrimPrefix(base, ".env.")
suffix := strings.TrimPrefix(base, ".env.")
return NormalizeEnvName(suffix)
}
return "development"
}
23 changes: 17 additions & 6 deletions packages/cli/internal/env/discover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func TestDeriveEnvFromFile(t *testing.T) {
expected string
}{
{".env", "development"},
{".env.local", "local"},
{".env.local", "development"},
{".env.development", "development"},
{".env.production", "production"},
{".env.staging", "staging"},
Expand Down Expand Up @@ -121,8 +121,8 @@ func TestDiscover_IncludesEnvLocal(t *testing.T) {
for _, c := range candidates {
if c.File == ".env.local" {
foundEnvLocal = true
if c.Env != "local" {
t.Errorf(".env.local should map to 'local', got %q", c.Env)
if c.Env != "development" {
t.Errorf(".env.local should map to 'development', got %q", c.Env)
}
}
}
Expand Down Expand Up @@ -215,13 +215,24 @@ func TestDeriveEnvFromFile_EdgeCases(t *testing.T) {
}{
// Edge cases
{".env.", ""}, // Empty suffix
{".env.dev.local", "dev.local"}, // Multiple dots
{"config", "development"}, // Not an env file format
{"", "development"}, // Empty string
{".env.PRODUCTION", "PRODUCTION"}, // Case preserved
{".env.PRODUCTION", "production"}, // Case normalized
{"./path/to/.env.staging", "staging"}, // Relative path
{"/absolute/path/.env.prod", "prod"}, // Absolute path
{".env.development.backup", "development.backup"}, // Multiple parts
// Alias mappings
{".env.local", "development"}, // Next.js/Vite local
{".env.dev", "development"}, // Common shorthand
{"/absolute/path/.env.prod", "production"},// Common shorthand
{".env.stage", "staging"}, // Common shorthand
{".env.development.local", "development"}, // Next.js local override
{".env.production.local", "production"}, // Next.js local override
{".env.staging.local", "staging"}, // Next.js local override
{".env.dev.local", "development"}, // Shorthand + local
{".env.prod.local", "production"}, // Shorthand + local
{".env.stage.local", "staging"}, // Shorthand + local
{".env.test.local", "development"}, // Next.js local override
{".env.stg", "staging"}, // Common shorthand
}

for _, tt := range tests {
Expand Down
5 changes: 4 additions & 1 deletion packages/docs/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ pnpm run clear # Clear Docusaurus cache
```
docs/
├── intro.md # Getting started (landing page)
├── installation.md # Installation guide
├── cli.md # CLI reference
├── api.md # API reference
├── ci-cd.md # CI/CD integration
├── mcp.md # MCP Server for AI tools
├── mcp.md # MCP Server for AI tools (9 tools)
├── ai-agents.md # AI agents integration (keyway run)
├── security.md # Security & permissions
├── organizations.md # Organizations & billing
└── integrations.md # Provider integrations (Vercel, etc.)
```

Expand Down
1 change: 1 addition & 0 deletions packages/docs/docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,4 @@ Upgrade: [keyway.sh/settings](https://keyway.sh/settings)
- [API](/api) - REST API and API keys
- [Organizations](/organizations) - Team billing and management
- [Security](/security) - Permissions and activity logs
- [Self-Hosting](/self-hosting) - Deploy on your own infrastructure
114 changes: 96 additions & 18 deletions packages/docs/docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,19 @@ See [Installation](/installation) if you haven't installed the CLI yet.

### keyway_list_secrets

List secret names (without values).
List all secret names in the vault for the current repository. Returns only the keys, not the values.

```json
{ "environment": "production" }
```

### keyway_get_secret

Get a specific secret value.

```json
{
"name": "DATABASE_URL",
"environment": "production"
}
```
| Parameter | Required | Description |
|-----------|----------|-------------|
| `environment` | No | Environment to list secrets from (default: `development`) |

### keyway_set_secret

Create or update a secret.
Create or update a secret in the vault.

```json
{
Expand All @@ -115,9 +108,15 @@ Create or update a secret.
}
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `name` | Yes | Secret name — must be UPPERCASE_WITH_UNDERSCORES |
| `value` | Yes | Secret value to store |
| `environment` | No | Environment to set secret in (default: `development`) |

### keyway_inject_run

Run a command with secrets as env vars.
Run a command with secrets injected as environment variables. Secrets are only available to this command and are never written to disk.

```json
{
Expand All @@ -130,7 +129,7 @@ Run a command with secrets as env vars.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `command` | Yes | Executable to run |
| `command` | Yes | Executable to run (e.g., `npm`, `python`) |
| `args` | No | Array of arguments |
| `environment` | No | Vault environment (default: `development`) |
| `timeout` | No | Max runtime in milliseconds (default: 300000 = 5min) |
Expand All @@ -139,28 +138,107 @@ The command runs with `shell: false` to prevent shell injection. Secret values a

### keyway_list_environments

List available environments.
List available environments for the current repository vault.

```json
{}
```

### keyway_generate

Generate a secure secret and store it directly in the vault. The secret value is never exposed in the AI conversation.

```json
{
"name": "JWT_SECRET",
"type": "jwt-secret",
"length": 64,
"environment": "production"
}
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `name` | Yes | Secret name — must be UPPERCASE_WITH_UNDERSCORES |
| `type` | No | Type of secret: `password`, `uuid`, `api-key`, `jwt-secret`, `hex`, `base64` (default: `password`) |
| `length` | No | Length of the secret, 8–256 (default: 32) |
| `environment` | No | Environment to store the secret in (default: `development`) |

### keyway_diff

Compare secrets between two environments to find differences. Shows which keys are missing, added, or have different values (without revealing the values).

```json
{
"env1": "development",
"env2": "production"
}
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `env1` | Yes | First environment (e.g., `development`) |
| `env2` | Yes | Second environment (e.g., `production`) |

### keyway_scan

Scan the codebase for potential secret leaks. Detects AWS keys, GitHub tokens, Stripe keys, private keys, and more.

```json
{
"path": ".",
"exclude": ["fixtures"]
}
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `path` | No | Path to scan (default: current directory) |
| `exclude` | No | Additional directories to exclude from scanning |

### keyway_validate

Validate that required secrets exist in an environment. Useful for pre-deployment checks. Can auto-detect required secrets from your codebase.

```json
{
"environment": "production",
"autoDetect": true
}
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `environment` | Yes | Environment to validate (e.g., `production`) |
| `required` | No | List of required secret names to check |
| `autoDetect` | No | Auto-detect required secrets from codebase (default: `false`) |
| `path` | No | Path to scan for auto-detection (default: current directory) |

---

## Example Prompts

**"What secrets are in production?"**
→ Uses `keyway_list_secrets`

**"Get the DATABASE_URL for staging"**
→ Uses `keyway_get_secret`

**"Run the tests with the development secrets"**
→ Uses `keyway_inject_run`

**"Add a new API_KEY secret with value xyz"**
→ Uses `keyway_set_secret`

**"Generate a secure JWT secret for production"**
→ Uses `keyway_generate`

**"What's different between staging and production?"**
→ Uses `keyway_diff`

**"Scan the codebase for leaked secrets"**
→ Uses `keyway_scan`

**"Are all required secrets set for production?"**
→ Uses `keyway_validate`

---

## Security
Expand Down
Loading
Loading