@steerpane/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,162 @@
1
+ /** Response shapes. Field names are the API's own, and fields the API adds later pass straight through. */
2
+ export type Json = Record<string, unknown>;
3
+ export type SessionStatus = "starting" | "running" | "idle" | "human_control" | "awaiting_help" | "stopped" | "error";
4
+ export type RunStatus = "queued" | "running" | "awaiting_human" | "completed" | "failed" | "cancelled";
5
+ export type HandbackOutcome = "done" | "not_done";
6
+ export interface Page<T> {
7
+ data: T[];
8
+ next_cursor: string | null;
9
+ }
10
+ export interface Failure {
11
+ category: string;
12
+ retryable: boolean;
13
+ message: string;
14
+ }
15
+ export interface Session extends Json {
16
+ id: string;
17
+ status: SessionStatus;
18
+ controller: "agent" | "human";
19
+ profile_id: string | null;
20
+ current_url: string | null;
21
+ page_title: string | null;
22
+ agent_name: string | null;
23
+ queued_commands: number;
24
+ help_request: Json | null;
25
+ error_message: string | null;
26
+ config: Json;
27
+ cdp_url: string;
28
+ live_url: string;
29
+ watch_url: string;
30
+ always_on: boolean;
31
+ last_handback: {
32
+ outcome: HandbackOutcome | null;
33
+ note: string | null;
34
+ at: string;
35
+ } | null;
36
+ failure: Failure | null;
37
+ }
38
+ export interface Profile extends Json {
39
+ id: string;
40
+ name: string;
41
+ tags: string[];
42
+ logged_in_sites: Json[];
43
+ cookie_count: number;
44
+ }
45
+ export interface FetchResult extends Json {
46
+ url: string;
47
+ final_url: string;
48
+ status: number;
49
+ format: string;
50
+ content: string;
51
+ engine: string;
52
+ title: string | null;
53
+ links?: string[];
54
+ metadata?: Json;
55
+ }
56
+ export interface SearchResponse extends Json {
57
+ query: string;
58
+ results: {
59
+ title: string;
60
+ url: string;
61
+ snippet: string;
62
+ }[];
63
+ provider: string;
64
+ }
65
+ export interface Capture extends Json {
66
+ id: string;
67
+ kind: "screenshot" | "pdf";
68
+ file_id: string;
69
+ url: string;
70
+ name: string;
71
+ mime: string;
72
+ size: number;
73
+ base64?: string;
74
+ }
75
+ export interface CrawlJob extends Json {
76
+ id: string;
77
+ url: string;
78
+ status: "queued" | "running" | "completed" | "failed" | "cancelled";
79
+ progress: {
80
+ done: number;
81
+ total: number;
82
+ failed: number;
83
+ };
84
+ error: string | null;
85
+ pages?: Json[];
86
+ next_cursor?: string | number | null;
87
+ link_check?: {
88
+ checked: number;
89
+ ok: number;
90
+ unverified: number;
91
+ blocked: number;
92
+ truncated: boolean;
93
+ broken: {
94
+ url: string;
95
+ found_on: string;
96
+ status: number | null;
97
+ error: string | null;
98
+ }[];
99
+ } | null;
100
+ }
101
+ export interface AgentRun extends Json {
102
+ id: string;
103
+ task: string;
104
+ status: RunStatus;
105
+ session_id: string | null;
106
+ model: string;
107
+ max_steps: number;
108
+ step_count: number;
109
+ cost_usd: number;
110
+ result: unknown;
111
+ error: string | null;
112
+ failure: Failure | null;
113
+ }
114
+ export interface StoredFile extends Json {
115
+ id: string;
116
+ name: string;
117
+ mime: string;
118
+ size_bytes: number;
119
+ source: "upload" | "download" | "api" | "embed";
120
+ status: "pending" | "ready";
121
+ session_id: string | null;
122
+ expires_at: string | null;
123
+ }
124
+ export interface UploadUrl extends Json {
125
+ file_id: string;
126
+ upload_url: string;
127
+ method: string;
128
+ headers: Record<string, string>;
129
+ expires_at: string;
130
+ curl: string;
131
+ requires_complete: boolean;
132
+ }
133
+ export interface EmbedLink extends Json {
134
+ id: string;
135
+ url: string;
136
+ expires_at: string | null;
137
+ mode: "read_only" | "interactive" | "interactive_takeover";
138
+ }
139
+ export interface Webhook extends Json {
140
+ id: string;
141
+ url: string;
142
+ events: string[];
143
+ enabled: boolean;
144
+ secret?: string;
145
+ }
146
+ export interface Credential extends Json {
147
+ id: string;
148
+ name: string;
149
+ domain: string;
150
+ username: string;
151
+ has_totp: boolean;
152
+ }
153
+ export interface HumanResult extends Json {
154
+ status: "handed_back" | "timeout" | "stopped";
155
+ note: string | null;
156
+ outcome: HandbackOutcome | null;
157
+ }
158
+ export interface HelpRequest extends Json {
159
+ request_id: string;
160
+ status: string;
161
+ session_id: string;
162
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.0";
@@ -0,0 +1,17 @@
1
+ import { SteerpaneError } from "./errors.js";
2
+ export declare const SIGNATURE_HEADER = "Steerpane-Signature";
3
+ export declare const DELIVERY_HEADER = "Steerpane-Delivery";
4
+ export declare class WebhookSignatureError extends SteerpaneError {
5
+ name: string;
6
+ }
7
+ export interface VerifyOptions {
8
+ toleranceS?: number;
9
+ /** Unix seconds; for tests. */
10
+ now?: number;
11
+ }
12
+ /** The signature header's value, or a request's headers: a `Headers` object, or Node's `req.headers`. */
13
+ export type SignatureSource = string | null | undefined | Headers | Record<string, string | string[] | undefined>;
14
+ /** True when `source` holds a fresh, valid signature of `rawBody`. Pass the body as received, not re-encoded JSON. */
15
+ export declare function verifySignature(rawBody: string | Uint8Array, source: SignatureSource, secret: string, options?: VerifyOptions): boolean;
16
+ /** Check the signature and return the parsed event, or throw `WebhookSignatureError`. */
17
+ export declare function constructEvent<T = Record<string, unknown>>(rawBody: string | Uint8Array, source: SignatureSource, secret: string, options?: VerifyOptions): T;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Check a webhook's `Steerpane-Signature` header.
3
+ *
4
+ * The header is `t=<unix seconds>,v1=<hex>`, where the hex is HMAC-SHA256 of `<t>.<raw body>` with the endpoint's secret. For 24 hours after
5
+ * a secret rotation it carries one `v1` per secret, and any match is good. Deliveries can repeat: deduplicate on the event's `id`.
6
+ */
7
+ import { createHmac, timingSafeEqual } from "node:crypto";
8
+ import { SteerpaneError } from "./errors.js";
9
+ export const SIGNATURE_HEADER = "Steerpane-Signature";
10
+ export const DELIVERY_HEADER = "Steerpane-Delivery";
11
+ /** What the header was called before the rename. Read only when `SIGNATURE_HEADER` is absent. */
12
+ const LEGACY_SIGNATURE_HEADER = "Conduit-Signature";
13
+ const DEFAULT_TOLERANCE_S = 300;
14
+ export class WebhookSignatureError extends SteerpaneError {
15
+ name = "WebhookSignatureError";
16
+ }
17
+ function signatureValue(source) {
18
+ if (source === null || source === undefined || typeof source === "string")
19
+ return source;
20
+ const names = [SIGNATURE_HEADER, LEGACY_SIGNATURE_HEADER];
21
+ if (typeof Headers !== "undefined" && source instanceof Headers) {
22
+ for (const name of names) {
23
+ const value = source.get(name);
24
+ if (value)
25
+ return value;
26
+ }
27
+ return null;
28
+ }
29
+ const lowered = new Map(Object.entries(source).map(([k, v]) => [k.toLowerCase(), v]));
30
+ for (const name of names) {
31
+ const value = lowered.get(name.toLowerCase());
32
+ if (value)
33
+ return Array.isArray(value) ? value[0] : value;
34
+ }
35
+ return null;
36
+ }
37
+ /** True when `source` holds a fresh, valid signature of `rawBody`. Pass the body as received, not re-encoded JSON. */
38
+ export function verifySignature(rawBody, source, secret, options = {}) {
39
+ const header = signatureValue(source);
40
+ if (!header || !secret)
41
+ return false;
42
+ const pairs = header.split(",").map((p) => {
43
+ const i = p.indexOf("=");
44
+ return i < 0 ? ["", ""] : [p.slice(0, i).trim(), p.slice(i + 1).trim()];
45
+ });
46
+ const timestamp = pairs.find(([k]) => k === "t")?.[1];
47
+ const signatures = pairs.filter(([k]) => k === "v1").map(([, v]) => v);
48
+ if (!timestamp || !/^\d+$/.test(timestamp) || signatures.length === 0)
49
+ return false;
50
+ const now = options.now ?? Date.now() / 1000;
51
+ if (Math.abs(now - Number(timestamp)) > (options.toleranceS ?? DEFAULT_TOLERANCE_S))
52
+ return false;
53
+ const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest();
54
+ return signatures.some((signature) => {
55
+ const given = Buffer.from(signature, "hex");
56
+ return given.length === expected.length && timingSafeEqual(given, expected);
57
+ });
58
+ }
59
+ /** Check the signature and return the parsed event, or throw `WebhookSignatureError`. */
60
+ export function constructEvent(rawBody, source, secret, options = {}) {
61
+ if (!verifySignature(rawBody, source, secret, options))
62
+ throw new WebhookSignatureError("Webhook signature is missing, invalid or too old");
63
+ return JSON.parse(typeof rawBody === "string" ? rawBody : Buffer.from(rawBody).toString("utf8"));
64
+ }
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
- {
2
- "name": "@steerpane/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@steerpane/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Node client for Steerpane: remote browsers for AI agents, with human takeover.",
5
+ "license": "Apache-2.0",
6
+ "homepage": "https://steerpane.app",
7
+ "repository": { "type": "git", "url": "git+https://github.com/Steerpane/sdks.git", "directory": "node" },
8
+ "keywords": ["browser", "automation", "agents", "playwright", "cdp", "mcp"],
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": ["dist", "README.md", "LICENSE"],
17
+ "engines": { "node": ">=20" },
18
+ "publishConfig": { "access": "public" },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "node --test test/*.test.mjs",
22
+ "prepack": "npm run build"
23
+ },
24
+ "peerDependencies": { "playwright-core": ">=1.45" },
25
+ "peerDependenciesMeta": { "playwright-core": { "optional": true } },
26
+ "devDependencies": {
27
+ "@types/node": "^22.0.0",
28
+ "playwright-core": "^1.50.0",
29
+ "typescript": "^5.6.0"
30
+ }
31
+ }