shadowkey-agent-sdk 1.0.0 â 1.2.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 +138 -97
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,54 +1,81 @@
|
|
|
1
1
|
# ShadowKey Agent SDK
|
|
2
2
|
|
|
3
|
-
Official SDK for integrating AI agents with ShadowKey's privacy-preserving data vault.
|
|
3
|
+
Official SDK for integrating AI agents with [ShadowKey](https://shadowkey-ai.vercel.app)'s privacy-preserving data vault on Base.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/shadowkey-agent-sdk) [](LICENSE) [](https://nodejs.org) [](https://www.typescriptlang.org)
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- **HMAC-SHA256 Request Signing** â Every request is cryptographically signed to prevent replay attacks
|
|
10
|
+
- **Automatic Retry** â Exponential backoff on server errors (skips retries on 4xx client errors)
|
|
11
|
+
- **Approval Polling** â Built-in `waitForApproval()` with configurable timeout and interval
|
|
12
|
+
- **Node.js 18+ & Browser** â Works in both environments using `globalThis.crypto`
|
|
13
|
+
- **TypeScript-First** â Full type definitions included (CJS + ESM + `.d.ts`)
|
|
14
|
+
- **Zero Dependencies** â No external deps, minimal bundle size (4.8 KB ESM)
|
|
15
|
+
- **Debug Mode** â Logs requests/responses with automatic redaction of sensitive fields
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
16
19
|
```bash
|
|
17
|
-
npm install
|
|
20
|
+
npm install shadowkey-agent-sdk
|
|
18
21
|
```
|
|
19
22
|
|
|
20
23
|
## Quick Start
|
|
21
24
|
|
|
22
25
|
```typescript
|
|
23
|
-
import { ShadowKeyClient } from '
|
|
26
|
+
import { ShadowKeyClient } from 'shadowkey-agent-sdk';
|
|
24
27
|
|
|
25
28
|
const client = new ShadowKeyClient({
|
|
26
29
|
apiUrl: 'https://your-project.supabase.co/functions/v1',
|
|
27
|
-
apiKey: '
|
|
28
|
-
debug: true
|
|
30
|
+
apiKey: 'sk_your_api_key_here',
|
|
31
|
+
debug: true,
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
// Request access to user data
|
|
34
|
+
// 1. Request access to user's vault data
|
|
32
35
|
const response = await client.requestAccess({
|
|
33
|
-
agentId: 'shopping-
|
|
36
|
+
agentId: 'shopping-bot-001',
|
|
34
37
|
agentName: 'Smart Shopping Assistant',
|
|
35
|
-
requestedFields: ['
|
|
36
|
-
purpose: 'Complete
|
|
37
|
-
category: '
|
|
38
|
+
requestedFields: ['shipping_address', 'billing_name', 'card_last4'],
|
|
39
|
+
purpose: 'Complete purchase of wireless headphones ($89.99)',
|
|
40
|
+
category: 'payment',
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
console.log('Request ID:', response.requestId);
|
|
44
|
+
// â User receives notification in their ShadowKey dashboard
|
|
41
45
|
|
|
42
|
-
// Wait for user
|
|
43
|
-
const result = await client.waitForApproval(response.requestId
|
|
46
|
+
// 2. Wait for user to approve/deny (polls every 2s, max 5 min)
|
|
47
|
+
const result = await client.waitForApproval(response.requestId);
|
|
44
48
|
|
|
45
49
|
if (result.status === 'approved') {
|
|
46
|
-
console.log('
|
|
50
|
+
console.log('Scoped access granted:', result.grantedFields);
|
|
51
|
+
console.log('Data:', result.grantedData);
|
|
52
|
+
// â Only the fields the user approved â nothing more
|
|
47
53
|
} else {
|
|
48
54
|
console.log('Access denied:', result.message);
|
|
49
55
|
}
|
|
50
56
|
```
|
|
51
57
|
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Your Agent ShadowKey SDK User's Vault
|
|
62
|
+
| | |
|
|
63
|
+
|-- requestAccess() ---------> | |
|
|
64
|
+
| |-- POST /sdk-access-request ->|
|
|
65
|
+
| | (HMAC-signed) |
|
|
66
|
+
| |<---- requestId, pending -----|
|
|
67
|
+
|<---- requestId --------------| |
|
|
68
|
+
| | |
|
|
69
|
+
|-- waitForApproval() -------> | |
|
|
70
|
+
| |-- GET /sdk-access-status --> |
|
|
71
|
+
| | (polls every 2s) |
|
|
72
|
+
| | User approves fields
|
|
73
|
+
| |<---- approved, grantedData --|
|
|
74
|
+
|<---- grantedData ------------| |
|
|
75
|
+
| | |
|
|
76
|
+
| Only approved fields received. Denied fields never leave the vault.
|
|
77
|
+
```
|
|
78
|
+
|
|
52
79
|
## API Reference
|
|
53
80
|
|
|
54
81
|
### Constructor
|
|
@@ -57,121 +84,135 @@ if (result.status === 'approved') {
|
|
|
57
84
|
new ShadowKeyClient(config: ShadowKeyConfig)
|
|
58
85
|
```
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|--------|------|---------|-------------|
|
|
89
|
+
| `apiUrl` | `string` | *required* | Supabase project URL + `/functions/v1` |
|
|
90
|
+
| `apiKey` | `string` | *required* | API key from ShadowKey Settings page |
|
|
91
|
+
| `timeout` | `number` | `30000` | Request timeout in ms |
|
|
92
|
+
| `retryAttempts` | `number` | `3` | Max retries on server errors |
|
|
93
|
+
| `debug` | `boolean` | `false` | Enable debug logging (redacts sensitive fields) |
|
|
66
94
|
|
|
67
|
-
###
|
|
95
|
+
### `requestAccess(request): Promise<AccessResponse>`
|
|
68
96
|
|
|
69
|
-
|
|
97
|
+
Request scoped access to a user's vault data. Creates a pending disclosure request that the vault owner must approve.
|
|
70
98
|
|
|
71
|
-
|
|
99
|
+
```typescript
|
|
100
|
+
const response = await client.requestAccess({
|
|
101
|
+
agentId: 'my-agent-001', // Your agent's unique ID
|
|
102
|
+
agentName: 'My AI Assistant', // Human-readable name (shown to user)
|
|
103
|
+
requestedFields: ['email', 'phone'], // Fields you need
|
|
104
|
+
purpose: 'Send order confirmation', // Why (shown to user)
|
|
105
|
+
category: 'identity', // Optional: payment, identity, health, etc.
|
|
106
|
+
expiresIn: 300, // Optional: seconds until request expires (default 300)
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `checkStatus(requestId): Promise<DisclosureStatus>`
|
|
111
|
+
|
|
112
|
+
Check the current status of an access request.
|
|
72
113
|
|
|
73
|
-
**Parameters:**
|
|
74
114
|
```typescript
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
agentName: string; // Human-readable agent name
|
|
78
|
-
requestedFields: string[]; // Array of field names to request
|
|
79
|
-
purpose: string; // Clear explanation of why you need access
|
|
80
|
-
category?: string; // Optional category (shopping, travel, etc)
|
|
81
|
-
expiresIn?: number; // Optional expiration time in seconds
|
|
82
|
-
}
|
|
115
|
+
const status = await client.checkStatus(response.requestId);
|
|
116
|
+
// status.status â 'pending' | 'approved' | 'denied' | 'expired'
|
|
83
117
|
```
|
|
84
118
|
|
|
85
|
-
|
|
119
|
+
### `waitForApproval(requestId, maxWaitMs?, pollIntervalMs?): Promise<AccessResponse>`
|
|
120
|
+
|
|
121
|
+
Poll for user approval with automatic timeout.
|
|
122
|
+
|
|
123
|
+
| Param | Default | Description |
|
|
124
|
+
|-------|---------|-------------|
|
|
125
|
+
| `requestId` | *required* | From `requestAccess()` |
|
|
126
|
+
| `maxWaitMs` | `300000` (5 min) | Max time to wait |
|
|
127
|
+
| `pollIntervalMs` | `2000` (2s) | Time between polls |
|
|
128
|
+
|
|
86
129
|
```typescript
|
|
87
|
-
|
|
88
|
-
requestId: string; // Use this to check status
|
|
89
|
-
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
90
|
-
grantedData?: Record<string, any>; // Present if approved
|
|
91
|
-
expiresAt?: string; // ISO timestamp
|
|
92
|
-
message?: string;
|
|
93
|
-
}
|
|
130
|
+
const result = await client.waitForApproval(response.requestId, 120000, 3000);
|
|
94
131
|
```
|
|
95
132
|
|
|
96
|
-
|
|
133
|
+
### `submitReverseDisclosure(request): Promise<ReverseDisclosureResponse>`
|
|
97
134
|
|
|
98
|
-
|
|
135
|
+
Submit data to a user's vault (reverse data flow â services sending data to users).
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const receipt = await client.submitReverseDisclosure({
|
|
139
|
+
serviceId: 'acme-corp',
|
|
140
|
+
serviceName: 'Acme Corporation',
|
|
141
|
+
dataOffered: [
|
|
142
|
+
{ field: 'loyalty_points', value: '4,250', category: 'preferences' },
|
|
143
|
+
{ field: 'member_since', value: '2024-01-15', category: 'identity' },
|
|
144
|
+
],
|
|
145
|
+
purpose: 'Share your loyalty program data',
|
|
146
|
+
});
|
|
147
|
+
```
|
|
99
148
|
|
|
100
|
-
|
|
149
|
+
## Integration Examples
|
|
101
150
|
|
|
102
|
-
|
|
151
|
+
Both examples import directly from `shadowkey-agent-sdk`:
|
|
103
152
|
|
|
104
|
-
|
|
105
|
-
- `requestId`: The ID returned from `requestAccess()`
|
|
106
|
-
- `maxWaitMs`: Maximum time to wait (default: 300000 = 5 minutes)
|
|
107
|
-
- `pollIntervalMs`: Time between polls (default: 2000 = 2 seconds)
|
|
153
|
+
### OpenRouter AI Agent (`/examples/openrouter/`)
|
|
108
154
|
|
|
109
|
-
|
|
155
|
+
An AI shopping agent that uses OpenRouter's free models with 4-model automatic failover:
|
|
110
156
|
|
|
111
|
-
|
|
157
|
+
```javascript
|
|
158
|
+
import { ShadowKeyClient } from 'shadowkey-agent-sdk';
|
|
112
159
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
field: string;
|
|
120
|
-
value: string;
|
|
121
|
-
category: string;
|
|
122
|
-
}>;
|
|
123
|
-
purpose: string;
|
|
124
|
-
}
|
|
160
|
+
const shadowKey = new ShadowKeyClient({
|
|
161
|
+
apiUrl: `${process.env.SUPABASE_URL}/functions/v1`,
|
|
162
|
+
apiKey: process.env.SHADOWKEY_API_KEY,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// AI determines needed fields â SDK requests access â user approves â AI completes task
|
|
125
166
|
```
|
|
126
167
|
|
|
127
|
-
|
|
168
|
+
### Express.js Server (`/examples/node-express/`)
|
|
169
|
+
|
|
170
|
+
A REST API backend that wraps the SDK for multi-agent architectures:
|
|
128
171
|
|
|
129
|
-
|
|
172
|
+
```javascript
|
|
173
|
+
import { ShadowKeyClient } from 'shadowkey-agent-sdk';
|
|
130
174
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
175
|
+
const shadowKey = new ShadowKeyClient({ ... });
|
|
176
|
+
|
|
177
|
+
app.post('/api/request-data', async (req, res) => {
|
|
178
|
+
const response = await shadowKey.requestAccess({ ... });
|
|
179
|
+
res.json(response);
|
|
180
|
+
});
|
|
181
|
+
```
|
|
135
182
|
|
|
136
183
|
## Error Handling
|
|
137
184
|
|
|
138
185
|
```typescript
|
|
139
186
|
try {
|
|
140
|
-
const response = await client.requestAccess({...});
|
|
187
|
+
const response = await client.requestAccess({ ... });
|
|
141
188
|
} catch (error) {
|
|
142
189
|
if (error.message.includes('timeout')) {
|
|
143
|
-
|
|
190
|
+
// Request timed out â check network or increase timeout
|
|
144
191
|
} else if (error.message.includes('401')) {
|
|
145
|
-
|
|
192
|
+
// Invalid or expired API key
|
|
193
|
+
} else if (error.message.includes('404')) {
|
|
194
|
+
// No vault found for this API key's user
|
|
146
195
|
} else {
|
|
147
|
-
|
|
196
|
+
// Server error â SDK auto-retried and all attempts failed
|
|
148
197
|
}
|
|
149
198
|
}
|
|
150
199
|
```
|
|
151
200
|
|
|
152
|
-
## Security
|
|
153
|
-
|
|
154
|
-
1. **Never expose your API key** in client-side code
|
|
155
|
-
2. **Use environment variables** to store credentials
|
|
156
|
-
3. **Rotate keys regularly** using the ShadowKey dashboard
|
|
157
|
-
4. **Set appropriate rate limits** for your use case
|
|
158
|
-
5. **Validate all data** received from the vault
|
|
159
|
-
6. **Use HTTPS** for all requests (enforced by default)
|
|
160
|
-
|
|
161
|
-
## Rate Limits
|
|
162
|
-
|
|
163
|
-
Default rate limits per API key:
|
|
164
|
-
- 100 requests per minute
|
|
165
|
-
- 1000 requests per hour
|
|
166
|
-
- 10000 requests per day
|
|
201
|
+
## Security
|
|
167
202
|
|
|
168
|
-
|
|
203
|
+
- **HMAC-SHA256 signing** â Every request includes timestamp, nonce, and signature headers
|
|
204
|
+
- **5-minute timestamp window** â Rejects requests with stale timestamps (prevents replay attacks)
|
|
205
|
+
- **Sensitive field redaction** â Debug logs automatically redact `grantedData`, `grantedFields`, and `response_data`
|
|
206
|
+
- **No secrets in transit** â SDK receives only the fields the user approved, never the full vault
|
|
169
207
|
|
|
170
|
-
##
|
|
208
|
+
## Links
|
|
171
209
|
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
210
|
+
- **npm:** [shadowkey-agent-sdk](https://www.npmjs.com/package/shadowkey-agent-sdk)
|
|
211
|
+
- **Live Demo:** [shadowkey-ai.vercel.app/agent-demo](https://shadowkey-ai.vercel.app/agent-demo)
|
|
212
|
+
- **Dashboard:** [shadowkey-ai.vercel.app](https://shadowkey-ai.vercel.app)
|
|
213
|
+
- **Smart Contract:** [Basescan (Verified)](https://basescan.org/address/0xC739f98B438620A9326Ddb7548201Bcd78a3DBAd#code)
|
|
214
|
+
- **GitHub:** [kimboltpro3-create/shadowkey](https://github.com/kimboltpro3-create/shadowkey)
|
|
215
|
+
- **Issues:** [GitHub Issues](https://github.com/kimboltpro3-create/shadowkey/issues)
|
|
175
216
|
|
|
176
217
|
## License
|
|
177
218
|
|
package/dist/index.js
CHANGED
|
@@ -59,12 +59,12 @@ var ShadowKeyClient = class {
|
|
|
59
59
|
async generateSignature(payload) {
|
|
60
60
|
const timestamp = Date.now();
|
|
61
61
|
const nonceBytes = new Uint8Array(12);
|
|
62
|
-
crypto.getRandomValues(nonceBytes);
|
|
62
|
+
globalThis.crypto.getRandomValues(nonceBytes);
|
|
63
63
|
const nonce = Array.from(nonceBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
64
64
|
const message = JSON.stringify({ ...payload, timestamp, nonce });
|
|
65
65
|
const encoder = new TextEncoder();
|
|
66
66
|
const data = encoder.encode(message + this.config.apiKey);
|
|
67
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
67
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
68
68
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
69
69
|
const signature = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
70
70
|
return { timestamp, nonce, signature };
|
|
@@ -118,10 +118,10 @@ var ShadowKeyClient = class {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
async requestAccess(request) {
|
|
121
|
-
return this.makeRequest("/access-request", "POST", request);
|
|
121
|
+
return this.makeRequest("/sdk-access-request", "POST", request);
|
|
122
122
|
}
|
|
123
123
|
async checkStatus(requestId) {
|
|
124
|
-
return this.makeRequest(`/access-status/${requestId}`, "GET");
|
|
124
|
+
return this.makeRequest(`/sdk-access-status/${requestId}`, "GET");
|
|
125
125
|
}
|
|
126
126
|
async submitReverseDisclosure(request) {
|
|
127
127
|
return this.makeRequest("/reverse-disclosure", "POST", request);
|
package/dist/index.mjs
CHANGED
|
@@ -33,12 +33,12 @@ var ShadowKeyClient = class {
|
|
|
33
33
|
async generateSignature(payload) {
|
|
34
34
|
const timestamp = Date.now();
|
|
35
35
|
const nonceBytes = new Uint8Array(12);
|
|
36
|
-
crypto.getRandomValues(nonceBytes);
|
|
36
|
+
globalThis.crypto.getRandomValues(nonceBytes);
|
|
37
37
|
const nonce = Array.from(nonceBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
38
38
|
const message = JSON.stringify({ ...payload, timestamp, nonce });
|
|
39
39
|
const encoder = new TextEncoder();
|
|
40
40
|
const data = encoder.encode(message + this.config.apiKey);
|
|
41
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
41
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
42
42
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
43
43
|
const signature = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
44
44
|
return { timestamp, nonce, signature };
|
|
@@ -92,10 +92,10 @@ var ShadowKeyClient = class {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
async requestAccess(request) {
|
|
95
|
-
return this.makeRequest("/access-request", "POST", request);
|
|
95
|
+
return this.makeRequest("/sdk-access-request", "POST", request);
|
|
96
96
|
}
|
|
97
97
|
async checkStatus(requestId) {
|
|
98
|
-
return this.makeRequest(`/access-status/${requestId}`, "GET");
|
|
98
|
+
return this.makeRequest(`/sdk-access-status/${requestId}`, "GET");
|
|
99
99
|
}
|
|
100
100
|
async submitReverseDisclosure(request) {
|
|
101
101
|
return this.makeRequest("/reverse-disclosure", "POST", request);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shadowkey-agent-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Official SDK for integrating AI agents with ShadowKey privacy vault",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -32,11 +32,13 @@
|
|
|
32
32
|
],
|
|
33
33
|
"author": "ShadowKey Team",
|
|
34
34
|
"license": "MIT",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
35
38
|
"repository": {
|
|
36
39
|
"type": "git",
|
|
37
40
|
"url": "https://github.com/kimboltpro3-create/shadowkey"
|
|
38
41
|
},
|
|
39
|
-
"dependencies": {},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"tsup": "^8.0.0",
|
|
42
44
|
"typescript": "^5.5.3",
|