@superclerk/sdk 0.0.0-stage → 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.
@@ -0,0 +1,2 @@
1
+ // Generated by script/generate.ts from the public OpenAPI document. Do not edit.
2
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const SDK_VERSION = "0.1.0";
2
+ /** The API behavior this SDK was generated for. Every request pins it with the Superclerk-Version header. */
3
+ export declare const API_VERSION = "2026-11-05";
4
+ export declare const DEFAULT_BASE_URL = "https://api.superclerk.com";
@@ -0,0 +1,5 @@
1
+ // Generated by script/generate.ts from the public OpenAPI document. Do not edit.
2
+ export const SDK_VERSION = "0.1.0";
3
+ /** The API behavior this SDK was generated for. Every request pins it with the Superclerk-Version header. */
4
+ export const API_VERSION = "2026-11-05";
5
+ export const DEFAULT_BASE_URL = "https://api.superclerk.com";
@@ -0,0 +1,4 @@
1
+ import type { ConnectLinkFailedEvent, ConnectLinkSucceededEvent, ConnectionRemovedEvent, ConnectionUpdatedEvent, ContactCreatedEvent, ContactMergedEvent, ConversationHandedOffEvent, ConversationMessageReceivedEvent, OpportunityStageChangedEvent, TaskCompletedEvent, ToolRunCompletedEvent, ToolRunFailedEvent } from "./types.js";
2
+ /** Every event a webhook endpoint receives, told apart by its `type`. */
3
+ export type WebhookEvent = ConnectLinkSucceededEvent | ConnectLinkFailedEvent | ConnectionUpdatedEvent | ConnectionRemovedEvent | ToolRunCompletedEvent | ToolRunFailedEvent | ConversationMessageReceivedEvent | ConversationHandedOffEvent | ContactCreatedEvent | ContactMergedEvent | OpportunityStageChangedEvent | TaskCompletedEvent;
4
+ export type WebhookEventType = WebhookEvent["type"];
@@ -0,0 +1,2 @@
1
+ // Generated by script/generate.ts from the public OpenAPI document. Do not edit.
2
+ export {};
package/index.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export { Superclerk } from "./generated/client.js";
2
+ export type { ClientOptions, HttpMethod, Operation, RequestOptions } from "./core/client.js";
3
+ export { APIPromise } from "./core/api-promise.js";
4
+ export { Page, PagePromise } from "./core/pagination.js";
5
+ export { APIConnectionError, APIError, SuperclerkError, ToolRunError, WebhookVerificationError, type FieldError, } from "./core/errors.js";
6
+ export type { ToolRunParams } from "./lib/tools.js";
7
+ export type { SettledToolRun } from "./lib/tools.js";
8
+ export type { ProxyInit } from "./lib/connections.js";
9
+ export type { MediaUploadParams } from "./lib/social.js";
10
+ export type { WebhookHeaders, WebhookSignOptions, WebhookVerifyOptions } from "./lib/webhooks.js";
11
+ export type * from "./generated/types.js";
12
+ export type { ToolID, Tools } from "./generated/tools.js";
13
+ export type { WebhookEvent, WebhookEventType } from "./generated/webhooks.js";
14
+ export { API_VERSION, SDK_VERSION } from "./generated/version.js";
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Superclerk } from "./generated/client.js";
2
+ export { APIPromise } from "./core/api-promise.js";
3
+ export { Page, PagePromise } from "./core/pagination.js";
4
+ export { APIConnectionError, APIError, SuperclerkError, ToolRunError, WebhookVerificationError, } from "./core/errors.js";
5
+ export { API_VERSION, SDK_VERSION } from "./generated/version.js";
@@ -0,0 +1,12 @@
1
+ import type { RequestOptions } from "../core/client.js";
2
+ import { ConnectBase, ConnectLinks as ConnectLinksBase } from "../generated/resources.js";
3
+ export declare class ConnectLinks extends ConnectLinksBase {
4
+ /**
5
+ * Reads a link until the person is done with it, and answers it once it has succeeded, failed or expired. Its
6
+ * `connection` is the account they connected. Aborting the signal stops the waiting; the link stays open.
7
+ */
8
+ wait(connectLinkID: string, options?: RequestOptions): Promise<import("../index.js").ConnectLink>;
9
+ }
10
+ export declare class Connect extends ConnectBase {
11
+ readonly links: ConnectLinks;
12
+ }
package/lib/connect.js ADDED
@@ -0,0 +1,14 @@
1
+ import { settle } from "../core/timing.js";
2
+ import { ConnectBase, ConnectLinks as ConnectLinksBase } from "../generated/resources.js";
3
+ export class ConnectLinks extends ConnectLinksBase {
4
+ /**
5
+ * Reads a link until the person is done with it, and answers it once it has succeeded, failed or expired. Its
6
+ * `connection` is the account they connected. Aborting the signal stops the waiting; the link stays open.
7
+ */
8
+ wait(connectLinkID, options = {}) {
9
+ return settle(this._client, (held) => this.get(connectLinkID, held), options);
10
+ }
11
+ }
12
+ export class Connect extends ConnectBase {
13
+ links = new ConnectLinks(this._client);
14
+ }
@@ -0,0 +1,17 @@
1
+ import { type RequestOptions } from "../core/client.js";
2
+ import { ConnectionsBase } from "../generated/resources.js";
3
+ export interface ProxyInit extends RequestOptions {
4
+ /** GET, POST or DELETE, as the app's `proxy.methods` allows. Defaults to GET. */
5
+ method?: "GET" | "POST" | "DELETE";
6
+ /** A JSON value, or a string, Blob, FormData or URLSearchParams sent as it is. */
7
+ body?: unknown;
8
+ }
9
+ export declare class Connections extends ConnectionsBase {
10
+ /**
11
+ * Calls the provider's own API through a connection, with its credentials added on the server, and answers the
12
+ * provider's response whatever its status. `path` is the provider's path and query, sent as written, such as
13
+ * `/me/adaccounts?fields=id,name`. A call still running when the API stops holding it is waited for. A request
14
+ * the gateway refuses, or a run that fails, such as a write whose outcome is unknown, throws APIError.
15
+ */
16
+ fetch(connectionID: string, path: string, init?: ProxyInit): Promise<Response>;
17
+ }
@@ -0,0 +1,28 @@
1
+ import { pathParam } from "../core/client.js";
2
+ import { holding, isPending, settle } from "../core/timing.js";
3
+ import { ConnectionsBase } from "../generated/resources.js";
4
+ export class Connections extends ConnectionsBase {
5
+ /**
6
+ * Calls the provider's own API through a connection, with its credentials added on the server, and answers the
7
+ * provider's response whatever its status. `path` is the provider's path and query, sent as written, such as
8
+ * `/me/adaccounts?fields=id,name`. A call still running when the API stops holding it is waited for. A request
9
+ * the gateway refuses, or a run that fails, such as a write whose outcome is unknown, throws APIError.
10
+ */
11
+ async fetch(connectionID, path, init = {}) {
12
+ const { method = "GET", body, ...options } = init;
13
+ // Only the path and query are the provider's; the API resolves them against the connection's provider.
14
+ const { pathname, search } = new URL(path, "https://provider.invalid");
15
+ const response = await this._client.request({
16
+ method,
17
+ path: `/v1/connections/${pathParam(connectionID)}/proxy${pathname}${search}`,
18
+ body,
19
+ idempotent: true,
20
+ response: "raw",
21
+ }, holding(this._client, options));
22
+ if (!isPending(response))
23
+ return response;
24
+ await response.body?.cancel();
25
+ const runID = response.headers.get("x-superclerk-run-id");
26
+ return settle(this._client, (held) => this.proxy.runs.get(runID, held), options);
27
+ }
28
+ }
@@ -0,0 +1,32 @@
1
+ import type { RequestOptions } from "../core/client.js";
2
+ import { SocialBase, SocialMedia as SocialMediaBase } from "../generated/resources.js";
3
+ import type { SocialMediaCreateParams } from "../generated/types.js";
4
+ export interface MediaUploadParams {
5
+ filename: string;
6
+ contentType: SocialMediaCreateParams["contentType"];
7
+ /** `public` gives the asset a URL anyone can open; `private` shares it only through short-lived links. */
8
+ storageAccess: SocialMediaCreateParams["storageAccess"];
9
+ }
10
+ export declare class SocialMedia extends SocialMediaBase {
11
+ /**
12
+ * Uploads an image or video and answers its asset once it's ready to attach to a post: it creates the asset, sends
13
+ * the bytes to the upload URL the API signs, and confirms them. To finish an upload that was cut off, call it again
14
+ * with the same `idempotencyKey`: an asset already ready comes back at once, and bytes already stored are confirmed.
15
+ */
16
+ upload(file: Blob | ArrayBuffer | Uint8Array, params: MediaUploadParams, options?: RequestOptions): Promise<{
17
+ id: string;
18
+ filename: string;
19
+ contentType: "image/jpeg" | "image/png" | "image/webp" | "video/mp4";
20
+ sizeBytes: number;
21
+ sha256: string;
22
+ storageAccess: "private" | "public";
23
+ status: "pending" | "ready";
24
+ url: string | null;
25
+ createdAt: number;
26
+ updatedAt: number;
27
+ confirmedAt: number | null;
28
+ }>;
29
+ }
30
+ export declare class Social extends SocialBase {
31
+ readonly media: SocialMedia;
32
+ }
package/lib/social.js ADDED
@@ -0,0 +1,27 @@
1
+ import { SuperclerkError } from "../core/errors.js";
2
+ import { SocialBase, SocialMedia as SocialMediaBase } from "../generated/resources.js";
3
+ export class SocialMedia extends SocialMediaBase {
4
+ /**
5
+ * Uploads an image or video and answers its asset once it's ready to attach to a post: it creates the asset, sends
6
+ * the bytes to the upload URL the API signs, and confirms them. To finish an upload that was cut off, call it again
7
+ * with the same `idempotencyKey`: an asset already ready comes back at once, and bytes already stored are confirmed.
8
+ */
9
+ async upload(file, params, options = {}) {
10
+ const bytes = new Uint8Array(file instanceof Blob ? await file.arrayBuffer() : file);
11
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
12
+ const sha256 = Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
13
+ const { asset, upload } = await this.create({ ...params, sha256, sizeBytes: bytes.length }, options);
14
+ if (asset.status === "ready")
15
+ return asset;
16
+ const stored = await this._client.attempt(upload.url, { method: upload.method, headers: upload.headers, body: bytes }, options);
17
+ await stored.body?.cancel();
18
+ // The URL writes once: 412 means an earlier try already stored the bytes, which confirm checks either way.
19
+ if (!stored.ok && stored.status !== 412) {
20
+ throw new SuperclerkError("upload_failed", `The upload URL answered ${stored.status}`);
21
+ }
22
+ return this.confirm(asset.id, options);
23
+ }
24
+ }
25
+ export class Social extends SocialBase {
26
+ media = new SocialMedia(this._client);
27
+ }
package/lib/tools.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { RequestOptions } from "../core/client.js";
2
+ import { ToolsBase, ToolsRuns as ToolsRunsBase } from "../generated/resources.js";
3
+ import type { ToolID, Tools as ToolTypes } from "../generated/tools.js";
4
+ import type { ToolsRunsGetResponse } from "../generated/types.js";
5
+ /** The tool to run, its input, and the connection or built-in app that runs it. */
6
+ export type ToolRunParams<Tool extends ToolID> = {
7
+ tool: Tool;
8
+ input: ToolTypes[Tool]["input"];
9
+ } & ({
10
+ connectionID: string;
11
+ appID?: never;
12
+ } | {
13
+ appID: string;
14
+ connectionID?: never;
15
+ });
16
+ /** A run that has ended, completed or failed: GET /v1/tools/runs/{runID} answers 202 while a run goes on, 200 after. */
17
+ export type SettledToolRun = Exclude<ToolsRunsGetResponse, {
18
+ status: "running";
19
+ }>;
20
+ export declare class ToolsRuns extends ToolsRunsBase {
21
+ /** Reads a run until it ends and answers it. Aborting the signal stops the waiting, not the run. */
22
+ wait(runID: string, options?: RequestOptions): Promise<SettledToolRun>;
23
+ }
24
+ export declare class Tools extends ToolsBase {
25
+ readonly runs: ToolsRuns;
26
+ /**
27
+ * Runs a tool and answers its output, typed by the tool, waiting while the run goes on. A run that ends failed or
28
+ * cancelled throws ToolRunError, whose `code` is the run's `error.kind`; a request the API refuses before any run
29
+ * starts throws APIError. Retries of this call, the SDK's own included, reuse one Idempotency-Key, so the tool runs
30
+ * once.
31
+ */
32
+ run<Tool extends ToolID>({ tool, ...params }: ToolRunParams<Tool>, options?: RequestOptions): Promise<ToolTypes[Tool]["output"]>;
33
+ }
package/lib/tools.js ADDED
@@ -0,0 +1,31 @@
1
+ import { APIError, ToolRunError } from "../core/errors.js";
2
+ import { holding, settle } from "../core/timing.js";
3
+ import { ToolsBase, ToolsRuns as ToolsRunsBase } from "../generated/resources.js";
4
+ export class ToolsRuns extends ToolsRunsBase {
5
+ /** Reads a run until it ends and answers it. Aborting the signal stops the waiting, not the run. */
6
+ wait(runID, options = {}) {
7
+ return settle(this._client, (held) => this.get(runID, held), options);
8
+ }
9
+ }
10
+ export class Tools extends ToolsBase {
11
+ runs = new ToolsRuns(this._client);
12
+ /**
13
+ * Runs a tool and answers its output, typed by the tool, waiting while the run goes on. A run that ends failed or
14
+ * cancelled throws ToolRunError, whose `code` is the run's `error.kind`; a request the API refuses before any run
15
+ * starts throws APIError. Retries of this call, the SDK's own included, reuse one Idempotency-Key, so the tool runs
16
+ * once.
17
+ */
18
+ async run({ tool, ...params }, options = {}) {
19
+ const created = await this.runs
20
+ .create({ toolID: tool, ...params }, holding(this._client, options))
21
+ .catch((error) => {
22
+ // A run that ended at submission answers with the run itself as its body, not the API's `{ error }`.
23
+ const run = error instanceof APIError ? error.body : undefined;
24
+ throw run?.status === "failed" || run?.status === "cancelled" ? new ToolRunError(run) : error;
25
+ });
26
+ const finished = created.status === "running" ? await this.runs.wait(created.id, options) : created;
27
+ if (finished.status !== "completed")
28
+ throw new ToolRunError(finished);
29
+ return finished.output;
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ import { WebhooksBase } from "../generated/resources.js";
2
+ import type { WebhookEvent } from "../generated/webhooks.js";
3
+ export type WebhookHeaders = Headers | Record<string, string | string[] | undefined>;
4
+ export interface WebhookVerifyOptions {
5
+ /** The endpoint's secret, `whsec_…`. Defaults to the client's webhookSecret. */
6
+ secret?: string;
7
+ /** Seconds an event's timestamp may differ from now. Defaults to 300, Standard Webhooks' replay window. */
8
+ tolerance?: number;
9
+ }
10
+ export interface WebhookSignOptions {
11
+ /** The endpoint's secret, `whsec_…`. Defaults to the client's webhookSecret. */
12
+ secret?: string;
13
+ /** The delivery's time, in seconds since the epoch. Defaults to now. */
14
+ timestamp?: number;
15
+ }
16
+ /** Your webhooks, and the events Superclerk delivers to them, verified as Standard Webhooks specify. */
17
+ export declare class Webhooks extends WebhooksBase {
18
+ #private;
19
+ /**
20
+ * Verifies an event and answers it, typed by its `type`. Pass the body exactly as it arrived, before any JSON
21
+ * parsing, with the request's headers.
22
+ */
23
+ unwrap(body: string, headers: WebhookHeaders, options?: WebhookVerifyOptions): Promise<WebhookEvent>;
24
+ /** Throws WebhookVerificationError unless the body carries a valid signature from the endpoint's secret. */
25
+ verify(body: string, headers: WebhookHeaders, { secret, tolerance }?: WebhookVerifyOptions): Promise<void>;
26
+ /**
27
+ * Signs an event as Superclerk signs its deliveries, so a test can post your endpoint a delivery that verifies:
28
+ * send `body` with `headers`. The event's `id` is the delivery's `webhook-id`, as in a real delivery.
29
+ */
30
+ sign(event: WebhookEvent, { secret, timestamp }?: WebhookSignOptions): Promise<{
31
+ body: string;
32
+ headers: {
33
+ "webhook-id": string;
34
+ "webhook-timestamp": string;
35
+ "webhook-signature": string;
36
+ };
37
+ }>;
38
+ }
@@ -0,0 +1,65 @@
1
+ import { SuperclerkError, WebhookVerificationError } from "../core/errors.js";
2
+ import { WebhooksBase } from "../generated/resources.js";
3
+ /** Your webhooks, and the events Superclerk delivers to them, verified as Standard Webhooks specify. */
4
+ export class Webhooks extends WebhooksBase {
5
+ /**
6
+ * Verifies an event and answers it, typed by its `type`. Pass the body exactly as it arrived, before any JSON
7
+ * parsing, with the request's headers.
8
+ */
9
+ async unwrap(body, headers, options = {}) {
10
+ await this.verify(body, headers, options);
11
+ return JSON.parse(body);
12
+ }
13
+ /** Throws WebhookVerificationError unless the body carries a valid signature from the endpoint's secret. */
14
+ async verify(body, headers, { secret, tolerance = 300 } = {}) {
15
+ const key = await this.#key(secret);
16
+ const read = new Headers(headers);
17
+ const [id, timestamp, signatures] = ["webhook-id", "webhook-timestamp", "webhook-signature"].map((name) => read.get(name));
18
+ // A missing or unreadable timestamp is NaN, which is never inside the window.
19
+ if (!(Math.abs(Date.now() / 1_000 - Number(timestamp)) <= tolerance)) {
20
+ throw new WebhookVerificationError("The event's timestamp is missing or too far from now");
21
+ }
22
+ const signed = new TextEncoder().encode(`${id}.${timestamp}.${body}`);
23
+ // A rotating endpoint sends a signature per secret, space-separated; any one that verifies is enough. A missing
24
+ // header, id or signature can't verify. crypto.subtle.verify compares in constant time.
25
+ for (const signature of (signatures ?? "").split(" ")) {
26
+ if (signature.startsWith("v1,") &&
27
+ (await crypto.subtle.verify("HMAC", key, base64(signature.slice(3)), signed))) {
28
+ return;
29
+ }
30
+ }
31
+ throw new WebhookVerificationError("No signature matches the endpoint's secret");
32
+ }
33
+ /**
34
+ * Signs an event as Superclerk signs its deliveries, so a test can post your endpoint a delivery that verifies:
35
+ * send `body` with `headers`. The event's `id` is the delivery's `webhook-id`, as in a real delivery.
36
+ */
37
+ async sign(event, { secret, timestamp } = {}) {
38
+ const key = await this.#key(secret);
39
+ const body = JSON.stringify(event);
40
+ const at = String(timestamp ?? Math.floor(Date.now() / 1_000));
41
+ const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${event.id}.${at}.${body}`));
42
+ const encoded = btoa(String.fromCharCode(...new Uint8Array(signature)));
43
+ return { body, headers: { "webhook-id": event.id, "webhook-timestamp": at, "webhook-signature": `v1,${encoded}` } };
44
+ }
45
+ /** The endpoint's secret as an HMAC key. A missing or malformed secret is an error of its own, not a failed check. */
46
+ async #key(secret = this._client.webhookSecret) {
47
+ if (!secret) {
48
+ throw new SuperclerkError("missing_webhook_secret", "Pass the secret, or set SUPERCLERK_WEBHOOK_SECRET");
49
+ }
50
+ const bytes = base64(secret.replace(/^whsec_/, ""));
51
+ if (bytes.length === 0) {
52
+ throw new SuperclerkError("invalid_webhook_secret", "The secret isn't the whsec_… value its endpoint was given");
53
+ }
54
+ return crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
55
+ }
56
+ }
57
+ // Bytes a sender wrote as base64; anything else decodes to no bytes, which verify nothing.
58
+ function base64(value) {
59
+ try {
60
+ return Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
61
+ }
62
+ catch {
63
+ return new Uint8Array();
64
+ }
65
+ }
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@superclerk/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "The official TypeScript SDK for the Superclerk API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://docs.superclerk.com/sdk",
8
+ "keywords": [
9
+ "superclerk",
10
+ "api",
11
+ "sdk",
12
+ "integrations",
13
+ "oauth",
14
+ "tools",
15
+ "agents",
16
+ "webhooks"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20.19.0"
20
+ },
21
+ "sideEffects": false,
22
+ "main": "./index.js",
23
+ "types": "./index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./index.d.ts",
27
+ "default": "./index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ }
31
+ }