@ursalock/agent 0.4.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 ADDED
@@ -0,0 +1,192 @@
1
+ # @ursalock/agent
2
+
3
+ Headless Node.js SDK for AI agents to read/write E2E encrypted documents.
4
+
5
+ ## Overview
6
+
7
+ `@ursalock/agent` is a **thin wrapper** around `@ursalock/client` that provides:
8
+
9
+ - **Base64 key handling** — Agents receive keys as strings, not Uint8Arrays
10
+ - **API key authentication** — Simple bearer token auth
11
+ - **Clean, self-documenting API** — Purpose-built for agent use
12
+ - **Zero crypto reimplementation** — Delegates to `@ursalock/client` and `@ursalock/crypto`
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @ursalock/agent
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { AgentVault } from '@ursalock/agent';
24
+
25
+ // Create vault with explicit keys
26
+ const vault = new AgentVault({
27
+ serverUrl: 'https://vault.ndlz.net',
28
+ apiKey: 'ulk_your_api_key',
29
+ vaultUid: 'vault-123',
30
+ encryptionKey: 'base64-encoded-key',
31
+ hmacKey: 'base64-encoded-hmac-key',
32
+ });
33
+
34
+ // Access a typed collection
35
+ interface Note {
36
+ title: string;
37
+ content: string;
38
+ }
39
+
40
+ const notes = vault.collection<Note>('notes');
41
+
42
+ // Create encrypted document
43
+ await notes.create({
44
+ title: 'Secret Note',
45
+ content: 'This is encrypted end-to-end',
46
+ });
47
+
48
+ // List all notes
49
+ const allNotes = await notes.list();
50
+
51
+ // Get specific note
52
+ const note = await notes.get('doc-uid');
53
+
54
+ // Update note
55
+ await notes.update('doc-uid', { title: 'Updated Title' });
56
+
57
+ // Delete note
58
+ await notes.delete('doc-uid');
59
+ ```
60
+
61
+ ## Using Master Key
62
+
63
+ If you have the full master key (highest trust level):
64
+
65
+ ```typescript
66
+ import { createAgentVaultFromMasterKey } from '@ursalock/agent';
67
+
68
+ const vault = await createAgentVaultFromMasterKey({
69
+ serverUrl: 'https://vault.ndlz.net',
70
+ apiKey: 'ulk_your_api_key',
71
+ vaultUid: 'vault-123',
72
+ masterKey: 'base64-encoded-master-key',
73
+ });
74
+
75
+ // Vault keys are automatically derived using HKDF
76
+ const notes = vault.collection<Note>('notes');
77
+ ```
78
+
79
+ ## API Reference
80
+
81
+ ### `AgentVault`
82
+
83
+ #### Constructor
84
+
85
+ ```typescript
86
+ new AgentVault(options: AgentVaultOptions)
87
+ ```
88
+
89
+ **Options:**
90
+ - `serverUrl` — Vault server URL
91
+ - `apiKey` — API key for authentication (starts with `ulk_`)
92
+ - `vaultUid` — Vault unique identifier
93
+ - `encryptionKey` — Base64-encoded 32-byte encryption key
94
+ - `hmacKey` — Optional base64-encoded 32-byte HMAC key
95
+
96
+ #### Methods
97
+
98
+ ##### `collection<T>(name: string): Collection<T>`
99
+
100
+ Get a typed collection.
101
+
102
+ **Example:**
103
+ ```typescript
104
+ const tasks = vault.collection<{ task: string; done: boolean }>('tasks');
105
+ ```
106
+
107
+ ### `createAgentVaultFromMasterKey`
108
+
109
+ ```typescript
110
+ async function createAgentVaultFromMasterKey(options: {
111
+ serverUrl: string;
112
+ apiKey: string;
113
+ vaultUid: string;
114
+ masterKey: string; // base64-encoded
115
+ }): Promise<AgentVault>
116
+ ```
117
+
118
+ Derives vault-specific encryption and HMAC keys from a master key using HKDF.
119
+
120
+ ### `Collection<T>`
121
+
122
+ See [@ursalock/client documentation](../client/README.md) for full Collection API.
123
+
124
+ **Methods:**
125
+ - `create(content: T): Promise<Document<T>>`
126
+ - `get(uid: string): Promise<Document<T>>`
127
+ - `list(options?: ListOptions): Promise<Document<T>[]>`
128
+ - `update(uid: string, content: Partial<T>): Promise<Document<T>>`
129
+ - `delete(uid: string): Promise<void>`
130
+ - `sync(since?: number): Promise<SyncResult<T>>`
131
+
132
+ ## Security
133
+
134
+ ### Key Handling
135
+
136
+ - **Never hardcode keys** — Load from environment variables or secure storage
137
+ - **Use HMAC keys** — Provides integrity verification (Encrypt-then-MAC)
138
+ - **Rotate API keys** — If compromised, revoke and generate new keys
139
+
140
+ ### API Key Format
141
+
142
+ API keys start with `ulk_` and should be treated as secrets:
143
+
144
+ ```typescript
145
+ const vault = new AgentVault({
146
+ serverUrl: process.env.VAULT_URL!,
147
+ apiKey: process.env.VAULT_API_KEY!,
148
+ vaultUid: process.env.VAULT_UID!,
149
+ encryptionKey: process.env.VAULT_ENC_KEY!,
150
+ hmacKey: process.env.VAULT_HMAC_KEY,
151
+ });
152
+ ```
153
+
154
+ ### Trust Model
155
+
156
+ 1. **Explicit keys** — Agent has encryption key only (read/write documents)
157
+ 2. **Master key** — Agent has full vault access (highest trust)
158
+ 3. **API key** — Server-side authentication (revocable)
159
+
160
+ ## Architecture
161
+
162
+ This package is a **thin wrapper** with ~150 lines of code:
163
+
164
+ ```
165
+ @ursalock/agent (this package)
166
+ ↓ wraps
167
+ @ursalock/client (Collection implementation)
168
+ ↓ uses
169
+ @ursalock/crypto (AES-GCM, HMAC, HKDF)
170
+ ```
171
+
172
+ **Design principles:**
173
+ - **DRY** — No crypto/collection logic duplication
174
+ - **Minimal** — Only agent-specific concerns (base64, API key auth)
175
+ - **Reusable** — Delegates to battle-tested client/crypto packages
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ # Build
181
+ pnpm build
182
+
183
+ # Test
184
+ pnpm test
185
+
186
+ # Type check
187
+ pnpm typecheck
188
+ ```
189
+
190
+ ## License
191
+
192
+ MIT
@@ -0,0 +1,140 @@
1
+ import { Collection } from '@ursalock/client';
2
+ export { Collection, Document, ListOptions, SyncResult } from '@ursalock/client';
3
+
4
+ /**
5
+ * AgentVault - Headless SDK for AI agents
6
+ *
7
+ * Thin wrapper around DocumentClient with:
8
+ * - Base64 key decoding (agents receive keys as strings)
9
+ * - API key authentication
10
+ * - Clean, self-documenting interface
11
+ */
12
+
13
+ /**
14
+ * Configuration options for AgentVault
15
+ */
16
+ interface AgentVaultOptions {
17
+ /** Server URL (e.g., "https://vault.ndlz.net") */
18
+ serverUrl: string;
19
+ /** API key for authentication (starts with "ulk_") */
20
+ apiKey: string;
21
+ /** Vault UID to access */
22
+ vaultUid: string;
23
+ /**
24
+ * Encryption key (base64-encoded 32-byte key)
25
+ * This is the vault's encryption key shared by the human
26
+ */
27
+ encryptionKey: string;
28
+ /**
29
+ * Optional HMAC key (base64-encoded 32-byte key)
30
+ * If not provided, HMAC integrity checking is disabled
31
+ */
32
+ hmacKey?: string;
33
+ }
34
+ /**
35
+ * AgentVault provides headless access to encrypted documents
36
+ *
37
+ * This is a thin wrapper around DocumentClient that:
38
+ * 1. Handles base64 key encoding/decoding
39
+ * 2. Injects API key authentication
40
+ * 3. Provides a clean API for agent use
41
+ *
42
+ * All encryption/decryption logic is delegated to Collection from @ursalock/client
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const vault = new AgentVault({
47
+ * serverUrl: 'https://vault.ndlz.net',
48
+ * apiKey: 'ulk_abc123...',
49
+ * vaultUid: 'vault-xyz',
50
+ * encryptionKey: 'base64-encoded-key',
51
+ * hmacKey: 'base64-encoded-hmac-key',
52
+ * });
53
+ *
54
+ * const notes = vault.collection<{ title: string; content: string }>('notes');
55
+ * await notes.create({ title: 'Secret', content: 'Hello world' });
56
+ * const allNotes = await notes.list();
57
+ * ```
58
+ */
59
+ declare class AgentVault {
60
+ private client;
61
+ constructor(options: AgentVaultOptions);
62
+ /**
63
+ * Get a typed collection
64
+ *
65
+ * Collections provide CRUD operations on encrypted documents.
66
+ * All documents are encrypted client-side before being sent to the server.
67
+ *
68
+ * @param name - Collection name (e.g., "notes", "tasks")
69
+ * @returns Typed collection instance
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * interface Note {
74
+ * title: string;
75
+ * content: string;
76
+ * }
77
+ *
78
+ * const notes = vault.collection<Note>('notes');
79
+ * await notes.create({ title: 'Shopping list', content: 'Milk, eggs' });
80
+ * ```
81
+ */
82
+ collection<T>(name: string): Collection<T>;
83
+ }
84
+ /**
85
+ * Create an AgentVault from a master key
86
+ *
87
+ * For cases where the agent has the full master key (highest trust level),
88
+ * this helper derives vault-specific encryption and HMAC keys using HKDF.
89
+ *
90
+ * The master key is typically:
91
+ * - Derived from a recovery key via Argon2id
92
+ * - Derived from ZKC PRF (passkey-based key derivation)
93
+ * - Shared directly by the human in a secure channel
94
+ *
95
+ * @param options - Configuration with master key
96
+ * @returns AgentVault instance ready to use
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const vault = await createAgentVaultFromMasterKey({
101
+ * serverUrl: 'https://vault.ndlz.net',
102
+ * apiKey: 'ulk_abc123...',
103
+ * vaultUid: 'vault-xyz',
104
+ * masterKey: 'base64-encoded-master-key',
105
+ * });
106
+ *
107
+ * const notes = vault.collection<Note>('notes');
108
+ * ```
109
+ */
110
+ declare function createAgentVaultFromMasterKey(options: {
111
+ serverUrl: string;
112
+ apiKey: string;
113
+ vaultUid: string;
114
+ masterKey: string;
115
+ }): Promise<AgentVault>;
116
+
117
+ /**
118
+ * Base64 encoding utilities for Node.js
119
+ *
120
+ * Agents receive encryption keys as base64 strings (for easy serialization)
121
+ * and need to convert them to Uint8Array for crypto operations.
122
+ */
123
+ /**
124
+ * Decode base64 string to Uint8Array
125
+ * Uses Node.js Buffer API (not available in browser)
126
+ *
127
+ * @param base64 - Base64-encoded string
128
+ * @returns Raw bytes as Uint8Array
129
+ */
130
+ declare function base64ToBytes(base64: string): Uint8Array;
131
+ /**
132
+ * Encode Uint8Array to base64 string
133
+ * Uses Node.js Buffer API (not available in browser)
134
+ *
135
+ * @param bytes - Raw bytes
136
+ * @returns Base64-encoded string
137
+ */
138
+ declare function bytesToBase64(bytes: Uint8Array): string;
139
+
140
+ export { AgentVault, type AgentVaultOptions, base64ToBytes, bytesToBase64, createAgentVaultFromMasterKey };
package/dist/index.js ADDED
@@ -0,0 +1,69 @@
1
+ // src/agent-vault.ts
2
+ import { DocumentClient } from "@ursalock/client";
3
+ import { deriveVaultKeys } from "@ursalock/crypto";
4
+
5
+ // src/types.ts
6
+ function base64ToBytes(base64) {
7
+ return new Uint8Array(Buffer.from(base64, "base64"));
8
+ }
9
+ function bytesToBase64(bytes) {
10
+ return Buffer.from(bytes).toString("base64");
11
+ }
12
+
13
+ // src/agent-vault.ts
14
+ var AgentVault = class {
15
+ client;
16
+ constructor(options) {
17
+ const encKey = base64ToBytes(options.encryptionKey);
18
+ const hmacKey = options.hmacKey ? base64ToBytes(options.hmacKey) : void 0;
19
+ this.client = new DocumentClient({
20
+ serverUrl: options.serverUrl,
21
+ vaultUid: options.vaultUid,
22
+ encryptionKey: encKey,
23
+ hmacKey,
24
+ getAuthHeader: () => ({
25
+ Authorization: `Bearer ${options.apiKey}`
26
+ })
27
+ });
28
+ }
29
+ /**
30
+ * Get a typed collection
31
+ *
32
+ * Collections provide CRUD operations on encrypted documents.
33
+ * All documents are encrypted client-side before being sent to the server.
34
+ *
35
+ * @param name - Collection name (e.g., "notes", "tasks")
36
+ * @returns Typed collection instance
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * interface Note {
41
+ * title: string;
42
+ * content: string;
43
+ * }
44
+ *
45
+ * const notes = vault.collection<Note>('notes');
46
+ * await notes.create({ title: 'Shopping list', content: 'Milk, eggs' });
47
+ * ```
48
+ */
49
+ collection(name) {
50
+ return this.client.collection(name);
51
+ }
52
+ };
53
+ async function createAgentVaultFromMasterKey(options) {
54
+ const masterKeyBytes = base64ToBytes(options.masterKey);
55
+ const keys = await deriveVaultKeys(masterKeyBytes, options.vaultUid);
56
+ return new AgentVault({
57
+ serverUrl: options.serverUrl,
58
+ apiKey: options.apiKey,
59
+ vaultUid: options.vaultUid,
60
+ encryptionKey: bytesToBase64(keys.encryptionKey),
61
+ hmacKey: bytesToBase64(keys.hmacKey)
62
+ });
63
+ }
64
+ export {
65
+ AgentVault,
66
+ base64ToBytes,
67
+ bytesToBase64,
68
+ createAgentVaultFromMasterKey
69
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@ursalock/agent",
3
+ "version": "0.4.0",
4
+ "description": "Headless Node.js SDK for AI agents to read/write encrypted documents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm --dts",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@ursalock/crypto": "^0.4.0",
26
+ "@ursalock/client": "^0.4.0"
27
+ },
28
+ "devDependencies": {
29
+ "tsup": "^8.0.0",
30
+ "typescript": "^5.4.0",
31
+ "vitest": "^1.6.0"
32
+ },
33
+ "keywords": [
34
+ "agent",
35
+ "sdk",
36
+ "e2ee",
37
+ "encryption",
38
+ "headless",
39
+ "nodejs",
40
+ "ai-agent"
41
+ ],
42
+ "license": "MIT",
43
+ "author": "Nicolas de Luz <ndlz@pm.me>",
44
+ "homepage": "https://github.com/nicodlz/ursalock#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/nicodlz/ursalock/issues"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/nicodlz/ursalock.git",
51
+ "directory": "packages/agent"
52
+ }
53
+ }