axvault 1.0.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/bin/axvault +2 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.js +108 -0
  6. package/dist/commands/credential.d.ts +13 -0
  7. package/dist/commands/credential.js +113 -0
  8. package/dist/commands/init.d.ts +8 -0
  9. package/dist/commands/init.js +50 -0
  10. package/dist/commands/key.d.ts +21 -0
  11. package/dist/commands/key.js +157 -0
  12. package/dist/commands/serve.d.ts +12 -0
  13. package/dist/commands/serve.js +93 -0
  14. package/dist/config.d.ts +22 -0
  15. package/dist/config.js +44 -0
  16. package/dist/db/client.d.ts +13 -0
  17. package/dist/db/client.js +38 -0
  18. package/dist/db/migrations.d.ts +12 -0
  19. package/dist/db/migrations.js +96 -0
  20. package/dist/db/repositories/api-keys.d.ts +42 -0
  21. package/dist/db/repositories/api-keys.js +86 -0
  22. package/dist/db/repositories/audit-log.d.ts +37 -0
  23. package/dist/db/repositories/audit-log.js +58 -0
  24. package/dist/db/repositories/credentials.d.ts +48 -0
  25. package/dist/db/repositories/credentials.js +79 -0
  26. package/dist/db/types.d.ts +44 -0
  27. package/dist/db/types.js +4 -0
  28. package/dist/handlers/delete-credential.d.ts +15 -0
  29. package/dist/handlers/delete-credential.js +50 -0
  30. package/dist/handlers/get-credential.d.ts +21 -0
  31. package/dist/handlers/get-credential.js +143 -0
  32. package/dist/handlers/list-credentials.d.ts +12 -0
  33. package/dist/handlers/list-credentials.js +29 -0
  34. package/dist/handlers/put-credential.d.ts +15 -0
  35. package/dist/handlers/put-credential.js +94 -0
  36. package/dist/index.d.ts +18 -0
  37. package/dist/index.js +14 -0
  38. package/dist/lib/encryption.d.ts +17 -0
  39. package/dist/lib/encryption.js +38 -0
  40. package/dist/lib/format.d.ts +92 -0
  41. package/dist/lib/format.js +216 -0
  42. package/dist/middleware/auth.d.ts +21 -0
  43. package/dist/middleware/auth.js +50 -0
  44. package/dist/middleware/validate-parameters.d.ts +10 -0
  45. package/dist/middleware/validate-parameters.js +26 -0
  46. package/dist/refresh/check-refresh.d.ts +40 -0
  47. package/dist/refresh/check-refresh.js +83 -0
  48. package/dist/refresh/log-refresh.d.ts +17 -0
  49. package/dist/refresh/log-refresh.js +35 -0
  50. package/dist/refresh/refresh-manager.d.ts +51 -0
  51. package/dist/refresh/refresh-manager.js +132 -0
  52. package/dist/server/routes.d.ts +12 -0
  53. package/dist/server/routes.js +44 -0
  54. package/dist/server/server.d.ts +18 -0
  55. package/dist/server/server.js +106 -0
  56. package/package.json +93 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Łukasz Jerciński
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # axvault
2
+
3
+ Remote credential storage server for a╳point.
4
+
5
+ ## Configuration
6
+
7
+ ### Environment Variables
8
+
9
+ | Variable | Description | Default |
10
+ | ---------------------------- | -------------------------------------------------------------------- | ------------------- |
11
+ | `AXVAULT_PORT` | Port to listen on | `3847` |
12
+ | `AXVAULT_HOST` | Host to bind to | `127.0.0.1` |
13
+ | `AXVAULT_DB_PATH` | Database file path | `./data/axvault.db` |
14
+ | `AXVAULT_ENCRYPTION_KEY` | Encryption key (min 32 chars, required) | — |
15
+ | `AXVAULT_REFRESH_THRESHOLD` | Refresh credentials expiring within this many seconds (0 to disable) | `3600` |
16
+ | `AXVAULT_REFRESH_TIMEOUT_MS` | Timeout for refresh operations in milliseconds | `30000` |
17
+
18
+ ### CLI Flags
19
+
20
+ The `serve` command accepts flags that override environment variables:
21
+
22
+ ```bash
23
+ axvault serve \
24
+ --port 8080 \
25
+ --host 0.0.0.0 \
26
+ --db-path /data/vault.db \
27
+ --refresh-threshold 7200 \
28
+ --refresh-timeout 60000
29
+ ```
30
+
31
+ Setting `--refresh-threshold 0` disables automatic credential refresh.
32
+
33
+ ## API Keys
34
+
35
+ API keys control access to the credential API. Each key has configurable read and write permissions.
36
+
37
+ ### Create an API Key
38
+
39
+ ```bash
40
+ # Full access
41
+ axvault key create --name "CI Pipeline" --read "*" --write "*"
42
+
43
+ # Restricted access
44
+ axvault key create --name "Claude Reader" --read "claude/*"
45
+ axvault key create --name "Deploy Script" --write "claude/prod,codex/prod"
46
+ ```
47
+
48
+ The command outputs metadata to stderr and the secret key to stdout for easy piping:
49
+
50
+ ```
51
+ # stderr (visible in terminal):
52
+ Created API key: CI Pipeline
53
+ ID: k_a1b2c3d4e5f6
54
+ Read access: *
55
+ Write access: *
56
+
57
+ Save this key securely - it cannot be retrieved later.
58
+
59
+ # stdout (can be piped):
60
+ axv_sk_0123456789abcdef0123456789abcdef
61
+ ```
62
+
63
+ To copy directly to clipboard: `axvault key create --name "My Key" --read "*" | pbcopy`
64
+
65
+ ### List Keys
66
+
67
+ ```bash
68
+ axvault key list
69
+ ```
70
+
71
+ ### Revoke a Key
72
+
73
+ ```bash
74
+ axvault key revoke k_a1b2c3d4e5f6
75
+ ```
76
+
77
+ ### Container Deployments
78
+
79
+ #### Running the Container
80
+
81
+ The image uses an external UID pattern - no user is baked into the image. Specify the runtime user with `-u`/`--user`:
82
+
83
+ ```bash
84
+ # Docker
85
+ docker run -d \
86
+ --name axvault \
87
+ -p 3847:3847 \
88
+ -u 1000:1000 \
89
+ -e AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!" \
90
+ -v /srv/axvault/data:/data \
91
+ ghcr.io/jercik/axvault:latest
92
+
93
+ # Podman
94
+ podman run -d \
95
+ --name axvault \
96
+ -p 3847:3847 \
97
+ --user 1000:1000 \
98
+ -e AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!" \
99
+ -v /srv/axvault/data:/data:Z \
100
+ ghcr.io/jercik/axvault:latest
101
+ ```
102
+
103
+ #### Volume Ownership
104
+
105
+ The data volume must be owned by the UID/GID the container runs as:
106
+
107
+ ```bash
108
+ # Create directory and set ownership before first run
109
+ sudo mkdir -p /srv/axvault/data
110
+ sudo chown 1000:1000 /srv/axvault/data
111
+ ```
112
+
113
+ For rootless Podman, use your user's UID or let Podman handle mapping automatically.
114
+
115
+ #### Quadlet (systemd)
116
+
117
+ Create `/etc/containers/systemd/axvault.container`:
118
+
119
+ ```ini
120
+ [Unit]
121
+ Description=axvault credential server
122
+
123
+ [Container]
124
+ Image=ghcr.io/jercik/axvault:latest
125
+ PublishPort=3847:3847
126
+ User=1000
127
+ Group=1000
128
+ Environment=AXVAULT_ENCRYPTION_KEY=your-secret-key-minimum-32-chars!
129
+ Volume=/srv/axvault/data:/data:Z
130
+
131
+ [Service]
132
+ Restart=always
133
+
134
+ [Install]
135
+ WantedBy=multi-user.target
136
+ ```
137
+
138
+ Then reload and start:
139
+
140
+ ```bash
141
+ sudo systemctl daemon-reload
142
+ sudo systemctl start axvault
143
+ ```
144
+
145
+ #### Managing Keys in Containers
146
+
147
+ Exec into the container to manage API keys:
148
+
149
+ ```bash
150
+ # Podman
151
+ sudo podman exec axvault /nodejs/bin/node node_modules/axvault/bin/axvault key create --name "My Key" --write "*"
152
+
153
+ # Docker
154
+ docker exec axvault /nodejs/bin/node node_modules/axvault/bin/axvault key create --name "My Key" --write "*"
155
+ ```
156
+
157
+ ## Credentials API
158
+
159
+ ### Store a Credential
160
+
161
+ ```bash
162
+ curl -X PUT https://vault.example.com/api/v1/credentials/claude/prod \
163
+ -H "Authorization: Bearer <api_key>" \
164
+ -H "Content-Type: application/json" \
165
+ -d '{
166
+ "data": {"accessToken": "...", "refreshToken": "..."},
167
+ "expiresAt": "2025-12-31T23:59:59Z"
168
+ }'
169
+ ```
170
+
171
+ ### Retrieve a Credential
172
+
173
+ ```bash
174
+ curl https://vault.example.com/api/v1/credentials/claude/prod \
175
+ -H "Authorization: Bearer <api_key>"
176
+ ```
177
+
178
+ ### Delete a Credential
179
+
180
+ ```bash
181
+ curl -X DELETE https://vault.example.com/api/v1/credentials/claude/prod \
182
+ -H "Authorization: Bearer <api_key>"
183
+ ```
184
+
185
+ ## Auto-Refresh
186
+
187
+ axvault automatically refreshes OAuth credentials that are near expiration when they are retrieved. This behavior is controlled by the refresh threshold setting.
188
+
189
+ ### Access Control Note
190
+
191
+ Auto-refresh is a server-side maintenance operation that occurs transparently during credential retrieval. Read-only API keys can trigger refresh because:
192
+
193
+ - The refresh uses the credential's own `refresh_token` (already authorized by the token owner)
194
+ - The credential's identity and ownership remain unchanged
195
+ - Only token values and expiry timestamps are updated
196
+ - This prevents wasteful repeated refreshes and rate limit issues
197
+
198
+ This follows the pattern used by credential vaults like HashiCorp Vault, where credential maintenance is handled transparently on reads.
199
+
200
+ ### Response Headers
201
+
202
+ When retrieving credentials, the response may include these headers:
203
+
204
+ | Header | Value | Description |
205
+ | -------------------------- | ------ | ------------------------------------------------------------ |
206
+ | `X-Axvault-Refreshed` | `true` | Credential was successfully refreshed during this request |
207
+ | `X-Axvault-Refresh-Failed` | `true` | Refresh was attempted but failed; stale credentials returned |
208
+
209
+ When `X-Axvault-Refresh-Failed` is present, the response still returns HTTP 200 with the existing (potentially expired) credentials. Error details are logged to the audit log.
210
+
211
+ ## License
212
+
213
+ MIT
package/bin/axvault ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * axvault - Remote credential storage server for axpoint.
4
+ *
5
+ * Stores agent credentials and serves them via API.
6
+ */
7
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * axvault - Remote credential storage server for axpoint.
4
+ *
5
+ * Stores agent credentials and serves them via API.
6
+ */
7
+ import { Command } from "@commander-js/extra-typings";
8
+ 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, } from "./commands/key.js";
12
+ import { handleServe } from "./commands/serve.js";
13
+ const program = new Command()
14
+ .name(packageJson.name)
15
+ .description(packageJson.description)
16
+ .version(packageJson.version, "-V, --version")
17
+ .showHelpAfterError("(add --help for additional information)")
18
+ .showSuggestionAfterError()
19
+ .helpCommand(false)
20
+ .addHelpText("after", String.raw `
21
+ Examples:
22
+ # Initialize database
23
+ axvault init
24
+
25
+ # Start server
26
+ axvault serve
27
+
28
+ # Start server on custom port
29
+ axvault serve --port 8080
30
+
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 "*"
39
+
40
+ # List all API keys
41
+ axvault key list
42
+
43
+ # Revoke an API key
44
+ axvault key revoke k_abc123def456
45
+
46
+ # List all stored credentials
47
+ axvault credential list
48
+
49
+ # Delete a credential
50
+ axvault credential delete claude/work`);
51
+ program
52
+ .command("init")
53
+ .description("Initialize database and configuration")
54
+ .option("--db-path <path>", "Database file path")
55
+ .action(handleInit);
56
+ program
57
+ .command("serve")
58
+ .description("Start the vault server")
59
+ .option("-p, --port <port>", "Port to listen on")
60
+ .option("-H, --host <host>", "Host to bind to")
61
+ .option("--db-path <path>", "Database file path")
62
+ .option("--refresh-threshold <seconds>", "Refresh credentials expiring within this many seconds (0 to disable)")
63
+ .option("--refresh-timeout <ms>", "Timeout for refresh operations in milliseconds")
64
+ .action(handleServe);
65
+ // API key management commands
66
+ const keyCommand = program
67
+ .command("key")
68
+ .description("Manage API keys")
69
+ .helpCommand(false);
70
+ keyCommand
71
+ .command("create")
72
+ .description("Create a new API key")
73
+ .requiredOption("-n, --name <name>", "Name for the API key")
74
+ .option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude/work,codex/ci' or '*')")
75
+ .option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude/ci' or '*')")
76
+ .option("--json", "Output as JSON")
77
+ .option("--db-path <path>", "Database file path")
78
+ .action(handleKeyCreate);
79
+ keyCommand
80
+ .command("list")
81
+ .description("List all API keys")
82
+ .option("--json", "Output as JSON")
83
+ .option("--db-path <path>", "Database file path")
84
+ .action(handleKeyList);
85
+ keyCommand
86
+ .command("revoke")
87
+ .description("Revoke an API key")
88
+ .argument("<id>", "API key ID (e.g., k_abc123def456)")
89
+ .option("--db-path <path>", "Database file path")
90
+ .action(handleKeyRevoke);
91
+ // Credential management commands
92
+ const credentialCommand = program
93
+ .command("credential")
94
+ .description("Manage stored credentials")
95
+ .helpCommand(false);
96
+ credentialCommand
97
+ .command("list")
98
+ .description("List all stored credentials")
99
+ .option("--json", "Output as JSON")
100
+ .option("--db-path <path>", "Database file path")
101
+ .action(handleCredentialList);
102
+ credentialCommand
103
+ .command("delete")
104
+ .description("Delete a credential")
105
+ .argument("<path>", "Credential path (agent/name, e.g., claude/work)")
106
+ .option("--db-path <path>", "Database file path")
107
+ .action(handleCredentialDelete);
108
+ await program.parseAsync(process.argv);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Credential management command handlers.
3
+ */
4
+ interface CredentialListOptions {
5
+ dbPath?: string;
6
+ json?: boolean;
7
+ }
8
+ interface CredentialDeleteOptions {
9
+ dbPath?: string;
10
+ }
11
+ export declare function handleCredentialList(options: CredentialListOptions): void;
12
+ export declare function handleCredentialDelete(path: string, options: CredentialDeleteOptions): void;
13
+ export {};
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Credential management command handlers.
3
+ */
4
+ import { getServerConfig } from "../config.js";
5
+ import { closeDatabase, getDatabase } from "../db/client.js";
6
+ import { runMigrations } from "../db/migrations.js";
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 */
10
+ function printCredentialTable(credentials) {
11
+ console.log("AGENT\tNAME\tEXPIRES\tUPDATED");
12
+ for (const cred of credentials) {
13
+ const agent = sanitizeForTsv(cred.agent);
14
+ const name = sanitizeForTsv(cred.name);
15
+ const expires = formatExpiresAt(cred.expiresAt);
16
+ const updated = formatRelativeTime(cred.updatedAt);
17
+ console.log(`${agent}\t${name}\t${expires}\t${updated}`);
18
+ }
19
+ }
20
+ /**
21
+ * Parse credential path (agent/name).
22
+ *
23
+ * Control characters are checked BEFORE trimming to prevent silent bypass
24
+ * (e.g., "\nclaude/work" would otherwise become "claude/work" after trim).
25
+ */
26
+ function parseCredentialPath(path) {
27
+ // Reject inputs containing control characters BEFORE trimming
28
+ if (containsControlChars(path)) {
29
+ return {
30
+ ok: false,
31
+ message: `Invalid credential path: contains control characters.\nExpected format: agent/name (e.g., claude/work)`,
32
+ };
33
+ }
34
+ const trimmed = path.trim();
35
+ const parts = trimmed.split("/");
36
+ if (parts.length !== 2) {
37
+ return {
38
+ ok: false,
39
+ message: `Invalid credential path: ${trimmed}\nExpected format: agent/name (e.g., claude/work)`,
40
+ };
41
+ }
42
+ const agent = parts[0]?.trim();
43
+ const name = parts[1]?.trim();
44
+ if (!agent || !name) {
45
+ return {
46
+ ok: false,
47
+ message: `Invalid credential path: ${trimmed}\nBoth agent and name are required.`,
48
+ };
49
+ }
50
+ return { ok: true, agent, name };
51
+ }
52
+ export function handleCredentialList(options) {
53
+ try {
54
+ const config = getServerConfig(options);
55
+ const database = getDatabase(config.databasePath);
56
+ runMigrations(database);
57
+ const credentials = listCredentials(database);
58
+ if (options.json) {
59
+ const output = credentials.map((cred) => ({
60
+ agent: cred.agent,
61
+ name: cred.name,
62
+ createdAt: cred.createdAt.toISOString(),
63
+ updatedAt: cred.updatedAt.toISOString(),
64
+ expiresAt: formatDateForJson(cred.expiresAt),
65
+ }));
66
+ console.log(JSON.stringify(output, undefined, 2));
67
+ }
68
+ else if (credentials.length === 0) {
69
+ console.error("No credentials stored.");
70
+ console.error("Store credentials via the API: PUT /api/v1/credentials/:agent/:name");
71
+ }
72
+ else {
73
+ printCredentialTable(credentials);
74
+ }
75
+ }
76
+ catch (error) {
77
+ console.error(`Error: Failed to list credentials: ${getErrorMessage(error)}`);
78
+ process.exitCode = 1;
79
+ }
80
+ finally {
81
+ closeDatabase();
82
+ }
83
+ }
84
+ export function handleCredentialDelete(path, options) {
85
+ // Parse credential path first (before config parsing)
86
+ const parsed = parseCredentialPath(path);
87
+ if (!parsed.ok) {
88
+ console.error(`Error: ${parsed.message}`);
89
+ process.exitCode = 2;
90
+ return;
91
+ }
92
+ const { agent, name } = parsed;
93
+ try {
94
+ const config = getServerConfig(options);
95
+ const database = getDatabase(config.databasePath);
96
+ runMigrations(database);
97
+ const deleted = deleteCredential(database, agent, name);
98
+ if (deleted) {
99
+ console.error(`Deleted credential: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
100
+ }
101
+ else {
102
+ console.error(`Error: Credential not found: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
103
+ process.exitCode = 1;
104
+ }
105
+ }
106
+ catch (error) {
107
+ console.error(`Error: Failed to delete credential: ${getErrorMessage(error)}`);
108
+ process.exitCode = 1;
109
+ }
110
+ finally {
111
+ closeDatabase();
112
+ }
113
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Initialize database command handler.
3
+ */
4
+ interface InitOptions {
5
+ dbPath?: string;
6
+ }
7
+ export declare function handleInit(options: InitOptions): void;
8
+ export {};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Initialize database command handler.
3
+ */
4
+ import { existsSync, mkdirSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { getServerConfig } from "../config.js";
7
+ import { closeDatabase, getDatabase } from "../db/client.js";
8
+ import { CURRENT_VERSION, getSchemaVersion, runMigrations, } from "../db/migrations.js";
9
+ export function handleInit(options) {
10
+ const config = getServerConfig(options);
11
+ // Ensure data directory exists
12
+ const dataDirectory = path.dirname(config.databasePath);
13
+ if (!existsSync(dataDirectory)) {
14
+ try {
15
+ mkdirSync(dataDirectory, { recursive: true });
16
+ console.error(`Created data directory: ${dataDirectory}`);
17
+ }
18
+ catch (error) {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ console.error(`Failed to create data directory '${dataDirectory}': ${message}`);
21
+ process.exitCode = 1;
22
+ return;
23
+ }
24
+ }
25
+ // Initialize database
26
+ try {
27
+ const database = getDatabase(config.databasePath);
28
+ const versionBefore = getSchemaVersion(database);
29
+ runMigrations(database);
30
+ const versionAfter = getSchemaVersion(database);
31
+ if (versionBefore === 0) {
32
+ console.error(`Database initialized at: ${config.databasePath}`);
33
+ console.error(`Schema version: ${versionAfter}`);
34
+ }
35
+ else if (versionBefore < versionAfter) {
36
+ console.error(`Database migrated from v${versionBefore} to v${versionAfter}`);
37
+ }
38
+ else {
39
+ console.error(`Database already at version ${versionAfter} (current: ${CURRENT_VERSION})`);
40
+ }
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ console.error(`Failed to initialize database: ${message}`);
45
+ process.exitCode = 1;
46
+ }
47
+ finally {
48
+ closeDatabase();
49
+ }
50
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * API key management command handlers.
3
+ */
4
+ interface KeyCreateOptions {
5
+ dbPath?: string;
6
+ name: string;
7
+ read?: string;
8
+ write?: string;
9
+ json?: boolean;
10
+ }
11
+ interface KeyListOptions {
12
+ dbPath?: string;
13
+ json?: boolean;
14
+ }
15
+ interface KeyRevokeOptions {
16
+ dbPath?: string;
17
+ }
18
+ export declare function handleKeyCreate(options: KeyCreateOptions): void;
19
+ export declare function handleKeyList(options: KeyListOptions): void;
20
+ export declare function handleKeyRevoke(id: string, options: KeyRevokeOptions): void;
21
+ export {};