@agentlair/sdk 0.1.0 → 0.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 +150 -90
- package/dist/client.d.ts +259 -97
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +418 -161
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +10 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -18
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +273 -0
- package/dist/index.test.js.map +1 -0
- package/dist/types.d.ts +171 -17
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1,156 +1,432 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @agentlair/sdk — AgentLairClient
|
|
2
|
+
* @agentlair/sdk — AgentLair + AgentLairClient
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `AgentLair` is the primary class. Accepts an API key string directly.
|
|
5
|
+
* `AgentLairClient` is the legacy class (accepts options object) — still fully supported.
|
|
6
6
|
*
|
|
7
|
-
* @example
|
|
7
|
+
* @example Three-line onboarding
|
|
8
|
+
* const lair = new AgentLair(process.env.AGENTLAIR_API_KEY!);
|
|
9
|
+
* const inbox = await lair.email.claim('my-agent'); // auto-expands to my-agent@agentlair.dev
|
|
10
|
+
* const { messages } = await lair.email.inbox('my-agent@agentlair.dev');
|
|
11
|
+
*
|
|
12
|
+
* @example Full flow
|
|
8
13
|
* // Create a new account (no API key needed)
|
|
9
|
-
* const { api_key
|
|
14
|
+
* const { api_key } = await AgentLair.createAccount({ name: 'my-agent' });
|
|
15
|
+
*
|
|
16
|
+
* const lair = new AgentLair(api_key);
|
|
17
|
+
* await lair.email.claim('my-agent');
|
|
18
|
+
* await lair.email.send({ from: 'my-agent@agentlair.dev', to: 'user@example.com', subject: 'Hello', text: 'Hi!' });
|
|
19
|
+
* const { messages } = await lair.email.inbox('my-agent@agentlair.dev');
|
|
10
20
|
*
|
|
11
|
-
* @
|
|
12
|
-
*
|
|
13
|
-
* const
|
|
14
|
-
* await client.claimAddress({ address: 'my-agent@agentlair.dev' });
|
|
15
|
-
* await client.sendEmail({ from: 'my-agent@agentlair.dev', to: 'user@example.com', subject: 'Hello', text: 'Hi!' });
|
|
16
|
-
* const inbox = await client.getInbox({ address: 'my-agent@agentlair.dev' });
|
|
21
|
+
* // Vault: store secrets (zero-knowledge — use @agentlair/vault-crypto to encrypt first)
|
|
22
|
+
* await lair.vault.put('openai-key', { ciphertext: encryptedBlob });
|
|
23
|
+
* const { ciphertext } = await lair.vault.get('openai-key');
|
|
17
24
|
*/
|
|
18
25
|
import { AgentLairError } from './errors.js';
|
|
19
26
|
const DEFAULT_BASE_URL = 'https://agentlair.dev';
|
|
20
|
-
// ───
|
|
27
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
21
28
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* The AgentLair Vault is a zero-knowledge secret store.
|
|
25
|
-
* The server stores opaque encrypted blobs — plaintext never leaves your client.
|
|
26
|
-
* Use @agentlair/vault-crypto for client-side encryption helpers.
|
|
29
|
+
* Expand a short name like "my-agent" to "my-agent@agentlair.dev".
|
|
30
|
+
* If the address already contains "@", returns it as-is.
|
|
27
31
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
function expandAddress(address) {
|
|
33
|
+
return address.includes('@') ? address : `${address}@agentlair.dev`;
|
|
34
|
+
}
|
|
35
|
+
// ─── Internal request helper (shared by both classes) ─────────────────────────
|
|
36
|
+
async function makeRequest(baseUrl, apiKey, method, path, opts = {}) {
|
|
37
|
+
const url = new URL(baseUrl + path);
|
|
38
|
+
if (opts.query) {
|
|
39
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
40
|
+
url.searchParams.set(k, v);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const headers = {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
};
|
|
46
|
+
if (opts.auth !== null) {
|
|
47
|
+
headers['Authorization'] = `Bearer ${opts.auth ?? apiKey}`;
|
|
48
|
+
}
|
|
49
|
+
const response = await fetch(url.toString(), {
|
|
50
|
+
method,
|
|
51
|
+
headers,
|
|
52
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
53
|
+
});
|
|
54
|
+
let data;
|
|
55
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
56
|
+
if (contentType.includes('application/json')) {
|
|
57
|
+
data = await response.json();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
data = await response.text();
|
|
61
|
+
}
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const body = data;
|
|
64
|
+
throw new AgentLairError(body?.message ?? `HTTP ${response.status}`, response.status, body?.error ?? 'error');
|
|
65
|
+
}
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
// ─── WebhooksNamespace ────────────────────────────────────────────────────────
|
|
69
|
+
class WebhooksNamespace {
|
|
70
|
+
_req;
|
|
71
|
+
constructor(_req) {
|
|
72
|
+
this._req = _req;
|
|
32
73
|
}
|
|
33
74
|
/**
|
|
34
|
-
*
|
|
75
|
+
* Register a webhook URL to receive real-time `email.received` events.
|
|
35
76
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
77
|
+
* @example
|
|
78
|
+
* const hook = await lair.email.webhooks.create({
|
|
79
|
+
* address: 'my-agent@agentlair.dev',
|
|
80
|
+
* url: 'https://myserver.com/webhook',
|
|
81
|
+
* secret: 'my-secret',
|
|
82
|
+
* });
|
|
83
|
+
*/
|
|
84
|
+
async create(options) {
|
|
85
|
+
return this._req('POST', '/v1/email/webhooks', {
|
|
86
|
+
body: { ...options, address: expandAddress(options.address) },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* List registered webhooks, optionally filtered by address.
|
|
38
91
|
*
|
|
39
92
|
* @example
|
|
40
|
-
*
|
|
41
|
-
* const vc = VaultCrypto.fromSeed(seed);
|
|
42
|
-
* const ciphertext = await vc.encrypt('sk-openai-...', 'openai-key');
|
|
43
|
-
* await client.vault.put('openai-key', { ciphertext });
|
|
93
|
+
* const { webhooks } = await lair.email.webhooks.list();
|
|
44
94
|
*/
|
|
45
|
-
async
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
async list(options = {}) {
|
|
96
|
+
const query = {};
|
|
97
|
+
if (options.address)
|
|
98
|
+
query.address = expandAddress(options.address);
|
|
99
|
+
return this._req('GET', '/v1/email/webhooks', { query });
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Delete a webhook by ID.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* await lair.email.webhooks.delete('wh_abc123');
|
|
106
|
+
*/
|
|
107
|
+
async delete(id) {
|
|
108
|
+
return this._req('DELETE', `/v1/email/webhooks/${encodeURIComponent(id)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ─── EmailNamespace ───────────────────────────────────────────────────────────
|
|
112
|
+
class EmailNamespace {
|
|
113
|
+
_req;
|
|
114
|
+
/** Webhook management: webhooks.create / webhooks.list / webhooks.delete */
|
|
115
|
+
webhooks;
|
|
116
|
+
constructor(_req) {
|
|
117
|
+
this._req = _req;
|
|
118
|
+
this.webhooks = new WebhooksNamespace(_req);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Claim an @agentlair.dev email address.
|
|
122
|
+
*
|
|
123
|
+
* Pass a short name (e.g. `"my-agent"`) and it auto-expands to `my-agent@agentlair.dev`.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* await lair.email.claim('my-agent'); // → my-agent@agentlair.dev
|
|
127
|
+
* await lair.email.claim('my-agent@agentlair.dev'); // also works
|
|
128
|
+
*/
|
|
129
|
+
async claim(address, options = {}) {
|
|
130
|
+
return this._req('POST', '/v1/email/claim', {
|
|
131
|
+
body: { ...options, address: expandAddress(address) },
|
|
48
132
|
});
|
|
49
133
|
}
|
|
50
134
|
/**
|
|
51
|
-
*
|
|
135
|
+
* List all @agentlair.dev addresses claimed by this account.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const { addresses } = await lair.email.addresses();
|
|
139
|
+
*/
|
|
140
|
+
async addresses() {
|
|
141
|
+
return this._req('GET', '/v1/email/addresses');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get inbox messages (previews — no full body).
|
|
145
|
+
* Use `read()` for the full body of a specific message.
|
|
52
146
|
*
|
|
53
|
-
*
|
|
147
|
+
* @example
|
|
148
|
+
* const { messages } = await lair.email.inbox('my-agent@agentlair.dev');
|
|
149
|
+
* const { messages } = await lair.email.inbox('my-agent', { limit: 5 });
|
|
150
|
+
*/
|
|
151
|
+
async inbox(address, options = {}) {
|
|
152
|
+
const query = { address: expandAddress(address) };
|
|
153
|
+
if (options.limit !== undefined)
|
|
154
|
+
query.limit = String(options.limit);
|
|
155
|
+
return this._req('GET', '/v1/email/inbox', { query });
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Read the full body of a message. Marks as read.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* const msg = await lair.email.read(inboxMsg.message_id_url, 'my-agent@agentlair.dev');
|
|
162
|
+
* console.log(msg.body);
|
|
163
|
+
*/
|
|
164
|
+
async read(messageId, address) {
|
|
165
|
+
return this._req('GET', `/v1/email/messages/${messageId}`, { query: { address: expandAddress(address) } });
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Delete a message.
|
|
54
169
|
*
|
|
55
170
|
* @example
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
171
|
+
* await lair.email.deleteMessage(inboxMsg.message_id_url, 'my-agent@agentlair.dev');
|
|
172
|
+
*/
|
|
173
|
+
async deleteMessage(messageId, address) {
|
|
174
|
+
return this._req('DELETE', `/v1/email/messages/${messageId}`, { query: { address: expandAddress(address) } });
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Update message properties (mark read/unread).
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* await lair.email.update(msg.message_id_url, 'my-agent@agentlair.dev', { read: false });
|
|
181
|
+
*/
|
|
182
|
+
async update(messageId, address, options) {
|
|
183
|
+
return this._req('PATCH', `/v1/email/messages/${messageId}`, { query: { address: expandAddress(address) }, body: options });
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Send an email from an address you own.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* await lair.email.send({
|
|
190
|
+
* from: 'my-agent@agentlair.dev',
|
|
191
|
+
* to: 'user@example.com',
|
|
192
|
+
* subject: 'Hello',
|
|
193
|
+
* text: 'Hi from an AI agent!',
|
|
194
|
+
* });
|
|
195
|
+
*/
|
|
196
|
+
async send(options) {
|
|
197
|
+
return this._req('POST', '/v1/email/send', { body: options });
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* List sent messages (outbox).
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* const { messages } = await lair.email.outbox({ limit: 5 });
|
|
204
|
+
*/
|
|
205
|
+
async outbox(options = {}) {
|
|
206
|
+
const query = {};
|
|
207
|
+
if (options.limit !== undefined)
|
|
208
|
+
query.limit = String(options.limit);
|
|
209
|
+
return this._req('GET', '/v1/email/outbox', { query });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ─── VaultNamespace ───────────────────────────────────────────────────────────
|
|
213
|
+
/**
|
|
214
|
+
* Zero-knowledge secret store.
|
|
215
|
+
* Server stores opaque encrypted blobs — plaintext never leaves your client.
|
|
216
|
+
* Use @agentlair/vault-crypto for client-side encryption.
|
|
217
|
+
*/
|
|
218
|
+
class VaultNamespace {
|
|
219
|
+
_req;
|
|
220
|
+
constructor(_req) {
|
|
221
|
+
this._req = _req;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Store an encrypted blob (versioned, append-only).
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* await lair.vault.put('openai-key', { ciphertext: encryptedBlob });
|
|
228
|
+
*/
|
|
229
|
+
async put(key, options) {
|
|
230
|
+
return this._req('PUT', `/v1/vault/${encodeURIComponent(key)}`, { body: options });
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Retrieve an encrypted blob. Returns latest version by default.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const { ciphertext } = await lair.vault.get('openai-key');
|
|
237
|
+
* const old = await lair.vault.get('openai-key', { version: 1 });
|
|
59
238
|
*/
|
|
60
239
|
async get(key, options = {}) {
|
|
61
240
|
const query = {};
|
|
62
241
|
if (options.version !== undefined)
|
|
63
242
|
query.version = String(options.version);
|
|
64
|
-
return this.
|
|
243
|
+
return this._req('GET', `/v1/vault/${encodeURIComponent(key)}`, { query });
|
|
65
244
|
}
|
|
66
245
|
/**
|
|
67
|
-
* List all Vault keys
|
|
246
|
+
* List all Vault keys (metadata only).
|
|
68
247
|
*
|
|
69
248
|
* @example
|
|
70
|
-
* const { keys, count, limit } = await
|
|
249
|
+
* const { keys, count, limit } = await lair.vault.list();
|
|
71
250
|
*/
|
|
72
251
|
async list() {
|
|
73
|
-
return this.
|
|
252
|
+
return this._req('GET', '/v1/vault/');
|
|
74
253
|
}
|
|
75
254
|
/**
|
|
76
|
-
* Delete a
|
|
77
|
-
*
|
|
78
|
-
* Pass `version` to delete only that version. Omit to delete all versions.
|
|
255
|
+
* Delete a key (all versions) or a specific version.
|
|
79
256
|
*
|
|
80
257
|
* @example
|
|
81
|
-
* await
|
|
82
|
-
* await
|
|
258
|
+
* await lair.vault.delete('openai-key');
|
|
259
|
+
* await lair.vault.delete('openai-key', { version: 2 });
|
|
83
260
|
*/
|
|
84
261
|
async delete(key, options = {}) {
|
|
85
262
|
const query = {};
|
|
86
263
|
if (options.version !== undefined)
|
|
87
264
|
query.version = String(options.version);
|
|
88
|
-
return this.
|
|
89
|
-
query,
|
|
90
|
-
});
|
|
265
|
+
return this._req('DELETE', `/v1/vault/${encodeURIComponent(key)}`, { query });
|
|
91
266
|
}
|
|
92
267
|
}
|
|
93
|
-
// ───
|
|
94
|
-
|
|
268
|
+
// ─── StacksNamespace ──────────────────────────────────────────────────────────
|
|
269
|
+
class StacksNamespace {
|
|
270
|
+
_req;
|
|
271
|
+
constructor(_req) {
|
|
272
|
+
this._req = _req;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Create a domain stack. Points nameservers to AgentLair DNS.
|
|
276
|
+
* Beta: DNS provisioning is stubbed. Full CF DNS integration coming Q2 2026.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* const stack = await lair.stacks.create({ domain: 'myagent.dev' });
|
|
280
|
+
*/
|
|
281
|
+
async create(options) {
|
|
282
|
+
return this._req('POST', '/v1/stack', { body: options });
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* List all domain stacks for this account.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* const { stacks } = await lair.stacks.list();
|
|
289
|
+
*/
|
|
290
|
+
async list() {
|
|
291
|
+
return this._req('GET', '/v1/stack');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ─── ObservationsNamespace ────────────────────────────────────────────────────
|
|
295
|
+
class ObservationsNamespace {
|
|
296
|
+
_req;
|
|
297
|
+
constructor(_req) {
|
|
298
|
+
this._req = _req;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Write a structured observation. Account-scoped by default.
|
|
302
|
+
* Set `shared: true` to make visible to all authenticated agents.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* await lair.observations.write({ topic: 'market-signals', content: 'BTC up 5%' });
|
|
306
|
+
*/
|
|
307
|
+
async write(options) {
|
|
308
|
+
return this._req('POST', '/v1/observations', { body: options });
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Read observations. Returns own + shared by default.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* const { observations } = await lair.observations.read({ topic: 'market-signals' });
|
|
315
|
+
* const shared = await lair.observations.read({ scope: 'shared' });
|
|
316
|
+
*/
|
|
317
|
+
async read(options = {}) {
|
|
318
|
+
const query = {};
|
|
319
|
+
if (options.topic)
|
|
320
|
+
query.topic = options.topic;
|
|
321
|
+
if (options.agent_id)
|
|
322
|
+
query.agent_id = options.agent_id;
|
|
323
|
+
if (options.since)
|
|
324
|
+
query.since = options.since;
|
|
325
|
+
if (options.scope)
|
|
326
|
+
query.scope = options.scope;
|
|
327
|
+
if (options.limit !== undefined)
|
|
328
|
+
query.limit = String(options.limit);
|
|
329
|
+
return this._req('GET', '/v1/observations', { query });
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* List distinct topics with observation count.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* const { topics } = await lair.observations.topics();
|
|
336
|
+
*/
|
|
337
|
+
async topics() {
|
|
338
|
+
return this._req('GET', '/v1/observations/topics');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// ─── AccountNamespace ─────────────────────────────────────────────────────────
|
|
342
|
+
class AccountNamespace {
|
|
343
|
+
_req;
|
|
344
|
+
constructor(_req) {
|
|
345
|
+
this._req = _req;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get the current account profile.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* const { account_id, tier } = await lair.account.me();
|
|
352
|
+
*/
|
|
353
|
+
async me() {
|
|
354
|
+
return this._req('GET', '/v1/account/me');
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get current usage statistics.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* const { emails } = await lair.account.usage();
|
|
361
|
+
* console.log(emails.daily_remaining);
|
|
362
|
+
*/
|
|
363
|
+
async usage() {
|
|
364
|
+
return this._req('GET', '/v1/usage');
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get billing information.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* const billing = await lair.account.billing();
|
|
371
|
+
*/
|
|
372
|
+
async billing() {
|
|
373
|
+
return this._req('GET', '/v1/billing');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// ─── AgentLair (primary class) ────────────────────────────────────────────────
|
|
377
|
+
/**
|
|
378
|
+
* AgentLair — primary SDK class.
|
|
379
|
+
*
|
|
380
|
+
* Accepts an API key string directly (or an options object for advanced config).
|
|
381
|
+
*
|
|
382
|
+
* @example Three-line onboarding
|
|
383
|
+
* const lair = new AgentLair(process.env.AGENTLAIR_API_KEY!);
|
|
384
|
+
* const inbox = await lair.email.claim('my-agent');
|
|
385
|
+
* const { messages } = await lair.email.inbox('my-agent@agentlair.dev');
|
|
386
|
+
*/
|
|
387
|
+
export class AgentLair {
|
|
95
388
|
_apiKey;
|
|
96
389
|
_baseUrl;
|
|
390
|
+
/** Email: claim / inbox / send / read / deleteMessage / update / outbox / addresses / webhooks */
|
|
391
|
+
email;
|
|
392
|
+
/** Vault: put / get / list / delete */
|
|
393
|
+
vault;
|
|
394
|
+
/** Stacks: create / list */
|
|
395
|
+
stacks;
|
|
396
|
+
/** Observations: write / read / topics */
|
|
397
|
+
observations;
|
|
398
|
+
/** Account: me / usage / billing */
|
|
399
|
+
account;
|
|
97
400
|
/**
|
|
98
|
-
*
|
|
401
|
+
* @param apiKeyOrOptions API key string (e.g. "al_live_...") or options object
|
|
99
402
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.vault = new VaultNamespace(this._request.bind(this));
|
|
105
|
-
}
|
|
106
|
-
// ─── Internal request helper ─────────────────────────────────────────────
|
|
107
|
-
async _request(method, path, opts = {}) {
|
|
108
|
-
const url = new URL(this._baseUrl + path);
|
|
109
|
-
if (opts.query) {
|
|
110
|
-
for (const [k, v] of Object.entries(opts.query)) {
|
|
111
|
-
url.searchParams.set(k, v);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const headers = {
|
|
115
|
-
'Content-Type': 'application/json',
|
|
116
|
-
};
|
|
117
|
-
// auth defaults to Bearer <apiKey>; pass null to skip auth
|
|
118
|
-
if (opts.auth !== null) {
|
|
119
|
-
headers['Authorization'] = `Bearer ${opts.auth ?? this._apiKey}`;
|
|
120
|
-
}
|
|
121
|
-
const response = await fetch(url.toString(), {
|
|
122
|
-
method,
|
|
123
|
-
headers,
|
|
124
|
-
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
125
|
-
});
|
|
126
|
-
let data;
|
|
127
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
128
|
-
if (contentType.includes('application/json')) {
|
|
129
|
-
data = await response.json();
|
|
403
|
+
constructor(apiKeyOrOptions) {
|
|
404
|
+
if (typeof apiKeyOrOptions === 'string') {
|
|
405
|
+
this._apiKey = apiKeyOrOptions;
|
|
406
|
+
this._baseUrl = DEFAULT_BASE_URL;
|
|
130
407
|
}
|
|
131
408
|
else {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!response.ok) {
|
|
135
|
-
const body = data;
|
|
136
|
-
throw new AgentLairError(body?.message ?? `HTTP ${response.status}`, response.status, body?.error ?? 'error');
|
|
409
|
+
this._apiKey = apiKeyOrOptions.apiKey;
|
|
410
|
+
this._baseUrl = (apiKeyOrOptions.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
137
411
|
}
|
|
138
|
-
|
|
412
|
+
const req = this._request.bind(this);
|
|
413
|
+
this.email = new EmailNamespace(req);
|
|
414
|
+
this.vault = new VaultNamespace(req);
|
|
415
|
+
this.stacks = new StacksNamespace(req);
|
|
416
|
+
this.observations = new ObservationsNamespace(req);
|
|
417
|
+
this.account = new AccountNamespace(req);
|
|
139
418
|
}
|
|
140
|
-
|
|
419
|
+
_request(method, path, opts = {}) {
|
|
420
|
+
return makeRequest(this._baseUrl, this._apiKey, method, path, opts);
|
|
421
|
+
}
|
|
422
|
+
// ─── Static: createAccount ─────────────────────────────────────────────────
|
|
141
423
|
/**
|
|
142
424
|
* Create a new AgentLair account and get an API key.
|
|
143
|
-
*
|
|
144
|
-
* No existing account or API key needed — this is the bootstrapping method.
|
|
145
|
-
* **Save the returned `api_key` immediately** — it will not be shown again.
|
|
146
|
-
*
|
|
147
|
-
* @param options Optional name and recovery email
|
|
148
|
-
* @param baseUrl Override API base URL (default: https://agentlair.dev)
|
|
425
|
+
* No existing account needed. **Save the returned `api_key` immediately** — not shown again.
|
|
149
426
|
*
|
|
150
427
|
* @example
|
|
151
|
-
* const { api_key
|
|
152
|
-
*
|
|
153
|
-
* const client = new AgentLairClient({ apiKey: api_key });
|
|
428
|
+
* const { api_key } = await AgentLair.createAccount({ name: 'my-agent' });
|
|
429
|
+
* const lair = new AgentLair(api_key);
|
|
154
430
|
*/
|
|
155
431
|
static async createAccount(options = {}, baseUrl = DEFAULT_BASE_URL) {
|
|
156
432
|
const url = baseUrl.replace(/\/$/, '') + '/v1/auth/keys';
|
|
@@ -165,77 +441,58 @@ export class AgentLairClient {
|
|
|
165
441
|
}
|
|
166
442
|
return data;
|
|
167
443
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
444
|
+
}
|
|
445
|
+
// ─── AgentLairClient (legacy — fully backward compatible) ─────────────────────
|
|
446
|
+
/**
|
|
447
|
+
* @deprecated Use `AgentLair` instead — simpler string constructor, same namespaces.
|
|
448
|
+
* `AgentLairClient` is fully retained for backward compatibility.
|
|
449
|
+
*/
|
|
450
|
+
export class AgentLairClient {
|
|
451
|
+
_apiKey;
|
|
452
|
+
_baseUrl;
|
|
453
|
+
/** Vault: put / get / list / delete */
|
|
454
|
+
vault;
|
|
455
|
+
/** Email: claim / inbox / send / read / outbox / addresses / webhooks */
|
|
456
|
+
email;
|
|
457
|
+
/** Stacks: create / list */
|
|
458
|
+
stacks;
|
|
459
|
+
/** Observations: write / read / topics */
|
|
460
|
+
observations;
|
|
461
|
+
/** Account: me / usage / billing */
|
|
462
|
+
account;
|
|
463
|
+
constructor(options) {
|
|
464
|
+
this._apiKey = options.apiKey;
|
|
465
|
+
this._baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
466
|
+
const req = this._request.bind(this);
|
|
467
|
+
this.vault = new VaultNamespace(req);
|
|
468
|
+
this.email = new EmailNamespace(req);
|
|
469
|
+
this.stacks = new StacksNamespace(req);
|
|
470
|
+
this.observations = new ObservationsNamespace(req);
|
|
471
|
+
this.account = new AccountNamespace(req);
|
|
472
|
+
}
|
|
473
|
+
_request(method, path, opts = {}) {
|
|
474
|
+
return makeRequest(this._baseUrl, this._apiKey, method, path, opts);
|
|
475
|
+
}
|
|
476
|
+
/** @deprecated Prefer `AgentLair.createAccount()` */
|
|
477
|
+
static async createAccount(options = {}, baseUrl = DEFAULT_BASE_URL) {
|
|
478
|
+
return AgentLair.createAccount(options, baseUrl);
|
|
479
|
+
}
|
|
480
|
+
// ─── Legacy flat methods (backward compatible) ────────────────────────────
|
|
481
|
+
/** @deprecated Use `client.email.claim(address)` */
|
|
181
482
|
async claimAddress(options) {
|
|
182
|
-
return this.
|
|
483
|
+
return this.email.claim(options.address, { public_key: options.public_key });
|
|
183
484
|
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Send a DKIM-signed email from an @agentlair.dev address you own.
|
|
187
|
-
*
|
|
188
|
-
* Supports plain text, HTML, or both. Supports threading via `in_reply_to`.
|
|
189
|
-
* Free tier: 10 emails/day per address.
|
|
190
|
-
*
|
|
191
|
-
* @example
|
|
192
|
-
* await client.sendEmail({
|
|
193
|
-
* from: 'my-agent@agentlair.dev',
|
|
194
|
-
* to: 'user@example.com',
|
|
195
|
-
* subject: 'Hello from AgentLair',
|
|
196
|
-
* text: 'Hi! This email was sent by an AI agent.',
|
|
197
|
-
* });
|
|
198
|
-
*/
|
|
485
|
+
/** @deprecated Use `client.email.send(options)` */
|
|
199
486
|
async sendEmail(options) {
|
|
200
|
-
return this.
|
|
487
|
+
return this.email.send(options);
|
|
201
488
|
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get the inbox for an @agentlair.dev address you own.
|
|
205
|
-
*
|
|
206
|
-
* Returns message previews (no full body). Use `readMessage()` for the full body.
|
|
207
|
-
*
|
|
208
|
-
* @example
|
|
209
|
-
* const { messages, count } = await client.getInbox({ address: 'my-agent@agentlair.dev' });
|
|
210
|
-
* for (const msg of messages) {
|
|
211
|
-
* console.log(msg.from, msg.subject, msg.snippet);
|
|
212
|
-
* }
|
|
213
|
-
*/
|
|
489
|
+
/** @deprecated Use `client.email.inbox(address, options)` */
|
|
214
490
|
async getInbox(options) {
|
|
215
|
-
|
|
216
|
-
if (options.limit !== undefined)
|
|
217
|
-
query.limit = String(options.limit);
|
|
218
|
-
return this._request('GET', '/v1/email/inbox', { query });
|
|
491
|
+
return this.email.inbox(options.address, { limit: options.limit });
|
|
219
492
|
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Read the full body of a specific message.
|
|
223
|
-
*
|
|
224
|
-
* Marks the message as read. For E2E-encrypted messages, returns
|
|
225
|
-
* `ciphertext` and `ephemeral_public_key` for client-side decryption.
|
|
226
|
-
*
|
|
227
|
-
* @param messageId Use `message_id_url` from inbox (URL-encoded) or raw `message_id`
|
|
228
|
-
* @param address The @agentlair.dev address that received the message
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* const msg = await client.readMessage({
|
|
232
|
-
* messageId: inboxMsg.message_id_url,
|
|
233
|
-
* address: 'my-agent@agentlair.dev',
|
|
234
|
-
* });
|
|
235
|
-
* console.log(msg.body);
|
|
236
|
-
*/
|
|
493
|
+
/** @deprecated Use `client.email.read(messageId, address)` */
|
|
237
494
|
async readMessage(options) {
|
|
238
|
-
return this.
|
|
495
|
+
return this.email.read(options.messageId, options.address);
|
|
239
496
|
}
|
|
240
497
|
}
|
|
241
498
|
//# sourceMappingURL=client.js.map
|