Skip to content

Commit 1d71c7a

Browse files
ranaroussiclaude
andcommitted
feat: add 'envapor keys' to list stored keys
- config.ListKeys returns key names from the keys directory - Marks the current repository's mapped key when run inside a repo - Prints a keygen hint when no keys exist yet 🤖 Generated with Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 299df9b commit 1d71c7a

5 files changed

Lines changed: 98 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Parsing **fails closed**: a value is left in plaintext *only* on an unambiguous
8989
| Command | Purpose |
9090
|---|---|
9191
| `envapor keygen NAME` | Generate a new key at `~/.config/envapor/keys/NAME` |
92+
| `envapor keys` | List stored keys, marking the current repository's key |
9293
| `envapor init NAME` or `--pem PATH` | Configure filters, hook, `.gitattributes`, and map the repo to a key (by stored name or key file) |
9394
| `envapor doctor` | Diagnose the setup (filters, hook, mapping, coverage, crypto round-trip) |
9495
| `envapor status` | Show the mapping and per-file encryption state |

‎docs/user-guide.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ If a managed `.env` file is git-ignored, it would silently never be committed. B
234234

235235
Generates a new key with safe defaults and writes it to `~/.config/envapor/keys/NAME`. Refuses to overwrite an existing key unless you pass `--force`.
236236

237+
### `envapor keys`
238+
239+
Lists the keys stored in the keys directory (`~/.config/envapor/keys/` by
240+
default), one per line. When run inside an initialized repository, the key
241+
mapped to that repository is marked. Prints a hint instead when no keys exist
242+
yet.
243+
237244
### `envapor init [KEY_NAME]` / `envapor init --pem PATH`
238245

239246
Sets up Envapor in the current repository. The key is given either as the name

‎src/internal/cmd/keys.go‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/automazeio/envapor/internal/config"
7+
"github.com/automazeio/envapor/internal/gitutil"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var keysCmd = &cobra.Command{
12+
Use: "keys",
13+
Short: "List stored encryption keys",
14+
Args: cobra.NoArgs,
15+
RunE: func(cmd *cobra.Command, args []string) error {
16+
names, err := config.ListKeys()
17+
if err != nil {
18+
return err
19+
}
20+
if len(names) == 0 {
21+
dir, err := config.KeysDir()
22+
if err != nil {
23+
return err
24+
}
25+
fmt.Printf("No keys found in %s (create one with 'envapor keygen NAME').\n", dir)
26+
return nil
27+
}
28+
active := ""
29+
if gitutil.InsideRepo() {
30+
if name, err := resolveKeyName(); err == nil {
31+
active = name
32+
}
33+
}
34+
for _, name := range names {
35+
if name == active {
36+
fmt.Printf("%s (this repository)\n", name)
37+
} else {
38+
fmt.Println(name)
39+
}
40+
}
41+
return nil
42+
},
43+
}
44+
45+
func init() {
46+
rootCmd.AddCommand(keysCmd)
47+
}

‎src/internal/config/config.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,30 @@ func SecureKey(name string) error {
211211
return nil
212212
}
213213

214+
// ListKeys returns the names of the keys stored in the keys directory, in
215+
// lexical order. A missing keys directory yields an empty list, not an error.
216+
func ListKeys() ([]string, error) {
217+
dir, err := KeysDir()
218+
if err != nil {
219+
return nil, err
220+
}
221+
entries, err := os.ReadDir(dir)
222+
if os.IsNotExist(err) {
223+
return nil, nil
224+
}
225+
if err != nil {
226+
return nil, fmt.Errorf("envapor: reading keys dir: %w", err)
227+
}
228+
var names []string
229+
for _, e := range entries {
230+
if e.IsDir() || strings.HasPrefix(e.Name(), ".") {
231+
continue
232+
}
233+
names = append(names, e.Name())
234+
}
235+
return names, nil
236+
}
237+
214238
// KeyExists reports whether a named key file is present.
215239
func KeyExists(name string) bool {
216240
path, err := KeyPath(name)

‎src/internal/config/config_test.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ func TestKeyPathRejectsUnsafeNames(t *testing.T) {
1919
}
2020
}
2121

22+
func TestListKeys(t *testing.T) {
23+
t.Setenv("ENVAPOR_HOME", t.TempDir())
24+
if names, err := ListKeys(); err != nil || len(names) != 0 {
25+
t.Fatalf("ListKeys on missing dir = %v, %v; want empty, nil", names, err)
26+
}
27+
for _, name := range []string{"team", "ci"} {
28+
if _, err := WriteKey(name, []byte("pem")); err != nil {
29+
t.Fatal(err)
30+
}
31+
}
32+
names, err := ListKeys()
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
if len(names) != 2 || names[0] != "ci" || names[1] != "team" {
37+
t.Fatalf("ListKeys = %v, want [ci team]", names)
38+
}
39+
}
40+
2241
func TestWriteKeyCorrectsPermissions(t *testing.T) {
2342
if runtime.GOOS == "windows" {
2443
t.Skip("Unix permission bits are not enforced on Windows")

0 commit comments

Comments
 (0)