@lizard-build/sdk 0.1.71 → 0.1.73

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.js CHANGED
@@ -27,10 +27,26 @@ __export(index_exports, {
27
27
  CodeSandbox: () => CodeSandbox,
28
28
  ConfigApplyError: () => ConfigApplyError,
29
29
  ConflictError: () => ConflictError,
30
+ DEFAULT_GIT_URL: () => DEFAULT_GIT_URL,
30
31
  DeployHandle: () => DeployHandle,
31
32
  Desktop: () => Desktop,
32
33
  DomainsAPI: () => DomainsAPI,
34
+ GitAPI: () => GitAPI,
35
+ GitActionsAPI: () => GitActionsAPI,
36
+ GitBranchRulesAPI: () => GitBranchRulesAPI,
37
+ GitClient: () => GitClient,
38
+ GitCollaboratorsAPI: () => GitCollaboratorsAPI,
33
39
  GitHubAPI: () => GitHubAPI,
40
+ GitIssuesOnlyAPI: () => GitIssuesOnlyAPI,
41
+ GitLabelsAPI: () => GitLabelsAPI,
42
+ GitNotificationsAPI: () => GitNotificationsAPI,
43
+ GitPullsAPI: () => GitPullsAPI,
44
+ GitReleasesAPI: () => GitReleasesAPI,
45
+ GitReposAPI: () => GitReposAPI,
46
+ GitSecretsAPI: () => GitSecretsAPI,
47
+ GitStatusesAPI: () => GitStatusesAPI,
48
+ GitTokensAPI: () => GitTokensAPI,
49
+ GitWebhooksAPI: () => GitWebhooksAPI,
34
50
  LimitExceededError: () => LimitExceededError,
35
51
  Lizard: () => Lizard,
36
52
  LizardCLI: () => LizardCLI,
@@ -179,9 +195,9 @@ async function handleApiError(res) {
179
195
  // src/platform/client.ts
180
196
  var segment = (value) => encodeURIComponent(value);
181
197
  function query(path, values) {
182
- const qs = new URLSearchParams();
183
- for (const [key, value] of Object.entries(values)) if (value !== void 0) qs.set(key, String(value));
184
- return qs.size ? `${path}?${qs}` : path;
198
+ const qs2 = new URLSearchParams();
199
+ for (const [key, value] of Object.entries(values)) if (value !== void 0) qs2.set(key, String(value));
200
+ return qs2.size ? `${path}?${qs2}` : path;
185
201
  }
186
202
  var PlatformClient = class {
187
203
  constructor(opts) {
@@ -535,6 +551,525 @@ var GitHubAPI = class {
535
551
  }
536
552
  };
537
553
 
554
+ // src/platform/git.ts
555
+ var DEFAULT_GIT_URL = "https://git.lizard.build";
556
+ var seg = encodeURIComponent;
557
+ function qs(path, values) {
558
+ const q = new URLSearchParams();
559
+ for (const [k, v] of Object.entries(values)) if (v !== void 0 && v !== "") q.set(k, String(v));
560
+ return q.size ? `${path}?${q}` : path;
561
+ }
562
+ function repoPath(repo) {
563
+ const m = /^([^/\s]+)\/([^/\s]+?)(\.git)?$/.exec(repo);
564
+ if (!m) throw new Error(`repository must be "owner/name", got "${repo}"`);
565
+ return `/api/repos/${seg(m[1])}/${seg(m[2])}`;
566
+ }
567
+ var GitClient = class {
568
+ constructor(config, url) {
569
+ this.config = config;
570
+ this.url = (url ?? process.env.LIZARD_GIT_URL ?? DEFAULT_GIT_URL).replace(/\/+$/, "");
571
+ }
572
+ async raw(method, path, body, headers = {}) {
573
+ const res = await fetch(this.url + path, {
574
+ method,
575
+ body,
576
+ headers: { "X-API-Key": this.config.apiKey, ...headers },
577
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs)
578
+ });
579
+ if (!res.ok) await handleApiError(res);
580
+ return res;
581
+ }
582
+ async request(method, path, body, headers = {}) {
583
+ const res = await this.raw(
584
+ method,
585
+ path,
586
+ body === void 0 ? void 0 : JSON.stringify(body),
587
+ body === void 0 ? headers : { "Content-Type": "application/json", ...headers }
588
+ );
589
+ const text = res.status === 204 ? "" : await res.text();
590
+ return text ? JSON.parse(text) : void 0;
591
+ }
592
+ get(path) {
593
+ return this.request("GET", path);
594
+ }
595
+ post(path, body) {
596
+ return this.request("POST", path, body ?? {});
597
+ }
598
+ put(path, body) {
599
+ return this.request("PUT", path, body ?? {});
600
+ }
601
+ patch(path, body) {
602
+ return this.request("PATCH", path, body);
603
+ }
604
+ delete(path) {
605
+ return this.request("DELETE", path);
606
+ }
607
+ async bytes(path) {
608
+ return new Uint8Array(await (await this.raw("GET", path)).arrayBuffer());
609
+ }
610
+ };
611
+ var GitReposAPI = class {
612
+ constructor(c) {
613
+ this.c = c;
614
+ }
615
+ /** Yours and the ones you collaborate on by default; `owner`, `q`, `starred` narrow it. */
616
+ list(opts = {}) {
617
+ return this.c.get(qs("/api/repos", opts));
618
+ }
619
+ /** Answers the new repository's `owner` and `name`. */
620
+ create(opts) {
621
+ return this.c.post("/api/repos", opts);
622
+ }
623
+ get(repo) {
624
+ return this.c.get(repoPath(repo));
625
+ }
626
+ update(repo, opts) {
627
+ return this.c.patch(repoPath(repo), opts);
628
+ }
629
+ delete(repo) {
630
+ return this.c.delete(repoPath(repo));
631
+ }
632
+ star(repo) {
633
+ return this.c.put(`${repoPath(repo)}/star`);
634
+ }
635
+ unstar(repo) {
636
+ return this.c.delete(`${repoPath(repo)}/star`);
637
+ }
638
+ /** Fork into your account (`name` defaults to the same name). */
639
+ fork(repo, opts = {}) {
640
+ return this.c.post(`${repoPath(repo)}/forks`, opts);
641
+ }
642
+ forks(repo) {
643
+ return this.c.get(`${repoPath(repo)}/forks`);
644
+ }
645
+ /** Branches and tags. */
646
+ refs(repo) {
647
+ return this.c.get(`${repoPath(repo)}/refs`);
648
+ }
649
+ createBranch(repo, name, from) {
650
+ return this.c.post(`${repoPath(repo)}/branches`, { name, from });
651
+ }
652
+ deleteBranch(repo, name) {
653
+ return this.c.delete(`${repoPath(repo)}/branches/${name.split("/").map(seg).join("/")}`);
654
+ }
655
+ tree(repo, opts = {}) {
656
+ return this.c.get(qs(`${repoPath(repo)}/tree`, opts));
657
+ }
658
+ /** A file as JSON (text in `content`). */
659
+ file(repo, path, ref) {
660
+ return this.c.get(qs(`${repoPath(repo)}/blob`, { path, ref }));
661
+ }
662
+ /** A file's bytes, whatever its size or type. */
663
+ raw(repo, path, ref) {
664
+ const [owner, name] = repo.split("/");
665
+ return this.c.bytes(`/raw/${seg(owner)}/${seg(name)}/${ref.split("/").map(seg).join("/")}/${path.split("/").map(seg).join("/")}`);
666
+ }
667
+ /** Every file path at a ref. */
668
+ files(repo, ref) {
669
+ return this.c.get(qs(`${repoPath(repo)}/files`, { ref }));
670
+ }
671
+ search(repo, q, ref) {
672
+ return this.c.get(qs(`${repoPath(repo)}/search`, { q, ref }));
673
+ }
674
+ commits(repo, opts = {}) {
675
+ return this.c.get(qs(`${repoPath(repo)}/commits`, opts));
676
+ }
677
+ commit(repo, sha) {
678
+ return this.c.get(`${repoPath(repo)}/commits/${seg(sha)}`);
679
+ }
680
+ compare(repo, base, head) {
681
+ return this.c.get(qs(`${repoPath(repo)}/compare`, { base, head }));
682
+ }
683
+ /** Commit files on the server: no clone, no push. Answers 409 when `expect` no longer matches. */
684
+ commitFiles(repo, opts) {
685
+ const { idempotencyKey, ...body } = opts;
686
+ return this.c.request("POST", `${repoPath(repo)}/commits`, body, idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {});
687
+ }
688
+ /** Rebase `head` (branch or commit) onto `onto` on the server and move `onto` there; retried on races. */
689
+ land(repo, opts) {
690
+ return this.c.post(`${repoPath(repo)}/land`, opts);
691
+ }
692
+ /** Who moved which branch, when. */
693
+ reflog(repo, ref) {
694
+ return this.c.get(qs(`${repoPath(repo)}/reflog`, { ref }));
695
+ }
696
+ archive(repo, ref, format = "tar.gz") {
697
+ return this.c.bytes(`${repoPath(repo)}/archive/${seg(ref)}.${format}`);
698
+ }
699
+ /** The HTTPS clone URL. With `withKey`, it carries the API key, so plain `git clone` works without a helper. */
700
+ cloneUrl(repo, opts = {}) {
701
+ repoPath(repo);
702
+ const u = new URL(`${this.c.url}/${repo.replace(/\.git$/, "")}.git`);
703
+ if (opts.withKey) {
704
+ u.username = "lizard";
705
+ u.password = this.c.config.apiKey;
706
+ }
707
+ return u.toString();
708
+ }
709
+ };
710
+ var GitIssuesAPI = class {
711
+ constructor(c, pulls = false) {
712
+ this.c = c;
713
+ this.pulls = pulls;
714
+ }
715
+ base(repo) {
716
+ return `${repoPath(repo)}/issues`;
717
+ }
718
+ list(repo, opts = {}) {
719
+ return this.c.get(qs(this.base(repo), { ...opts, pulls: this.pulls ? 1 : void 0 }));
720
+ }
721
+ get(repo, number) {
722
+ return this.c.get(`${this.base(repo)}/${number}`);
723
+ }
724
+ update(repo, number, opts) {
725
+ return this.c.patch(`${this.base(repo)}/${number}`, opts);
726
+ }
727
+ comment(repo, number, body) {
728
+ return this.c.post(`${this.base(repo)}/${number}/comments`, { body });
729
+ }
730
+ close(repo, number) {
731
+ return this.update(repo, number, { state: "closed" });
732
+ }
733
+ reopen(repo, number) {
734
+ return this.update(repo, number, { state: "open" });
735
+ }
736
+ };
737
+ var GitIssuesOnlyAPI = class extends GitIssuesAPI {
738
+ constructor(c) {
739
+ super(c, false);
740
+ }
741
+ create(repo, opts) {
742
+ return this.c.post(this.base(repo), opts);
743
+ }
744
+ };
745
+ var GitPullsAPI = class extends GitIssuesAPI {
746
+ constructor(c) {
747
+ super(c, true);
748
+ }
749
+ create(repo, opts) {
750
+ return this.c.post(this.base(repo), opts);
751
+ }
752
+ /** Mark a draft ready for review. */
753
+ ready(repo, number) {
754
+ return this.update(repo, number, { draft: false });
755
+ }
756
+ merge(repo, number, opts = {}) {
757
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/merge`, opts);
758
+ }
759
+ diff(repo, number) {
760
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/diff`);
761
+ }
762
+ commits(repo, number) {
763
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/commits`);
764
+ }
765
+ reviews(repo, number) {
766
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/reviews`);
767
+ }
768
+ review(repo, number, opts) {
769
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/reviews`, opts);
770
+ }
771
+ approve(repo, number, body = "") {
772
+ return this.review(repo, number, { event: "APPROVE", body });
773
+ }
774
+ requestChanges(repo, number, body) {
775
+ return this.review(repo, number, { event: "REQUEST_CHANGES", body });
776
+ }
777
+ lineComment(repo, number, c) {
778
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/comments`, c);
779
+ }
780
+ deleteLineComment(repo, number, id) {
781
+ return this.c.delete(`${repoPath(repo)}/pulls/${number}/comments/${id}`);
782
+ }
783
+ /** The combined commit status of the pull request's head (CI and external checks). */
784
+ async checks(repo, number) {
785
+ const { pull } = await this.get(repo, number);
786
+ if (!pull?.headSha) throw new Error(`pull request #${number} has no head commit`);
787
+ return this.c.get(`${repoPath(repo)}/commits/${pull.headSha}/status`);
788
+ }
789
+ };
790
+ var GitSecretsAPI = class {
791
+ constructor(c) {
792
+ this.c = c;
793
+ }
794
+ list(repo) {
795
+ return this.c.get(`${repoPath(repo)}/actions/secrets`);
796
+ }
797
+ set(repo, name, value) {
798
+ return this.c.put(`${repoPath(repo)}/actions/secrets/${seg(name)}`, { value });
799
+ }
800
+ delete(repo, name) {
801
+ return this.c.delete(`${repoPath(repo)}/actions/secrets/${seg(name)}`);
802
+ }
803
+ };
804
+ var GitActionsAPI = class {
805
+ constructor(c) {
806
+ this.c = c;
807
+ this.secrets = new GitSecretsAPI(c);
808
+ }
809
+ runs(repo, opts = {}) {
810
+ return this.c.get(qs(`${repoPath(repo)}/actions/runs`, { ...opts }));
811
+ }
812
+ run(repo, number) {
813
+ return this.c.get(`${repoPath(repo)}/actions/runs/${number}`);
814
+ }
815
+ cancel(repo, number) {
816
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/cancel`);
817
+ }
818
+ /** Run the same workflow at the same commit again (a new run). */
819
+ rerun(repo, number) {
820
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/rerun`);
821
+ }
822
+ /** Let a run from a fork's pull request start (it waits for someone with write access). */
823
+ approve(repo, number) {
824
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/approve`);
825
+ }
826
+ /** Part of a job's log from `offset`; follow `next` until `done`. */
827
+ log(repo, jobId, offset = 0) {
828
+ return this.c.get(qs(`${repoPath(repo)}/actions/jobs/${jobId}/log`, { offset }));
829
+ }
830
+ async fullLog(repo, jobId) {
831
+ return new TextDecoder().decode(await this.c.bytes(`${repoPath(repo)}/actions/jobs/${jobId}/log.txt`));
832
+ }
833
+ /** Each new piece of the log as it's written, until the job ends. */
834
+ async *followLog(repo, jobId, opts = {}) {
835
+ let offset = 0;
836
+ while (!opts.signal?.aborted) {
837
+ const chunk = await this.log(repo, jobId, offset);
838
+ if (chunk.text) yield chunk.text;
839
+ offset = chunk.next;
840
+ if (chunk.done && offset >= chunk.size) return;
841
+ if (!chunk.text) await new Promise((r) => setTimeout(r, opts.intervalMs ?? 1500));
842
+ }
843
+ }
844
+ /** Wait until the run ends (success, failure, cancelled, ...). */
845
+ async wait(repo, number, opts = {}) {
846
+ const until = Date.now() + (opts.timeoutMs ?? 30 * 6e4);
847
+ for (; ; ) {
848
+ const run = await this.run(repo, number);
849
+ if (!["queued", "running", "waiting", "pending"].includes(run.status)) return run;
850
+ if (Date.now() > until) throw new Error(`run ${number} is still ${run.status}`);
851
+ await new Promise((r) => setTimeout(r, opts.intervalMs ?? 3e3));
852
+ }
853
+ }
854
+ artifacts(repo, number) {
855
+ return this.c.get(`${repoPath(repo)}/actions/runs/${number}/artifacts`);
856
+ }
857
+ /** An artifact as a .tar.gz. */
858
+ downloadArtifact(repo, number, name) {
859
+ return this.c.bytes(`${repoPath(repo)}/actions/runs/${number}/artifacts/${seg(name)}`);
860
+ }
861
+ workflows(repo) {
862
+ return this.c.get(`${repoPath(repo)}/actions/workflows`);
863
+ }
864
+ /** Start a workflow that has `on: workflow_dispatch`. */
865
+ dispatch(repo, opts) {
866
+ return this.c.post(`${repoPath(repo)}/actions/workflows/dispatch`, opts);
867
+ }
868
+ /** Latest run status per commit, for the given commits. */
869
+ status(repo, shas) {
870
+ return this.c.get(qs(`${repoPath(repo)}/actions/status`, { shas: shas.join(",") }));
871
+ }
872
+ };
873
+ var GitCollaboratorsAPI = class {
874
+ constructor(c) {
875
+ this.c = c;
876
+ }
877
+ list(repo) {
878
+ return this.c.get(`${repoPath(repo)}/collaborators`);
879
+ }
880
+ /** Invite someone with a Lizard Git account (they sign in once first); they're in once they accept. */
881
+ invite(repo, login, role = "write") {
882
+ return this.c.post(`${repoPath(repo)}/collaborators`, { login, role });
883
+ }
884
+ setRole(repo, login, role) {
885
+ return this.c.patch(`${repoPath(repo)}/collaborators/${seg(login)}`, { role });
886
+ }
887
+ /** Remove someone (or yourself: leave). */
888
+ remove(repo, login) {
889
+ return this.c.delete(`${repoPath(repo)}/collaborators/${seg(login)}`);
890
+ }
891
+ cancelInvitation(repo, id) {
892
+ return this.c.delete(`${repoPath(repo)}/invitations/${id}`);
893
+ }
894
+ /** Invitations waiting for you. */
895
+ invitations() {
896
+ return this.c.get("/api/user/invitations");
897
+ }
898
+ accept(id) {
899
+ return this.c.post(`/api/user/invitations/${id}/accept`);
900
+ }
901
+ decline(id) {
902
+ return this.c.post(`/api/user/invitations/${id}/decline`);
903
+ }
904
+ };
905
+ var GitWebhooksAPI = class {
906
+ constructor(c) {
907
+ this.c = c;
908
+ }
909
+ list(repo) {
910
+ return this.c.get(`${repoPath(repo)}/hooks`);
911
+ }
912
+ /** GitHub-style payloads, signed with `X-Hub-Signature-256` when there's a secret. */
913
+ create(repo, opts) {
914
+ return this.c.post(`${repoPath(repo)}/hooks`, opts);
915
+ }
916
+ get(repo, id) {
917
+ return this.c.get(`${repoPath(repo)}/hooks/${id}`);
918
+ }
919
+ update(repo, id, opts) {
920
+ return this.c.patch(`${repoPath(repo)}/hooks/${id}`, opts);
921
+ }
922
+ delete(repo, id) {
923
+ return this.c.delete(`${repoPath(repo)}/hooks/${id}`);
924
+ }
925
+ ping(repo, id) {
926
+ return this.c.post(`${repoPath(repo)}/hooks/${id}/pings`);
927
+ }
928
+ deliveries(repo, id) {
929
+ return this.c.get(`${repoPath(repo)}/hooks/${id}/deliveries`);
930
+ }
931
+ delivery(repo, id, deliveryId) {
932
+ return this.c.get(`${repoPath(repo)}/hooks/${id}/deliveries/${deliveryId}`);
933
+ }
934
+ redeliver(repo, id, deliveryId) {
935
+ return this.c.post(`${repoPath(repo)}/hooks/${id}/deliveries/${deliveryId}/redeliver`);
936
+ }
937
+ };
938
+ var GitBranchRulesAPI = class {
939
+ constructor(c) {
940
+ this.c = c;
941
+ }
942
+ list(repo) {
943
+ return this.c.get(`${repoPath(repo)}/branch-rules`);
944
+ }
945
+ create(repo, opts) {
946
+ return this.c.post(`${repoPath(repo)}/branch-rules`, opts);
947
+ }
948
+ update(repo, id, opts) {
949
+ return this.c.patch(`${repoPath(repo)}/branch-rules/${id}`, opts);
950
+ }
951
+ delete(repo, id) {
952
+ return this.c.delete(`${repoPath(repo)}/branch-rules/${id}`);
953
+ }
954
+ };
955
+ var GitLabelsAPI = class {
956
+ constructor(c) {
957
+ this.c = c;
958
+ }
959
+ list(repo) {
960
+ return this.c.get(`${repoPath(repo)}/labels`);
961
+ }
962
+ create(repo, opts) {
963
+ return this.c.post(`${repoPath(repo)}/labels`, opts);
964
+ }
965
+ update(repo, name, opts) {
966
+ return this.c.patch(`${repoPath(repo)}/labels/${seg(name)}`, opts);
967
+ }
968
+ delete(repo, name) {
969
+ return this.c.delete(`${repoPath(repo)}/labels/${seg(name)}`);
970
+ }
971
+ };
972
+ var GitReleasesAPI = class {
973
+ constructor(c) {
974
+ this.c = c;
975
+ }
976
+ list(repo) {
977
+ return this.c.get(`${repoPath(repo)}/releases`);
978
+ }
979
+ /** Creates the tag too when it doesn't exist (at `target`, default branch by default). */
980
+ create(repo, opts) {
981
+ return this.c.post(`${repoPath(repo)}/releases`, opts);
982
+ }
983
+ get(repo, id) {
984
+ return this.c.get(`${repoPath(repo)}/releases/${id}`);
985
+ }
986
+ getByTag(repo, tag) {
987
+ return this.c.get(`${repoPath(repo)}/releases/tags/${seg(tag)}`);
988
+ }
989
+ update(repo, id, opts) {
990
+ return this.c.patch(`${repoPath(repo)}/releases/${id}`, opts);
991
+ }
992
+ delete(repo, id) {
993
+ return this.c.delete(`${repoPath(repo)}/releases/${id}`);
994
+ }
995
+ async uploadAsset(repo, id, name, data, contentType = "application/octet-stream") {
996
+ const body = typeof data === "string" || data instanceof Blob ? data : new Blob([new Uint8Array(data)]);
997
+ const res = await this.c.raw("POST", qs(`${repoPath(repo)}/releases/${id}/assets`, { name }), body, { "Content-Type": contentType });
998
+ return res.json();
999
+ }
1000
+ downloadAsset(repo, id, assetId) {
1001
+ return this.c.bytes(`${repoPath(repo)}/releases/${id}/assets/${assetId}`);
1002
+ }
1003
+ deleteAsset(repo, id, assetId) {
1004
+ return this.c.delete(`${repoPath(repo)}/releases/${id}/assets/${assetId}`);
1005
+ }
1006
+ };
1007
+ var GitStatusesAPI = class {
1008
+ constructor(c) {
1009
+ this.c = c;
1010
+ }
1011
+ /** Report a check on a commit (what required checks in branch rules wait for). */
1012
+ create(repo, sha, status) {
1013
+ return this.c.post(`${repoPath(repo)}/statuses/${seg(sha)}`, status);
1014
+ }
1015
+ list(repo, ref) {
1016
+ return this.c.get(`${repoPath(repo)}/commits/${seg(ref)}/statuses`);
1017
+ }
1018
+ combined(repo, ref) {
1019
+ return this.c.get(`${repoPath(repo)}/commits/${seg(ref)}/status`);
1020
+ }
1021
+ };
1022
+ var GitNotificationsAPI = class {
1023
+ constructor(c) {
1024
+ this.c = c;
1025
+ }
1026
+ list(opts = {}) {
1027
+ return this.c.get(qs("/api/notifications", { all: opts.all ? 1 : void 0 }));
1028
+ }
1029
+ markRead(ids) {
1030
+ return this.c.post("/api/notifications/read", ids?.length ? { ids } : { all: true });
1031
+ }
1032
+ };
1033
+ var GitTokensAPI = class {
1034
+ constructor(c) {
1035
+ this.c = c;
1036
+ }
1037
+ list() {
1038
+ return this.c.get("/api/user/tokens");
1039
+ }
1040
+ /** A Lizard Git token (lzg_...), optionally limited to repos, refs, paths, or read-only. The secret is in `token`, once. */
1041
+ create(opts) {
1042
+ return this.c.post("/api/user/tokens", opts);
1043
+ }
1044
+ delete(id) {
1045
+ return this.c.delete(`/api/user/tokens/${id}`);
1046
+ }
1047
+ };
1048
+ var GitAPI = class {
1049
+ constructor(config, url) {
1050
+ const c = this.client = new GitClient(config, url);
1051
+ this.repos = new GitReposAPI(c);
1052
+ this.issues = new GitIssuesOnlyAPI(c);
1053
+ this.pulls = new GitPullsAPI(c);
1054
+ this.actions = new GitActionsAPI(c);
1055
+ this.collaborators = new GitCollaboratorsAPI(c);
1056
+ this.webhooks = new GitWebhooksAPI(c);
1057
+ this.branchRules = new GitBranchRulesAPI(c);
1058
+ this.labels = new GitLabelsAPI(c);
1059
+ this.releases = new GitReleasesAPI(c);
1060
+ this.statuses = new GitStatusesAPI(c);
1061
+ this.notifications = new GitNotificationsAPI(c);
1062
+ this.tokens = new GitTokensAPI(c);
1063
+ }
1064
+ /** Who the key signs in as on Lizard Git. */
1065
+ me() {
1066
+ return this.client.get("/api/session");
1067
+ }
1068
+ user(login) {
1069
+ return this.client.get(`/api/users/${seg(login)}`);
1070
+ }
1071
+ };
1072
+
538
1073
  // src/project.ts
539
1074
  var import_node_crypto = require("crypto");
540
1075
  var projectIdCache = /* @__PURE__ */ new Map();
@@ -866,7 +1401,7 @@ var Fs = class {
866
1401
  * }, 1000)
867
1402
  * ```
868
1403
  *
869
- * Not available on Firecracker sandboxes yet: the call throws `LizardError` (501).
1404
+ * Not available yet: the call throws `LizardError` (501).
870
1405
  */
871
1406
  async watch(path, opts) {
872
1407
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/watch`, {
@@ -1331,7 +1866,7 @@ var Sandbox = class extends SandboxClient {
1331
1866
  * snapshot. A Firecracker snapshot is `ready` as soon as this returns: about 2 s, up to
1332
1867
  * a few minutes for a sandbox that filled most of its memory (its memory is uploaded
1333
1868
  * first), so the call waits up to 11 minutes. Start copies of it with {@link Sandbox.restore}
1334
- * (about 0.4 s each). The source keeps running. `poolSize` applies to container sandboxes only.
1869
+ * (about 0.4 s each). The source keeps running. Firecracker snapshots ignore `poolSize`.
1335
1870
  */
1336
1871
  snapshot(name = `Snapshot ${this.sandboxId}`, opts = {}) {
1337
1872
  return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name, poolSize: opts.poolSize ?? 5, scope: opts.scope }, SNAPSHOT_TIMEOUT_MS);
@@ -1339,7 +1874,7 @@ var Sandbox = class extends SandboxClient {
1339
1874
  unexpose(port) {
1340
1875
  return new PlatformClient(this.resolveOpts()).delete(`/api/sandboxes/${this.sandboxId}/expose/${port}`);
1341
1876
  }
1342
- /** Stream the sandbox's logs. Not available on Firecracker sandboxes yet (`LizardError`, 501). */
1877
+ /** Stream the sandbox's logs. Not available yet (`LizardError`, 501). */
1343
1878
  logs(opts = {}) {
1344
1879
  return new PlatformClient(this.resolveOpts()).events(query(`/api/sandboxes/${this.sandboxId}/logs`, { tail: opts.tail }), { signal: opts.signal });
1345
1880
  }
@@ -1378,7 +1913,7 @@ var Sandbox = class extends SandboxClient {
1378
1913
  static setSnapshotWarmPool(snapshotId, poolSize, opts) {
1379
1914
  return new PlatformClient(opts ?? {}).patch(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`, { poolSize });
1380
1915
  }
1381
- /** Release idle warm copies (container snapshots); retain saved state and existing claimed sandboxes. */
1916
+ /** Release idle warm copies of an older snapshot; retain saved state and existing claimed sandboxes. Firecracker snapshots have no warm copies. */
1382
1917
  static pauseSnapshot(snapshotId, opts) {
1383
1918
  return new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/pause`, {});
1384
1919
  }
@@ -1414,8 +1949,8 @@ var ProjectsAPI = class {
1414
1949
  }
1415
1950
  /** List all projects the API key can access. */
1416
1951
  list(opts) {
1417
- const qs = opts?.workspaceId ? `?workspaceId=${opts.workspaceId}` : "";
1418
- return this.client.get(`/api/projects${qs}`);
1952
+ const qs2 = opts?.workspaceId ? `?workspaceId=${opts.workspaceId}` : "";
1953
+ return this.client.get(`/api/projects${qs2}`);
1419
1954
  }
1420
1955
  /** Get a project by ID. */
1421
1956
  get(id) {
@@ -1808,8 +2343,8 @@ var WorkspacesAPI = class {
1808
2343
  * volumes and snapshots first.
1809
2344
  */
1810
2345
  delete(id, opts) {
1811
- const qs = opts?.force ? "" : "?requireEmpty=true";
1812
- return this.client.delete(`/api/workspaces/${id}${qs}`);
2346
+ const qs2 = opts?.force ? "" : "?requireEmpty=true";
2347
+ return this.client.delete(`/api/workspaces/${id}${qs2}`);
1813
2348
  }
1814
2349
  /**
1815
2350
  * Find a workspace by name, slug, or id, or `null` if there is no such workspace.
@@ -1999,13 +2534,11 @@ var Volume = class _Volume {
1999
2534
  /**
2000
2535
  * Resize a volume, by name or by id, to `sizeGb`.
2001
2536
  *
2002
- * A Firecracker volume (the default) can only grow. The change is in place and
2003
- * online: a sandbox with the volume attached keeps running and sees the new size.
2004
- * A smaller size throws `LizardError` (400) with `code: 'volume_shrink_unsupported'`.
2537
+ * A volume grows in place and online: a sandbox with the volume attached keeps
2538
+ * running and sees the new size. A smaller size throws `LizardError` (400) with
2539
+ * `code: 'volume_shrink_unsupported'`.
2005
2540
  *
2006
- * On a container volume the change is in place and online: the size is a quota,
2007
- * so no data is copied and a mounted sandbox sees the new size immediately. Both
2008
- * growing and shrinking are allowed, but a shrink must leave at least 10% of the
2541
+ * An older volume can also shrink, but the shrink must leave at least 10% of the
2009
2542
  * new size free — otherwise it throws `ConflictError` with
2010
2543
  * `code: 'volume_too_full_to_shrink'`.
2011
2544
  *
@@ -2015,7 +2548,7 @@ var Volume = class _Volume {
2015
2548
  * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
2016
2549
  * the size is left unchanged).
2017
2550
  *
2018
- * @returns The volume's updated info, including `sizeEnforced` for a container volume.
2551
+ * @returns The volume's updated info, including `sizeEnforced` for an older volume.
2019
2552
  */
2020
2553
  static async resize(projectId, nameOrId, sizeGb, opts) {
2021
2554
  const config = new ConnectionConfig(opts);
@@ -2093,6 +2626,7 @@ var Lizard = class {
2093
2626
  this.metrics = new MetricsAPI(platform);
2094
2627
  this.storage = new StorageAPI(platform);
2095
2628
  this.github = new GitHubAPI(platform);
2629
+ this.git = new GitAPI(this.config, opts.gitUrl);
2096
2630
  }
2097
2631
  // ── Sandbox convenience methods (backward-compatible) ──────────────────
2098
2632
  /** Resolve the client's project reference to a stable project ID (cached). */
@@ -2705,10 +3239,26 @@ var LizardCLI = class {
2705
3239
  CodeSandbox,
2706
3240
  ConfigApplyError,
2707
3241
  ConflictError,
3242
+ DEFAULT_GIT_URL,
2708
3243
  DeployHandle,
2709
3244
  Desktop,
2710
3245
  DomainsAPI,
3246
+ GitAPI,
3247
+ GitActionsAPI,
3248
+ GitBranchRulesAPI,
3249
+ GitClient,
3250
+ GitCollaboratorsAPI,
2711
3251
  GitHubAPI,
3252
+ GitIssuesOnlyAPI,
3253
+ GitLabelsAPI,
3254
+ GitNotificationsAPI,
3255
+ GitPullsAPI,
3256
+ GitReleasesAPI,
3257
+ GitReposAPI,
3258
+ GitSecretsAPI,
3259
+ GitStatusesAPI,
3260
+ GitTokensAPI,
3261
+ GitWebhooksAPI,
2712
3262
  LimitExceededError,
2713
3263
  Lizard,
2714
3264
  LizardCLI,