axvault 1.8.1 → 1.8.2

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 (48) hide show
  1. package/README.md +120 -36
  2. package/dist/cli.js +7 -7
  3. package/dist/commands/credential.d.ts +1 -1
  4. package/dist/commands/credential.js +26 -32
  5. package/dist/commands/init.js +12 -11
  6. package/dist/commands/serve.js +1 -0
  7. package/dist/db/migrations.d.ts +3 -2
  8. package/dist/db/migrations.js +25 -120
  9. package/dist/db/repositories/api-key-utilities.d.ts +3 -3
  10. package/dist/db/repositories/api-key-utilities.js +9 -10
  11. package/dist/db/repositories/audit-log.d.ts +3 -5
  12. package/dist/db/repositories/audit-log.js +7 -8
  13. package/dist/db/repositories/credentials-queries.d.ts +9 -5
  14. package/dist/db/repositories/credentials-queries.js +28 -12
  15. package/dist/db/repositories/credentials.d.ts +31 -14
  16. package/dist/db/repositories/credentials.js +39 -21
  17. package/dist/db/repositories/list-credentials-paginated.d.ts +26 -0
  18. package/dist/db/repositories/list-credentials-paginated.js +69 -0
  19. package/dist/db/repositories/parse-credential-row.d.ts +2 -9
  20. package/dist/db/repositories/parse-credential-row.js +2 -17
  21. package/dist/db/types.d.ts +3 -12
  22. package/dist/db/types.js +1 -1
  23. package/dist/handlers/delete-credential.d.ts +2 -3
  24. package/dist/handlers/delete-credential.js +8 -11
  25. package/dist/handlers/get-credential.d.ts +6 -3
  26. package/dist/handlers/get-credential.js +35 -78
  27. package/dist/handlers/list-credentials.d.ts +19 -3
  28. package/dist/handlers/list-credentials.js +83 -8
  29. package/dist/handlers/put-credential.d.ts +10 -3
  30. package/dist/handlers/put-credential.js +25 -78
  31. package/dist/handlers/refresh-credential-on-read.d.ts +26 -0
  32. package/dist/handlers/refresh-credential-on-read.js +145 -0
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/lib/credential-name.d.ts +10 -0
  36. package/dist/lib/credential-name.js +12 -0
  37. package/dist/lib/format.d.ts +1 -7
  38. package/dist/lib/format.js +7 -55
  39. package/dist/middleware/validate-parameters.d.ts +3 -3
  40. package/dist/middleware/validate-parameters.js +7 -14
  41. package/dist/server/routes.js +3 -3
  42. package/package.json +9 -9
  43. package/dist/refresh/check-refresh.d.ts +0 -29
  44. package/dist/refresh/check-refresh.js +0 -51
  45. package/dist/refresh/log-refresh.d.ts +0 -17
  46. package/dist/refresh/log-refresh.js +0 -35
  47. package/dist/refresh/refresh-manager.d.ts +0 -54
  48. package/dist/refresh/refresh-manager.js +0 -137
package/README.md CHANGED
@@ -2,41 +2,70 @@
2
2
 
3
3
  Remote credential storage server for a╳kit.
4
4
 
5
+ ## Prerequisites
6
+
7
+ - Node.js 22.14+
8
+ - `pnpm` (for `pnpm dlx axvault`) or `npx` (for `npx -y axvault`)
9
+ - `jq` for scripting against `--json` output
10
+
11
+ If `axvault` is not installed globally, prefix commands with `npx -y axvault` (or `pnpm dlx axvault`).
12
+
5
13
  ## Quick start
6
14
 
7
15
  ```bash
8
- export AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!"
9
- axvault init
10
- axvault serve
16
+ umask 077
17
+ printf 'AXVAULT_ENCRYPTION_KEY=' > .env
18
+ openssl rand -base64 32 >> .env
19
+ chmod 600 .env
20
+ set -a
21
+ . ./.env
22
+ set +a
23
+ mkdir -p ./data
24
+ npx -y axvault init
25
+ npx -y axvault serve
11
26
  ```
12
27
 
28
+ Keep the `.env` file and reuse the same key between restarts to avoid losing access to existing credentials.
29
+
30
+ In a new shell, you must re-export the variables from `.env` before running `axvault` commands (the server does not load `.env` automatically).
31
+
13
32
  In another shell, create an API key:
14
33
 
15
34
  ```bash
16
- axvault key create --name "Admin" --read "*" --write "*" --grant "*"
35
+ npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
17
36
  ```
18
37
 
19
38
  Add `--verbose` to commands like `init`, `serve`, `key revoke`, `key update`, and
20
39
  `credential delete` to see status output.
21
40
 
41
+ ## Output formats
42
+
43
+ List commands output TSV by default. Use `--json` on `key create`, `key list`,
44
+ `key update`, and `credential list` for structured output (note: `key create
45
+ --json` includes the secret key).
46
+
47
+ ```bash
48
+ npx -y axvault key list --json | jq -r '.[].id'
49
+ ```
50
+
22
51
  ## Pipeline examples
23
52
 
24
53
  ### Extract key IDs
25
54
 
26
55
  ```bash
27
- axvault key list | tail -n +2 | cut -f1
56
+ npx -y axvault key list | tail -n +2 | cut -f1
28
57
  ```
29
58
 
30
- ### Count credentials by type
59
+ ### Count credentials by creation date
31
60
 
32
61
  ```bash
33
- axvault credential list | tail -n +2 | cut -f3 | sort | uniq -c | sort -rn
62
+ npx -y axvault credential list --json | jq -r '.[].createdAt | split("T")[0]' | sort | uniq -c | sort -rn
34
63
  ```
35
64
 
36
- ### List credential paths for a single agent
65
+ ### List credential names in the `claude` namespace
37
66
 
38
67
  ```bash
39
- axvault credential list | tail -n +2 | awk -F'\t' '$1 == "claude" {print $1 "/" $2}'
68
+ npx -y axvault credential list | tail -n +2 | awk -F'\t' '$1 ~ /^claude\./ {print $1}'
40
69
  ```
41
70
 
42
71
  ## Agent Rule
@@ -49,8 +78,8 @@ Add to your `CLAUDE.md` or `AGENTS.md`:
49
78
  Run `npx -y axvault --help` to learn available options.
50
79
 
51
80
  Use `axvault` when you need to initialize the vault database, manage API keys,
52
- or list/delete stored credentials. It outputs TSV tables for list commands, so
53
- you can pipe them into standard Unix tools.
81
+ or list/delete stored credentials. List commands output TSV by default; add
82
+ `--json` for structured output you can pipe into `jq`.
54
83
  ```
55
84
 
56
85
  ## Configuration
@@ -71,7 +100,7 @@ you can pipe them into standard Unix tools.
71
100
  The `serve` command accepts flags that override environment variables:
72
101
 
73
102
  ```bash
74
- axvault serve \
103
+ npx -y axvault serve \
75
104
  --port 8080 \
76
105
  --host 0.0.0.0 \
77
106
  --db-path /data/vault.db \
@@ -81,6 +110,11 @@ axvault serve \
81
110
 
82
111
  Setting `--refresh-threshold 0` disables automatic credential refresh.
83
112
 
113
+ ## Confirmation flags
114
+
115
+ Destructive commands require confirmation: `axvault key revoke` and `axvault
116
+ credential delete` require `--force` (alias `--yes`).
117
+
84
118
  ## API Keys
85
119
 
86
120
  API keys control access to the credential API. Each key has configurable permissions:
@@ -93,17 +127,17 @@ API keys control access to the credential API. Each key has configurable permiss
93
127
 
94
128
  ```bash
95
129
  # Full access (read, write, and grant)
96
- axvault key create --name "Admin" --read "*" --write "*" --grant "*"
130
+ npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
97
131
 
98
132
  # Read/write access only
99
- axvault key create --name "CI Pipeline" --read "*" --write "*"
133
+ npx -y axvault key create --name "CI Pipeline" --read "*" --write "*"
100
134
 
101
135
  # Restricted access
102
- axvault key create --name "Claude Reader" --read "claude/work,claude/ci"
103
- axvault key create --name "Deploy Script" --write "claude/prod,codex/prod"
136
+ npx -y axvault key create --name "Claude Reader" --read "claude.work,claude.ci"
137
+ npx -y axvault key create --name "Deploy Script" --write "claude.prod,codex.prod"
104
138
 
105
139
  # Grant-only key (for delegation, does not allow direct read/write)
106
- axvault key create --name "Issuer" --grant "claude/work,claude/ci"
140
+ npx -y axvault key create --name "Issuer" --grant "claude.work,claude.ci"
107
141
  ```
108
142
 
109
143
  The command outputs metadata to stderr and the secret key to stdout for easy piping:
@@ -122,12 +156,12 @@ Save this key securely - it cannot be retrieved later.
122
156
  axv_sk_0123456789abcdef0123456789abcdef
123
157
  ```
124
158
 
125
- To copy directly to clipboard: `axvault key create --name "My Key" --read "*" | pbcopy`
159
+ To copy directly to clipboard: `npx -y axvault key create --name "My Key" --read "*" | pbcopy`
126
160
 
127
161
  ### List Keys
128
162
 
129
163
  ```bash
130
- axvault key list
164
+ npx -y axvault key list
131
165
  ```
132
166
 
133
167
  ### Update a Key
@@ -136,22 +170,22 @@ Modify an existing key's permissions:
136
170
 
137
171
  ```bash
138
172
  # Add read access for new credentials
139
- axvault key update k_a1b2c3d4e5f6 --add-read "gemini/prod"
173
+ npx -y axvault key update k_a1b2c3d4e5f6 --add-read "gemini.prod"
140
174
 
141
175
  # Remove write access
142
- axvault key update k_a1b2c3d4e5f6 --remove-write "claude/test"
176
+ npx -y axvault key update k_a1b2c3d4e5f6 --remove-write "claude.test"
143
177
 
144
178
  # Add grant permissions
145
- axvault key update k_a1b2c3d4e5f6 --add-grant "claude/work"
179
+ npx -y axvault key update k_a1b2c3d4e5f6 --add-grant "claude.work"
146
180
 
147
181
  # Multiple changes at once
148
- axvault key update k_a1b2c3d4e5f6 --add-read "codex/ci" --remove-write "claude/dev"
182
+ npx -y axvault key update k_a1b2c3d4e5f6 --add-read "codex.ci" --remove-write "claude.dev"
149
183
  ```
150
184
 
151
185
  ### Revoke a Key
152
186
 
153
187
  ```bash
154
- axvault key revoke k_a1b2c3d4e5f6 --force
188
+ npx -y axvault key revoke k_a1b2c3d4e5f6 --force
155
189
  ```
156
190
 
157
191
  This command requires `--force` or `--yes` to confirm.
@@ -243,7 +277,7 @@ docker exec axvault node /app/node_modules/axvault/bin/axvault key create --name
243
277
  ### Store a Credential
244
278
 
245
279
  ```bash
246
- curl -X PUT https://vault.example.com/api/v1/credentials/claude/prod \
280
+ curl -X PUT https://vault.example.com/api/v1/credentials/claude.prod \
247
281
  -H "Authorization: Bearer <api_key>" \
248
282
  -H "Content-Type: application/json" \
249
283
  -d '{
@@ -254,25 +288,75 @@ curl -X PUT https://vault.example.com/api/v1/credentials/claude/prod \
254
288
  }'
255
289
  ```
256
290
 
291
+ Returns 201 with the stored credential's metadata:
292
+
293
+ ```json
294
+ {
295
+ "name": "claude.prod",
296
+ "createdAt": "2026-01-15T10:30:00.000Z",
297
+ "updatedAt": "2026-01-15T10:30:00.000Z"
298
+ }
299
+ ```
300
+
257
301
  The `type` field is required and must be one of:
258
302
 
259
303
  - `"oauth-credentials"` — Full OAuth with refresh_token (eligible for auto-refresh)
260
304
  - `"oauth-token"` — Long-lived OAuth token like `CLAUDE_CODE_OAUTH_TOKEN` (static)
261
305
  - `"api-key"` — API key (static)
262
306
 
263
- The `provider` field is optional and stores the provider name for multi-provider agents. For example, OpenCode credentials can specify `"anthropic"`, `"openai"`, `"gemini"`, or `"opencode"` to distinguish which provider the credential is for.
307
+ The `provider` field is optional for single-provider agents. For OpenCode (multi-provider), `provider` is required (e.g., `"anthropic"`, `"openai"`, `"gemini"`).
308
+
309
+ ### List Credentials
310
+
311
+ ```bash
312
+ curl https://vault.example.com/api/v1/credentials \
313
+ -H "Authorization: Bearer <api_key>"
314
+ ```
315
+
316
+ Returns metadata for all accessible credentials. Supports opt-in cursor-based pagination:
317
+
318
+ | Parameter | Description | Default |
319
+ | --------- | -------------------------------------------------------- | ----------- |
320
+ | `limit` | Results per page (1–1000; values above 1000 are clamped) | All results |
321
+ | `cursor` | Name from previous page's `nextCursor` | — |
322
+
323
+ Without `limit`, all accessible credentials are returned (backward-compatible). `cursor` requires `limit` — providing `cursor` without `limit` returns 400.
324
+
325
+ ```bash
326
+ # First page
327
+ curl 'https://vault.example.com/api/v1/credentials?limit=50' \
328
+ -H "Authorization: Bearer <api_key>"
329
+
330
+ # Next page (cursor is the last name from the previous response)
331
+ curl 'https://vault.example.com/api/v1/credentials?limit=50&cursor=claude.prod' \
332
+ -H "Authorization: Bearer <api_key>"
333
+ ```
334
+
335
+ Response includes `nextCursor` when `limit` is set and more results are available:
336
+
337
+ ```json
338
+ {
339
+ "credentials": [
340
+ { "name": "claude.staging", "createdAt": "...", "updatedAt": "..." },
341
+ { "name": "gemini.ci", "createdAt": "...", "updatedAt": "..." }
342
+ ],
343
+ "nextCursor": "gemini.ci"
344
+ }
345
+ ```
264
346
 
265
347
  ### Retrieve a Credential
266
348
 
267
349
  ```bash
268
- curl https://vault.example.com/api/v1/credentials/claude/prod \
350
+ curl https://vault.example.com/api/v1/credentials/claude.prod \
269
351
  -H "Authorization: Bearer <api_key>"
270
352
  ```
271
353
 
272
354
  ### Delete a Credential
273
355
 
356
+ Returns 204 No Content on success.
357
+
274
358
  ```bash
275
- curl -X DELETE https://vault.example.com/api/v1/credentials/claude/prod \
359
+ curl -X DELETE https://vault.example.com/api/v1/credentials/claude.prod \
276
360
  -H "Authorization: Bearer <api_key>"
277
361
  ```
278
362
 
@@ -352,13 +436,13 @@ Alternatively, use separate environment variables:
352
436
 
353
437
  ### Common Errors
354
438
 
355
- | Error | Cause | Solution |
356
- | ---------------- | ------------------------------------------ | -------------------------------------------------------------------------------- |
357
- | `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON |
358
- | `unauthorized` | Invalid API key | Verify key with `axvault key list`, create new if needed |
359
- | `forbidden` | No access to credential | Add credential to key's read access: `axvault key update <id> --add-read <path>` |
360
- | `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>` |
361
- | `unreachable` | Network issue or server down | Check vault URL, verify server is running |
439
+ | Error | Cause | Solution |
440
+ | ---------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- |
441
+ | `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON |
442
+ | `unauthorized` | Invalid API key | Verify key with `npx -y axvault key list`, create new if needed |
443
+ | `forbidden` | No access to credential | Add credential to key's read access: `npx -y axvault key update <id> --add-read <path>` |
444
+ | `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>` |
445
+ | `unreachable` | Network issue or server down | Check vault URL, verify server is running |
362
446
 
363
447
  ### Debugging
364
448
 
@@ -371,7 +455,7 @@ Alternatively, use separate environment variables:
371
455
  2. Verify API key permissions:
372
456
 
373
457
  ```bash
374
- axvault key list # on server
458
+ npx -y axvault key list # on server
375
459
  ```
376
460
 
377
461
  3. Check credential exists:
package/dist/cli.js CHANGED
@@ -29,10 +29,10 @@ Examples:
29
29
  axvault serve --port 8080
30
30
 
31
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"
32
+ axvault key create --name "CI Pipeline" --read "claude.work,codex.ci"
33
33
 
34
34
  # Create a write-only API key
35
- axvault key create --name "Uploader" --write "claude/backups"
35
+ axvault key create --name "Uploader" --write "claude.backups"
36
36
 
37
37
  # Create an admin API key with full access
38
38
  axvault key create --name "Admin" --read "*" --write "*" --grant "*"
@@ -44,7 +44,7 @@ Examples:
44
44
  axvault key list | tail -n +2 | cut -f1
45
45
 
46
46
  # Update an API key's permissions
47
- axvault key update k_abc123def456 --add-read "claude/new"
47
+ axvault key update k_abc123def456 --add-read "claude.new"
48
48
 
49
49
  # Revoke an API key
50
50
  axvault key revoke k_abc123def456 --force
@@ -53,7 +53,7 @@ Examples:
53
53
  axvault credential list
54
54
 
55
55
  # Delete a credential
56
- axvault credential delete claude/work --force`);
56
+ axvault credential delete claude.work --force`);
57
57
  program
58
58
  .command("init")
59
59
  .description("Initialize database and configuration")
@@ -79,8 +79,8 @@ keyCommand
79
79
  .command("create")
80
80
  .description("Create a new API key")
81
81
  .requiredOption("-n, --name <name>", "Name for the API key")
82
- .option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude/work,codex/ci' or '*')")
83
- .option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude/ci' or '*')")
82
+ .option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude.work,codex.ci' or '*')")
83
+ .option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude.ci' or '*')")
84
84
  .option("-g, --grant <access>", "Comma-separated grant access list (can delegate these to other keys)")
85
85
  .option("--json", "Output as JSON")
86
86
  .option("--db-path <path>", "Database file path")
@@ -128,7 +128,7 @@ credentialCommand
128
128
  credentialCommand
129
129
  .command("delete")
130
130
  .description("Delete a credential")
131
- .argument("<path>", "Credential path (agent/name, e.g., claude/work)")
131
+ .argument("<name>", "Credential name (e.g., claude.work)")
132
132
  .option("-f, --force", "Confirm destructive action")
133
133
  .option("-y, --yes", "Alias for --force")
134
134
  .option("-v, --verbose", "Enable verbose output")
@@ -12,5 +12,5 @@ interface CredentialDeleteOptions {
12
12
  verbose?: boolean;
13
13
  }
14
14
  export declare function handleCredentialList(options: CredentialListOptions): void;
15
- export declare function handleCredentialDelete(path: string, options: CredentialDeleteOptions): void;
15
+ export declare function handleCredentialDelete(credentialName: string, options: CredentialDeleteOptions): void;
16
16
  export {};
@@ -5,50 +5,46 @@ import { getServerConfig } from "../config.js";
5
5
  import { closeDatabase, getDatabase } from "../db/client.js";
6
6
  import { runMigrations } from "../db/migrations.js";
7
7
  import { deleteCredential, listCredentials, } from "../db/repositories/credentials.js";
8
- import { containsControlChars, formatDateForJson, formatExpiresAt, formatRelativeTime, getErrorMessage, sanitizeForTsv, } from "../lib/format.js";
9
- /** Print credentials as TSV table */
8
+ import { containsControlChars, formatRelativeTime, getErrorMessage, sanitizeForTsv, } from "../lib/format.js";
9
+ import { CREDENTIAL_NAME_FORMAT_DESCRIPTION, isValidCredentialName, } from "../lib/credential-name.js";
10
+ /** Print credentials as TSV table (opaque schema - no type/expires columns) */
10
11
  function printCredentialTable(credentials) {
11
- console.log("AGENT\tNAME\tTYPE\tEXPIRES\tUPDATED");
12
+ console.log("NAME\tCREATED\tUPDATED");
12
13
  for (const cred of credentials) {
13
- const agent = sanitizeForTsv(cred.agent);
14
14
  const name = sanitizeForTsv(cred.name);
15
- const type = sanitizeForTsv(cred.type);
16
- const expires = formatExpiresAt(cred.expiresAt);
15
+ const created = formatRelativeTime(cred.createdAt);
17
16
  const updated = formatRelativeTime(cred.updatedAt);
18
- console.log(`${agent}\t${name}\t${type}\t${expires}\t${updated}`);
17
+ console.log(`${name}\t${created}\t${updated}`);
19
18
  }
20
19
  }
21
20
  /**
22
- * Parse credential path (agent/name).
21
+ * Parse and validate credential name.
23
22
  *
24
23
  * Control characters are checked BEFORE trimming to prevent silent bypass
25
- * (e.g., "\nclaude/work" would otherwise become "claude/work" after trim).
24
+ * (e.g., "\nwork" would otherwise become "work" after trim).
26
25
  */
27
- function parseCredentialPath(path) {
26
+ function parseCredentialName(name) {
28
27
  // Reject inputs containing control characters BEFORE trimming
29
- if (containsControlChars(path)) {
28
+ if (containsControlChars(name)) {
30
29
  return {
31
30
  ok: false,
32
- message: `Invalid credential path: contains control characters.\nExpected format: agent/name (e.g., claude/work)`,
31
+ message: "Invalid credential name: contains control characters.",
33
32
  };
34
33
  }
35
- const trimmed = path.trim();
36
- const parts = trimmed.split("/");
37
- if (parts.length !== 2) {
34
+ const trimmed = name.trim();
35
+ if (!trimmed) {
38
36
  return {
39
37
  ok: false,
40
- message: `Invalid credential path: ${trimmed}\nExpected format: agent/name (e.g., claude/work)`,
38
+ message: "Invalid credential name: name is required.",
41
39
  };
42
40
  }
43
- const agent = parts[0]?.trim();
44
- const name = parts[1]?.trim();
45
- if (!agent || !name) {
41
+ if (!isValidCredentialName(trimmed)) {
46
42
  return {
47
43
  ok: false,
48
- message: `Invalid credential path: ${trimmed}\nBoth agent and name are required.`,
44
+ message: `Invalid credential name format. Must be ${CREDENTIAL_NAME_FORMAT_DESCRIPTION}.`,
49
45
  };
50
46
  }
51
- return { ok: true, agent, name };
47
+ return { ok: true, name: trimmed };
52
48
  }
53
49
  export function handleCredentialList(options) {
54
50
  try {
@@ -57,19 +53,17 @@ export function handleCredentialList(options) {
57
53
  runMigrations(database);
58
54
  const credentials = listCredentials(database);
59
55
  if (options.json) {
56
+ // Opaque schema: type/provider/expiresAt not available without decrypting
60
57
  const output = credentials.map((cred) => ({
61
- agent: cred.agent,
62
58
  name: cred.name,
63
- type: cred.type,
64
59
  createdAt: cred.createdAt.toISOString(),
65
60
  updatedAt: cred.updatedAt.toISOString(),
66
- expiresAt: formatDateForJson(cred.expiresAt),
67
61
  }));
68
62
  console.log(JSON.stringify(output, undefined, 2));
69
63
  }
70
64
  else if (credentials.length === 0) {
71
65
  console.error("No credentials stored.");
72
- console.error("Store credentials via the API: PUT /api/v1/credentials/:agent/:name");
66
+ console.error("Store credentials via the API: PUT /api/v1/credentials/:name");
73
67
  }
74
68
  else {
75
69
  printCredentialTable(credentials);
@@ -83,9 +77,9 @@ export function handleCredentialList(options) {
83
77
  closeDatabase();
84
78
  }
85
79
  }
86
- export function handleCredentialDelete(path, options) {
87
- // Parse credential path first (before config parsing)
88
- const parsed = parseCredentialPath(path);
80
+ export function handleCredentialDelete(credentialName, options) {
81
+ // Validate credential name first (before config parsing)
82
+ const parsed = parseCredentialName(credentialName);
89
83
  if (!parsed.ok) {
90
84
  console.error(`Error: ${parsed.message}`);
91
85
  process.exitCode = 2;
@@ -97,19 +91,19 @@ export function handleCredentialDelete(path, options) {
97
91
  process.exitCode = 2;
98
92
  return;
99
93
  }
100
- const { agent, name } = parsed;
94
+ const { name } = parsed;
101
95
  try {
102
96
  const config = getServerConfig(options);
103
97
  const database = getDatabase(config.databasePath);
104
98
  runMigrations(database);
105
- const deleted = deleteCredential(database, agent, name);
99
+ const deleted = deleteCredential(database, name);
106
100
  if (deleted) {
107
101
  if (options.verbose) {
108
- console.error(`Deleted credential: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
102
+ console.error(`Deleted credential: ${sanitizeForTsv(name)}`);
109
103
  }
110
104
  }
111
105
  else {
112
- console.error(`Error: Credential not found: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
106
+ console.error(`Error: Credential not found: ${sanitizeForTsv(name)}`);
113
107
  process.exitCode = 1;
114
108
  }
115
109
  }
@@ -30,22 +30,23 @@ export function handleInit(options) {
30
30
  const versionBefore = getSchemaVersion(database);
31
31
  runMigrations(database);
32
32
  const versionAfter = getSchemaVersion(database);
33
- if (verbose) {
34
- if (versionBefore === 0) {
35
- console.error(`Database initialized at: ${config.databasePath}`);
36
- console.error(`Schema version: ${versionAfter}`);
37
- }
38
- else if (versionBefore < versionAfter) {
39
- console.error(`Database migrated from v${versionBefore} to v${versionAfter}`);
40
- }
41
- else {
42
- console.error(`Database already at version ${versionAfter} (current: ${CURRENT_VERSION})`);
43
- }
33
+ if (!verbose)
34
+ return;
35
+ if (versionBefore === 0) {
36
+ console.error(`Database initialized at: ${config.databasePath}`);
37
+ console.error(`Schema version: ${versionAfter}`);
38
+ return;
39
+ }
40
+ if (versionBefore === versionAfter) {
41
+ console.error(`Database already at version ${versionAfter} (current: ${CURRENT_VERSION})`);
42
+ return;
44
43
  }
44
+ console.error(`Database migrated from v${versionBefore} to v${versionAfter}`);
45
45
  }
46
46
  catch (error) {
47
47
  const message = error instanceof Error ? error.message : String(error);
48
48
  console.error(`Failed to initialize database: ${message}`);
49
+ console.error("If this is a schema version mismatch, delete the database file and re-run `axvault init`.");
49
50
  process.exitCode = 1;
50
51
  }
51
52
  finally {
@@ -57,6 +57,7 @@ export async function handleServe(options) {
57
57
  catch (error) {
58
58
  const message = error instanceof Error ? error.message : String(error);
59
59
  console.error(`Failed to initialize database: ${message}`);
60
+ console.error(`If this is a schema version mismatch, delete '${config.databasePath}' and restart.`);
60
61
  // eslint-disable-next-line unicorn/no-process-exit -- CLI startup failure
61
62
  process.exit(1);
62
63
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Database schema migrations.
3
3
  *
4
- * Uses a simple version-based migration system.
4
+ * Simple schema with opaque credential storage.
5
+ * Credentials are identified by name only - the blob contains all metadata.
5
6
  */
6
7
  import type Database from "better-sqlite3";
7
- declare const CURRENT_VERSION = 7;
8
+ declare const CURRENT_VERSION = 1;
8
9
  /** Run all pending migrations */
9
10
  declare function runMigrations(database: Database.Database): void;
10
11
  /** Get current schema version */