@scalebrowser/sdk 0.4.0 → 0.5.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/dist/index.d.cts CHANGED
@@ -1,76 +1,5 @@
1
1
  import WebSocket from 'ws';
2
2
 
3
- /**
4
- * Error model: stable numeric API error codes (SPEC §5,
5
- * `crates/core/src/error.rs`) and the typed exceptions the SDK throws.
6
- */
7
- /** Stable numeric API error codes (4001–4012, append-only: a retired code stays
8
- * reserved rather than being reused). */
9
- declare const ErrorCode: {
10
- readonly NotFound: 4001;
11
- readonly ProxyGeo: 4002;
12
- readonly Capacity: 4003;
13
- readonly EngineMissing: 4004;
14
- readonly Preflight: 4005;
15
- readonly AlreadyRunning: 4006;
16
- /** Retired: the removed Methode-D (VM) tier produced this. Reserved (never reused)
17
- * to match the server's append-only code contract; never returned now. */
18
- readonly VmProvisionFailed: 4007;
19
- /** The subscription's abo-wide concurrent-browser allowance is used up (DEC-012).
20
- * Distinct from Capacity (4003 = this machine is full, wait): the caller's move
21
- * is to stop a running browser anywhere or upgrade the plan. */
22
- readonly ConcurrencyLimit: 4008;
23
- readonly Unauthorized: 4010;
24
- /** No account is connected to this machine, so nothing may run (DEC-014).
25
- * Distinct from Unauthorized (4010 = the daemon's own bearer token is wrong, so
26
- * re-read the token): 4011 means the machine needs to be signed in, and no token
27
- * a client holds can substitute for that. Connect it in the desktop app. */
28
- readonly NotSignedIn: 4011;
29
- /** This ONE profile is open on another machine of the same account (DEC-015).
30
- * Distinct from ConcurrencyLimit (4008 = the plan is full, buy more or free any
31
- * browser): the caller's move here costs nothing. Close it on that machine, or
32
- * take it over from the app running there. */
33
- readonly ProfileInUse: 4012;
34
- };
35
- type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
36
- /** Error body shape: `{ code, message }` (docs/features/api.md). */
37
- interface ApiErrorBody {
38
- code?: number;
39
- message?: string;
40
- }
41
- /** Base class for every error this SDK throws, so callers can `catch` one type. */
42
- declare class ScalebrowserError extends Error {
43
- constructor(message: string);
44
- }
45
- /**
46
- * A typed transport/contract error. Carries the HTTP `status` and the daemon's
47
- * numeric `code` so callers (and the auth layer) can react, e.g. 401 / code
48
- * 4010 → re-authenticate.
49
- */
50
- declare class ApiError extends ScalebrowserError {
51
- readonly status: number;
52
- readonly code?: number;
53
- readonly body?: ApiErrorBody;
54
- constructor(status: number, body?: ApiErrorBody, fallbackMessage?: string);
55
- /** True for auth failures that must trigger re-authentication. */
56
- get isAuthError(): boolean;
57
- /** Short label for the error code, if any. */
58
- get codeLabel(): string | undefined;
59
- }
60
- /** A network-level failure (daemon unreachable, socket error, fetch rejected). */
61
- declare class NetworkError extends ScalebrowserError {
62
- readonly cause?: unknown;
63
- constructor(message: string, cause?: unknown);
64
- }
65
- /** A CDP command that returned an `error` frame, or a closed CDP connection. */
66
- declare class CdpError extends ScalebrowserError {
67
- readonly code: number;
68
- readonly data?: unknown;
69
- constructor(code: number, message: string, data?: unknown);
70
- }
71
- /** Narrow an unknown thrown value to a user-facing message. */
72
- declare function errorMessage(err: unknown): string;
73
-
74
3
  interface Screen {
75
4
  width: number;
76
5
  height: number;
@@ -177,6 +106,281 @@ interface Persona {
177
106
  misc: PersonaMisc;
178
107
  }
179
108
 
109
+ type RecordingKind = 'showcase' | 'proof';
110
+ type RecordingState = 'recording' | 'finishing' | 'finished' | 'failed';
111
+ type VideoState = 'queued' | 'rendering' | 'done' | 'failed' | 'cancelled';
112
+ type ExportPresetName = 'web' | 'social' | '4k' | 'editing' | 'gif';
113
+ type AudioStatus = 'ok' | 'silent' | 'off' | 'failed';
114
+ type RenderStage = 'planning' | 'narration' | 'compositing' | 'encoding' | 'finishing';
115
+ /** Which files a finished video has. Booleans on purpose, never paths. */
116
+ interface VideoAssets {
117
+ video: boolean;
118
+ poster: boolean;
119
+ preview_gif: boolean;
120
+ srt: boolean;
121
+ vtt: boolean;
122
+ proof: boolean;
123
+ timestamp: boolean;
124
+ extras: ExportPresetName[];
125
+ }
126
+ interface RenderProgress {
127
+ stage: RenderStage;
128
+ frames_done: number;
129
+ frames_total: number | null;
130
+ eta_ms: number | null;
131
+ /** Which attempt this is; three is the last one. */
132
+ attempts: number;
133
+ heartbeat_at: number;
134
+ }
135
+ /** A chapter in OUTPUT time: it survives cuts and time-lapse. */
136
+ interface Chapter {
137
+ out_ms: number;
138
+ src_ms: number;
139
+ label: string;
140
+ mark_id?: string | null;
141
+ step_seq?: number | null;
142
+ }
143
+ interface VideoFlags {
144
+ page_audio: AudioStatus;
145
+ tightened: boolean;
146
+ masked_regions: number;
147
+ gaps: number;
148
+ ai_label: boolean;
149
+ gpu: string;
150
+ h264_profile: string;
151
+ }
152
+ type TimestampState = {
153
+ state: 'pending';
154
+ } | {
155
+ state: 'stamped';
156
+ at: number;
157
+ tsa: string;
158
+ } | {
159
+ state: 'failed';
160
+ reason: string;
161
+ };
162
+ interface TranscriptWord {
163
+ text: string;
164
+ from_ms: number;
165
+ to_ms: number;
166
+ }
167
+ /** One video row: queued and rendering renders included. */
168
+ interface Video {
169
+ id: string;
170
+ recording_id: string;
171
+ run_id: string | null;
172
+ profile_id: string;
173
+ job_id: string | null;
174
+ state: VideoState;
175
+ progress: RenderProgress | null;
176
+ title: string;
177
+ kind: RecordingKind;
178
+ preset_name: string;
179
+ /** The resolved recipe this video renders (or rendered) under. */
180
+ preset: unknown;
181
+ export: ExportPresetName;
182
+ assets: VideoAssets;
183
+ duration_ms: number | null;
184
+ width: number | null;
185
+ height: number | null;
186
+ fps: number | null;
187
+ bytes: number | null;
188
+ sha256: string | null;
189
+ timestamp: TimestampState | null;
190
+ chapters: Chapter[];
191
+ transcript: TranscriptWord[] | null;
192
+ flags: VideoFlags | null;
193
+ share: unknown | null;
194
+ error: string | null;
195
+ created_at: number;
196
+ updated_at: number;
197
+ }
198
+ /** One recording row. The row outlives its raw bytes (`raw_purged_at`). */
199
+ interface Recording {
200
+ id: string;
201
+ profile_id: string;
202
+ run_id: string | null;
203
+ fence: number;
204
+ kind: RecordingKind;
205
+ state: RecordingState;
206
+ title: string | null;
207
+ preset_id: string | null;
208
+ preset: unknown;
209
+ manifest_version: number;
210
+ css_width: number;
211
+ css_height: number;
212
+ dpr: number;
213
+ capture_width: number;
214
+ started_at: number;
215
+ ended_at: number | null;
216
+ duration_ms: number | null;
217
+ frames: number;
218
+ frames_dropped: number;
219
+ gaps: number;
220
+ anchors: number;
221
+ bytes: number;
222
+ audio: AudioStatus;
223
+ audio_reason: string | null;
224
+ end_reason: string | null;
225
+ raw_purged_at: number | null;
226
+ purge_after: number | null;
227
+ error: string | null;
228
+ }
229
+ interface NarrationLine {
230
+ mark_id?: string;
231
+ step_seq?: number;
232
+ text: string;
233
+ }
234
+ /**
235
+ * Where a directing intent looks. `region` coordinates are page fractions
236
+ * in 0..1; the other kinds need no coordinates.
237
+ */
238
+ interface ShotFocus {
239
+ kind: 'overview' | 'target' | 'container' | 'region';
240
+ x?: number;
241
+ y?: number;
242
+ w?: number;
243
+ h?: number;
244
+ }
245
+ /**
246
+ * One directing intent: you decide WHAT is on screen at a moment, the
247
+ * camera executes it smoothly and keeps its guarantees. Anchor it to one
248
+ * action (`at_anchor`, the ids listed in `plan.actions`) or to a source
249
+ * moment (`at_ms`). Malformed intents are refused as `shot_invalid`;
250
+ * anchors the recording does not know are skipped with a note.
251
+ */
252
+ interface ShotIntent {
253
+ at_anchor?: string;
254
+ at_ms?: number;
255
+ focus: ShotFocus;
256
+ zoom?: 'off' | 'gentle' | 'medium' | 'strong';
257
+ /** Minimum dwell in ms before the next camera move (max 20000). */
258
+ hold_ms?: number;
259
+ note?: string;
260
+ }
261
+ /** The shared render-order schema (SPEC §5.2: one producer, every surface). */
262
+ interface RenderRequestBody {
263
+ preset?: string;
264
+ export?: ExportPresetName;
265
+ title?: string;
266
+ subtitle?: string;
267
+ /** `auto`, or a BCP-47 tag such as `de` or `en-US`. */
268
+ language?: string;
269
+ narration?: NarrationLine[];
270
+ /** Directing intents; empty means the built-in direction decides. */
271
+ shots?: ShotIntent[];
272
+ }
273
+ interface ListVideosParams {
274
+ q?: string;
275
+ profile_id?: string;
276
+ run_id?: string;
277
+ kind?: RecordingKind;
278
+ preset?: string;
279
+ state?: VideoState;
280
+ page?: number;
281
+ }
282
+ interface VideosPage {
283
+ videos: Video[];
284
+ next: number | null;
285
+ }
286
+ /**
287
+ * Which file an access grant is minted for.
288
+ *
289
+ * The extras carry the same prefix the daemon's own key register uses
290
+ * (`extra:<preset>`), so a second export is reachable under the name it was
291
+ * stored with rather than a name a caller invents.
292
+ */
293
+ type VideoAssetName = 'video' | 'poster' | 'preview' | 'srt' | 'vtt' | 'proof' | 'timestamp' | `extra:${ExportPresetName}`;
294
+ interface VideoAccessGrant {
295
+ /** Relative URL carrying the grant; fetch it against the daemon base. */
296
+ url: string;
297
+ expires_at: number;
298
+ }
299
+ interface VideosStorage {
300
+ recordings_bytes: number;
301
+ videos_bytes: number;
302
+ link_bytes: number;
303
+ link_limit: number;
304
+ uploads_today: number;
305
+ uploads_limit: number;
306
+ capacity: {
307
+ recordings_active: number;
308
+ recordings_max: number;
309
+ };
310
+ }
311
+ /** What the machine can encode with, and the one switch an operator has. */
312
+ interface CodecsStatus {
313
+ openh264: {
314
+ /** The operator's decision. */
315
+ enabled: boolean;
316
+ /** Measured now: the library is on disk and its hash is the pinned one. */
317
+ installed: boolean;
318
+ version: string | null;
319
+ /** `downloaded` | `manual` | `absent`. */
320
+ source: string;
321
+ notice: string;
322
+ };
323
+ /** Hardware encoders that passed a real test encode, in preference order. */
324
+ hardware: string[];
325
+ }
326
+ /** What kind of file a media asset is. */
327
+ type MediaKind = 'image' | 'music' | 'subtitle_style';
328
+ /** One uploaded file a preset refers to by id. */
329
+ interface MediaAsset {
330
+ id: string;
331
+ kind: MediaKind;
332
+ name: string;
333
+ mime: string;
334
+ bytes: number;
335
+ /** The content's checksum. It is also the file name on disk, so the same
336
+ * bytes are stored once however often they are uploaded. */
337
+ sha256: string;
338
+ created_at: number;
339
+ }
340
+ /** A speech provider that has a key stored.
341
+ *
342
+ * There is deliberately no `api_key` here: the daemon answers with a hint and
343
+ * never with the value. A client that could read it back would be the one
344
+ * place it lands in a log again. */
345
+ interface TtsProviderView {
346
+ provider: 'elevenlabs' | 'fish_audio' | 'openai';
347
+ /** The last few characters, never more. */
348
+ key_hint: string;
349
+ label: string | null;
350
+ verified_at: number | null;
351
+ updated_at: number;
352
+ }
353
+ /** What one real call to the provider proved.
354
+ *
355
+ * A stored key and a working key are two things, so this speaks a short word
356
+ * and reports what came back. Only an answer that carried both audio and word
357
+ * timings counts: a 200 with an empty body proves nothing.
358
+ */
359
+ interface TtsVerifyResult {
360
+ provider: 'elevenlabs' | 'fish_audio' | 'openai';
361
+ /** Unix seconds. The same value the provider row now carries. */
362
+ verified_at: number;
363
+ wav_bytes: number;
364
+ words: number;
365
+ }
366
+ /** Where a share link stands ON THIS MACHINE: an order, not a copy.
367
+ *
368
+ * `outbox` is what is still owed: `pending` while the files are going up,
369
+ * `live` once the control plane confirmed, `revoke_pending` after a revoke that
370
+ * has not been acknowledged yet. A revoke-pending link STILL BLOCKS: between
371
+ * the order and the confirmation the page may still answer. */
372
+ interface ShareLinkLocal {
373
+ link_id: string;
374
+ url: string;
375
+ version: number;
376
+ outbox: 'pending' | 'live' | 'revoke_pending';
377
+ /** Whether the link asks for an access code. Never the code itself. */
378
+ passcode: boolean;
379
+ download: boolean;
380
+ updated_at: number;
381
+ error: string | null;
382
+ }
383
+
180
384
  /**
181
385
  * Wire types: a 1:1 mirror of the daemon's serialized domain model
182
386
  * (`crates/core/src/model.rs`, `persona.rs`, `events.rs`) and the endpoint
@@ -298,6 +502,211 @@ interface Preset {
298
502
  config: PresetConfig;
299
503
  created_at: number;
300
504
  }
505
+ /** Was hinter der Seite liegt: ein Verlauf, eine Farbe, ein Bild, oder nichts. */
506
+ type VideoBackground = {
507
+ kind: 'gradient';
508
+ colors: string[];
509
+ } | {
510
+ kind: 'solid';
511
+ color: string;
512
+ } | {
513
+ kind: 'image';
514
+ media_id: string;
515
+ } | {
516
+ kind: 'none';
517
+ };
518
+ /** Intro und Outro. `hold_result` haelt das letzte Bild statt einer Karte. */
519
+ type VideoCard = {
520
+ style: 'title_card';
521
+ seconds: number;
522
+ } | {
523
+ style: 'hold_result';
524
+ seconds: number;
525
+ };
526
+ /** Wohin das fertige Video geht. */
527
+ type VideoDestination = {
528
+ kind: 'local';
529
+ } | {
530
+ kind: 'link';
531
+ expires_days: number | null;
532
+ download: boolean;
533
+ passcode: boolean;
534
+ };
535
+ /** Eine Zusatz-Ausgabe mit eigenen Massen. Objekte, nie blosse Strings. */
536
+ interface ExtraOutput {
537
+ preset: ExportPresetName;
538
+ width?: number | null;
539
+ height?: number | null;
540
+ fps?: number | null;
541
+ colors?: number | null;
542
+ max_bytes?: number | null;
543
+ }
544
+ /** Hintergrundmusik, per Medien-Id, mit dem Pegel. */
545
+ interface MusicBed {
546
+ media_id: string;
547
+ gain_db: number;
548
+ }
549
+ /**
550
+ * Das Rezept eines Videos: alles ueber die FORM, nichts ueber den Inhalt.
551
+ *
552
+ * Schritte, Laenge, Titel und Sprechtext kommen aus dem Auftrag, nie von hier
553
+ * (AC-CTL-001); das einzige freie Textfeld ist `ai_label.text`.
554
+ */
555
+ interface VideoPresetConfig {
556
+ output: {
557
+ aspect: '16:9' | '9:16' | '1:1';
558
+ resolution: '720p' | '1080p' | '4k';
559
+ fps: number;
560
+ };
561
+ frame: {
562
+ background: VideoBackground;
563
+ padding_pct: number;
564
+ corner_radius_px: number;
565
+ shadow: 'none' | 'soft' | 'hard';
566
+ inset_px: number;
567
+ window_chrome: 'none' | 'plain' | 'address_bar';
568
+ };
569
+ cursor: {
570
+ scale: number;
571
+ style: 'arrow_dark' | 'arrow_light' | 'dot' | 'touch';
572
+ click: 'ripple' | 'ring' | 'none';
573
+ /** `null` haelt den Zeiger fuer immer sichtbar. */
574
+ hide_after_ms: number | null;
575
+ loop: boolean;
576
+ };
577
+ camera: {
578
+ zoom: 'off' | 'gentle' | 'medium' | 'strong';
579
+ motion_blur: boolean;
580
+ tightening: 'none' | 'normal' | 'strong';
581
+ ride: 'calm' | 'standard' | 'brisk';
582
+ zoom_scope: 'canvas' | 'card';
583
+ };
584
+ audio: {
585
+ /** `"<anbieter>:<stimme>"` oder `null` fuer ein stummes Video. */
586
+ voice: string | null;
587
+ language: string;
588
+ subtitles: 'burned' | 'file' | 'off';
589
+ subtitle_style: string;
590
+ page_audio: boolean;
591
+ music: MusicBed | null;
592
+ };
593
+ cards: {
594
+ intro: VideoCard | null;
595
+ outro: VideoCard | null;
596
+ };
597
+ masking: {
598
+ level: 'off' | 'secrets' | 'secrets_and_ids';
599
+ style: 'bar' | 'pixelate' | 'blur' | 'placeholder';
600
+ };
601
+ export: {
602
+ preset: ExportPresetName;
603
+ also: ExtraOutput[];
604
+ destination: VideoDestination;
605
+ };
606
+ ai_label: {
607
+ enabled: boolean;
608
+ text: string;
609
+ position: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right';
610
+ };
611
+ }
612
+ /**
613
+ * A video RECIPE: how a recording becomes a video (`/v1/video-presets`).
614
+ *
615
+ * `config` was an open object for a long time, on the argument that a second
616
+ * TypeScript copy of a two-dozen-enum schema is a second place for it to rot.
617
+ * The copy exists now because a form over an open object is a pile of casts and
618
+ * a typo in it only surfaces as a 400 after somebody pressed save; what took
619
+ * the argument's teeth is that the copy is held against the Rust source field
620
+ * by field (`preset-schema-mirror`), so it cannot rot quietly.
621
+ */
622
+ interface VideoPreset {
623
+ id: string;
624
+ name: string;
625
+ /** Shipped with the product; can be duplicated but not edited or deleted. */
626
+ builtin: boolean;
627
+ config: VideoPresetConfig;
628
+ created_at: number;
629
+ updated_at: number;
630
+ }
631
+ /** One named frame of the sample film the preview can jump to, with the clip window around it. */
632
+ interface PreviewMoment {
633
+ key: string;
634
+ label: string;
635
+ frame: number;
636
+ from: number;
637
+ to: number;
638
+ }
639
+ /** A preset field the sample film cannot show, and why. */
640
+ interface PreviewHidden {
641
+ field: string;
642
+ why: string;
643
+ }
644
+ /** `POST /v1/video-presets/preview`: one still of the sample film under an unsaved config. */
645
+ interface VideoPresetPreviewBody {
646
+ config: VideoPresetConfig;
647
+ frame?: number;
648
+ moment?: string;
649
+ name?: string;
650
+ /** Draw at this short side instead of the recipe's own, 360 to 2160. */
651
+ proxy_short_side?: number;
652
+ }
653
+ /** The reply: the picture as a data URL plus what the film looks like under this config. */
654
+ interface VideoPresetPreview {
655
+ width: number;
656
+ height: number;
657
+ fps: number;
658
+ frames: number;
659
+ frame: number;
660
+ moments: PreviewMoment[];
661
+ hidden: PreviewHidden[];
662
+ notes: string[];
663
+ image: string;
664
+ }
665
+ /** `POST /v1/video-presets/preview/clip`: frames `from_frame..to_frame` of the sample film as an MP4. */
666
+ interface VideoPresetClipBody {
667
+ config: VideoPresetConfig;
668
+ /** Absent means the start of the film. */
669
+ from_frame?: number;
670
+ /** Exclusive; absent means the end of the film. */
671
+ to_frame?: number;
672
+ name?: string;
673
+ /** Draw at this short side instead of the recipe's own, 360 to 2160. */
674
+ proxy_short_side?: number;
675
+ }
676
+ /** One progress frame of the clip's SSE stream (`event: progress`). */
677
+ interface VideoPresetClipProgress {
678
+ done: number;
679
+ total: number;
680
+ }
681
+ /**
682
+ * `event: started`, the FIRST frame of the SSE answer: the id under which the
683
+ * bytes can already be followed (`?follow=1`), and the shape of the film.
684
+ *
685
+ * It exists so a player can begin while the render still runs. Without it the
686
+ * id arrives with `done`, and by then there is nothing left to follow.
687
+ */
688
+ interface VideoPresetClipStarted {
689
+ id: string;
690
+ frames: number;
691
+ fps: number;
692
+ width: number;
693
+ height: number;
694
+ }
695
+ /** The finished clip (`event: done`, or the blocking JSON reply); the bytes come from `GET /v1/video-presets/preview/clip/:id`. */
696
+ interface VideoPresetClip {
697
+ id: string;
698
+ from_frame: number;
699
+ to_frame: number;
700
+ fps: number;
701
+ width: number;
702
+ height: number;
703
+ frames: number;
704
+ seconds: number;
705
+ bytes: number;
706
+ encoder: string;
707
+ cached: boolean;
708
+ audio: boolean;
709
+ }
301
710
  /** `GET /v1/persona/constraints`: the values a preset may constrain to. */
302
711
  interface PersonaConstraintOptions {
303
712
  countries: string[];
@@ -400,7 +809,7 @@ type TrustedInputBody = {
400
809
  delta_y?: number;
401
810
  humanize?: boolean;
402
811
  };
403
- /** Who is driving: an agent holding a reservation, or a person at a window. */
812
+ /** Who is driving: an agent holding a lease, or a person at a window. */
404
813
  type RunActor = 'agent' | 'human';
405
814
  /** How a run ended. `reached`/`partial` appear only when a source claimed it. */
406
815
  type RunOutcome = 'running' | 'reached' | 'partial' | 'blocked' | 'interrupted' | 'unknown';
@@ -450,6 +859,144 @@ interface ProfileActivity {
450
859
  /** The browser did not answer the last sweep; this is the last known value. */
451
860
  stale: boolean;
452
861
  }
862
+ /** How far a claim reaches. `machine` means this computer has no account key yet. */
863
+ type ExitScope = 'machine' | 'account';
864
+ /** One exit address a profile owns. */
865
+ interface ExitClaim {
866
+ /** The comparison unit: the whole address for IPv4, the `/64` block for IPv6. */
867
+ addr: string;
868
+ /** `mobile` | `residential` | `datacenter` | `unknown`. Sets how long it is held. */
869
+ class: string;
870
+ scope: ExitScope;
871
+ first_seen: number;
872
+ last_seen: number;
873
+ expires_at: number;
874
+ }
875
+ /** A profile's exit-address situation. */
876
+ interface ExitStatus {
877
+ /** Whether the rule applies to this installation at all. */
878
+ enabled: boolean;
879
+ /** Derived from the newest claim, not from configuration. */
880
+ scope: ExitScope;
881
+ /** The address in use, when one is claimed. */
882
+ current: string | null;
883
+ owned: number;
884
+ claims: ExitClaim[];
885
+ /** Set when the profile has no proxy and is therefore exempt. */
886
+ note: string | null;
887
+ }
888
+
889
+ /**
890
+ * The per-profile task list and the profile's own durable memory.
891
+ *
892
+ * Mirrors `web-ui/src/api/types/tasks.ts`, the shared source of contract truth.
893
+ */
894
+ /**
895
+ * Where a task stands.
896
+ *
897
+ * Four words and no more. There is deliberately no `blocked` and no `partial`:
898
+ * `in_progress` already carries "somebody tried and did not finish", and why it
899
+ * failed belongs in the run trail, which is built to hold exactly that.
900
+ *
901
+ * `cancelled` is `done` with a different word. Both are endings, both fall out
902
+ * of the list a day later; the separate word is so a reader can tell "we did it"
903
+ * from "we called it off".
904
+ */
905
+ type TaskState = 'open' | 'in_progress' | 'done' | 'cancelled';
906
+ /** One task on one profile. */
907
+ interface ProfileTask {
908
+ id: string;
909
+ profile_id: string;
910
+ /** Carried along so a fleet-wide list can say whose task a row is. */
911
+ profile_name: string;
912
+ /**
913
+ * The IANA zone `due_at` should be READ in: the profile's own.
914
+ *
915
+ * Without it a viewer reads their own clock and acts on the wrong number, and
916
+ * an account posting at three in the morning local time is exactly the
917
+ * behavioural tell this product avoids everywhere else. Derived at query time,
918
+ * never stored on the task: `geo_mode = follow_exit` rewrites the persona's
919
+ * zone at every launch.
920
+ */
921
+ profile_timezone: string | null;
922
+ /** One line saying what to do. A task names its material, it does not hold it. */
923
+ title: string;
924
+ state: TaskState;
925
+ /**
926
+ * Deadline in unix seconds (UTC), or `null` for work that is owed but not owed
927
+ * at any particular moment.
928
+ *
929
+ * Read it in the PROFILE's timezone, not the viewer's: a task due at nine means
930
+ * nine where the identity lives, and an account that posts at three in the
931
+ * morning local time is a behavioural tell.
932
+ *
933
+ * It is a deadline, not a trigger. Nothing runs by itself.
934
+ */
935
+ due_at: number | null;
936
+ created_at: number;
937
+ /** When the current claim was taken; cleared when the task leaves `in_progress`. */
938
+ claimed_at: number | null;
939
+ finished_at: number | null;
940
+ /**
941
+ * Set when a claim nobody finished was handed back after six hours.
942
+ *
943
+ * Whoever picks such a task up has to check whether the work already happened:
944
+ * the previous run may have published the post and died before it could say so.
945
+ */
946
+ last_attempt_at: number | null;
947
+ }
948
+ /** Filters for listing tasks. All are narrowings; absent means unconstrained. */
949
+ interface ListTasksParams {
950
+ profile_id?: string;
951
+ state?: TaskState;
952
+ /** Only tasks whose deadline is at or before this unix second. */
953
+ due_before?: number;
954
+ /** Substring of the title, case-insensitive. */
955
+ q?: string;
956
+ limit?: number;
957
+ offset?: number;
958
+ }
959
+ /** Add the same task to one or many profiles, one row each. */
960
+ interface CreateTasksBody {
961
+ profile_ids: string[];
962
+ title: string;
963
+ due_at?: number | null;
964
+ }
965
+ /** What one create call did. */
966
+ interface CreateTasksResult {
967
+ created: ProfileTask[];
968
+ /**
969
+ * Profiles that no longer exist. Named rather than dropped: adding a task to
970
+ * twenty profiles of which three were deleted writes seventeen rows and says
971
+ * which three it could not.
972
+ */
973
+ missing: string[];
974
+ }
975
+ /** The one transition this surface performs. */
976
+ interface PatchTaskBody {
977
+ /** Only `cancelled`, and only on a task nobody has claimed. */
978
+ state: 'cancelled';
979
+ }
980
+ /**
981
+ * A profile's durable memory: the `PROFILE.md` at the top of its directory.
982
+ *
983
+ * Same purpose as a repository's `CLAUDE.md`. What goes in it is the customer's
984
+ * business; nothing writes a scaffold or seeds a value, and a profile starts with
985
+ * no file rather than an empty one.
986
+ */
987
+ interface ProfileMemory {
988
+ /** `null` while nobody has written one. */
989
+ content: string | null;
990
+ /** The absolute path, so a person can open it in their own editor. */
991
+ path: string;
992
+ updated_at: number | null;
993
+ /** The ceiling a write is measured against, in bytes. */
994
+ max_bytes: number;
995
+ }
996
+ /** Replace a profile's memory. The text is stored verbatim. */
997
+ interface WriteMemoryBody {
998
+ content: string;
999
+ }
453
1000
 
454
1001
  /**
455
1002
  * A daemon lifecycle event. Named `LifecycleEvent` (not `Event`) to avoid
@@ -532,11 +1079,73 @@ type LifecycleEvent = {
532
1079
  profile_id: string;
533
1080
  at: number;
534
1081
  }
1082
+ /** A recording appeared or changed: the whole row, upsert it. */
1083
+ | {
1084
+ type: 'recording_changed';
1085
+ recording: Recording;
1086
+ at: number;
1087
+ }
1088
+ /**
1089
+ * A video appeared or changed. Carries state AND progress: while it renders
1090
+ * this fires every 2 % or 2 s, and the row is the whole truth.
1091
+ */
1092
+ | {
1093
+ type: 'video_changed';
1094
+ video: Video;
1095
+ at: number;
1096
+ }
1097
+ /** A video is gone: deleted, remove its row. */
1098
+ | {
1099
+ type: 'video_gone';
1100
+ video_id: string;
1101
+ at: number;
1102
+ }
1103
+ /**
1104
+ * A task appeared or changed. One variant for both, because the row is
1105
+ * complete: a reader writes it whether or not it knew the task before, and a
1106
+ * separate "created" event would only buy a distinction that goes wrong the
1107
+ * first time one is missed.
1108
+ */
1109
+ | {
1110
+ type: 'task_changed';
1111
+ task: ProfileTask;
1112
+ at: number;
1113
+ }
1114
+ /** A task is gone: cancelled and swept, finished and swept, or deleted. */
1115
+ | {
1116
+ type: 'task_gone';
1117
+ task_id: string;
1118
+ profile_id: string;
1119
+ at: number;
1120
+ }
535
1121
  /**
536
1122
  * Frames were lost; re-read rather than carry on. Raised when the daemon
537
1123
  * notices this connection fell behind. `dropped` counts what it skipped.
538
1124
  */
1125
+ /**
1126
+ * The exit-address guard's verdict for one running profile (DEC-017).
1127
+ *
1128
+ * The whole state, like every other live row here: a display rebuilt from
1129
+ * parts is what makes it confidently wrong, and the one frame that matters
1130
+ * most is the wire being cut.
1131
+ */
1132
+ | {
1133
+ type: 'exit_guard_changed';
1134
+ profile_id: string;
1135
+ state: 'ok' | 'degraded' | 'unverified' | 'severed';
1136
+ addr: string;
1137
+ class: string;
1138
+ scope: ExitScope;
1139
+ owned: number;
1140
+ detail: string | null;
1141
+ at: number;
1142
+ }
1143
+ /** A profile stopped: its guard row goes away. */
539
1144
  | {
1145
+ type: 'exit_guard_gone';
1146
+ profile_id: string;
1147
+ at: number;
1148
+ } | {
540
1149
  type: 'resync';
541
1150
  dropped: number;
542
1151
  at: number;
@@ -558,7 +1167,14 @@ type StreamedEvent = LifecycleEvent & {
558
1167
  * Read as `text/event-stream` over `fetch` (so the Bearer header is attached, which
559
1168
  * the browser `EventSource` cannot do that) and reconnect with backoff. A
560
1169
  * `/v1/ws` websocket carries the same events; SSE is sufficient for one-way
561
- * fan-out. Mirrors `web-ui/src/api/events.ts`.
1170
+ * fan-out.
1171
+ *
1172
+ * Mirrors `web-ui/src/api/events.ts` line for line, deliberately: this package
1173
+ * ships to npm and should carry no dependency it does not need, so a shared
1174
+ * package would cost more than the copy does. The copy pays for itself with a
1175
+ * gate instead: `scripts/verify-mirrored-values.mjs` holds `MAX_BACKOFF_MS` and
1176
+ * the backoff line itself against the other side. Two clients of the same daemon
1177
+ * that come back at different speeds would be a decision nobody made.
562
1178
  */
563
1179
 
564
1180
  /** Injectable `fetch`; defaults to the global at call time. */
@@ -764,7 +1380,7 @@ declare class ControlApi extends ClientCore {
764
1380
  /**
765
1381
  * Hand the daemon a file so `interact action=upload` has something to attach.
766
1382
  *
767
- * The bytes are bound to the PROFILE, not to a reservation: set a file up
1383
+ * The bytes are bound to the PROFILE, not to a lease: set a file up
768
1384
  * before leasing, and a lease released mid-task does not destroy it.
769
1385
  */
770
1386
  putArtifact(profileId: string, bytes: Uint8Array | ArrayBuffer, name?: string): Promise<ArtifactPutResult>;
@@ -861,6 +1477,12 @@ interface TakeoverResult {
861
1477
  interface StopProfileResult {
862
1478
  stopped: boolean;
863
1479
  }
1480
+ /** `POST /v1/profiles/:id/release`: `released: false` is an honest no-op,
1481
+ * there was nothing to let go. */
1482
+ interface ReleaseProfileResult {
1483
+ released: boolean;
1484
+ stopped: boolean;
1485
+ }
864
1486
  interface BulkCreateBody {
865
1487
  preset_id: string;
866
1488
  count: number;
@@ -1360,6 +1982,53 @@ declare class IdentityApi extends ClientCore {
1360
1982
  revealCookies(profileId: string, body: RevealCookiesBody): Promise<RevealCookiesResult>;
1361
1983
  }
1362
1984
 
1985
+ declare class TasksApi extends ClientCore {
1986
+ /**
1987
+ * What is still to be done. No filter means every task of every profile.
1988
+ *
1989
+ * Pass `state: 'open'` for what is outstanding; without it, tasks finished
1990
+ * within the last day are included too.
1991
+ */
1992
+ listTasks(params?: ListTasksParams, signal?: AbortSignal): Promise<ProfileTask[]>;
1993
+ /**
1994
+ * Put the same task on one or many profiles, one row each.
1995
+ *
1996
+ * A task is a LINE, not a document: name the work and leave the material where
1997
+ * it lives. `due_at` is a deadline, not a trigger; nothing runs by itself.
1998
+ *
1999
+ * Profiles that no longer exist come back in `missing` instead of failing the
2000
+ * whole call.
2001
+ */
2002
+ createTasks(body: CreateTasksBody): Promise<CreateTasksResult>;
2003
+ /**
2004
+ * Call a task off.
2005
+ *
2006
+ * Only a task nobody has claimed can be cancelled here. One an agent is working
2007
+ * on right now is stopped by telling that agent, which is a channel that already
2008
+ * reaches it; this surface answers `409` for it rather than pretending.
2009
+ */
2010
+ cancelTask(id: string): Promise<ProfileTask>;
2011
+ /** Remove a task outright. */
2012
+ deleteTask(id: string): Promise<void>;
2013
+ /**
2014
+ * A profile's durable memory, the `PROFILE.md` at the top of its directory.
2015
+ *
2016
+ * `content` is `null` while nobody has written one: a profile starts with no
2017
+ * file, not with an empty one.
2018
+ */
2019
+ getProfileMemory(profileId: string, signal?: AbortSignal): Promise<ProfileMemory>;
2020
+ /**
2021
+ * Replace a profile's memory.
2022
+ *
2023
+ * Stored verbatim: no trimming, no appended newline, no line-ending
2024
+ * translation. The last write wins and nothing warns, exactly as with a file
2025
+ * two people can open.
2026
+ */
2027
+ putProfileMemory(profileId: string, content: string): Promise<ProfileMemory>;
2028
+ /** Remove a profile's memory. Removing one that is not there is not an error. */
2029
+ deleteProfileMemory(profileId: string): Promise<void>;
2030
+ }
2031
+
1363
2032
  /**
1364
2033
  * Agent runs and live activity (`/v1/runs`, `/v1/activity`).
1365
2034
  *
@@ -1379,7 +2048,7 @@ type OutcomeSource = 'agent' | 'check' | 'human' | 'derived';
1379
2048
  type GoalSource = 'agent' | 'derived';
1380
2049
  interface AgentRun {
1381
2050
  id: string;
1382
- /** Groups runs that shared one profile reservation. */
2051
+ /** Groups runs that shared one profile lease. */
1383
2052
  session_id: string;
1384
2053
  profile_id: string;
1385
2054
  profile_name: string;
@@ -1438,6 +2107,96 @@ declare class RunsApi extends ClientCore {
1438
2107
  getActivity(signal?: AbortSignal): Promise<ActivitySnapshot>;
1439
2108
  }
1440
2109
 
2110
+ declare class VideosApi extends ClientCore {
2111
+ /** One gallery page, newest first. Running renders are videos too. */
2112
+ listVideos(params?: ListVideosParams, signal?: AbortSignal): Promise<VideosPage>;
2113
+ /** One video row, progress included while it renders. */
2114
+ getVideo(videoId: string, signal?: AbortSignal): Promise<Video>;
2115
+ /** Rename. The title also lands in the MP4's metadata on the NEXT render. */
2116
+ renameVideo(videoId: string, title: string, signal?: AbortSignal): Promise<Video>;
2117
+ /**
2118
+ * Delete a video (`raw: true` also purges its recording's raw material).
2119
+ * A live share link is revoked first; 409 `job_running` while it renders.
2120
+ */
2121
+ deleteVideo(videoId: string, opts?: {
2122
+ raw?: boolean;
2123
+ }, signal?: AbortSignal): Promise<void>;
2124
+ /** Cancel a queued or rendering video. `done` stays done (409). */
2125
+ cancelVideo(videoId: string, signal?: AbortSignal): Promise<Video>;
2126
+ /** Render a failed or cancelled video again: same id, new job. */
2127
+ rerenderVideo(videoId: string, body?: RenderRequestBody, signal?: AbortSignal): Promise<Video>;
2128
+ /** A short-lived fetchable address for one asset (default the video). */
2129
+ videoAccess(videoId: string, asset?: VideoAssetName, signal?: AbortSignal): Promise<VideoAccessGrant>;
2130
+ /** One asset's bytes, through a freshly minted grant. */
2131
+ downloadVideoFile(videoId: string, asset?: VideoAssetName, signal?: AbortSignal): Promise<ArtifactBytes>;
2132
+ /** What recordings and videos cost on disk, plus the caps. */
2133
+ videosStorage(signal?: AbortSignal): Promise<VideosStorage>;
2134
+ /** Recordings, newest first. The row outlives its raw bytes. */
2135
+ listRecordings(params?: {
2136
+ profile_id?: string;
2137
+ }, signal?: AbortSignal): Promise<Recording[]>;
2138
+ getRecording(recordingId: string, signal?: AbortSignal): Promise<Recording>;
2139
+ /**
2140
+ * Purge a recording's RAW material; the row stays forever
2141
+ * (`raw_purged_at`). 409 `job_running` while a render of it is open.
2142
+ */
2143
+ deleteRecordingRaw(recordingId: string, signal?: AbortSignal): Promise<void>;
2144
+ /** A NEW video from this recording (the same body the `video` tool takes). */
2145
+ renderRecording(recordingId: string, body?: RenderRequestBody, signal?: AbortSignal): Promise<Video>;
2146
+ /** What this machine can encode with. The hardware list is PROBED, not read
2147
+ * off the binary: `h264_nvenc` is always listed and fails without a driver. */
2148
+ getCodecs(signal?: AbortSignal): Promise<CodecsStatus>;
2149
+ /** Turn Cisco's OpenH264 on or off. The hardware encoders are not switchable. */
2150
+ setCodecEnabled(codec: string, enabled: boolean, signal?: AbortSignal): Promise<CodecsStatus>;
2151
+ /** Make a finished video public. Measures every file, asks the control plane
2152
+ * for permission, uploads, and has the result confirmed. */
2153
+ share(videoId: string, body?: {
2154
+ expires_days?: number | null;
2155
+ passcode?: string | null;
2156
+ download?: boolean;
2157
+ }, signal?: AbortSignal): Promise<ShareLinkLocal>;
2158
+ /** The share ORDER as this machine knows it. How many people watched and
2159
+ * whether the link still lives is read from the control plane, not from
2160
+ * here: a number carried along would be wrong the moment another machine
2161
+ * revoked. */
2162
+ shareStatus(videoId: string, signal?: AbortSignal): Promise<ShareLinkLocal>;
2163
+ /** Revoke the link. The files fall at once; the order stays until the control
2164
+ * plane confirms. */
2165
+ revokeShare(videoId: string, signal?: AbortSignal): Promise<void>;
2166
+ /** Files a preset points at: background image, music bed, subtitle font. */
2167
+ listMedia(kind?: MediaKind, signal?: AbortSignal): Promise<MediaAsset[]>;
2168
+ /** Upload one file. The body IS the file; `name` and `mime` ride in the query. */
2169
+ uploadMedia(kind: MediaKind, name: string, mime: string, bytes: Uint8Array, signal?: AbortSignal): Promise<MediaAsset>;
2170
+ /** Remove a file. Refuses with 409 while a preset still points at it. */
2171
+ deleteMedia(id: string, signal?: AbortSignal): Promise<void>;
2172
+ /** The file itself. */
2173
+ mediaFile(id: string, signal?: AbortSignal): Promise<ArtifactBytes>;
2174
+ /** Which speech providers have a key. The key itself never comes back: the
2175
+ * answer carries a hint, and that is on purpose. */
2176
+ listTtsProviders(signal?: AbortSignal): Promise<TtsProviderView[]>;
2177
+ /** Store a key, or rename one. Leaving `apiKey` out keeps the stored key.
2178
+ *
2179
+ * `apiKey` may also be a secret reference (`{{sb:...}}`), which is what an
2180
+ * agent gets back after fetching a key from the provider's own site: it is
2181
+ * not allowed to read the value. A reference needs `profileId`, because the
2182
+ * value belongs to one profile and there is nothing to open without it.
2183
+ */
2184
+ putTtsProvider(provider: string, body: {
2185
+ apiKey?: string;
2186
+ label?: string;
2187
+ profileId?: string;
2188
+ }, signal?: AbortSignal): Promise<TtsProviderView>;
2189
+ /** Call the provider once for real, and record that the key works.
2190
+ *
2191
+ * Costs one short sentence of the account's own quota. Until this runs, a
2192
+ * provider row says `verified_at: null`, which means untested rather than
2193
+ * broken.
2194
+ */
2195
+ verifyTtsProvider(provider: string, signal?: AbortSignal): Promise<TtsVerifyResult>;
2196
+ /** Forget a provider's key. */
2197
+ deleteTtsProvider(provider: string, signal?: AbortSignal): Promise<void>;
2198
+ }
2199
+
1441
2200
  /**
1442
2201
  * Direct-CDP driver (DEC-006, AC-FP-005): a minimal, nodriver-style Chrome
1443
2202
  * DevTools Protocol client over a raw websocket. This is the recommended SDK
@@ -1667,6 +2426,13 @@ declare class ScalebrowserClient extends ClientCore {
1667
2426
  /** Start a profile → CDP ws endpoint. Errors: 4002/4003/4005/4006/4008. */
1668
2427
  startProfile(id: string, body?: StartProfileBody): Promise<StartProfileResult>;
1669
2428
  stopProfile(id: string): Promise<StopProfileResult>;
2429
+ /** Drop the profile's lease, the operator's way out.
2430
+ *
2431
+ * An agent releases with its handle; this call needs none. The daemon stops
2432
+ * the browser first when one is still up, then lets the lease go, so a dead
2433
+ * session cannot park the profile until its TTL runs out. `released: false`
2434
+ * is an honest no-op: there was nothing to let go. */
2435
+ releaseProfile(id: string): Promise<ReleaseProfileResult>;
1670
2436
  /** Ask for a profile that is open on another machine of this account.
1671
2437
  *
1672
2438
  * Closes nothing here and nothing there: the control plane revokes the holder's
@@ -1702,6 +2468,31 @@ declare class ScalebrowserClient extends ClientCore {
1702
2468
  createPreset(body: CreatePresetBody): Promise<Preset>;
1703
2469
  updatePreset(id: string, body: UpdatePresetBody): Promise<Preset>;
1704
2470
  deletePreset(id: string): Promise<void>;
2471
+ listVideoPresets(signal?: AbortSignal): Promise<VideoPreset[]>;
2472
+ getVideoPreset(id: string, signal?: AbortSignal): Promise<VideoPreset>;
2473
+ /** The daemon validates `config` strictly and names the failing field path. */
2474
+ createVideoPreset(body: {
2475
+ name: string;
2476
+ config?: Record<string, unknown>;
2477
+ }): Promise<VideoPreset>;
2478
+ /** A built-in preset refuses edits. Duplicate it and edit the copy. */
2479
+ updateVideoPreset(id: string, body: {
2480
+ name?: string;
2481
+ config?: Record<string, unknown>;
2482
+ }): Promise<VideoPreset>;
2483
+ /** The only way to make a built-in preset yours: copy, then edit the copy. */
2484
+ duplicateVideoPreset(id: string, body?: {
2485
+ name?: string;
2486
+ }): Promise<VideoPreset>;
2487
+ deleteVideoPreset(id: string): Promise<void>;
2488
+ /** One still of the sample film under an UNSAVED config, drawn by the daemon's own renderer.
2489
+ * `moment` wins over `frame`; both absent means the daemon's default moment. */
2490
+ previewVideoPreset(body: VideoPresetPreviewBody, signal?: AbortSignal): Promise<VideoPresetPreview>;
2491
+ /** A clip of the sample film, blocking until it is encoded (the editor uses the SSE form of the
2492
+ * same route; a script waits). Fetch the bytes with `downloadVideoPresetClip`. */
2493
+ previewVideoPresetClip(body: VideoPresetClipBody, signal?: AbortSignal): Promise<VideoPresetClip>;
2494
+ /** The clip's MP4 bytes. */
2495
+ downloadVideoPresetClip(id: string, signal?: AbortSignal): Promise<ArtifactBytes>;
1705
2496
  /**
1706
2497
  * The values a preset's `constraints` may take. Ask rather than hardcode: the
1707
2498
  * region list comes from the daemon's embedded persona model, so a pinned copy
@@ -1725,6 +2516,13 @@ declare class ScalebrowserClient extends ClientCore {
1725
2516
  startAudit(profileId: string): Promise<AuditStatus>;
1726
2517
  /** State plus the last stored report. */
1727
2518
  getAudit(profileId: string, signal?: AbortSignal): Promise<AuditStatus>;
2519
+ /** Which exit addresses this profile owns, and how far the rule reaches.
2520
+ *
2521
+ * `scope` is `'machine'` while this computer has no account key. That is the
2522
+ * ordinary state of a second machine until sync is unlocked there once, and it is
2523
+ * reported rather than hidden: believing the rule spans three machines when it
2524
+ * spans one is worse than knowing. */
2525
+ getExitStatus(profileId: string, signal?: AbortSignal): Promise<ExitStatus>;
1728
2526
  exportSession(profileId: string, body: SessionExportBody): Promise<SessionExportResult>;
1729
2527
  importSession(profileId: string, body: SessionImportBody): Promise<unknown>;
1730
2528
  sendInput(profileId: string, body: TrustedInputBody): Promise<unknown>;
@@ -1744,9 +2542,86 @@ declare class ScalebrowserClient extends ClientCore {
1744
2542
  */
1745
2543
  launch(profileId: string, body?: StartProfileBody): Promise<LaunchHandle>;
1746
2544
  }
1747
- interface ScalebrowserClient extends IdentityApi, RunsApi, ControlApi, ExtensionsApi {
2545
+ interface ScalebrowserClient extends IdentityApi, RunsApi, VideosApi, ControlApi, ExtensionsApi, TasksApi {
1748
2546
  }
1749
2547
 
2548
+ /**
2549
+ * Error model: stable numeric API error codes (SPEC §5,
2550
+ * `crates/core/src/error.rs`) and the typed exceptions the SDK throws.
2551
+ */
2552
+ /** Stable numeric API error codes (4001–4013, append-only: a retired code stays
2553
+ * reserved rather than being reused). */
2554
+ declare const ErrorCode: {
2555
+ readonly NotFound: 4001;
2556
+ readonly ProxyGeo: 4002;
2557
+ readonly Capacity: 4003;
2558
+ readonly EngineMissing: 4004;
2559
+ readonly Preflight: 4005;
2560
+ readonly AlreadyRunning: 4006;
2561
+ /** Retired: the removed Methode-D (VM) tier produced this. Reserved (never reused)
2562
+ * to match the server's append-only code contract; never returned now. */
2563
+ readonly VmProvisionFailed: 4007;
2564
+ /** The subscription's abo-wide concurrent-browser allowance is used up (DEC-012).
2565
+ * Distinct from Capacity (4003 = this machine is full, wait): the caller's move
2566
+ * is to stop a running browser anywhere or upgrade the plan. */
2567
+ readonly ConcurrencyLimit: 4008;
2568
+ readonly Unauthorized: 4010;
2569
+ /** No account is connected to this machine, so nothing may run (DEC-014).
2570
+ * Distinct from Unauthorized (4010 = the daemon's own bearer token is wrong, so
2571
+ * re-read the token): 4011 means the machine needs to be signed in, and no token
2572
+ * a client holds can substitute for that. Connect it in the desktop app. */
2573
+ readonly NotSignedIn: 4011;
2574
+ /** This ONE profile is open on another machine of the same account (DEC-015).
2575
+ * Distinct from ConcurrencyLimit (4008 = the plan is full, buy more or free any
2576
+ * browser): the caller's move here costs nothing. Close it on that machine, or
2577
+ * take it over from the app running there. */
2578
+ readonly ProfileInUse: 4012;
2579
+ /** The exit address this start drew belongs to another profile (DEC-017).
2580
+ * Distinct from ProxyGeo (4002 = wrong country or wrong kind of exit, so change
2581
+ * the proxy): here the proxy is fine and the address is simply spoken for. Wait
2582
+ * for a term to run out, let the daemon draw again, or turn exclusivity off for
2583
+ * this profile. */
2584
+ readonly ExitAddressTaken: 4013;
2585
+ };
2586
+ type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
2587
+ /** Error body shape: `{ code, message }` (docs/features/api.md). */
2588
+ interface ApiErrorBody {
2589
+ code?: number;
2590
+ message?: string;
2591
+ }
2592
+ /** Base class for every error this SDK throws, so callers can `catch` one type. */
2593
+ declare class ScalebrowserError extends Error {
2594
+ constructor(message: string);
2595
+ }
2596
+ /**
2597
+ * A typed transport/contract error. Carries the HTTP `status` and the daemon's
2598
+ * numeric `code` so callers (and the auth layer) can react, e.g. 401 / code
2599
+ * 4010 → re-authenticate.
2600
+ */
2601
+ declare class ApiError extends ScalebrowserError {
2602
+ readonly status: number;
2603
+ readonly code?: number;
2604
+ readonly body?: ApiErrorBody;
2605
+ constructor(status: number, body?: ApiErrorBody, fallbackMessage?: string);
2606
+ /** True for auth failures that must trigger re-authentication. */
2607
+ get isAuthError(): boolean;
2608
+ /** Short label for the error code, if any. */
2609
+ get codeLabel(): string | undefined;
2610
+ }
2611
+ /** A network-level failure (daemon unreachable, socket error, fetch rejected). */
2612
+ declare class NetworkError extends ScalebrowserError {
2613
+ readonly cause?: unknown;
2614
+ constructor(message: string, cause?: unknown);
2615
+ }
2616
+ /** A CDP command that returned an `error` frame, or a closed CDP connection. */
2617
+ declare class CdpError extends ScalebrowserError {
2618
+ readonly code: number;
2619
+ readonly data?: unknown;
2620
+ constructor(code: number, message: string, data?: unknown);
2621
+ }
2622
+ /** Narrow an unknown thrown value to a user-facing message. */
2623
+ declare function errorMessage(err: unknown): string;
2624
+
1750
2625
  /**
1751
2626
  * `@scalebrowser/sdk`: the official Node/TypeScript SDK for the Scalebrowser
1752
2627
  * daemon. A typed REST client plus a direct-CDP driver plane (nodriver-style,
@@ -1764,6 +2639,6 @@ interface ScalebrowserClient extends IdentityApi, RunsApi, ControlApi, Extension
1764
2639
  * ```
1765
2640
  */
1766
2641
 
1767
- declare const VERSION = "0.3.0";
2642
+ declare const VERSION = "0.5.0";
1768
2643
 
1769
- export { type Account, type ActivitySnapshot, type AgentRun, ApiError, type ApiErrorBody, type ArtifactBytes, type ArtifactPutResult, type AttachOptions, type AuditDetectorReport, type AuditReport, type AuditStatus, type AuditVerdict, type BindInboxBody, type CdpConnectOptions, CdpError, type CdpEvent, CdpSession, type ClickOptions, type CreateGroupBody, type CreateInboxBody, type CreatePresetBody, DEFAULT_CAPACITY, type DeleteInterruptionRuleParams, type EffectiveInterruptionLock, ErrorCode, type ErrorCodeValue, type EvaluateOptions, type FetchLike, GEO_MODES, type GeoMode, type GoalSource, type Group, HOST_MODES, type HealthStatus, type HostMode, type Inbox, type InboxBinding, type InboxBindings, type InboxChannel, type InputClient, type InterruptionChoice, type InterruptionLock, type InterruptionLockRow, type InterruptionLockView, type InterruptionRuleRow, type LaunchHandle, type ListRunsParams, type ListStepsParams, type NavigateOptions, NetworkError, type OsFamily, type OutcomeSource, PROXY_KINDS, type PasskeyRow, type PersonaConstraintOptions, type Preset, type PresetConfig, type PresetConstraints, type Profile, type ProfileActivity, type ProtectionLevel, type ProtectionStatus, type Proxy, type ProxyKind, ROTATIONS, RUNTIME_STATES, type ReadyStatus, type RevealCookiesBody, type RevealCookiesResult, type RevealedCookie, type Rotation, type RunActor, type RunOutcome, type RunStep, type RuntimeState, ScalebrowserClient, type ScalebrowserClientOptions, ScalebrowserError, type ScrollOptions, type SecretBindingMode, type SecretDeleteResult, type SecretKind, type SecretListResult, type SecretOrigin, type SecretPatchBody, type SecretRow, type SecretRunBody, type SecretRunMention, type SecretRunResult, type SecretScrubResult, type SecretState, type SessionExportBody, type SessionExportResult, type SessionImportBody, type SessionKind, type SetInterruptionLockBody, type SetInterruptionRuleBody, type StepKind, type StepTarget, type StreamHandlers, type StreamParams, type StreamStatus, type TrustedInputBody, type UpdateGroupBody, type UpdateInboxBody, type UpdatePresetBody, VERSION, deriveMetricsFromProfiles, errorMessage, eventStream, openCdpSession, parseSseFrame, subscribeEvents };
2644
+ export { type Account, type ActivitySnapshot, type AgentRun, ApiError, type ApiErrorBody, type ArtifactBytes, type ArtifactPutResult, type AttachOptions, type AudioStatus, type AuditDetectorReport, type AuditReport, type AuditStatus, type AuditVerdict, type BindInboxBody, type CdpConnectOptions, CdpError, type CdpEvent, CdpSession, type Chapter, type ClickOptions, type CodecsStatus, type CreateGroupBody, type CreateInboxBody, type CreatePresetBody, type CreateTasksBody, type CreateTasksResult, DEFAULT_CAPACITY, type DeleteInterruptionRuleParams, type EffectiveInterruptionLock, ErrorCode, type ErrorCodeValue, type EvaluateOptions, type ExitClaim, type ExitScope, type ExitStatus, type ExportPresetName, type ExtraOutput, type FetchLike, GEO_MODES, type GeoMode, type GoalSource, type Group, HOST_MODES, type HealthStatus, type HostMode, type Inbox, type InboxBinding, type InboxBindings, type InboxChannel, type InputClient, type InterruptionChoice, type InterruptionLock, type InterruptionLockRow, type InterruptionLockView, type InterruptionRuleRow, type LaunchHandle, type ListRunsParams, type ListStepsParams, type ListTasksParams, type ListVideosParams, type MediaAsset, type MediaKind, type MusicBed, type NarrationLine, type NavigateOptions, NetworkError, type OsFamily, type OutcomeSource, PROXY_KINDS, type PasskeyRow, type PatchTaskBody, type PersonaConstraintOptions, type Preset, type PresetConfig, type PresetConstraints, type PreviewHidden, type PreviewMoment, type Profile, type ProfileActivity, type ProfileMemory, type ProfileTask, type ProtectionLevel, type ProtectionStatus, type Proxy, type ProxyKind, ROTATIONS, RUNTIME_STATES, type ReadyStatus, type Recording, type RecordingKind, type RecordingState, type RenderProgress, type RenderRequestBody, type RenderStage, type RevealCookiesBody, type RevealCookiesResult, type RevealedCookie, type Rotation, type RunActor, type RunOutcome, type RunStep, type RuntimeState, ScalebrowserClient, type ScalebrowserClientOptions, ScalebrowserError, type ScrollOptions, type SecretBindingMode, type SecretDeleteResult, type SecretKind, type SecretListResult, type SecretOrigin, type SecretPatchBody, type SecretRow, type SecretRunBody, type SecretRunMention, type SecretRunResult, type SecretScrubResult, type SecretState, type SessionExportBody, type SessionExportResult, type SessionImportBody, type SessionKind, type SetInterruptionLockBody, type SetInterruptionRuleBody, type ShareLinkLocal, type ShotFocus, type ShotIntent, type StepKind, type StepTarget, type StreamHandlers, type StreamParams, type StreamStatus, type TaskState, type TimestampState, type TranscriptWord, type TrustedInputBody, type TtsProviderView, type TtsVerifyResult, type UpdateGroupBody, type UpdateInboxBody, type UpdatePresetBody, VERSION, type Video, type VideoAccessGrant, type VideoAssetName, type VideoAssets, type VideoBackground, type VideoCard, type VideoDestination, type VideoFlags, type VideoPreset, type VideoPresetClip, type VideoPresetClipBody, type VideoPresetClipProgress, type VideoPresetClipStarted, type VideoPresetConfig, type VideoPresetPreview, type VideoPresetPreviewBody, type VideoState, type VideosPage, type VideosStorage, type WriteMemoryBody, deriveMetricsFromProfiles, errorMessage, eventStream, openCdpSession, parseSseFrame, subscribeEvents };