axvault 1.12.0 → 1.13.1
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.
- package/README.md +64 -123
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -104
- package/dist/cli.js.map +1 -1
- package/dist/commands/serve.d.ts +4 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +82 -62
- package/dist/commands/serve.js.map +1 -1
- package/dist/config.d.ts +58 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -37
- package/dist/config.js.map +1 -1
- package/dist/db/bootstrap-api-key.d.ts +14 -0
- package/dist/db/bootstrap-api-key.d.ts.map +1 -0
- package/dist/db/bootstrap-api-key.js +26 -0
- package/dist/db/bootstrap-api-key.js.map +1 -0
- package/dist/db/migrations/001-initial.sql +6 -5
- package/dist/db/repositories/audit-log.d.ts +2 -0
- package/dist/db/repositories/audit-log.d.ts.map +1 -1
- package/dist/db/repositories/audit-log.js +4 -2
- package/dist/db/repositories/audit-log.js.map +1 -1
- package/dist/db/run-migrations.d.ts +12 -0
- package/dist/db/run-migrations.d.ts.map +1 -1
- package/dist/db/run-migrations.js +19 -44
- package/dist/db/run-migrations.js.map +1 -1
- package/dist/db/types.d.ts +1 -0
- package/dist/db/types.d.ts.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/lib/access-list.d.ts +13 -0
- package/dist/lib/access-list.d.ts.map +1 -0
- package/dist/lib/access-list.js +22 -0
- package/dist/lib/access-list.js.map +1 -0
- package/dist/lib/credential-name.d.ts +1 -6
- package/dist/lib/credential-name.d.ts.map +1 -1
- package/dist/lib/credential-name.js +1 -4
- package/dist/lib/credential-name.js.map +1 -1
- package/dist/schemas/request.d.ts +35 -0
- package/dist/schemas/request.d.ts.map +1 -0
- package/dist/schemas/request.js +76 -0
- package/dist/schemas/request.js.map +1 -0
- package/dist/schemas/{api.d.ts → response.d.ts} +6 -32
- package/dist/schemas/response.d.ts.map +1 -0
- package/dist/schemas/response.js +59 -0
- package/dist/schemas/response.js.map +1 -0
- package/dist/server/plugins/auth.d.ts +19 -0
- package/dist/server/plugins/auth.d.ts.map +1 -0
- package/dist/server/plugins/auth.js +51 -0
- package/dist/server/plugins/auth.js.map +1 -0
- package/dist/server/plugins/config.d.ts +14 -0
- package/dist/server/plugins/config.d.ts.map +1 -0
- package/dist/server/plugins/config.js +19 -0
- package/dist/server/plugins/config.js.map +1 -0
- package/dist/server/plugins/database.d.ts +12 -0
- package/dist/server/plugins/database.d.ts.map +1 -0
- package/dist/server/plugins/database.js +20 -0
- package/dist/server/plugins/database.js.map +1 -0
- package/dist/server/routes/credentials.d.ts +8 -0
- package/dist/server/routes/credentials.d.ts.map +1 -0
- package/dist/server/routes/credentials.js +82 -0
- package/dist/server/routes/credentials.js.map +1 -0
- package/dist/server/routes/handle-create-key.d.ts +10 -0
- package/dist/server/routes/handle-create-key.d.ts.map +1 -0
- package/dist/server/routes/handle-create-key.js +44 -0
- package/dist/server/routes/handle-create-key.js.map +1 -0
- package/dist/server/routes/handle-delete-credential.d.ts +10 -0
- package/dist/server/routes/handle-delete-credential.d.ts.map +1 -0
- package/dist/{handlers/delete-credential.js → server/routes/handle-delete-credential.js} +19 -17
- package/dist/server/routes/handle-delete-credential.js.map +1 -0
- package/dist/server/routes/handle-delete-key.d.ts +11 -0
- package/dist/server/routes/handle-delete-key.d.ts.map +1 -0
- package/dist/server/routes/handle-delete-key.js +40 -0
- package/dist/server/routes/handle-delete-key.js.map +1 -0
- package/dist/server/routes/handle-get-credential.d.ts +18 -0
- package/dist/server/routes/handle-get-credential.d.ts.map +1 -0
- package/dist/{handlers/get-credential.js → server/routes/handle-get-credential.js} +17 -34
- package/dist/server/routes/handle-get-credential.js.map +1 -0
- package/dist/server/routes/handle-list-credentials.d.ts +13 -0
- package/dist/server/routes/handle-list-credentials.d.ts.map +1 -0
- package/dist/{handlers/list-credentials.js → server/routes/handle-list-credentials.js} +6 -24
- package/dist/server/routes/handle-list-credentials.js.map +1 -0
- package/dist/server/routes/handle-put-credential.d.ts +14 -0
- package/dist/server/routes/handle-put-credential.d.ts.map +1 -0
- package/dist/{handlers/put-credential.js → server/routes/handle-put-credential.js} +26 -33
- package/dist/server/routes/handle-put-credential.js.map +1 -0
- package/dist/server/routes/handle-update-key.d.ts +13 -0
- package/dist/server/routes/handle-update-key.d.ts.map +1 -0
- package/dist/server/routes/handle-update-key.js +74 -0
- package/dist/server/routes/handle-update-key.js.map +1 -0
- package/dist/server/routes/health.d.ts +7 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +31 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/keys.d.ts +12 -0
- package/dist/server/routes/keys.d.ts.map +1 -0
- package/dist/server/routes/keys.js +119 -0
- package/dist/server/routes/keys.js.map +1 -0
- package/dist/server/routes/log-grant-event.d.ts +7 -0
- package/dist/server/routes/log-grant-event.d.ts.map +1 -0
- package/dist/server/routes/log-grant-event.js +15 -0
- package/dist/server/routes/log-grant-event.js.map +1 -0
- package/dist/server/server.d.ts +6 -17
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +46 -54
- package/dist/server/server.js.map +1 -1
- package/package.json +24 -18
- package/dist/commands/credential.d.ts +0 -17
- package/dist/commands/credential.d.ts.map +0 -1
- package/dist/commands/credential.js +0 -144
- package/dist/commands/credential.js.map +0 -1
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -42
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/key-create.d.ts +0 -14
- package/dist/commands/key-create.d.ts.map +0 -1
- package/dist/commands/key-create.js +0 -111
- package/dist/commands/key-create.js.map +0 -1
- package/dist/commands/key-list.d.ts +0 -10
- package/dist/commands/key-list.d.ts.map +0 -1
- package/dist/commands/key-list.js +0 -57
- package/dist/commands/key-list.js.map +0 -1
- package/dist/commands/key-revoke.d.ts +0 -12
- package/dist/commands/key-revoke.d.ts.map +0 -1
- package/dist/commands/key-revoke.js +0 -64
- package/dist/commands/key-revoke.js.map +0 -1
- package/dist/commands/key-update.d.ts +0 -17
- package/dist/commands/key-update.d.ts.map +0 -1
- package/dist/commands/key-update.js +0 -106
- package/dist/commands/key-update.js.map +0 -1
- package/dist/commands/key.d.ts +0 -10
- package/dist/commands/key.d.ts.map +0 -1
- package/dist/commands/key.js +0 -10
- package/dist/commands/key.js.map +0 -1
- package/dist/handlers/create-key.d.ts +0 -14
- package/dist/handlers/create-key.d.ts.map +0 -1
- package/dist/handlers/create-key.js +0 -25
- package/dist/handlers/create-key.js.map +0 -1
- package/dist/handlers/delete-credential.d.ts +0 -15
- package/dist/handlers/delete-credential.d.ts.map +0 -1
- package/dist/handlers/delete-credential.js.map +0 -1
- package/dist/handlers/delete-key.d.ts +0 -15
- package/dist/handlers/delete-key.d.ts.map +0 -1
- package/dist/handlers/delete-key.js +0 -24
- package/dist/handlers/delete-key.js.map +0 -1
- package/dist/handlers/get-credential.d.ts +0 -27
- package/dist/handlers/get-credential.d.ts.map +0 -1
- package/dist/handlers/get-credential.js.map +0 -1
- package/dist/handlers/get-key.d.ts +0 -15
- package/dist/handlers/get-key.d.ts.map +0 -1
- package/dist/handlers/get-key.js +0 -20
- package/dist/handlers/get-key.js.map +0 -1
- package/dist/handlers/list-credentials.d.ts +0 -27
- package/dist/handlers/list-credentials.d.ts.map +0 -1
- package/dist/handlers/list-credentials.js.map +0 -1
- package/dist/handlers/list-keys.d.ts +0 -11
- package/dist/handlers/list-keys.d.ts.map +0 -1
- package/dist/handlers/list-keys.js +0 -18
- package/dist/handlers/list-keys.js.map +0 -1
- package/dist/handlers/put-credential.d.ts +0 -24
- package/dist/handlers/put-credential.d.ts.map +0 -1
- package/dist/handlers/put-credential.js.map +0 -1
- package/dist/handlers/update-key.d.ts +0 -17
- package/dist/handlers/update-key.d.ts.map +0 -1
- package/dist/handlers/update-key.js +0 -47
- package/dist/handlers/update-key.js.map +0 -1
- package/dist/lib/format-key-details.d.ts +0 -17
- package/dist/lib/format-key-details.d.ts.map +0 -1
- package/dist/lib/format-key-details.js +0 -25
- package/dist/lib/format-key-details.js.map +0 -1
- package/dist/lib/format.d.ts +0 -89
- package/dist/lib/format.d.ts.map +0 -1
- package/dist/lib/format.js +0 -180
- package/dist/lib/format.js.map +0 -1
- package/dist/lib/parse-access-options.d.ts +0 -38
- package/dist/lib/parse-access-options.d.ts.map +0 -1
- package/dist/lib/parse-access-options.js +0 -85
- package/dist/lib/parse-access-options.js.map +0 -1
- package/dist/middleware/auth.d.ts +0 -22
- package/dist/middleware/auth.d.ts.map +0 -1
- package/dist/middleware/auth.js +0 -49
- package/dist/middleware/auth.js.map +0 -1
- package/dist/middleware/require-grant-access.d.ts +0 -10
- package/dist/middleware/require-grant-access.d.ts.map +0 -1
- package/dist/middleware/require-grant-access.js +0 -14
- package/dist/middleware/require-grant-access.js.map +0 -1
- package/dist/schemas/api.d.ts.map +0 -1
- package/dist/schemas/api.js +0 -119
- package/dist/schemas/api.js.map +0 -1
- package/dist/server/routes.d.ts +0 -14
- package/dist/server/routes.d.ts.map +0 -1
- package/dist/server/routes.js +0 -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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
Keep the `.env` file and reuse the same encryption key between restarts to avoid losing access to existing credentials.
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
## Architecture
|
|
42
41
|
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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`
|
|
90
|
-
|
|
91
|
-
|
|
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**:
|
|
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
|
-
|
|
144
|
-
npx -y axvault key create --name "CI Pipeline" --read "*" --write "*"
|
|
108
|
+
### Bootstrap Key
|
|
145
109
|
|
|
146
|
-
|
|
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
|
-
|
|
151
|
-
npx -y axvault key create --name "Issuer" --grant "claude.work,claude.ci"
|
|
152
|
-
```
|
|
112
|
+
### Managing Keys via API
|
|
153
113
|
|
|
154
|
-
|
|
114
|
+
All key management is done through the HTTP API:
|
|
155
115
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
187
|
-
|
|
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
|
-
#
|
|
190
|
-
|
|
135
|
+
# List all keys
|
|
136
|
+
curl https://vault.example.com/api/v1/keys \
|
|
137
|
+
-H "Authorization: Bearer $API_KEY"
|
|
191
138
|
|
|
192
|
-
#
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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 |
|
|
464
|
-
| `forbidden` | No access to credential |
|
|
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.
|
|
416
|
+
2. List API keys (requires grant access):
|
|
477
417
|
|
|
478
418
|
```bash
|
|
479
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
#
|
|
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
|
-
#
|
|
32
|
-
axvault
|
|
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":";AACA
|
|
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"}
|
package/dist/commands/serve.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/commands/serve.js
CHANGED
|
@@ -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 {
|
|
11
|
-
import {
|
|
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
|
|
15
|
-
const verbose = options.verbose === true;
|
|
23
|
+
let buildConfig;
|
|
16
24
|
try {
|
|
17
|
-
|
|
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
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
47
|
-
await
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
|
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
|
|
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
|
}
|