@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 +192 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +69 -0
- package/package.json +53 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|