zanii-connect 0.1.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,66 @@
1
+ # zanii-connect — TypeScript SDK
2
+
3
+ TypeScript/JavaScript client for [Zanii Connect](https://vault.zanii.agency/api/docs): OAuth
4
+ connections, encrypted token vault, and audited tool execution for AI agents.
5
+
6
+ **Zero runtime dependencies** — uses the platform `fetch` (Node 18+, browsers, edge runtimes).
7
+
8
+ ## Install & build
9
+
10
+ ```bash
11
+ cd sdk/typescript && npm install && npm run build # from the monorepo
12
+ ```
13
+
14
+ ## Quickstart
15
+
16
+ ```ts
17
+ import { ZaniiClient } from "zanii-connect";
18
+
19
+ // Agents/services authenticate with a scoped API key:
20
+ const nc = new ZaniiClient({ apiKey: "nsk_..." });
21
+
22
+ // Or as a user (JWT):
23
+ const nc = new ZaniiClient();
24
+ await nc.login("info@zanii.agency", "password");
25
+
26
+ console.log((await nc.me()).organization.name);
27
+
28
+ // Connect a provider (open the returned URL in a browser to complete OAuth):
29
+ const { url } = await nc.connect("gmail",
30
+ "https://vault.zanii.agency/api/v1/connect/oauth/callback");
31
+
32
+ // Discover tools and execute one:
33
+ const conn = (await nc.connections()).connections[0];
34
+ const found = await nc.execute("gmail.search", conn.id, { query: "invoice" });
35
+
36
+ // Actions that need human approval return an approval link instead of executing:
37
+ const res = await nc.execute("gmail.sendEmail", conn.id,
38
+ { to: "info@zanii.agency", subject: "Hi", body: "Hello" });
39
+ if (res.requires_approval) console.log("Ask a human to confirm:", res.approval_url);
40
+
41
+ // Long-running work goes through the job queue:
42
+ const job = await nc.executeAsync("browser.run", conn.id, { /* ... */ });
43
+ const result = await nc.waitForJob(job.job_id, 300);
44
+ ```
45
+
46
+ ## Behavior
47
+
48
+ - **Retries**: 429/502/503/504 are retried up to 3 times with exponential backoff.
49
+ - **Idempotency**: pass `{ idempotencyKey }` to `execute()` to make retries safe for side-effecting actions.
50
+ - **Errors**: non-2xx responses throw `ZaniiError` with `.statusCode` and `.detail`.
51
+ - **MCP**: AI agents that speak MCP don't need this SDK — point them at
52
+ `POST https://vault.zanii.agency/api/v1/mcp` with an `X-API-Key` header.
53
+
54
+ ## Surface
55
+
56
+ Mirrors the Python SDK (`sdk/python`), camelCased — covers the full API:
57
+
58
+ - **Account**: `signup`, `login`, `me`, `updateProfile`, `changePassword`, `forgotPassword`, `resetPassword`, email verification, MFA (`mfaEnroll`/`mfaEnable`/`mfaDisable`)
59
+ - **API keys**: `createApiKey` (plaintext returned once), `apiKeys`, `deleteApiKey` (soft revoke)
60
+ - **Org & team**: `org`, `usage`, `updateOrg`, `exportOrg`, `deleteOrg`, `users`, `inviteUser`, `updateUser`, `deactivateUser`, roles (`permissions`, `roles`, `upsertRole`, `deleteRole`), workspaces/projects/environments
61
+ - **Registry**: `providers`, `provider`, `providerActions`, `actions`, `action`, `tools`, `tool`, dynamic-registry admin (`registerProvider`, `registerAction`, `unregisterAction`)
62
+ - **Connections**: `connect`, `connections`, `connectionsHealth`, `deleteConnection`, `shareConnection`
63
+ - **Execution**: `execute`, `executeAsync`, `job`, `waitForJob` · approvals (`approvals`, `approve`)
64
+ - **Browser sandbox**: `createBrowserSession`, `browserSession`, `browserRun`, `browserRunAsync`
65
+ - **Secrets / automation**: `secrets`, `setSecret`, `deleteSecret`, triggers, webhook subscriptions
66
+ - **Observability**: `executions`, `audit`, `auditExportCsv`, `health`
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Zanii Connect SDK.
3
+ *
4
+ * import { ZaniiClient } from "zanii-connect";
5
+ *
6
+ * const nc = new ZaniiClient({ apiKey: "nsk_..." }); // agents / services
7
+ * // or a user session:
8
+ * const nc = new ZaniiClient();
9
+ * await nc.login("info@zanii.agency", "password");
10
+ *
11
+ * const tools = await nc.tools();
12
+ * const res = await nc.execute("gmail.search", connId, { query: "invoice" });
13
+ *
14
+ * Zero dependencies — uses the platform fetch (Node 18+, browsers, edge).
15
+ */
16
+ export declare const DEFAULT_BASE_URL = "https://vault.zanii.agency/api/v1";
17
+ export declare class ZaniiError extends Error {
18
+ statusCode: number;
19
+ detail: string;
20
+ constructor(statusCode: number, detail: string);
21
+ }
22
+ export declare class JobTimeout extends ZaniiError {
23
+ jobId: string;
24
+ constructor(jobId: string, timeoutSeconds: number);
25
+ }
26
+ export interface ZaniiClientOptions {
27
+ apiKey?: string;
28
+ token?: string;
29
+ baseUrl?: string;
30
+ /** Request timeout in milliseconds (default 60 000). */
31
+ timeoutMs?: number;
32
+ }
33
+ export declare class ZaniiClient {
34
+ private baseUrl;
35
+ private apiKey?;
36
+ private token?;
37
+ private timeoutMs;
38
+ constructor(opts?: ZaniiClientOptions);
39
+ private req;
40
+ /** Service health incl. dependency checks (no auth required). */
41
+ health(): Promise<any>;
42
+ /** Create a new organization + first admin user. Returns a session token. */
43
+ signup(orgName: string, orgSlug: string, email: string, name: string, password: string): Promise<any>;
44
+ /** Log in with email/password; stores the JWT on the client. */
45
+ login(email: string, password: string, mfaCode?: string): Promise<any>;
46
+ /** Current user, organization, role, and effective permissions. */
47
+ me(): Promise<any>;
48
+ updateProfile(name: string): Promise<any>;
49
+ changePassword(currentPassword: string, newPassword: string): Promise<any>;
50
+ forgotPassword(email: string): Promise<any>;
51
+ resetPassword(token: string, newPassword: string): Promise<any>;
52
+ requestEmailVerification(): Promise<any>;
53
+ confirmEmailVerification(token: string): Promise<any>;
54
+ /** Begin TOTP enrollment. Returns the otpauth secret/URI to show the user. */
55
+ mfaEnroll(): Promise<any>;
56
+ mfaEnable(code: string): Promise<any>;
57
+ mfaDisable(code: string): Promise<any>;
58
+ /** Create a scoped API key. The plaintext key is returned ONCE. */
59
+ createApiKey(name: string, scopes?: string[]): Promise<any>;
60
+ apiKeys(): Promise<any>;
61
+ /** Revoke an API key (kept in the list with revoked=true for audit). */
62
+ deleteApiKey(keyId: string): Promise<any>;
63
+ org(): Promise<any>;
64
+ /** Current-month execution usage vs quota. */
65
+ usage(): Promise<any>;
66
+ /** Update status / plan / monthly_execution_quota (admin). */
67
+ updateOrg(fields: {
68
+ status?: string;
69
+ plan?: string;
70
+ monthly_execution_quota?: number;
71
+ }): Promise<any>;
72
+ /** Portable JSON export of all tenant data (GDPR, admin). */
73
+ exportOrg(): Promise<any>;
74
+ /** Hard-delete the organization and ALL tenant data (GDPR erasure, admin). */
75
+ deleteOrg(): Promise<any>;
76
+ users(): Promise<any>;
77
+ /** Invite a teammate. Without password they get a set-your-password email. */
78
+ inviteUser(email: string, name: string, role?: string, password?: string): Promise<any>;
79
+ updateUser(userId: string, fields: {
80
+ role?: string;
81
+ is_active?: boolean;
82
+ }): Promise<any>;
83
+ deactivateUser(userId: string): Promise<any>;
84
+ /** The full permission catalog custom roles may grant from. */
85
+ permissions(): Promise<any>;
86
+ roles(): Promise<any>;
87
+ upsertRole(name: string, permissions: string[]): Promise<any>;
88
+ deleteRole(name: string): Promise<any>;
89
+ workspaces(): Promise<any>;
90
+ createWorkspace(name: string, slug?: string): Promise<any>;
91
+ deleteWorkspace(workspaceId: string): Promise<any>;
92
+ projects(): Promise<any>;
93
+ createProject(name: string, description?: string): Promise<any>;
94
+ deleteProject(projectId: string): Promise<any>;
95
+ environments(projectId: string): Promise<any>;
96
+ createEnvironment(projectId: string, name: string, slug?: string): Promise<any>;
97
+ providers(): Promise<any>;
98
+ provider(name: string): Promise<any>;
99
+ providerActions(provider: string): Promise<any>;
100
+ actions(provider?: string): Promise<any>;
101
+ action(name: string): Promise<any>;
102
+ /** Admin: register a runtime-defined provider in the dynamic registry. */
103
+ registerProvider(provider: Record<string, unknown>): Promise<any>;
104
+ /** Admin: register a runtime-defined action (generic REST mapping). */
105
+ registerAction(action: Record<string, unknown>): Promise<any>;
106
+ unregisterAction(name: string): Promise<any>;
107
+ /** MCP-shaped tool list (name, description, input_schema). */
108
+ tools(provider?: string): Promise<any>;
109
+ tool(name: string): Promise<any>;
110
+ /** Start OAuth for a provider. Returns { url } to open in a browser. */
111
+ connect(provider: string, redirectUrl: string): Promise<any>;
112
+ connections(): Promise<any>;
113
+ /** Per-connection health with needs_reauth flags. */
114
+ connectionsHealth(): Promise<any>;
115
+ deleteConnection(connectionId: string): Promise<any>;
116
+ shareConnection(connectionId: string, shared?: boolean): Promise<any>;
117
+ /**
118
+ * Run a tool synchronously. If the action needs human approval the response
119
+ * has status="pending" and an approval_url to hand to a human.
120
+ */
121
+ execute(action: string, connectionId: string, parameters?: Record<string, unknown>, opts?: {
122
+ idempotencyKey?: string;
123
+ }): Promise<any>;
124
+ /** Queue a long-running tool job. Returns { job_id, status }. */
125
+ executeAsync(action: string, connectionId: string, parameters?: Record<string, unknown>): Promise<any>;
126
+ job(jobId: string): Promise<any>;
127
+ /** Poll a job until it finishes. Throws JobTimeout on deadline. */
128
+ waitForJob(jobId: string, timeoutSeconds?: number, pollIntervalSeconds?: number): Promise<any>;
129
+ /** Start a Playwright browser session for no-API automation. */
130
+ createBrowserSession(targetUrl: string): Promise<any>;
131
+ browserSession(sessionId: string): Promise<any>;
132
+ /** Run browser steps (navigate/click/fill/extract/...) synchronously. */
133
+ browserRun(sessionId: string, steps: Record<string, unknown>[]): Promise<any>;
134
+ /** Queue browser steps as a job. Poll with job()/waitForJob(). */
135
+ browserRunAsync(sessionId: string, steps: Record<string, unknown>[]): Promise<any>;
136
+ approvals(status?: string, limit?: number, offset?: number): Promise<any>;
137
+ /** Decide a pending approval as the requesting user (JWT auth). */
138
+ approve(approvalId: string, approved?: boolean): Promise<any>;
139
+ secrets(): Promise<any>;
140
+ setSecret(name: string, value: string): Promise<any>;
141
+ deleteSecret(name: string): Promise<any>;
142
+ triggers(): Promise<any>;
143
+ createTrigger(trigger: Record<string, unknown>): Promise<any>;
144
+ deleteTrigger(triggerId: string): Promise<any>;
145
+ webhookSubscriptions(): Promise<any>;
146
+ createWebhookSubscription(subscription: Record<string, unknown>): Promise<any>;
147
+ deleteWebhookSubscription(subscriptionId: string): Promise<any>;
148
+ executions(opts?: {
149
+ action?: string;
150
+ status?: string;
151
+ limit?: number;
152
+ offset?: number;
153
+ }): Promise<any>;
154
+ audit(opts?: {
155
+ action?: string;
156
+ limit?: number;
157
+ offset?: number;
158
+ }): Promise<any>;
159
+ /** Full audit log as CSV text. */
160
+ auditExportCsv(): Promise<string>;
161
+ }
package/dist/index.js ADDED
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Zanii Connect SDK.
3
+ *
4
+ * import { ZaniiClient } from "zanii-connect";
5
+ *
6
+ * const nc = new ZaniiClient({ apiKey: "nsk_..." }); // agents / services
7
+ * // or a user session:
8
+ * const nc = new ZaniiClient();
9
+ * await nc.login("info@zanii.agency", "password");
10
+ *
11
+ * const tools = await nc.tools();
12
+ * const res = await nc.execute("gmail.search", connId, { query: "invoice" });
13
+ *
14
+ * Zero dependencies — uses the platform fetch (Node 18+, browsers, edge).
15
+ */
16
+ export const DEFAULT_BASE_URL = "https://vault.zanii.agency/api/v1";
17
+ const RETRY_STATUSES = new Set([429, 502, 503, 504]);
18
+ const RETRIES = 3;
19
+ export class ZaniiError extends Error {
20
+ statusCode;
21
+ detail;
22
+ constructor(statusCode, detail) {
23
+ super(`[${statusCode}] ${detail}`);
24
+ this.statusCode = statusCode;
25
+ this.detail = detail;
26
+ this.name = "ZaniiError";
27
+ }
28
+ }
29
+ export class JobTimeout extends ZaniiError {
30
+ jobId;
31
+ constructor(jobId, timeoutSeconds) {
32
+ super(408, `Job ${jobId} still running after ${timeoutSeconds}s`);
33
+ this.jobId = jobId;
34
+ this.name = "JobTimeout";
35
+ }
36
+ }
37
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
38
+ export class ZaniiClient {
39
+ baseUrl;
40
+ apiKey;
41
+ token;
42
+ timeoutMs;
43
+ constructor(opts = {}) {
44
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
45
+ this.apiKey = opts.apiKey;
46
+ this.token = opts.token;
47
+ this.timeoutMs = opts.timeoutMs ?? 60_000;
48
+ }
49
+ async req(method, path, o = {}) {
50
+ const url = new URL(this.baseUrl + path);
51
+ for (const [k, v] of Object.entries(o.params ?? {})) {
52
+ if (v !== undefined)
53
+ url.searchParams.set(k, String(v));
54
+ }
55
+ const headers = { ...o.headers };
56
+ if (!o.noauth) {
57
+ if (this.apiKey)
58
+ headers["X-API-Key"] = this.apiKey;
59
+ else if (this.token)
60
+ headers["Authorization"] = `Bearer ${this.token}`;
61
+ }
62
+ if (o.json !== undefined)
63
+ headers["Content-Type"] = "application/json";
64
+ let resp;
65
+ for (let attempt = 0;; attempt++) {
66
+ resp = await fetch(url, {
67
+ method,
68
+ headers,
69
+ body: o.json !== undefined ? JSON.stringify(o.json) : undefined,
70
+ signal: AbortSignal.timeout(this.timeoutMs),
71
+ });
72
+ if (RETRY_STATUSES.has(resp.status) && attempt < RETRIES) {
73
+ await sleep(Math.min(2 ** attempt, 8) * 1000);
74
+ continue;
75
+ }
76
+ break;
77
+ }
78
+ if (resp.status >= 400) {
79
+ let detail = await resp.text();
80
+ try {
81
+ detail = JSON.parse(detail).detail ?? detail;
82
+ }
83
+ catch {
84
+ /* plain-text error body */
85
+ }
86
+ throw new ZaniiError(resp.status, detail);
87
+ }
88
+ return o.raw ? resp.text() : resp.json();
89
+ }
90
+ // -- platform ---------------------------------------------------------
91
+ /** Service health incl. dependency checks (no auth required). */
92
+ health() {
93
+ return this.req("GET", "/health", { noauth: true });
94
+ }
95
+ // -- identity / account -------------------------------------------------
96
+ /** Create a new organization + first admin user. Returns a session token. */
97
+ signup(orgName, orgSlug, email, name, password) {
98
+ return this.req("POST", "/auth/signup", {
99
+ noauth: true,
100
+ json: { org_name: orgName, org_slug: orgSlug, email, name, password },
101
+ });
102
+ }
103
+ /** Log in with email/password; stores the JWT on the client. */
104
+ async login(email, password, mfaCode) {
105
+ const result = await this.req("POST", "/auth/login", {
106
+ noauth: true,
107
+ json: { email, password, ...(mfaCode ? { mfa_code: mfaCode } : {}) },
108
+ });
109
+ this.token = result.token;
110
+ return result;
111
+ }
112
+ /** Current user, organization, role, and effective permissions. */
113
+ me() {
114
+ return this.req("GET", "/auth/me");
115
+ }
116
+ updateProfile(name) {
117
+ return this.req("PATCH", "/users/me", { json: { name } });
118
+ }
119
+ changePassword(currentPassword, newPassword) {
120
+ return this.req("POST", "/auth/password/change", {
121
+ json: { current_password: currentPassword, new_password: newPassword },
122
+ });
123
+ }
124
+ forgotPassword(email) {
125
+ return this.req("POST", "/auth/password/forgot", { noauth: true, json: { email } });
126
+ }
127
+ resetPassword(token, newPassword) {
128
+ return this.req("POST", "/auth/password/reset", {
129
+ noauth: true,
130
+ json: { token, new_password: newPassword },
131
+ });
132
+ }
133
+ requestEmailVerification() {
134
+ return this.req("POST", "/auth/verify/request");
135
+ }
136
+ confirmEmailVerification(token) {
137
+ return this.req("POST", "/auth/verify/confirm", { noauth: true, json: { token } });
138
+ }
139
+ /** Begin TOTP enrollment. Returns the otpauth secret/URI to show the user. */
140
+ mfaEnroll() {
141
+ return this.req("POST", "/auth/mfa/enroll");
142
+ }
143
+ mfaEnable(code) {
144
+ return this.req("POST", "/auth/mfa/enable", { json: { code } });
145
+ }
146
+ mfaDisable(code) {
147
+ return this.req("POST", "/auth/mfa/disable", { json: { code } });
148
+ }
149
+ // -- API keys (for agents/services) ---------------------------------------
150
+ /** Create a scoped API key. The plaintext key is returned ONCE. */
151
+ createApiKey(name, scopes) {
152
+ return this.req("POST", "/auth/keys", { json: { name, scopes } });
153
+ }
154
+ apiKeys() {
155
+ return this.req("GET", "/auth/keys");
156
+ }
157
+ /** Revoke an API key (kept in the list with revoked=true for audit). */
158
+ deleteApiKey(keyId) {
159
+ return this.req("DELETE", `/auth/keys/${keyId}`);
160
+ }
161
+ // -- organization ---------------------------------------------------------
162
+ org() {
163
+ return this.req("GET", "/org");
164
+ }
165
+ /** Current-month execution usage vs quota. */
166
+ usage() {
167
+ return this.req("GET", "/org/usage");
168
+ }
169
+ /** Update status / plan / monthly_execution_quota (admin). */
170
+ updateOrg(fields) {
171
+ return this.req("PATCH", "/org", { json: fields });
172
+ }
173
+ /** Portable JSON export of all tenant data (GDPR, admin). */
174
+ exportOrg() {
175
+ return this.req("GET", "/org/export");
176
+ }
177
+ /** Hard-delete the organization and ALL tenant data (GDPR erasure, admin). */
178
+ deleteOrg() {
179
+ return this.req("DELETE", "/org");
180
+ }
181
+ // -- users ------------------------------------------------------------------
182
+ users() {
183
+ return this.req("GET", "/users");
184
+ }
185
+ /** Invite a teammate. Without password they get a set-your-password email. */
186
+ inviteUser(email, name, role = "member", password) {
187
+ return this.req("POST", "/users", {
188
+ json: { email, name, role, ...(password ? { password } : {}) },
189
+ });
190
+ }
191
+ updateUser(userId, fields) {
192
+ return this.req("PATCH", `/users/${userId}`, { json: fields });
193
+ }
194
+ deactivateUser(userId) {
195
+ return this.req("DELETE", `/users/${userId}`);
196
+ }
197
+ // -- roles / permissions ------------------------------------------------------
198
+ /** The full permission catalog custom roles may grant from. */
199
+ permissions() {
200
+ return this.req("GET", "/roles/permissions");
201
+ }
202
+ roles() {
203
+ return this.req("GET", "/roles");
204
+ }
205
+ upsertRole(name, permissions) {
206
+ return this.req("PUT", "/roles", { json: { name, permissions } });
207
+ }
208
+ deleteRole(name) {
209
+ return this.req("DELETE", `/roles/${name}`);
210
+ }
211
+ // -- workspaces / projects ------------------------------------------------------
212
+ workspaces() {
213
+ return this.req("GET", "/workspaces");
214
+ }
215
+ createWorkspace(name, slug) {
216
+ return this.req("POST", "/workspaces", { json: { name, slug } });
217
+ }
218
+ deleteWorkspace(workspaceId) {
219
+ return this.req("DELETE", `/workspaces/${workspaceId}`);
220
+ }
221
+ projects() {
222
+ return this.req("GET", "/projects");
223
+ }
224
+ createProject(name, description) {
225
+ return this.req("POST", "/projects", { json: { name, description } });
226
+ }
227
+ deleteProject(projectId) {
228
+ return this.req("DELETE", `/projects/${projectId}`);
229
+ }
230
+ environments(projectId) {
231
+ return this.req("GET", `/projects/${projectId}/environments`);
232
+ }
233
+ createEnvironment(projectId, name, slug) {
234
+ return this.req("POST", `/projects/${projectId}/environments`, { json: { name, slug } });
235
+ }
236
+ // -- registry / tools ---------------------------------------------------------
237
+ providers() {
238
+ return this.req("GET", "/registry/providers");
239
+ }
240
+ provider(name) {
241
+ return this.req("GET", `/registry/providers/${name}`);
242
+ }
243
+ providerActions(provider) {
244
+ return this.req("GET", `/registry/providers/${provider}/actions`);
245
+ }
246
+ actions(provider) {
247
+ return this.req("GET", "/registry/actions", { params: { provider } });
248
+ }
249
+ action(name) {
250
+ return this.req("GET", `/registry/actions/${name}`);
251
+ }
252
+ /** Admin: register a runtime-defined provider in the dynamic registry. */
253
+ registerProvider(provider) {
254
+ return this.req("POST", "/registry/providers", { json: provider });
255
+ }
256
+ /** Admin: register a runtime-defined action (generic REST mapping). */
257
+ registerAction(action) {
258
+ return this.req("POST", "/registry/actions", { json: action });
259
+ }
260
+ unregisterAction(name) {
261
+ return this.req("DELETE", `/registry/actions/${name}`);
262
+ }
263
+ /** MCP-shaped tool list (name, description, input_schema). */
264
+ tools(provider) {
265
+ return this.req("GET", "/tools", { params: { provider } });
266
+ }
267
+ tool(name) {
268
+ return this.req("GET", `/tools/${name}`);
269
+ }
270
+ // -- connections -----------------------------------------------------------
271
+ /** Start OAuth for a provider. Returns { url } to open in a browser. */
272
+ connect(provider, redirectUrl) {
273
+ return this.req("POST", "/connect", { json: { provider, redirect_url: redirectUrl } });
274
+ }
275
+ connections() {
276
+ return this.req("GET", "/connect/connections");
277
+ }
278
+ /** Per-connection health with needs_reauth flags. */
279
+ connectionsHealth() {
280
+ return this.req("GET", "/connect/connections/health");
281
+ }
282
+ deleteConnection(connectionId) {
283
+ return this.req("DELETE", `/connect/connection/${connectionId}`);
284
+ }
285
+ shareConnection(connectionId, shared = true) {
286
+ return this.req("PATCH", `/connect/connection/${connectionId}/share`, {
287
+ params: { shared },
288
+ });
289
+ }
290
+ // -- execution ---------------------------------------------------------------
291
+ /**
292
+ * Run a tool synchronously. If the action needs human approval the response
293
+ * has status="pending" and an approval_url to hand to a human.
294
+ */
295
+ execute(action, connectionId, parameters = {}, opts = {}) {
296
+ return this.req("POST", "/tools/execute", {
297
+ headers: opts.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : undefined,
298
+ json: { action, connection_id: connectionId, parameters },
299
+ });
300
+ }
301
+ /** Queue a long-running tool job. Returns { job_id, status }. */
302
+ executeAsync(action, connectionId, parameters = {}) {
303
+ return this.req("POST", "/tools/execute_async", {
304
+ json: { action, connection_id: connectionId, parameters },
305
+ });
306
+ }
307
+ job(jobId) {
308
+ return this.req("GET", `/jobs/${jobId}`);
309
+ }
310
+ /** Poll a job until it finishes. Throws JobTimeout on deadline. */
311
+ async waitForJob(jobId, timeoutSeconds = 300, pollIntervalSeconds = 2) {
312
+ const deadline = Date.now() + timeoutSeconds * 1000;
313
+ while (Date.now() < deadline) {
314
+ const job = await this.job(jobId);
315
+ if (["success", "failure", "dead"].includes(job.status))
316
+ return job;
317
+ await sleep(pollIntervalSeconds * 1000);
318
+ }
319
+ throw new JobTimeout(jobId, timeoutSeconds);
320
+ }
321
+ // -- browser sandbox ------------------------------------------------------------
322
+ /** Start a Playwright browser session for no-API automation. */
323
+ createBrowserSession(targetUrl) {
324
+ return this.req("POST", "/browser/session", { params: { target_url: targetUrl } });
325
+ }
326
+ browserSession(sessionId) {
327
+ return this.req("GET", `/browser/session/${sessionId}`);
328
+ }
329
+ /** Run browser steps (navigate/click/fill/extract/...) synchronously. */
330
+ browserRun(sessionId, steps) {
331
+ return this.req("POST", `/browser/session/${sessionId}/run`, { json: { steps } });
332
+ }
333
+ /** Queue browser steps as a job. Poll with job()/waitForJob(). */
334
+ browserRunAsync(sessionId, steps) {
335
+ return this.req("POST", `/browser/session/${sessionId}/run_async`, { json: { steps } });
336
+ }
337
+ // -- approvals ---------------------------------------------------------------
338
+ approvals(status = "pending", limit = 50, offset = 0) {
339
+ return this.req("GET", "/tools/approvals", { params: { status, limit, offset } });
340
+ }
341
+ /** Decide a pending approval as the requesting user (JWT auth). */
342
+ approve(approvalId, approved = true) {
343
+ return this.req("POST", "/tools/approval", { json: { approval_id: approvalId, approved } });
344
+ }
345
+ // -- secrets ---------------------------------------------------------------
346
+ secrets() {
347
+ return this.req("GET", "/secrets");
348
+ }
349
+ setSecret(name, value) {
350
+ return this.req("PUT", "/secrets", { json: { name, value } });
351
+ }
352
+ deleteSecret(name) {
353
+ return this.req("DELETE", `/secrets/${name}`);
354
+ }
355
+ // -- automation ---------------------------------------------------------------
356
+ triggers() {
357
+ return this.req("GET", "/triggers");
358
+ }
359
+ createTrigger(trigger) {
360
+ return this.req("POST", "/triggers", { json: trigger });
361
+ }
362
+ deleteTrigger(triggerId) {
363
+ return this.req("DELETE", `/triggers/${triggerId}`);
364
+ }
365
+ webhookSubscriptions() {
366
+ return this.req("GET", "/webhook-subscriptions");
367
+ }
368
+ createWebhookSubscription(subscription) {
369
+ return this.req("POST", "/webhook-subscriptions", { json: subscription });
370
+ }
371
+ deleteWebhookSubscription(subscriptionId) {
372
+ return this.req("DELETE", `/webhook-subscriptions/${subscriptionId}`);
373
+ }
374
+ // -- observability ---------------------------------------------------------------
375
+ executions(opts = {}) {
376
+ return this.req("GET", "/executions", {
377
+ params: { action: opts.action, status: opts.status, limit: opts.limit ?? 50, offset: opts.offset ?? 0 },
378
+ });
379
+ }
380
+ audit(opts = {}) {
381
+ return this.req("GET", "/audit", {
382
+ params: { action: opts.action, limit: opts.limit ?? 50, offset: opts.offset ?? 0 },
383
+ });
384
+ }
385
+ /** Full audit log as CSV text. */
386
+ auditExportCsv() {
387
+ return this.req("GET", "/audit/export", { raw: true });
388
+ }
389
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "zanii-connect",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for Zanii Connect — OAuth connections and tool execution for AI agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "engines": { "node": ">=18" },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "tsc"
19
+ },
20
+ "author": "Zanii Agency",
21
+ "license": "UNLICENSED",
22
+ "devDependencies": {
23
+ "typescript": "^5.5.0"
24
+ }
25
+ }