axvault 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +64 -123
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +5 -104
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/serve.d.ts +4 -0
  7. package/dist/commands/serve.d.ts.map +1 -1
  8. package/dist/commands/serve.js +82 -62
  9. package/dist/commands/serve.js.map +1 -1
  10. package/dist/config.d.ts +58 -7
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +52 -37
  13. package/dist/config.js.map +1 -1
  14. package/dist/db/bootstrap-api-key.d.ts +14 -0
  15. package/dist/db/bootstrap-api-key.d.ts.map +1 -0
  16. package/dist/db/bootstrap-api-key.js +26 -0
  17. package/dist/db/bootstrap-api-key.js.map +1 -0
  18. package/dist/db/migrations/001-initial.sql +6 -5
  19. package/dist/db/repositories/audit-log.d.ts +2 -0
  20. package/dist/db/repositories/audit-log.d.ts.map +1 -1
  21. package/dist/db/repositories/audit-log.js +4 -2
  22. package/dist/db/repositories/audit-log.js.map +1 -1
  23. package/dist/db/run-migrations.d.ts +12 -0
  24. package/dist/db/run-migrations.d.ts.map +1 -1
  25. package/dist/db/run-migrations.js +19 -44
  26. package/dist/db/run-migrations.js.map +1 -1
  27. package/dist/db/types.d.ts +1 -0
  28. package/dist/db/types.d.ts.map +1 -1
  29. package/dist/index.d.ts +12 -6
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +14 -6
  32. package/dist/index.js.map +1 -1
  33. package/dist/lib/access-list.d.ts +13 -0
  34. package/dist/lib/access-list.d.ts.map +1 -0
  35. package/dist/lib/access-list.js +22 -0
  36. package/dist/lib/access-list.js.map +1 -0
  37. package/dist/lib/credential-name.d.ts +1 -6
  38. package/dist/lib/credential-name.d.ts.map +1 -1
  39. package/dist/lib/credential-name.js +1 -4
  40. package/dist/lib/credential-name.js.map +1 -1
  41. package/dist/schemas/request.d.ts +35 -0
  42. package/dist/schemas/request.d.ts.map +1 -0
  43. package/dist/schemas/request.js +76 -0
  44. package/dist/schemas/request.js.map +1 -0
  45. package/dist/schemas/{api.d.ts → response.d.ts} +6 -32
  46. package/dist/schemas/response.d.ts.map +1 -0
  47. package/dist/schemas/response.js +59 -0
  48. package/dist/schemas/response.js.map +1 -0
  49. package/dist/server/plugins/auth.d.ts +19 -0
  50. package/dist/server/plugins/auth.d.ts.map +1 -0
  51. package/dist/server/plugins/auth.js +51 -0
  52. package/dist/server/plugins/auth.js.map +1 -0
  53. package/dist/server/plugins/config.d.ts +14 -0
  54. package/dist/server/plugins/config.d.ts.map +1 -0
  55. package/dist/server/plugins/config.js +19 -0
  56. package/dist/server/plugins/config.js.map +1 -0
  57. package/dist/server/plugins/database.d.ts +12 -0
  58. package/dist/server/plugins/database.d.ts.map +1 -0
  59. package/dist/server/plugins/database.js +20 -0
  60. package/dist/server/plugins/database.js.map +1 -0
  61. package/dist/server/routes/credentials.d.ts +8 -0
  62. package/dist/server/routes/credentials.d.ts.map +1 -0
  63. package/dist/server/routes/credentials.js +82 -0
  64. package/dist/server/routes/credentials.js.map +1 -0
  65. package/dist/server/routes/handle-create-key.d.ts +10 -0
  66. package/dist/server/routes/handle-create-key.d.ts.map +1 -0
  67. package/dist/server/routes/handle-create-key.js +44 -0
  68. package/dist/server/routes/handle-create-key.js.map +1 -0
  69. package/dist/server/routes/handle-delete-credential.d.ts +10 -0
  70. package/dist/server/routes/handle-delete-credential.d.ts.map +1 -0
  71. package/dist/{handlers/delete-credential.js → server/routes/handle-delete-credential.js} +19 -17
  72. package/dist/server/routes/handle-delete-credential.js.map +1 -0
  73. package/dist/server/routes/handle-delete-key.d.ts +11 -0
  74. package/dist/server/routes/handle-delete-key.d.ts.map +1 -0
  75. package/dist/server/routes/handle-delete-key.js +40 -0
  76. package/dist/server/routes/handle-delete-key.js.map +1 -0
  77. package/dist/server/routes/handle-get-credential.d.ts +18 -0
  78. package/dist/server/routes/handle-get-credential.d.ts.map +1 -0
  79. package/dist/{handlers/get-credential.js → server/routes/handle-get-credential.js} +17 -34
  80. package/dist/server/routes/handle-get-credential.js.map +1 -0
  81. package/dist/server/routes/handle-list-credentials.d.ts +13 -0
  82. package/dist/server/routes/handle-list-credentials.d.ts.map +1 -0
  83. package/dist/{handlers/list-credentials.js → server/routes/handle-list-credentials.js} +6 -24
  84. package/dist/server/routes/handle-list-credentials.js.map +1 -0
  85. package/dist/server/routes/handle-put-credential.d.ts +14 -0
  86. package/dist/server/routes/handle-put-credential.d.ts.map +1 -0
  87. package/dist/{handlers/put-credential.js → server/routes/handle-put-credential.js} +26 -33
  88. package/dist/server/routes/handle-put-credential.js.map +1 -0
  89. package/dist/server/routes/handle-update-key.d.ts +13 -0
  90. package/dist/server/routes/handle-update-key.d.ts.map +1 -0
  91. package/dist/server/routes/handle-update-key.js +74 -0
  92. package/dist/server/routes/handle-update-key.js.map +1 -0
  93. package/dist/server/routes/health.d.ts +7 -0
  94. package/dist/server/routes/health.d.ts.map +1 -0
  95. package/dist/server/routes/health.js +25 -0
  96. package/dist/server/routes/health.js.map +1 -0
  97. package/dist/server/routes/keys.d.ts +12 -0
  98. package/dist/server/routes/keys.d.ts.map +1 -0
  99. package/dist/server/routes/keys.js +119 -0
  100. package/dist/server/routes/keys.js.map +1 -0
  101. package/dist/server/routes/log-grant-event.d.ts +7 -0
  102. package/dist/server/routes/log-grant-event.d.ts.map +1 -0
  103. package/dist/server/routes/log-grant-event.js +15 -0
  104. package/dist/server/routes/log-grant-event.js.map +1 -0
  105. package/dist/server/send-sensible-error.d.ts +7 -0
  106. package/dist/server/send-sensible-error.d.ts.map +1 -0
  107. package/dist/server/send-sensible-error.js +40 -0
  108. package/dist/server/send-sensible-error.js.map +1 -0
  109. package/dist/server/server.d.ts +6 -17
  110. package/dist/server/server.d.ts.map +1 -1
  111. package/dist/server/server.js +44 -53
  112. package/dist/server/server.js.map +1 -1
  113. package/package.json +7 -1
  114. package/dist/commands/credential.d.ts +0 -17
  115. package/dist/commands/credential.d.ts.map +0 -1
  116. package/dist/commands/credential.js +0 -144
  117. package/dist/commands/credential.js.map +0 -1
  118. package/dist/commands/init.d.ts +0 -10
  119. package/dist/commands/init.d.ts.map +0 -1
  120. package/dist/commands/init.js +0 -42
  121. package/dist/commands/init.js.map +0 -1
  122. package/dist/commands/key-create.d.ts +0 -14
  123. package/dist/commands/key-create.d.ts.map +0 -1
  124. package/dist/commands/key-create.js +0 -111
  125. package/dist/commands/key-create.js.map +0 -1
  126. package/dist/commands/key-list.d.ts +0 -10
  127. package/dist/commands/key-list.d.ts.map +0 -1
  128. package/dist/commands/key-list.js +0 -57
  129. package/dist/commands/key-list.js.map +0 -1
  130. package/dist/commands/key-revoke.d.ts +0 -12
  131. package/dist/commands/key-revoke.d.ts.map +0 -1
  132. package/dist/commands/key-revoke.js +0 -64
  133. package/dist/commands/key-revoke.js.map +0 -1
  134. package/dist/commands/key-update.d.ts +0 -17
  135. package/dist/commands/key-update.d.ts.map +0 -1
  136. package/dist/commands/key-update.js +0 -106
  137. package/dist/commands/key-update.js.map +0 -1
  138. package/dist/commands/key.d.ts +0 -10
  139. package/dist/commands/key.d.ts.map +0 -1
  140. package/dist/commands/key.js +0 -10
  141. package/dist/commands/key.js.map +0 -1
  142. package/dist/handlers/create-key.d.ts +0 -14
  143. package/dist/handlers/create-key.d.ts.map +0 -1
  144. package/dist/handlers/create-key.js +0 -25
  145. package/dist/handlers/create-key.js.map +0 -1
  146. package/dist/handlers/delete-credential.d.ts +0 -15
  147. package/dist/handlers/delete-credential.d.ts.map +0 -1
  148. package/dist/handlers/delete-credential.js.map +0 -1
  149. package/dist/handlers/delete-key.d.ts +0 -15
  150. package/dist/handlers/delete-key.d.ts.map +0 -1
  151. package/dist/handlers/delete-key.js +0 -24
  152. package/dist/handlers/delete-key.js.map +0 -1
  153. package/dist/handlers/get-credential.d.ts +0 -27
  154. package/dist/handlers/get-credential.d.ts.map +0 -1
  155. package/dist/handlers/get-credential.js.map +0 -1
  156. package/dist/handlers/get-key.d.ts +0 -15
  157. package/dist/handlers/get-key.d.ts.map +0 -1
  158. package/dist/handlers/get-key.js +0 -20
  159. package/dist/handlers/get-key.js.map +0 -1
  160. package/dist/handlers/list-credentials.d.ts +0 -27
  161. package/dist/handlers/list-credentials.d.ts.map +0 -1
  162. package/dist/handlers/list-credentials.js.map +0 -1
  163. package/dist/handlers/list-keys.d.ts +0 -11
  164. package/dist/handlers/list-keys.d.ts.map +0 -1
  165. package/dist/handlers/list-keys.js +0 -18
  166. package/dist/handlers/list-keys.js.map +0 -1
  167. package/dist/handlers/put-credential.d.ts +0 -24
  168. package/dist/handlers/put-credential.d.ts.map +0 -1
  169. package/dist/handlers/put-credential.js.map +0 -1
  170. package/dist/handlers/update-key.d.ts +0 -17
  171. package/dist/handlers/update-key.d.ts.map +0 -1
  172. package/dist/handlers/update-key.js +0 -47
  173. package/dist/handlers/update-key.js.map +0 -1
  174. package/dist/lib/format-key-details.d.ts +0 -17
  175. package/dist/lib/format-key-details.d.ts.map +0 -1
  176. package/dist/lib/format-key-details.js +0 -25
  177. package/dist/lib/format-key-details.js.map +0 -1
  178. package/dist/lib/format.d.ts +0 -89
  179. package/dist/lib/format.d.ts.map +0 -1
  180. package/dist/lib/format.js +0 -180
  181. package/dist/lib/format.js.map +0 -1
  182. package/dist/lib/parse-access-options.d.ts +0 -38
  183. package/dist/lib/parse-access-options.d.ts.map +0 -1
  184. package/dist/lib/parse-access-options.js +0 -85
  185. package/dist/lib/parse-access-options.js.map +0 -1
  186. package/dist/middleware/auth.d.ts +0 -22
  187. package/dist/middleware/auth.d.ts.map +0 -1
  188. package/dist/middleware/auth.js +0 -49
  189. package/dist/middleware/auth.js.map +0 -1
  190. package/dist/middleware/require-grant-access.d.ts +0 -10
  191. package/dist/middleware/require-grant-access.d.ts.map +0 -1
  192. package/dist/middleware/require-grant-access.js +0 -14
  193. package/dist/middleware/require-grant-access.js.map +0 -1
  194. package/dist/schemas/api.d.ts.map +0 -1
  195. package/dist/schemas/api.js +0 -119
  196. package/dist/schemas/api.js.map +0 -1
  197. package/dist/server/routes.d.ts +0 -14
  198. package/dist/server/routes.d.ts.map +0 -1
  199. package/dist/server/routes.js +0 -200
  200. package/dist/server/routes.js.map +0 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ Remote credential storage server for a╳kit.
7
7
  - Node.js 22.19+
8
8
  - PostgreSQL 14+ (local install or via Docker: `docker run -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:17-alpine`)
9
9
  - `pnpm` (for `pnpm dlx axvault`) or `npx` (for `npx -y axvault`)
10
- - `jq` for scripting against `--json` output
10
+ - `jq` for scripting against JSON API responses
11
11
 
12
12
  If `axvault` is not installed globally, prefix commands with `npx -y axvault` (or `pnpm dlx axvault`).
13
13
 
@@ -29,53 +29,29 @@ set -a
29
29
  . ./.env
30
30
  set +a
31
31
 
32
- # Initialize database and start server
33
- npx -y axvault init
32
+ # Start server (runs migrations and creates bootstrap admin key on first startup)
34
33
  npx -y axvault serve
35
34
  ```
36
35
 
37
- Keep the `.env` file and reuse the same key between restarts to avoid losing access to existing credentials.
36
+ On first startup, axvault runs database migrations and creates a bootstrap admin API key with full access. The secret is printed to stderr — **save it immediately**, it cannot be retrieved later.
38
37
 
39
- In a new shell, you must re-export the variables from `.env` before running `axvault` commands (the server does not load `.env` automatically).
38
+ Keep the `.env` file and reuse the same encryption key between restarts to avoid losing access to existing credentials.
40
39
 
41
- In another shell, create an API key:
40
+ ## Architecture
42
41
 
43
- ```bash
44
- npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
45
- ```
46
-
47
- Add `--verbose` to commands like `init`, `serve`, `key revoke`, `key update`, and
48
- `credential delete` to see status output.
49
-
50
- ## Output formats
42
+ axvault is a server-only tool. The CLI has a single command (`serve`) that starts the HTTP server. All key and credential management is done through the HTTP API.
51
43
 
52
- List commands output TSV by default. Use `--json` on `key create`, `key list`,
53
- `key update`, and `credential list` for structured output (note: `key create
54
- --json` includes the secret key).
55
-
56
- ```bash
57
- npx -y axvault key list --json | jq -r '.[].id'
58
- ```
44
+ On startup, the server:
59
45
 
60
- ## Pipeline examples
46
+ 1. Runs database migrations (idempotent, so they are safe to re-run on every startup)
47
+ 2. Creates a bootstrap admin API key if no keys exist (serialized with an advisory lock, prints secret to stderr)
48
+ 3. Starts listening for HTTP requests
61
49
 
62
- ### Extract key IDs
63
-
64
- ```bash
65
- npx -y axvault key list | tail -n +2 | cut -f1
66
- ```
67
-
68
- ### Count credentials by creation date
69
-
70
- ```bash
71
- npx -y axvault credential list --json | jq -r '.[].createdAt | split("T")[0]' | sort | uniq -c | sort -rn
72
- ```
73
-
74
- ### List credential names in the `claude` namespace
75
-
76
- ```bash
77
- npx -y axvault credential list | tail -n +2 | awk -F'\t' '$1 ~ /^claude\./ {print $1}'
78
- ```
50
+ Migration files must remain replay-safe because axvault re-runs them on every
51
+ startup instead of tracking applied versions. Use guarded SQL such as
52
+ `CREATE ... IF NOT EXISTS` or `ALTER ... ADD COLUMN IF NOT EXISTS`. If a future
53
+ schema change cannot be written safely that way, switch back to tracked
54
+ migrations rather than adding a one-shot file to this runner.
79
55
 
80
56
  ## Agent Rule
81
57
 
@@ -86,9 +62,9 @@ Add to your `CLAUDE.md` or `AGENTS.md`:
86
62
 
87
63
  Run `npx -y axvault --help` to learn available options.
88
64
 
89
- Use `axvault` when you need to initialize the vault database, manage API keys,
90
- or list/delete stored credentials. List commands output TSV by default; add
91
- `--json` for structured output you can pipe into `jq`.
65
+ Use `axvault serve` to start the credential vault server. All key and credential
66
+ management is done via the HTTP API. On first startup, a bootstrap admin API key
67
+ is created and printed to stderr.
92
68
  ```
93
69
 
94
70
  ## Configuration
@@ -121,86 +97,60 @@ npx -y axvault serve \
121
97
 
122
98
  Setting `--refresh-threshold 0` disables automatic credential refresh.
123
99
 
124
- ## Confirmation flags
125
-
126
- Destructive commands require confirmation: `axvault key revoke` and `axvault
127
- credential delete` require `--force` (alias `--yes`).
128
-
129
100
  ## API Keys
130
101
 
131
102
  API keys control access to the credential API. Each key has configurable permissions:
132
103
 
133
104
  - **Read**: retrieve credentials
134
105
  - **Write**: store and delete credentials
135
- - **Grant**: delegate access to other keys (enforcement coming in future release)
136
-
137
- ### Create an API Key
138
-
139
- ```bash
140
- # Full access (read, write, and grant)
141
- npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
106
+ - **Grant**: manage other API keys
142
107
 
143
- # Read/write access only
144
- npx -y axvault key create --name "CI Pipeline" --read "*" --write "*"
108
+ ### Bootstrap Key
145
109
 
146
- # Restricted access
147
- npx -y axvault key create --name "Claude Reader" --read "claude.work,claude.ci"
148
- npx -y axvault key create --name "Deploy Script" --write "claude.prod,codex.prod"
110
+ On first startup (when no keys exist), axvault creates a "Bootstrap Admin" key with full access (`*` for read, write, and grant). The secret is printed to stderr.
149
111
 
150
- # Grant-only key (for delegation, does not allow direct read/write)
151
- npx -y axvault key create --name "Issuer" --grant "claude.work,claude.ci"
152
- ```
112
+ ### Managing Keys via API
153
113
 
154
- The command outputs metadata to stderr and the secret key to stdout for easy piping:
114
+ All key management is done through the HTTP API:
155
115
 
156
- ```
157
- # stderr (visible in terminal):
158
- Created API key: CI Pipeline
159
- ID: k_a1b2c3d4e5f6
160
- Read access: *
161
- Write access: *
162
- Grant access: (none)
163
-
164
- Save this key securely - it cannot be retrieved later.
165
-
166
- # stdout (can be piped):
167
- axv_sk_0123456789abcdef0123456789abcdef
168
- ```
169
-
170
- To copy directly to clipboard: `npx -y axvault key create --name "My Key" --read "*" | pbcopy`
171
-
172
- ### List Keys
173
-
174
- ```bash
175
- npx -y axvault key list
176
- ```
177
-
178
- ### Update a Key
179
-
180
- Modify an existing key's permissions:
116
+ - `POST /api/v1/keys` accepts either a bootstrap/admin key or a scoped grant
117
+ key, as long as every requested read/write/grant entry stays within the
118
+ caller's `grantAccess` list.
119
+ - `PATCH /api/v1/keys/:id` only works when the target key's current and resulting
120
+ permissions both stay within the caller's `grantAccess` list. In practice,
121
+ scoped grant keys can only update keys that are already fully inside their
122
+ scope.
123
+ - `GET /api/v1/keys`, `GET /api/v1/keys/:id`, and `DELETE /api/v1/keys/:id`
124
+ require full grant access (`grantAccess: ["*"]`).
181
125
 
182
126
  ```bash
183
- # Add read access for new credentials
184
- npx -y axvault key update k_a1b2c3d4e5f6 --add-read "gemini.prod"
127
+ API_KEY="axv_sk_..." # Bootstrap/admin key with grantAccess ["*"]
185
128
 
186
- # Remove write access
187
- npx -y axvault key update k_a1b2c3d4e5f6 --remove-write "claude.test"
129
+ # Create a new key
130
+ curl -X POST https://vault.example.com/api/v1/keys \
131
+ -H "Authorization: Bearer $API_KEY" \
132
+ -H "Content-Type: application/json" \
133
+ -d '{"name": "CI Pipeline", "readAccess": ["*"], "writeAccess": ["*"], "grantAccess": []}'
188
134
 
189
- # Add grant permissions
190
- npx -y axvault key update k_a1b2c3d4e5f6 --add-grant "claude.work"
135
+ # List all keys
136
+ curl https://vault.example.com/api/v1/keys \
137
+ -H "Authorization: Bearer $API_KEY"
191
138
 
192
- # Multiple changes at once
193
- npx -y axvault key update k_a1b2c3d4e5f6 --add-read "codex.ci" --remove-write "claude.dev"
194
- ```
139
+ # Get a single key
140
+ curl https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
141
+ -H "Authorization: Bearer $API_KEY"
195
142
 
196
- ### Revoke a Key
143
+ # Update key permissions
144
+ curl -X PATCH https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
145
+ -H "Authorization: Bearer $API_KEY" \
146
+ -H "Content-Type: application/json" \
147
+ -d '{"readAccess": ["claude.work", "codex.ci"]}'
197
148
 
198
- ```bash
199
- npx -y axvault key revoke k_a1b2c3d4e5f6 --force
149
+ # Revoke a key
150
+ curl -X DELETE https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
151
+ -H "Authorization: Bearer $API_KEY"
200
152
  ```
201
153
 
202
- This command requires `--force` or `--yes` to confirm.
203
-
204
154
  ### Container Deployments
205
155
 
206
156
  Container images are published automatically to `registry.j4k.dev/axvault` on every release (multi-arch: amd64 + arm64). To rebuild manually, run `workflow_dispatch` on `publish-image` and provide the required `version` input (for example `1.7.0`).
@@ -233,6 +183,8 @@ podman run -d \
233
183
 
234
184
  **Note:** `AXVAULT_DATABASE_URL` must point to an accessible PostgreSQL instance. The Containerfile default (`postgresql://localhost:5432/axvault`) refers to the container itself, so standalone `docker run` users must provide this variable. For a batteries-included setup, use `docker-compose.yml` which includes a PostgreSQL service.
235
185
 
186
+ The bootstrap admin key is created on first startup and printed to the container logs. Retrieve it with `docker logs axvault` or `podman logs axvault`.
187
+
236
188
  #### Quadlet (systemd)
237
189
 
238
190
  This example references `axvault-db.service`, which is a PostgreSQL container you must provide separately as a companion Quadlet (`axvault-db.container`). Alternatively, point `AXVAULT_DATABASE_URL` at an existing PostgreSQL instance and remove the `Requires`/`After` lines.
@@ -267,18 +219,6 @@ sudo systemctl daemon-reload
267
219
  sudo systemctl start axvault
268
220
  ```
269
221
 
270
- #### Managing Keys in Containers
271
-
272
- Exec into the container to manage API keys:
273
-
274
- ```bash
275
- # Podman
276
- sudo podman exec axvault node /app/node_modules/axvault/bin/axvault key create --name "My Key" --read "*" --write "*"
277
-
278
- # Docker
279
- docker exec axvault node /app/node_modules/axvault/bin/axvault key create --name "My Key" --read "*" --write "*"
280
- ```
281
-
282
222
  ## Credentials API
283
223
 
284
224
  ### Store a Credential
@@ -457,13 +397,13 @@ Alternatively, use separate environment variables:
457
397
 
458
398
  ### Common Errors
459
399
 
460
- | Error | Cause | Solution |
461
- | ---------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- |
462
- | `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON |
463
- | `unauthorized` | Invalid API key | Verify key with `npx -y axvault key list`, create new if needed |
464
- | `forbidden` | No access to credential | Add credential to key's read access: `npx -y axvault key update <id> --add-read <path>` |
465
- | `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>` |
466
- | `unreachable` | Network issue or server down | Check vault URL, verify server is running |
400
+ | Error | Cause | Solution |
401
+ | ---------------- | ------------------------------------------ | ------------------------------------------------------------------------- |
402
+ | `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON |
403
+ | `unauthorized` | Invalid API key | Check the key via the keys API, create a new one if needed |
404
+ | `forbidden` | No access to credential | Update key permissions via `PATCH /api/v1/keys/:id` |
405
+ | `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>` |
406
+ | `unreachable` | Network issue or server down | Check vault URL, verify server is running |
467
407
 
468
408
  ### Debugging
469
409
 
@@ -473,10 +413,11 @@ Alternatively, use separate environment variables:
473
413
  curl -I $AXVAULT_URL/api/v1/health
474
414
  ```
475
415
 
476
- 2. Verify API key permissions:
416
+ 2. List API keys (requires grant access):
477
417
 
478
418
  ```bash
479
- npx -y axvault key list # on server
419
+ curl -H "Authorization: Bearer $AXVAULT_API_KEY" \
420
+ $AXVAULT_URL/api/v1/keys
480
421
  ```
481
422
 
482
423
  3. Check credential exists:
package/dist/cli.d.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * axvault - Remote credential storage server for axkit.
4
4
  *
5
- * Stores agent credentials and serves them via API.
5
+ * Starts the vault server. All key and credential management is done
6
+ * via the HTTP API once the server is running.
6
7
  */
7
8
  export {};
8
9
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/cli.js CHANGED
@@ -2,13 +2,11 @@
2
2
  /**
3
3
  * axvault - Remote credential storage server for axkit.
4
4
  *
5
- * Stores agent credentials and serves them via API.
5
+ * Starts the vault server. All key and credential management is done
6
+ * via the HTTP API once the server is running.
6
7
  */
7
8
  import { Command } from "@commander-js/extra-typings";
8
9
  import packageJson from "../package.json" with { type: "json" };
9
- import { handleCredentialDelete, handleCredentialList, } from "./commands/credential.js";
10
- import { handleInit } from "./commands/init.js";
11
- import { handleKeyCreate, handleKeyList, handleKeyRevoke, handleKeyUpdate, } from "./commands/key.js";
12
10
  import { handleServe } from "./commands/serve.js";
13
11
  const program = new Command()
14
12
  .name(packageJson.name)
@@ -19,47 +17,14 @@ const program = new Command()
19
17
  .helpCommand(false)
20
18
  .addHelpText("after", String.raw `
21
19
  Examples:
22
- # Initialize database
23
- axvault init
24
-
25
- # Start server
20
+ # Start server (runs migrations and creates bootstrap key on first run)
26
21
  axvault serve
27
22
 
28
23
  # Start server on custom port
29
24
  axvault serve --port 8080
30
25
 
31
- # Create an API key with read access (at least one of --read/--write required)
32
- axvault key create --name "CI Pipeline" --read "claude.work,codex.ci"
33
-
34
- # Create a write-only API key
35
- axvault key create --name "Uploader" --write "claude.backups"
36
-
37
- # Create an admin API key with full access
38
- axvault key create --name "Admin" --read "*" --write "*" --grant "*"
39
-
40
- # List all API keys
41
- axvault key list
42
-
43
- # Extract key IDs for scripting (pipeline example)
44
- axvault key list | tail -n +2 | cut -f1
45
-
46
- # Update an API key's permissions
47
- axvault key update k_abc123def456 --add-read "claude.new"
48
-
49
- # Revoke an API key
50
- axvault key revoke k_abc123def456 --force
51
-
52
- # List all stored credentials
53
- axvault credential list
54
-
55
- # Delete a credential
56
- axvault credential delete claude.work --force`);
57
- program
58
- .command("init")
59
- .description("Initialize database and configuration")
60
- .option("--database-url <url>", "PostgreSQL connection URL")
61
- .option("-v, --verbose", "Enable verbose output")
62
- .action(handleInit);
26
+ # Start with debug logging
27
+ axvault serve --log-level debug`);
63
28
  program
64
29
  .command("serve")
65
30
  .description("Start the vault server")
@@ -71,69 +36,5 @@ program
71
36
  .option("--log-level <level>", "Log level (trace, debug, info, warn, error, fatal, silent)")
72
37
  .option("-v, --verbose", "Enable verbose output")
73
38
  .action(handleServe);
74
- // API key management commands
75
- const keyCommand = program
76
- .command("key")
77
- .description("Manage API keys")
78
- .helpCommand(false);
79
- keyCommand
80
- .command("create")
81
- .description("Create a new API key")
82
- .requiredOption("-n, --name <name>", "Name for the API key")
83
- .option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude.work,codex.ci' or '*')")
84
- .option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude.ci' or '*')")
85
- .option("-g, --grant <access>", "Comma-separated grant access list (can delegate these to other keys)")
86
- .option("--json", "Output as JSON")
87
- .option("--database-url <url>", "PostgreSQL connection URL")
88
- .action(handleKeyCreate);
89
- keyCommand
90
- .command("list")
91
- .description("List all API keys")
92
- .option("--json", "Output as JSON")
93
- .option("--database-url <url>", "PostgreSQL connection URL")
94
- .action(handleKeyList);
95
- keyCommand
96
- .command("revoke")
97
- .description("Revoke an API key")
98
- .argument("<id>", "API key ID (e.g., k_abc123def456)")
99
- .option("-f, --force", "Confirm destructive action")
100
- .option("-y, --yes", "Alias for --force")
101
- .option("-v, --verbose", "Enable verbose output")
102
- .option("--database-url <url>", "PostgreSQL connection URL")
103
- .action(handleKeyRevoke);
104
- keyCommand
105
- .command("update")
106
- .description("Update an API key's permissions")
107
- .argument("<id>", "API key ID (e.g., k_abc123def456)")
108
- .option("--add-read <access>", "Add read access entries")
109
- .option("--add-write <access>", "Add write access entries")
110
- .option("--add-grant <access>", "Add grant access entries")
111
- .option("--remove-read <access>", "Remove read access entries")
112
- .option("--remove-write <access>", "Remove write access entries")
113
- .option("--remove-grant <access>", "Remove grant access entries")
114
- .option("--json", "Output as JSON")
115
- .option("-v, --verbose", "Enable verbose output")
116
- .option("--database-url <url>", "PostgreSQL connection URL")
117
- .action(handleKeyUpdate);
118
- // Credential management commands
119
- const credentialCommand = program
120
- .command("credential")
121
- .description("Manage stored credentials")
122
- .helpCommand(false);
123
- credentialCommand
124
- .command("list")
125
- .description("List all stored credentials")
126
- .option("--json", "Output as JSON")
127
- .option("--database-url <url>", "PostgreSQL connection URL")
128
- .action(handleCredentialList);
129
- credentialCommand
130
- .command("delete")
131
- .description("Delete a credential")
132
- .argument("<name>", "Credential name (e.g., claude.work)")
133
- .option("-f, --force", "Confirm destructive action")
134
- .option("-y, --yes", "Alias for --force")
135
- .option("-v, --verbose", "Enable verbose output")
136
- .option("--database-url <url>", "PostgreSQL connection URL")
137
- .action(handleCredentialDelete);
138
39
  await program.parseAsync(process.argv);
139
40
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAEtD,OAAO,WAAW,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;KACtB,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC;KACpC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,CAAC;KAC7C,kBAAkB,CAAC,yCAAyC,CAAC;KAC7D,wBAAwB,EAAE;KAC1B,WAAW,CAAC,KAAK,CAAC;KAClB,WAAW,CACV,OAAO,EACP,MAAM,CAAC,GAAG,CAAA;;;;;;;;;kCASoB,CAC/B,CAAC;AAEJ,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,wBAAwB,CAAC;KACrC,MAAM,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;KAChD,MAAM,CAAC,mBAAmB,EAAE,iBAAiB,CAAC;KAC9C,MAAM,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;KAC3D,MAAM,CACL,+BAA+B,EAC/B,sEAAsE,CACvE;KACA,MAAM,CACL,wBAAwB,EACxB,gDAAgD,CACjD;KACA,MAAM,CACL,qBAAqB,EACrB,4DAA4D,CAC7D;KACA,MAAM,CAAC,eAAe,EAAE,uBAAuB,CAAC;KAChD,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -1,5 +1,9 @@
1
1
  /**
2
2
  * Start server command handler.
3
+ *
4
+ * Builds the Fastify app, registers plugins in dependency order
5
+ * (database → auth → routes), runs migrations, creates a bootstrap
6
+ * API key on first startup, and starts listening.
3
7
  */
4
8
  interface ServeOptions {
5
9
  port?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FtE"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkBH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2GtE"}
@@ -1,25 +1,28 @@
1
1
  /**
2
2
  * Start server command handler.
3
+ *
4
+ * Builds the Fastify app, registers plugins in dependency order
5
+ * (database → auth → routes), runs migrations, creates a bootstrap
6
+ * API key on first startup, and starts listening.
3
7
  */
4
8
  import { fileURLToPath } from "node:url";
5
9
  import closeWithGrace from "close-with-grace";
6
- import { getServerConfig } from "../config.js";
7
- import { closePool, createPool } from "../db/create-pool.js";
8
- import { runMigrations } from "../db/run-migrations.js";
9
10
  import { formatErrorMessage } from "../format-error-message.js";
10
- import { createHealthPlugin, createCredentialPlugin, createKeyPlugin, } from "../server/routes.js";
11
- import { createServer } from "../server/server.js";
11
+ import { resolveBuildAppConfig } from "../config.js";
12
+ import { bootstrapApiKey } from "../db/bootstrap-api-key.js";
13
+ import { runMigrations } from "../db/run-migrations.js";
14
+ import configPlugin from "../server/plugins/config.js";
15
+ import databasePlugin from "../server/plugins/database.js";
16
+ import authPlugin from "../server/plugins/auth.js";
17
+ import credentialRoutes from "../server/routes/credentials.js";
18
+ import healthRoutes from "../server/routes/health.js";
19
+ import keyRoutes from "../server/routes/keys.js";
20
+ import { buildApp } from "../server/server.js";
12
21
  const migrationsDirectory = fileURLToPath(new URL("../db/migrations", import.meta.url));
13
22
  export async function handleServe(options) {
14
- let config;
15
- const verbose = options.verbose === true;
23
+ let buildConfig;
16
24
  try {
17
- config = getServerConfig({
18
- port: options.port,
19
- host: options.host,
20
- databaseUrl: options.databaseUrl,
21
- refreshThresholdSeconds: options.refreshThreshold,
22
- refreshTimeoutMs: options.refreshTimeout,
25
+ buildConfig = resolveBuildAppConfig({
23
26
  logLevel: options.logLevel,
24
27
  });
25
28
  }
@@ -29,72 +32,89 @@ export async function handleServe(options) {
29
32
  // eslint-disable-next-line unicorn/no-process-exit -- CLI config error
30
33
  process.exit(1);
31
34
  }
32
- // Check encryption key is set
33
- const encryptionKey = process.env["AXVAULT_ENCRYPTION_KEY"];
34
- if (!encryptionKey || encryptionKey.length < 32) {
35
- console.error("Error: AXVAULT_ENCRYPTION_KEY must be set to at least 32 characters");
36
- // eslint-disable-next-line unicorn/no-process-exit -- CLI config error
35
+ // Build core app with security middleware and error handling
36
+ const app = buildApp(buildConfig);
37
+ try {
38
+ // Register plugins in dependency order: config database → auth → routes
39
+ await app.register(configPlugin, {
40
+ overrides: {
41
+ port: options.port,
42
+ host: options.host,
43
+ databaseUrl: options.databaseUrl,
44
+ refreshThresholdSeconds: options.refreshThreshold,
45
+ refreshTimeoutMs: options.refreshTimeout,
46
+ logLevel: options.logLevel,
47
+ },
48
+ });
49
+ await app.register(databasePlugin, (parent) => ({
50
+ connectionString: parent.config.databaseUrl,
51
+ }));
52
+ await app.register(authPlugin);
53
+ await app.register(healthRoutes);
54
+ await app.register(credentialRoutes, (parent) => ({
55
+ refreshThresholdSeconds: parent.config.refreshThresholdSeconds,
56
+ refreshTimeoutMs: parent.config.refreshTimeoutMs,
57
+ }));
58
+ await app.register(keyRoutes);
59
+ // Initialize plugins (creates pool, decorators, validated config)
60
+ await app.ready();
61
+ }
62
+ catch (error) {
63
+ const message = formatErrorMessage(error);
64
+ console.error(`Error: ${message}`);
65
+ await app.close();
66
+ // eslint-disable-next-line unicorn/no-process-exit -- CLI config/startup error
37
67
  process.exit(1);
38
68
  }
39
- // Create pool and run migrations
40
- const pool = createPool(config.databaseUrl);
69
+ // Run migrations using the pool created by @fastify/postgres
41
70
  try {
42
- await runMigrations(pool, migrationsDirectory);
71
+ await runMigrations(app.pg.pool, migrationsDirectory);
43
72
  }
44
73
  catch (error) {
45
74
  const message = formatErrorMessage(error);
46
- console.error(`Failed to initialize database: ${message}`);
47
- await closePool(pool);
75
+ app.log.error(`Failed to initialize database: ${message}`);
76
+ await app.close();
48
77
  // eslint-disable-next-line unicorn/no-process-exit -- CLI startup failure
49
78
  process.exit(1);
50
79
  }
51
- // Create server with plugins using the pool for DB access
52
- const server = createServer(config, [
53
- createHealthPlugin(),
54
- createCredentialPlugin(pool, {
55
- refreshThresholdSeconds: config.refreshThresholdSeconds,
56
- refreshTimeoutMs: config.refreshTimeoutMs,
57
- }),
58
- createKeyPlugin(pool),
59
- ]);
60
- // Graceful shutdown with close-with-grace.
61
- // The composition root owns both the server and the pool, so it is
62
- // responsible for closing both in the correct order.
63
- closeWithGrace({
64
- delay: 5000,
65
- logger: false,
66
- }, async ({ signal, err }) => {
67
- try {
68
- if (err) {
69
- server.app.log.error({ err, signal }, "Shutting down due to error");
70
- }
71
- else if (verbose) {
72
- server.app.log.info({ signal }, "Shutting down...");
73
- }
74
- }
75
- catch {
76
- // Server not started yet; use console.error as fallback
77
- if (err) {
78
- console.error("Shutting down due to error:", err);
79
- }
80
- else if (verbose) {
81
- console.error(`Shutting down (signal: ${signal})...`);
82
- }
80
+ // Create bootstrap admin key on first startup (no keys exist)
81
+ try {
82
+ const bootstrapKey = await bootstrapApiKey(app.pg);
83
+ if (bootstrapKey) {
84
+ app.log.info({ keyId: bootstrapKey.id }, "Created bootstrap admin API key — save this secret, it cannot be retrieved later:");
85
+ // Print the secret to stderr so it's visible but not mixed with structured logs
86
+ console.error(`\n ${bootstrapKey.key}\n`);
83
87
  }
84
- try {
85
- await server.stop();
88
+ }
89
+ catch (error) {
90
+ const message = formatErrorMessage(error);
91
+ app.log.error(`Failed to create bootstrap key: ${message}`);
92
+ await app.close();
93
+ // eslint-disable-next-line unicorn/no-process-exit -- CLI startup failure
94
+ process.exit(1);
95
+ }
96
+ // Graceful shutdown
97
+ closeWithGrace({ delay: 5000, logger: false }, async ({ signal, err }) => {
98
+ if (err) {
99
+ app.log.error({ err, signal }, "Shutting down due to error");
86
100
  }
87
- finally {
88
- await closePool(pool);
101
+ else if (options.verbose) {
102
+ app.log.info({ signal }, "Shutting down...");
89
103
  }
104
+ await app.close();
90
105
  });
106
+ // Start listening
91
107
  try {
92
- await server.start();
108
+ const address = await app.listen({
109
+ port: app.config.port,
110
+ host: app.config.host,
111
+ });
112
+ app.log.info({ address }, "axvault listening");
93
113
  }
94
114
  catch (error) {
95
115
  const message = formatErrorMessage(error);
96
- console.error(`Failed to start server on http://${config.host}:${config.port}:`, message);
97
- await closePool(pool);
116
+ console.error(`Failed to start server on http://${app.config.host}:${app.config.port}:`, message);
117
+ await app.close();
98
118
  // eslint-disable-next-line unicorn/no-process-exit -- CLI startup failure
99
119
  process.exit(1);
100
120
  }