@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.cjs CHANGED
@@ -31,7 +31,13 @@ var ErrorCode = {
31
31
  * Distinct from ConcurrencyLimit (4008 = the plan is full, buy more or free any
32
32
  * browser): the caller's move here costs nothing. Close it on that machine, or
33
33
  * take it over from the app running there. */
34
- ProfileInUse: 4012
34
+ ProfileInUse: 4012,
35
+ /** The exit address this start drew belongs to another profile (DEC-017).
36
+ * Distinct from ProxyGeo (4002 = wrong country or wrong kind of exit, so change
37
+ * the proxy): here the proxy is fine and the address is simply spoken for. Wait
38
+ * for a term to run out, let the daemon draw again, or turn exclusivity off for
39
+ * this profile. */
40
+ ExitAddressTaken: 4013
35
41
  };
36
42
  var CODE_LABELS = {
37
43
  4001: "Not found",
@@ -43,7 +49,8 @@ var CODE_LABELS = {
43
49
  4008: "Concurrent-browser limit reached (subscription-wide)",
44
50
  4010: "Unauthorized",
45
51
  4011: "No account connected to this machine",
46
- 4012: "This profile is open on another machine"
52
+ 4012: "This profile is open on another machine",
53
+ 4013: "This exit address belongs to another profile"
47
54
  };
48
55
  var ScalebrowserError = class extends Error {
49
56
  constructor(message) {
@@ -254,7 +261,7 @@ var ControlApi = class extends ClientCore {
254
261
  /**
255
262
  * Hand the daemon a file so `interact action=upload` has something to attach.
256
263
  *
257
- * The bytes are bound to the PROFILE, not to a reservation: set a file up
264
+ * The bytes are bound to the PROFILE, not to a lease: set a file up
258
265
  * before leasing, and a lease released mid-task does not destroy it.
259
266
  */
260
267
  putArtifact(profileId, bytes, name) {
@@ -572,6 +579,83 @@ var IdentityApi = class extends ClientCore {
572
579
  }
573
580
  };
574
581
 
582
+ // src/client-tasks.ts
583
+ var TasksApi = class extends ClientCore {
584
+ // -- Tasks ------------------------------------------------------------------
585
+ //
586
+ // Fleet-wide by default, and deliberately so: the question this surface exists
587
+ // for is "what is outstanding across my twenty accounts", and a per-profile
588
+ // route would make a client ask it twenty times and sort the answers itself.
589
+ //
590
+ // None of these starts a browser. A task is a row, so reading the whole fleet's
591
+ // list costs nothing from the plan's concurrent-browser allowance. Reserve only
592
+ // the profiles that turn out to have work.
593
+ /**
594
+ * What is still to be done. No filter means every task of every profile.
595
+ *
596
+ * Pass `state: 'open'` for what is outstanding; without it, tasks finished
597
+ * within the last day are included too.
598
+ */
599
+ listTasks(params, signal) {
600
+ return this.request("GET", "/v1/tasks", void 0, {
601
+ query: { ...params },
602
+ signal
603
+ });
604
+ }
605
+ /**
606
+ * Put the same task on one or many profiles, one row each.
607
+ *
608
+ * A task is a LINE, not a document: name the work and leave the material where
609
+ * it lives. `due_at` is a deadline, not a trigger; nothing runs by itself.
610
+ *
611
+ * Profiles that no longer exist come back in `missing` instead of failing the
612
+ * whole call.
613
+ */
614
+ createTasks(body) {
615
+ return this.request("POST", "/v1/tasks", body);
616
+ }
617
+ /**
618
+ * Call a task off.
619
+ *
620
+ * Only a task nobody has claimed can be cancelled here. One an agent is working
621
+ * on right now is stopped by telling that agent, which is a channel that already
622
+ * reaches it; this surface answers `409` for it rather than pretending.
623
+ */
624
+ cancelTask(id) {
625
+ return this.request("PATCH", `/v1/tasks/${enc(id)}`, { state: "cancelled" });
626
+ }
627
+ /** Remove a task outright. */
628
+ deleteTask(id) {
629
+ return this.request("DELETE", `/v1/tasks/${enc(id)}`);
630
+ }
631
+ // -- Profile memory ---------------------------------------------------------
632
+ /**
633
+ * A profile's durable memory, the `PROFILE.md` at the top of its directory.
634
+ *
635
+ * `content` is `null` while nobody has written one: a profile starts with no
636
+ * file, not with an empty one.
637
+ */
638
+ getProfileMemory(profileId, signal) {
639
+ return this.request("GET", `/v1/profiles/${enc(profileId)}/memory`, void 0, {
640
+ signal
641
+ });
642
+ }
643
+ /**
644
+ * Replace a profile's memory.
645
+ *
646
+ * Stored verbatim: no trimming, no appended newline, no line-ending
647
+ * translation. The last write wins and nothing warns, exactly as with a file
648
+ * two people can open.
649
+ */
650
+ putProfileMemory(profileId, content) {
651
+ return this.request("PUT", `/v1/profiles/${enc(profileId)}/memory`, { content });
652
+ }
653
+ /** Remove a profile's memory. Removing one that is not there is not an error. */
654
+ deleteProfileMemory(profileId) {
655
+ return this.request("DELETE", `/v1/profiles/${enc(profileId)}/memory`);
656
+ }
657
+ };
658
+
575
659
  // src/client-runs.ts
576
660
  var RunsApi = class extends ClientCore {
577
661
  // ── Agent runs ─────────────────────────────────────────────────────────────
@@ -608,6 +692,205 @@ var RunsApi = class extends ClientCore {
608
692
  }
609
693
  };
610
694
 
695
+ // src/client-videos.ts
696
+ var VideosApi = class extends ClientCore {
697
+ // ── Videos ────────────────────────────────────────────────────────────────
698
+ /** One gallery page, newest first. Running renders are videos too. */
699
+ listVideos(params = {}, signal) {
700
+ return this.request("GET", "/v1/videos", void 0, {
701
+ query: {
702
+ q: params.q,
703
+ profile_id: params.profile_id,
704
+ run_id: params.run_id,
705
+ kind: params.kind,
706
+ preset: params.preset,
707
+ state: params.state,
708
+ page: params.page
709
+ },
710
+ signal
711
+ });
712
+ }
713
+ /** One video row, progress included while it renders. */
714
+ getVideo(videoId, signal) {
715
+ return this.request("GET", `/v1/videos/${enc(videoId)}`, void 0, { signal });
716
+ }
717
+ /** Rename. The title also lands in the MP4's metadata on the NEXT render. */
718
+ renameVideo(videoId, title, signal) {
719
+ return this.request("PATCH", `/v1/videos/${enc(videoId)}`, { title }, { signal });
720
+ }
721
+ /**
722
+ * Delete a video (`raw: true` also purges its recording's raw material).
723
+ * A live share link is revoked first; 409 `job_running` while it renders.
724
+ */
725
+ deleteVideo(videoId, opts = {}, signal) {
726
+ return this.request("DELETE", `/v1/videos/${enc(videoId)}`, void 0, {
727
+ query: { raw: opts.raw },
728
+ signal
729
+ });
730
+ }
731
+ /** Cancel a queued or rendering video. `done` stays done (409). */
732
+ cancelVideo(videoId, signal) {
733
+ return this.request("POST", `/v1/videos/${enc(videoId)}/cancel`, void 0, {
734
+ signal
735
+ });
736
+ }
737
+ /** Render a failed or cancelled video again: same id, new job. */
738
+ rerenderVideo(videoId, body = {}, signal) {
739
+ return this.request("POST", `/v1/videos/${enc(videoId)}/renders`, body, { signal });
740
+ }
741
+ /** A short-lived fetchable address for one asset (default the video). */
742
+ videoAccess(videoId, asset, signal) {
743
+ return this.request(
744
+ "POST",
745
+ `/v1/videos/${enc(videoId)}/access`,
746
+ asset ? { asset } : {},
747
+ { signal }
748
+ );
749
+ }
750
+ /** One asset's bytes, through a freshly minted grant. */
751
+ async downloadVideoFile(videoId, asset = "video", signal) {
752
+ const grant = await this.videoAccess(videoId, asset, signal);
753
+ const grantParam = new URL(grant.url, "http://x").searchParams.get("grant") ?? "";
754
+ return this.requestBytes("GET", `/v1/videos/${enc(videoId)}/file`, {
755
+ query: { asset, grant: grantParam },
756
+ signal
757
+ });
758
+ }
759
+ /** What recordings and videos cost on disk, plus the caps. */
760
+ videosStorage(signal) {
761
+ return this.request("GET", "/v1/videos/storage", void 0, { signal });
762
+ }
763
+ // ── Recordings ────────────────────────────────────────────────────────────
764
+ /** Recordings, newest first. The row outlives its raw bytes. */
765
+ listRecordings(params = {}, signal) {
766
+ return this.request("GET", "/v1/recordings", void 0, {
767
+ query: { profile_id: params.profile_id },
768
+ signal
769
+ });
770
+ }
771
+ getRecording(recordingId, signal) {
772
+ return this.request("GET", `/v1/recordings/${enc(recordingId)}`, void 0, {
773
+ signal
774
+ });
775
+ }
776
+ /**
777
+ * Purge a recording's RAW material; the row stays forever
778
+ * (`raw_purged_at`). 409 `job_running` while a render of it is open.
779
+ */
780
+ deleteRecordingRaw(recordingId, signal) {
781
+ return this.request("DELETE", `/v1/recordings/${enc(recordingId)}`, void 0, {
782
+ signal
783
+ });
784
+ }
785
+ /** A NEW video from this recording (the same body the `video` tool takes). */
786
+ renderRecording(recordingId, body = {}, signal) {
787
+ return this.request("POST", `/v1/recordings/${enc(recordingId)}/renders`, body, {
788
+ signal
789
+ });
790
+ }
791
+ // ── Codecs ────────────────────────────────────────────────────────────────
792
+ /** What this machine can encode with. The hardware list is PROBED, not read
793
+ * off the binary: `h264_nvenc` is always listed and fails without a driver. */
794
+ getCodecs(signal) {
795
+ return this.request("GET", "/v1/codecs", void 0, { signal });
796
+ }
797
+ /** Turn Cisco's OpenH264 on or off. The hardware encoders are not switchable. */
798
+ setCodecEnabled(codec, enabled, signal) {
799
+ return this.request("PUT", `/v1/codecs/${enc(codec)}`, { enabled }, { signal });
800
+ }
801
+ // -- Sharing ----------------------------------------------------------------
802
+ /** Make a finished video public. Measures every file, asks the control plane
803
+ * for permission, uploads, and has the result confirmed. */
804
+ share(videoId, body = {}, signal) {
805
+ return this.request("POST", `/v1/videos/${enc(videoId)}/share`, body, {
806
+ signal
807
+ });
808
+ }
809
+ /** The share ORDER as this machine knows it. How many people watched and
810
+ * whether the link still lives is read from the control plane, not from
811
+ * here: a number carried along would be wrong the moment another machine
812
+ * revoked. */
813
+ shareStatus(videoId, signal) {
814
+ return this.request(
815
+ "GET",
816
+ `/v1/videos/${enc(videoId)}/share`,
817
+ void 0,
818
+ { signal }
819
+ );
820
+ }
821
+ /** Revoke the link. The files fall at once; the order stays until the control
822
+ * plane confirms. */
823
+ revokeShare(videoId, signal) {
824
+ return this.request("DELETE", `/v1/videos/${enc(videoId)}/share`, void 0, {
825
+ signal
826
+ });
827
+ }
828
+ // -- Media ------------------------------------------------------------------
829
+ /** Files a preset points at: background image, music bed, subtitle font. */
830
+ listMedia(kind, signal) {
831
+ return this.request("GET", "/v1/media", void 0, {
832
+ query: { kind },
833
+ signal
834
+ });
835
+ }
836
+ /** Upload one file. The body IS the file; `name` and `mime` ride in the query. */
837
+ uploadMedia(kind, name, mime, bytes, signal) {
838
+ return this.request("POST", "/v1/media", bytes, {
839
+ query: { kind, name, mime },
840
+ signal
841
+ });
842
+ }
843
+ /** Remove a file. Refuses with 409 while a preset still points at it. */
844
+ deleteMedia(id, signal) {
845
+ return this.request("DELETE", `/v1/media/${enc(id)}`, void 0, { signal });
846
+ }
847
+ /** The file itself. */
848
+ mediaFile(id, signal) {
849
+ return this.requestBytes("GET", `/v1/media/${enc(id)}/file`, { signal });
850
+ }
851
+ // -- Voice providers --------------------------------------------------------
852
+ /** Which speech providers have a key. The key itself never comes back: the
853
+ * answer carries a hint, and that is on purpose. */
854
+ listTtsProviders(signal) {
855
+ return this.request("GET", "/v1/tts-providers", void 0, { signal });
856
+ }
857
+ /** Store a key, or rename one. Leaving `apiKey` out keeps the stored key.
858
+ *
859
+ * `apiKey` may also be a secret reference (`{{sb:...}}`), which is what an
860
+ * agent gets back after fetching a key from the provider's own site: it is
861
+ * not allowed to read the value. A reference needs `profileId`, because the
862
+ * value belongs to one profile and there is nothing to open without it.
863
+ */
864
+ putTtsProvider(provider, body, signal) {
865
+ return this.request(
866
+ "PUT",
867
+ `/v1/tts-providers/${enc(provider)}`,
868
+ { api_key: body.apiKey, label: body.label, profile_id: body.profileId },
869
+ { signal }
870
+ );
871
+ }
872
+ /** Call the provider once for real, and record that the key works.
873
+ *
874
+ * Costs one short sentence of the account's own quota. Until this runs, a
875
+ * provider row says `verified_at: null`, which means untested rather than
876
+ * broken.
877
+ */
878
+ verifyTtsProvider(provider, signal) {
879
+ return this.request(
880
+ "POST",
881
+ `/v1/tts-providers/${enc(provider)}/verify`,
882
+ void 0,
883
+ { signal }
884
+ );
885
+ }
886
+ /** Forget a provider's key. */
887
+ deleteTtsProvider(provider, signal) {
888
+ return this.request("DELETE", `/v1/tts-providers/${enc(provider)}`, void 0, {
889
+ signal
890
+ });
891
+ }
892
+ };
893
+
611
894
  // src/events.ts
612
895
  var MAX_BACKOFF_MS = 15e3;
613
896
  function subscribeEvents(params, handlers) {
@@ -1111,6 +1394,15 @@ var ScalebrowserClient = class extends ClientCore {
1111
1394
  stopProfile(id) {
1112
1395
  return this.request("POST", `/v1/profiles/${enc(id)}/stop`, {});
1113
1396
  }
1397
+ /** Drop the profile's lease, the operator's way out.
1398
+ *
1399
+ * An agent releases with its handle; this call needs none. The daemon stops
1400
+ * the browser first when one is still up, then lets the lease go, so a dead
1401
+ * session cannot park the profile until its TTL runs out. `released: false`
1402
+ * is an honest no-op: there was nothing to let go. */
1403
+ releaseProfile(id) {
1404
+ return this.request("POST", `/v1/profiles/${enc(id)}/release`, {});
1405
+ }
1114
1406
  /** Ask for a profile that is open on another machine of this account.
1115
1407
  *
1116
1408
  * Closes nothing here and nothing there: the control plane revokes the holder's
@@ -1182,6 +1474,42 @@ var ScalebrowserClient = class extends ClientCore {
1182
1474
  deletePreset(id) {
1183
1475
  return this.request("DELETE", `/v1/presets/${enc(id)}`);
1184
1476
  }
1477
+ // ── Video presets ──────────────────────────────────────────────────────────
1478
+ listVideoPresets(signal) {
1479
+ return this.request("GET", "/v1/video-presets", void 0, { signal });
1480
+ }
1481
+ getVideoPreset(id, signal) {
1482
+ return this.request("GET", `/v1/video-presets/${enc(id)}`, void 0, { signal });
1483
+ }
1484
+ /** The daemon validates `config` strictly and names the failing field path. */
1485
+ createVideoPreset(body) {
1486
+ return this.request("POST", "/v1/video-presets", body);
1487
+ }
1488
+ /** A built-in preset refuses edits. Duplicate it and edit the copy. */
1489
+ updateVideoPreset(id, body) {
1490
+ return this.request("PATCH", `/v1/video-presets/${enc(id)}`, body);
1491
+ }
1492
+ /** The only way to make a built-in preset yours: copy, then edit the copy. */
1493
+ duplicateVideoPreset(id, body = {}) {
1494
+ return this.request("POST", `/v1/video-presets/${enc(id)}/duplicate`, body);
1495
+ }
1496
+ deleteVideoPreset(id) {
1497
+ return this.request("DELETE", `/v1/video-presets/${enc(id)}`);
1498
+ }
1499
+ /** One still of the sample film under an UNSAVED config, drawn by the daemon's own renderer.
1500
+ * `moment` wins over `frame`; both absent means the daemon's default moment. */
1501
+ previewVideoPreset(body, signal) {
1502
+ return this.request("POST", "/v1/video-presets/preview", body, { signal });
1503
+ }
1504
+ /** A clip of the sample film, blocking until it is encoded (the editor uses the SSE form of the
1505
+ * same route; a script waits). Fetch the bytes with `downloadVideoPresetClip`. */
1506
+ previewVideoPresetClip(body, signal) {
1507
+ return this.request("POST", "/v1/video-presets/preview/clip", body, { signal });
1508
+ }
1509
+ /** The clip's MP4 bytes. */
1510
+ downloadVideoPresetClip(id, signal) {
1511
+ return this.requestBytes("GET", `/v1/video-presets/preview/clip/${enc(id)}`, { signal });
1512
+ }
1185
1513
  /**
1186
1514
  * The values a preset's `constraints` may take. Ask rather than hardcode: the
1187
1515
  * region list comes from the daemon's embedded persona model, so a pinned copy
@@ -1238,6 +1566,18 @@ var ScalebrowserClient = class extends ClientCore {
1238
1566
  signal
1239
1567
  });
1240
1568
  }
1569
+ // ── Exit-address exclusivity (DEC-017) ──────────────────────────────────────
1570
+ /** Which exit addresses this profile owns, and how far the rule reaches.
1571
+ *
1572
+ * `scope` is `'machine'` while this computer has no account key. That is the
1573
+ * ordinary state of a second machine until sync is unlocked there once, and it is
1574
+ * reported rather than hidden: believing the rule spans three machines when it
1575
+ * spans one is worse than knowing. */
1576
+ getExitStatus(profileId, signal) {
1577
+ return this.request("GET", `/v1/profiles/${enc(profileId)}/exit`, void 0, {
1578
+ signal
1579
+ });
1580
+ }
1241
1581
  // ── Session bundles (AC-ST-002/003) ─────────────────────────────────────────
1242
1582
  exportSession(profileId, body) {
1243
1583
  return this.request("POST", `/v1/profiles/${enc(profileId)}/session/export`, body);
@@ -1317,7 +1657,14 @@ function deriveMetricsFromProfiles(profiles) {
1317
1657
  profiles: []
1318
1658
  };
1319
1659
  }
1320
- applyMixins(ScalebrowserClient, [IdentityApi, RunsApi, ControlApi, ExtensionsApi]);
1660
+ applyMixins(ScalebrowserClient, [
1661
+ IdentityApi,
1662
+ RunsApi,
1663
+ VideosApi,
1664
+ ControlApi,
1665
+ ExtensionsApi,
1666
+ TasksApi
1667
+ ]);
1321
1668
 
1322
1669
  // src/types.ts
1323
1670
  var RUNTIME_STATES = ["stopped", "starting", "running", "crashed"];
@@ -1327,7 +1674,7 @@ var PROXY_KINDS = ["http", "socks5", "gateway"];
1327
1674
  var ROTATIONS = ["sticky", "rotating"];
1328
1675
 
1329
1676
  // src/index.ts
1330
- var VERSION = "0.3.0";
1677
+ var VERSION = "0.5.0";
1331
1678
 
1332
1679
  exports.ApiError = ApiError;
1333
1680
  exports.CdpError = CdpError;