axvault 1.9.4 → 1.10.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.
package/README.md CHANGED
@@ -308,6 +308,13 @@ The `type` field is required and must be one of:
308
308
 
309
309
  The `provider` field is optional for single-provider agents. For OpenCode (multi-provider), `provider` is required (e.g., `"anthropic"`, `"openai"`, `"gemini"`).
310
310
 
311
+ Optional metadata fields:
312
+
313
+ - `"displayName"` — Human-readable label (e.g., `"Claude (Work)"`)
314
+ - `"notes"` — Free-form notes about the credential
315
+
316
+ These fields are stored as plaintext metadata (not encrypted) and returned in list and get responses.
317
+
311
318
  ### List Credentials
312
319
 
313
320
  ```bash
@@ -339,13 +346,20 @@ Response includes `nextCursor` when `limit` is set and more results are availabl
339
346
  ```json
340
347
  {
341
348
  "credentials": [
342
- { "name": "claude.staging", "createdAt": "...", "updatedAt": "..." },
349
+ {
350
+ "name": "claude.staging",
351
+ "displayName": "Claude (Staging)",
352
+ "createdAt": "...",
353
+ "updatedAt": "..."
354
+ },
343
355
  { "name": "gemini.ci", "createdAt": "...", "updatedAt": "..." }
344
356
  ],
345
357
  "nextCursor": "gemini.ci"
346
358
  }
347
359
  ```
348
360
 
361
+ Optional fields (`agent`, `provider`, `displayName`, `notes`) are included in responses only when set.
362
+
349
363
  ### Retrieve a Credential
350
364
 
351
365
  ```bash
@@ -9,14 +9,15 @@ import { containsControlChars, formatRelativeTime, getErrorMessage, sanitizeForT
9
9
  import { CREDENTIAL_NAME_FORMAT_DESCRIPTION, isValidCredentialName, } from "../lib/credential-name.js";
10
10
  /** Print credentials as TSV table */
11
11
  function printCredentialTable(credentials) {
12
- console.log("NAME\tAGENT\tPROVIDER\tCREATED\tUPDATED");
12
+ console.log("NAME\tAGENT\tPROVIDER\tDISPLAY_NAME\tCREATED\tUPDATED");
13
13
  for (const cred of credentials) {
14
14
  const name = sanitizeForTsv(cred.name);
15
- const agent = cred.agent || "";
16
- const provider = cred.provider ?? "";
15
+ const agent = sanitizeForTsv(cred.agent || "");
16
+ const provider = sanitizeForTsv(cred.provider ?? "");
17
+ const displayName = sanitizeForTsv(cred.displayName ?? "");
17
18
  const created = formatRelativeTime(cred.createdAt);
18
19
  const updated = formatRelativeTime(cred.updatedAt);
19
- console.log(`${name}\t${agent}\t${provider}\t${created}\t${updated}`);
20
+ console.log(`${name}\t${agent}\t${provider}\t${displayName}\t${created}\t${updated}`);
20
21
  }
21
22
  }
22
23
  /**
@@ -59,6 +60,10 @@ export function handleCredentialList(options) {
59
60
  name: cred.name,
60
61
  ...(cred.agent !== "" && { agent: cred.agent }),
61
62
  ...(cred.provider !== undefined && { provider: cred.provider }),
63
+ ...(cred.displayName !== undefined && {
64
+ displayName: cred.displayName,
65
+ }),
66
+ ...(cred.notes !== undefined && { notes: cred.notes }),
62
67
  createdAt: cred.createdAt.toISOString(),
63
68
  updatedAt: cred.updatedAt.toISOString(),
64
69
  }));
package/dist/config.js CHANGED
@@ -26,7 +26,7 @@ export function getServerConfig(overrides = {}) {
26
26
  function parsePort(value) {
27
27
  if (!value)
28
28
  return DEFAULT_PORT;
29
- const port = Number.parseInt(value);
29
+ const port = Number.parseInt(value, 10);
30
30
  if (Number.isNaN(port) || port < 1 || port > 65_535) {
31
31
  throw new Error(`Invalid port: ${value}`);
32
32
  }
@@ -35,7 +35,7 @@ function parsePort(value) {
35
35
  function parseNonNegativeInt(value, defaultValue, name) {
36
36
  if (!value)
37
37
  return defaultValue;
38
- const parsed = Number.parseInt(value);
38
+ const parsed = Number.parseInt(value, 10);
39
39
  if (Number.isNaN(parsed) || parsed < 0) {
40
40
  console.warn(`Invalid ${name} value "${value}", using default ${defaultValue}`);
41
41
  return defaultValue;
@@ -5,7 +5,7 @@
5
5
  * Credentials are identified by name only - the blob contains all metadata.
6
6
  */
7
7
  import type Database from "better-sqlite3";
8
- declare const CURRENT_VERSION = 2;
8
+ declare const CURRENT_VERSION = 3;
9
9
  /** Run all pending migrations */
10
10
  declare function runMigrations(database: Database.Database): void;
11
11
  /** Get current schema version */
@@ -4,7 +4,7 @@
4
4
  * Simple schema with opaque credential storage.
5
5
  * Credentials are identified by name only - the blob contains all metadata.
6
6
  */
7
- const CURRENT_VERSION = 2;
7
+ const CURRENT_VERSION = 3;
8
8
  /** Run all pending migrations */
9
9
  function runMigrations(database) {
10
10
  let version = getSchemaVersion(database);
@@ -22,6 +22,11 @@ function runMigrations(database) {
22
22
  version = 2;
23
23
  continue;
24
24
  }
25
+ if (version === 2) {
26
+ migrateToV3(database);
27
+ version = 3;
28
+ continue;
29
+ }
25
30
  throw new Error(`Unsupported database schema version v${version} (expected v${CURRENT_VERSION}). Delete the database file to reinitialize.`);
26
31
  }
27
32
  }
@@ -113,4 +118,23 @@ function migrateToV2(database) {
113
118
  setSchemaVersion(database, 2);
114
119
  })();
115
120
  }
121
+ /**
122
+ * Migration to version 3: Add display_name and notes columns
123
+ *
124
+ * Optional metadata for credential identification. display_name provides
125
+ * a human-readable label (e.g., "Claude (Work)") and notes provides
126
+ * additional context. Both flow through axauth to axusage for
127
+ * multi-instance identification.
128
+ */
129
+ function migrateToV3(database) {
130
+ database.transaction(() => {
131
+ database.exec(`
132
+ ALTER TABLE credentials ADD COLUMN display_name TEXT DEFAULT NULL
133
+ `);
134
+ database.exec(`
135
+ ALTER TABLE credentials ADD COLUMN notes TEXT DEFAULT NULL
136
+ `);
137
+ setSchemaVersion(database, 3);
138
+ })();
139
+ }
116
140
  export { CURRENT_VERSION, getSchemaVersion, runMigrations };
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * SQL queries for credentials repository.
3
3
  */
4
- export declare const UPSERT_CREDENTIAL = "\n INSERT INTO credentials (name, agent, provider, encrypted_data, salt, iv, auth_tag, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET\n agent = excluded.agent, provider = excluded.provider,\n encrypted_data = excluded.encrypted_data, salt = excluded.salt, iv = excluded.iv, auth_tag = excluded.auth_tag,\n updated_at = excluded.updated_at\n RETURNING created_at, updated_at";
4
+ export declare const UPSERT_CREDENTIAL = "\n INSERT INTO credentials (name, agent, provider, display_name, notes, encrypted_data, salt, iv, auth_tag, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET\n agent = excluded.agent, provider = excluded.provider,\n display_name = excluded.display_name, notes = excluded.notes,\n encrypted_data = excluded.encrypted_data, salt = excluded.salt, iv = excluded.iv, auth_tag = excluded.auth_tag,\n updated_at = excluded.updated_at\n RETURNING created_at, updated_at";
5
5
  export declare const UPDATE_CREDENTIAL_IF_UPDATED_AT_MATCHES = "\n UPDATE credentials\n SET encrypted_data = ?, salt = ?, iv = ?, auth_tag = ?, updated_at = ?\n WHERE name = ? AND updated_at = ?";
6
- export declare const SELECT_CREDENTIAL = "\n SELECT name, agent, provider, encrypted_data, salt, iv, auth_tag, created_at, updated_at\n FROM credentials WHERE name = ?";
7
- export declare const SELECT_ALL_METADATA = "\n SELECT name, agent, provider, created_at, updated_at\n FROM credentials ORDER BY name";
8
- export declare const SELECT_METADATA_PAGINATED = "\n SELECT name, agent, provider, created_at, updated_at\n FROM credentials\n WHERE name > ?\n ORDER BY name\n LIMIT ?";
9
- export declare const SELECT_METADATA_FIRST_PAGE = "\n SELECT name, agent, provider, created_at, updated_at\n FROM credentials\n ORDER BY name\n LIMIT ?";
6
+ export declare const SELECT_CREDENTIAL = "\n SELECT name, agent, provider, display_name, notes, encrypted_data, salt, iv, auth_tag, created_at, updated_at\n FROM credentials WHERE name = ?";
7
+ export declare const SELECT_ALL_METADATA = "\n SELECT name, agent, provider, display_name, notes, created_at, updated_at\n FROM credentials ORDER BY name";
8
+ export declare const SELECT_METADATA_PAGINATED = "\n SELECT name, agent, provider, display_name, notes, created_at, updated_at\n FROM credentials\n WHERE name > ?\n ORDER BY name\n LIMIT ?";
9
+ export declare const SELECT_METADATA_FIRST_PAGE = "\n SELECT name, agent, provider, display_name, notes, created_at, updated_at\n FROM credentials\n ORDER BY name\n LIMIT ?";
10
10
  export declare const DELETE_CREDENTIAL = "\n DELETE FROM credentials WHERE name = ?";
@@ -2,10 +2,11 @@
2
2
  * SQL queries for credentials repository.
3
3
  */
4
4
  export const UPSERT_CREDENTIAL = `
5
- INSERT INTO credentials (name, agent, provider, encrypted_data, salt, iv, auth_tag, created_at, updated_at)
6
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5
+ INSERT INTO credentials (name, agent, provider, display_name, notes, encrypted_data, salt, iv, auth_tag, created_at, updated_at)
6
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7
7
  ON CONFLICT(name) DO UPDATE SET
8
8
  agent = excluded.agent, provider = excluded.provider,
9
+ display_name = excluded.display_name, notes = excluded.notes,
9
10
  encrypted_data = excluded.encrypted_data, salt = excluded.salt, iv = excluded.iv, auth_tag = excluded.auth_tag,
10
11
  updated_at = excluded.updated_at
11
12
  RETURNING created_at, updated_at`;
@@ -14,19 +15,19 @@ export const UPDATE_CREDENTIAL_IF_UPDATED_AT_MATCHES = `
14
15
  SET encrypted_data = ?, salt = ?, iv = ?, auth_tag = ?, updated_at = ?
15
16
  WHERE name = ? AND updated_at = ?`;
16
17
  export const SELECT_CREDENTIAL = `
17
- SELECT name, agent, provider, encrypted_data, salt, iv, auth_tag, created_at, updated_at
18
+ SELECT name, agent, provider, display_name, notes, encrypted_data, salt, iv, auth_tag, created_at, updated_at
18
19
  FROM credentials WHERE name = ?`;
19
20
  export const SELECT_ALL_METADATA = `
20
- SELECT name, agent, provider, created_at, updated_at
21
+ SELECT name, agent, provider, display_name, notes, created_at, updated_at
21
22
  FROM credentials ORDER BY name`;
22
23
  export const SELECT_METADATA_PAGINATED = `
23
- SELECT name, agent, provider, created_at, updated_at
24
+ SELECT name, agent, provider, display_name, notes, created_at, updated_at
24
25
  FROM credentials
25
26
  WHERE name > ?
26
27
  ORDER BY name
27
28
  LIMIT ?`;
28
29
  export const SELECT_METADATA_FIRST_PAGE = `
29
- SELECT name, agent, provider, created_at, updated_at
30
+ SELECT name, agent, provider, display_name, notes, created_at, updated_at
30
31
  FROM credentials
31
32
  ORDER BY name
32
33
  LIMIT ?`;
@@ -16,6 +16,8 @@ declare function upsertCredential(database: Database.Database, credential: {
16
16
  name: string;
17
17
  agent: string;
18
18
  provider: string | undefined;
19
+ displayName: string | undefined;
20
+ notes: string | undefined;
19
21
  encryptedData: Buffer;
20
22
  salt: Buffer;
21
23
  iv: Buffer;
@@ -11,7 +11,7 @@ function upsertCredential(database, credential) {
11
11
  const now = Date.now();
12
12
  const row = database
13
13
  .prepare(SQL.UPSERT_CREDENTIAL)
14
- .get(credential.name, credential.agent, credential.provider ?? undefined, credential.encryptedData, credential.salt, credential.iv, credential.authTag, now, now);
14
+ .get(credential.name, credential.agent, credential.provider ?? undefined, credential.displayName ?? undefined, credential.notes ?? undefined, credential.encryptedData, credential.salt, credential.iv, credential.authTag, now, now);
15
15
  return {
16
16
  createdAt: new Date(row.created_at),
17
17
  updatedAt: new Date(row.updated_at),
@@ -48,7 +48,7 @@ function listCredentialsForApiKey(database, readAccess) {
48
48
  return [];
49
49
  const placeholders = readAccess.map(() => "?").join(", ");
50
50
  const sql = `
51
- SELECT name, created_at, updated_at
51
+ SELECT name, agent, provider, display_name, notes, created_at, updated_at
52
52
  FROM credentials
53
53
  WHERE name IN (${placeholders})
54
54
  ORDER BY name`;
@@ -16,7 +16,7 @@ function buildFilteredQuery(names, cursor, limit) {
16
16
  const placeholders = names.map(() => "?").join(", ");
17
17
  const cursorClause = cursor ? " AND name > ?" : "";
18
18
  const sql = `
19
- SELECT name, agent, provider, created_at, updated_at
19
+ SELECT name, agent, provider, display_name, notes, created_at, updated_at
20
20
  FROM credentials
21
21
  WHERE name IN (${placeholders})${cursorClause}
22
22
  ORDER BY name
@@ -9,6 +9,8 @@ interface CredentialRecord {
9
9
  name: string;
10
10
  agent: string;
11
11
  provider: string | undefined;
12
+ displayName: string | undefined;
13
+ notes: string | undefined;
12
14
  encryptedData: Buffer;
13
15
  salt: Buffer;
14
16
  iv: Buffer;
@@ -21,6 +23,8 @@ interface CredentialMetadata {
21
23
  name: string;
22
24
  agent: string;
23
25
  provider: string | undefined;
26
+ displayName: string | undefined;
27
+ notes: string | undefined;
24
28
  createdAt: Date;
25
29
  updatedAt: Date;
26
30
  }
@@ -9,6 +9,8 @@ function rowToRecord(row) {
9
9
  name: row.name,
10
10
  agent: row.agent,
11
11
  provider: row.provider ?? undefined,
12
+ displayName: row.display_name ?? undefined,
13
+ notes: row.notes ?? undefined,
12
14
  encryptedData: row.encrypted_data,
13
15
  salt: row.salt,
14
16
  iv: row.iv,
@@ -23,6 +25,8 @@ function rowToMetadata(row) {
23
25
  name: row.name,
24
26
  agent: row.agent,
25
27
  provider: row.provider ?? undefined,
28
+ displayName: row.display_name ?? undefined,
29
+ notes: row.notes ?? undefined,
26
30
  createdAt: new Date(row.created_at),
27
31
  updatedAt: new Date(row.updated_at),
28
32
  };
@@ -28,6 +28,8 @@ export interface CredentialRow {
28
28
  name: string;
29
29
  agent: string;
30
30
  provider: string | null;
31
+ display_name: string | null;
32
+ notes: string | null;
31
33
  encrypted_data: Buffer;
32
34
  salt: Buffer;
33
35
  iv: Buffer;
@@ -40,6 +42,8 @@ export interface MetadataRow {
40
42
  name: string;
41
43
  agent: string;
42
44
  provider: string | null;
45
+ display_name: string | null;
46
+ notes: string | null;
43
47
  created_at: number;
44
48
  updated_at: number;
45
49
  }
@@ -103,6 +103,10 @@ function createGetCredentialHandler(database, config) {
103
103
  ...(credential.provider !== undefined && {
104
104
  provider: credential.provider,
105
105
  }),
106
+ ...(credential.displayName !== undefined && {
107
+ displayName: credential.displayName,
108
+ }),
109
+ ...(credential.notes !== undefined && { notes: credential.notes }),
106
110
  credential: finalBlob,
107
111
  updatedAt: finalUpdatedAt.toISOString(),
108
112
  });
@@ -16,6 +16,8 @@ function formatCredentialMetadata(cred) {
16
16
  name: cred.name,
17
17
  ...(cred.agent !== "" && { agent: cred.agent }),
18
18
  ...(cred.provider !== undefined && { provider: cred.provider }),
19
+ ...(cred.displayName !== undefined && { displayName: cred.displayName }),
20
+ ...(cred.notes !== undefined && { notes: cred.notes }),
19
21
  createdAt: cred.createdAt.toISOString(),
20
22
  updatedAt: cred.updatedAt.toISOString(),
21
23
  };
@@ -54,13 +54,25 @@ function createPutCredentialHandler(database) {
54
54
  const provider = typeof bodyRecord.provider === "string"
55
55
  ? bodyRecord.provider
56
56
  : undefined;
57
- // Encrypt the credential blob (agent/provider stored as columns, not in blob)
58
- const credentialBlob = Object.fromEntries(Object.entries(bodyRecord).filter(([key]) => key !== "agent" && key !== "provider"));
57
+ const displayName = typeof bodyRecord.displayName === "string"
58
+ ? bodyRecord.displayName
59
+ : undefined;
60
+ const notes = typeof bodyRecord.notes === "string" ? bodyRecord.notes : undefined;
61
+ // Encrypt the credential blob (metadata stored as columns, not in blob)
62
+ const metadataKeys = new Set([
63
+ "agent",
64
+ "provider",
65
+ "displayName",
66
+ "notes",
67
+ ]);
68
+ const credentialBlob = Object.fromEntries(Object.entries(bodyRecord).filter(([key]) => !metadataKeys.has(key)));
59
69
  const encrypted = encryptCredential(credentialBlob);
60
70
  const timestamps = upsertCredential(database, {
61
71
  name,
62
72
  agent,
63
73
  provider,
74
+ displayName,
75
+ notes,
64
76
  ...encrypted,
65
77
  });
66
78
  logAccess(database, {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axvault",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.9.4",
5
+ "version": "1.10.0",
6
6
  "description": "Remote credential storage server for axkit",
7
7
  "repository": {
8
8
  "type": "git",
@@ -49,10 +49,10 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@commander-js/extra-typings": "^14.0.0",
52
- "axauth": "^3.1.4",
52
+ "axauth": "^3.1.6",
53
53
  "axshared": "5.0.0",
54
54
  "better-sqlite3": "^12.6.2",
55
- "commander": "^14.0.2",
55
+ "commander": "^14.0.3",
56
56
  "express": "^5.2.1"
57
57
  },
58
58
  "keywords": [
@@ -70,7 +70,7 @@
70
70
  "automation",
71
71
  "coding-assistant"
72
72
  ],
73
- "packageManager": "pnpm@10.28.1",
73
+ "packageManager": "pnpm@10.30.1",
74
74
  "engines": {
75
75
  "node": ">=22.14.0"
76
76
  },
@@ -78,15 +78,15 @@
78
78
  "@total-typescript/ts-reset": "^0.6.1",
79
79
  "@types/better-sqlite3": "^7.6.13",
80
80
  "@types/express": "^5.0.6",
81
- "@types/node": "^25.0.10",
81
+ "@types/node": "^25.3.0",
82
82
  "@vitest/coverage-v8": "^4.0.18",
83
- "eslint": "^9.39.2",
84
- "eslint-config-axkit": "^1.1.0",
83
+ "eslint": "^10.0.1",
84
+ "eslint-config-axkit": "^1.3.0",
85
85
  "fta-check": "^1.5.1",
86
86
  "fta-cli": "^3.0.0",
87
- "knip": "^5.82.1",
87
+ "knip": "^5.85.0",
88
88
  "prettier": "3.8.1",
89
- "semantic-release": "^25.0.2",
89
+ "semantic-release": "^25.0.3",
90
90
  "typescript": "^5.9.3",
91
91
  "vitest": "^4.0.18"
92
92
  }