@lizard-build/sdk 0.1.72 → 0.1.74

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,537 @@ 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 segs(p) {
563
+ const parts = p.split("/");
564
+ if (parts.some((x) => x === "" || x === "." || x === "..")) throw new Error(`bad path: "${p}"`);
565
+ return parts.map(seg).join("/");
566
+ }
567
+ function repoPath(repo) {
568
+ const m = /^([^/\s]+)\/([^/\s]+?)(\.git)?$/.exec(repo);
569
+ if (!m || [m[1], m[2]].some((x) => x === "." || x === "..")) throw new Error(`repository must be "owner/name", got "${repo}"`);
570
+ return `/api/repos/${seg(m[1])}/${seg(m[2])}`;
571
+ }
572
+ var GitClient = class {
573
+ constructor(config, url) {
574
+ this.config = config;
575
+ this.url = (url ?? process.env.LIZARD_GIT_URL ?? DEFAULT_GIT_URL).replace(/\/+$/, "");
576
+ }
577
+ async raw(method, path, body, headers = {}) {
578
+ const res = await fetch(this.url + path, {
579
+ method,
580
+ body,
581
+ headers: { "X-API-Key": this.config.apiKey, ...headers },
582
+ redirect: "error",
583
+ // a redirect would carry the key to wherever it points
584
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs)
585
+ });
586
+ if (!res.ok) await handleApiError(res);
587
+ return res;
588
+ }
589
+ async request(method, path, body, headers = {}) {
590
+ const res = await this.raw(
591
+ method,
592
+ path,
593
+ body === void 0 ? void 0 : JSON.stringify(body),
594
+ body === void 0 ? headers : { "Content-Type": "application/json", ...headers }
595
+ );
596
+ const text = res.status === 204 ? "" : await res.text();
597
+ return text ? JSON.parse(text) : void 0;
598
+ }
599
+ get(path) {
600
+ return this.request("GET", path);
601
+ }
602
+ post(path, body) {
603
+ return this.request("POST", path, body ?? {});
604
+ }
605
+ put(path, body) {
606
+ return this.request("PUT", path, body ?? {});
607
+ }
608
+ patch(path, body) {
609
+ return this.request("PATCH", path, body);
610
+ }
611
+ delete(path) {
612
+ return this.request("DELETE", path);
613
+ }
614
+ async bytes(path) {
615
+ return new Uint8Array(await (await this.raw("GET", path)).arrayBuffer());
616
+ }
617
+ };
618
+ var GitReposAPI = class {
619
+ constructor(c) {
620
+ this.c = c;
621
+ }
622
+ /** Yours and the ones you collaborate on by default; `owner`, `q`, `starred` narrow it. */
623
+ list(opts = {}) {
624
+ return this.c.get(qs("/api/repos", opts));
625
+ }
626
+ /** Answers the new repository's `owner` and `name`. */
627
+ create(opts) {
628
+ return this.c.post("/api/repos", opts);
629
+ }
630
+ get(repo) {
631
+ return this.c.get(repoPath(repo));
632
+ }
633
+ update(repo, opts) {
634
+ return this.c.patch(repoPath(repo), opts);
635
+ }
636
+ delete(repo) {
637
+ return this.c.delete(repoPath(repo));
638
+ }
639
+ star(repo) {
640
+ return this.c.put(`${repoPath(repo)}/star`);
641
+ }
642
+ unstar(repo) {
643
+ return this.c.delete(`${repoPath(repo)}/star`);
644
+ }
645
+ /** Fork into your account (`name` defaults to the same name). */
646
+ fork(repo, opts = {}) {
647
+ return this.c.post(`${repoPath(repo)}/forks`, opts);
648
+ }
649
+ forks(repo) {
650
+ return this.c.get(`${repoPath(repo)}/forks`);
651
+ }
652
+ /** Branches and tags. */
653
+ refs(repo) {
654
+ return this.c.get(`${repoPath(repo)}/refs`);
655
+ }
656
+ createBranch(repo, name, from) {
657
+ return this.c.post(`${repoPath(repo)}/branches`, { name, from });
658
+ }
659
+ deleteBranch(repo, name) {
660
+ return this.c.delete(`${repoPath(repo)}/branches/${segs(name)}`);
661
+ }
662
+ tree(repo, opts = {}) {
663
+ return this.c.get(qs(`${repoPath(repo)}/tree`, opts));
664
+ }
665
+ /** A file as JSON (text in `content`). */
666
+ file(repo, path, ref) {
667
+ return this.c.get(qs(`${repoPath(repo)}/blob`, { path, ref }));
668
+ }
669
+ /** A file's bytes, whatever its size or type. */
670
+ raw(repo, path, ref) {
671
+ repoPath(repo);
672
+ const [owner, name] = repo.split("/");
673
+ return this.c.bytes(`/raw/${seg(owner)}/${seg(name)}/${segs(ref)}/${segs(path)}`);
674
+ }
675
+ /** Every file path at a ref. */
676
+ files(repo, ref) {
677
+ return this.c.get(qs(`${repoPath(repo)}/files`, { ref }));
678
+ }
679
+ search(repo, q, ref) {
680
+ return this.c.get(qs(`${repoPath(repo)}/search`, { q, ref }));
681
+ }
682
+ commits(repo, opts = {}) {
683
+ return this.c.get(qs(`${repoPath(repo)}/commits`, opts));
684
+ }
685
+ commit(repo, sha) {
686
+ return this.c.get(`${repoPath(repo)}/commits/${seg(sha)}`);
687
+ }
688
+ compare(repo, base, head) {
689
+ return this.c.get(qs(`${repoPath(repo)}/compare`, { base, head }));
690
+ }
691
+ /** Commit files on the server: no clone, no push. Answers 409 when `expect` no longer matches. */
692
+ commitFiles(repo, opts) {
693
+ const { idempotencyKey, ...body } = opts;
694
+ return this.c.request("POST", `${repoPath(repo)}/commits`, body, idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {});
695
+ }
696
+ /** Rebase `head` (branch or commit) onto `onto` on the server and move `onto` there; retried on races. */
697
+ land(repo, opts) {
698
+ return this.c.post(`${repoPath(repo)}/land`, opts);
699
+ }
700
+ /** Who moved which branch, when. */
701
+ reflog(repo, ref) {
702
+ return this.c.get(qs(`${repoPath(repo)}/reflog`, { ref }));
703
+ }
704
+ archive(repo, ref, format = "tar.gz") {
705
+ return this.c.bytes(`${repoPath(repo)}/archive/${seg(ref)}.${format}`);
706
+ }
707
+ /**
708
+ * The HTTPS clone URL. With `withKey`, it carries the API key, so plain `git clone` works
709
+ * without a helper; git then keeps it in .git/config. For anything shared or logged, use a
710
+ * limited Lizard Git token (`tokens.create`) as `apiKey` instead of the account's key.
711
+ */
712
+ cloneUrl(repo, opts = {}) {
713
+ repoPath(repo);
714
+ const u = new URL(`${this.c.url}/${repo.replace(/\.git$/, "")}.git`);
715
+ if (opts.withKey) {
716
+ u.username = "lizard";
717
+ u.password = this.c.config.apiKey;
718
+ }
719
+ return u.toString();
720
+ }
721
+ };
722
+ var GitIssuesAPI = class {
723
+ constructor(c, pulls = false) {
724
+ this.c = c;
725
+ this.pulls = pulls;
726
+ }
727
+ base(repo) {
728
+ return `${repoPath(repo)}/issues`;
729
+ }
730
+ list(repo, opts = {}) {
731
+ return this.c.get(qs(this.base(repo), { ...opts, pulls: this.pulls ? 1 : void 0 }));
732
+ }
733
+ get(repo, number) {
734
+ return this.c.get(`${this.base(repo)}/${number}`);
735
+ }
736
+ update(repo, number, opts) {
737
+ return this.c.patch(`${this.base(repo)}/${number}`, opts);
738
+ }
739
+ comment(repo, number, body) {
740
+ return this.c.post(`${this.base(repo)}/${number}/comments`, { body });
741
+ }
742
+ close(repo, number) {
743
+ return this.update(repo, number, { state: "closed" });
744
+ }
745
+ reopen(repo, number) {
746
+ return this.update(repo, number, { state: "open" });
747
+ }
748
+ };
749
+ var GitIssuesOnlyAPI = class extends GitIssuesAPI {
750
+ constructor(c) {
751
+ super(c, false);
752
+ }
753
+ create(repo, opts) {
754
+ return this.c.post(this.base(repo), opts);
755
+ }
756
+ };
757
+ var GitPullsAPI = class extends GitIssuesAPI {
758
+ constructor(c) {
759
+ super(c, true);
760
+ }
761
+ create(repo, opts) {
762
+ return this.c.post(this.base(repo), opts);
763
+ }
764
+ /** Mark a draft ready for review. */
765
+ ready(repo, number) {
766
+ return this.update(repo, number, { draft: false });
767
+ }
768
+ merge(repo, number, opts = {}) {
769
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/merge`, opts);
770
+ }
771
+ diff(repo, number) {
772
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/diff`);
773
+ }
774
+ commits(repo, number) {
775
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/commits`);
776
+ }
777
+ reviews(repo, number) {
778
+ return this.c.get(`${repoPath(repo)}/pulls/${number}/reviews`);
779
+ }
780
+ review(repo, number, opts) {
781
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/reviews`, opts);
782
+ }
783
+ approve(repo, number, body = "") {
784
+ return this.review(repo, number, { event: "APPROVE", body });
785
+ }
786
+ requestChanges(repo, number, body) {
787
+ return this.review(repo, number, { event: "REQUEST_CHANGES", body });
788
+ }
789
+ lineComment(repo, number, c) {
790
+ return this.c.post(`${repoPath(repo)}/pulls/${number}/comments`, c);
791
+ }
792
+ deleteLineComment(repo, number, id) {
793
+ return this.c.delete(`${repoPath(repo)}/pulls/${number}/comments/${id}`);
794
+ }
795
+ /** The combined commit status of the pull request's head (CI and external checks). */
796
+ async checks(repo, number) {
797
+ const { pull } = await this.get(repo, number);
798
+ if (!pull?.headSha) throw new Error(`pull request #${number} has no head commit`);
799
+ return this.c.get(`${repoPath(repo)}/commits/${pull.headSha}/status`);
800
+ }
801
+ };
802
+ var GitSecretsAPI = class {
803
+ constructor(c) {
804
+ this.c = c;
805
+ }
806
+ list(repo) {
807
+ return this.c.get(`${repoPath(repo)}/actions/secrets`);
808
+ }
809
+ set(repo, name, value) {
810
+ return this.c.put(`${repoPath(repo)}/actions/secrets/${seg(name)}`, { value });
811
+ }
812
+ delete(repo, name) {
813
+ return this.c.delete(`${repoPath(repo)}/actions/secrets/${seg(name)}`);
814
+ }
815
+ };
816
+ var GitActionsAPI = class {
817
+ constructor(c) {
818
+ this.c = c;
819
+ this.secrets = new GitSecretsAPI(c);
820
+ }
821
+ runs(repo, opts = {}) {
822
+ return this.c.get(qs(`${repoPath(repo)}/actions/runs`, { ...opts }));
823
+ }
824
+ run(repo, number) {
825
+ return this.c.get(`${repoPath(repo)}/actions/runs/${number}`);
826
+ }
827
+ cancel(repo, number) {
828
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/cancel`);
829
+ }
830
+ /** Run the same workflow at the same commit again (a new run). */
831
+ rerun(repo, number) {
832
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/rerun`);
833
+ }
834
+ /** Let a run from a fork's pull request start (it waits for someone with write access). */
835
+ approve(repo, number) {
836
+ return this.c.post(`${repoPath(repo)}/actions/runs/${number}/approve`);
837
+ }
838
+ /** Part of a job's log from `offset`; follow `next` until `done`. */
839
+ log(repo, jobId, offset = 0) {
840
+ return this.c.get(qs(`${repoPath(repo)}/actions/jobs/${jobId}/log`, { offset }));
841
+ }
842
+ async fullLog(repo, jobId) {
843
+ return new TextDecoder().decode(await this.c.bytes(`${repoPath(repo)}/actions/jobs/${jobId}/log.txt`));
844
+ }
845
+ /** Each new piece of the log as it's written, until the job ends. */
846
+ async *followLog(repo, jobId, opts = {}) {
847
+ let offset = 0;
848
+ while (!opts.signal?.aborted) {
849
+ const chunk = await this.log(repo, jobId, offset);
850
+ if (chunk.text) yield chunk.text;
851
+ offset = chunk.next;
852
+ if (chunk.done && offset >= chunk.size) return;
853
+ if (!chunk.text) await new Promise((r) => setTimeout(r, opts.intervalMs ?? 1500));
854
+ }
855
+ }
856
+ /** Wait until the run ends (success, failure, cancelled, ...). */
857
+ async wait(repo, number, opts = {}) {
858
+ const until = Date.now() + (opts.timeoutMs ?? 30 * 6e4);
859
+ for (; ; ) {
860
+ const run = await this.run(repo, number);
861
+ if (!["queued", "running", "waiting", "pending"].includes(run.status)) return run;
862
+ if (Date.now() > until) throw new Error(`run ${number} is still ${run.status}`);
863
+ await new Promise((r) => setTimeout(r, opts.intervalMs ?? 3e3));
864
+ }
865
+ }
866
+ artifacts(repo, number) {
867
+ return this.c.get(`${repoPath(repo)}/actions/runs/${number}/artifacts`);
868
+ }
869
+ /** An artifact as a .tar.gz. */
870
+ downloadArtifact(repo, number, name) {
871
+ return this.c.bytes(`${repoPath(repo)}/actions/runs/${number}/artifacts/${seg(name)}`);
872
+ }
873
+ workflows(repo) {
874
+ return this.c.get(`${repoPath(repo)}/actions/workflows`);
875
+ }
876
+ /** Start a workflow that has `on: workflow_dispatch`. */
877
+ dispatch(repo, opts) {
878
+ return this.c.post(`${repoPath(repo)}/actions/workflows/dispatch`, opts);
879
+ }
880
+ /** Latest run status per commit, for the given commits. */
881
+ status(repo, shas) {
882
+ return this.c.get(qs(`${repoPath(repo)}/actions/status`, { shas: shas.join(",") }));
883
+ }
884
+ };
885
+ var GitCollaboratorsAPI = class {
886
+ constructor(c) {
887
+ this.c = c;
888
+ }
889
+ list(repo) {
890
+ return this.c.get(`${repoPath(repo)}/collaborators`);
891
+ }
892
+ /** Invite someone with a Lizard Git account (they sign in once first); they're in once they accept. */
893
+ invite(repo, login, role = "write") {
894
+ return this.c.post(`${repoPath(repo)}/collaborators`, { login, role });
895
+ }
896
+ setRole(repo, login, role) {
897
+ return this.c.patch(`${repoPath(repo)}/collaborators/${seg(login)}`, { role });
898
+ }
899
+ /** Remove someone (or yourself: leave). */
900
+ remove(repo, login) {
901
+ return this.c.delete(`${repoPath(repo)}/collaborators/${seg(login)}`);
902
+ }
903
+ cancelInvitation(repo, id) {
904
+ return this.c.delete(`${repoPath(repo)}/invitations/${id}`);
905
+ }
906
+ /** Invitations waiting for you. */
907
+ invitations() {
908
+ return this.c.get("/api/user/invitations");
909
+ }
910
+ accept(id) {
911
+ return this.c.post(`/api/user/invitations/${id}/accept`);
912
+ }
913
+ decline(id) {
914
+ return this.c.post(`/api/user/invitations/${id}/decline`);
915
+ }
916
+ };
917
+ var GitWebhooksAPI = class {
918
+ constructor(c) {
919
+ this.c = c;
920
+ }
921
+ list(repo) {
922
+ return this.c.get(`${repoPath(repo)}/hooks`);
923
+ }
924
+ /** GitHub-style payloads, signed with `X-Hub-Signature-256` when there's a secret. */
925
+ create(repo, opts) {
926
+ return this.c.post(`${repoPath(repo)}/hooks`, opts);
927
+ }
928
+ get(repo, id) {
929
+ return this.c.get(`${repoPath(repo)}/hooks/${id}`);
930
+ }
931
+ update(repo, id, opts) {
932
+ return this.c.patch(`${repoPath(repo)}/hooks/${id}`, opts);
933
+ }
934
+ delete(repo, id) {
935
+ return this.c.delete(`${repoPath(repo)}/hooks/${id}`);
936
+ }
937
+ ping(repo, id) {
938
+ return this.c.post(`${repoPath(repo)}/hooks/${id}/pings`);
939
+ }
940
+ deliveries(repo, id) {
941
+ return this.c.get(`${repoPath(repo)}/hooks/${id}/deliveries`);
942
+ }
943
+ delivery(repo, id, deliveryId) {
944
+ return this.c.get(`${repoPath(repo)}/hooks/${id}/deliveries/${deliveryId}`);
945
+ }
946
+ redeliver(repo, id, deliveryId) {
947
+ return this.c.post(`${repoPath(repo)}/hooks/${id}/deliveries/${deliveryId}/redeliver`);
948
+ }
949
+ };
950
+ var GitBranchRulesAPI = class {
951
+ constructor(c) {
952
+ this.c = c;
953
+ }
954
+ list(repo) {
955
+ return this.c.get(`${repoPath(repo)}/branch-rules`);
956
+ }
957
+ create(repo, opts) {
958
+ return this.c.post(`${repoPath(repo)}/branch-rules`, opts);
959
+ }
960
+ update(repo, id, opts) {
961
+ return this.c.patch(`${repoPath(repo)}/branch-rules/${id}`, opts);
962
+ }
963
+ delete(repo, id) {
964
+ return this.c.delete(`${repoPath(repo)}/branch-rules/${id}`);
965
+ }
966
+ };
967
+ var GitLabelsAPI = class {
968
+ constructor(c) {
969
+ this.c = c;
970
+ }
971
+ list(repo) {
972
+ return this.c.get(`${repoPath(repo)}/labels`);
973
+ }
974
+ create(repo, opts) {
975
+ return this.c.post(`${repoPath(repo)}/labels`, opts);
976
+ }
977
+ update(repo, name, opts) {
978
+ return this.c.patch(`${repoPath(repo)}/labels/${seg(name)}`, opts);
979
+ }
980
+ delete(repo, name) {
981
+ return this.c.delete(`${repoPath(repo)}/labels/${seg(name)}`);
982
+ }
983
+ };
984
+ var GitReleasesAPI = class {
985
+ constructor(c) {
986
+ this.c = c;
987
+ }
988
+ list(repo) {
989
+ return this.c.get(`${repoPath(repo)}/releases`);
990
+ }
991
+ /** Creates the tag too when it doesn't exist (at `target`, default branch by default). */
992
+ create(repo, opts) {
993
+ return this.c.post(`${repoPath(repo)}/releases`, opts);
994
+ }
995
+ get(repo, id) {
996
+ return this.c.get(`${repoPath(repo)}/releases/${id}`);
997
+ }
998
+ getByTag(repo, tag) {
999
+ return this.c.get(`${repoPath(repo)}/releases/tags/${seg(tag)}`);
1000
+ }
1001
+ update(repo, id, opts) {
1002
+ return this.c.patch(`${repoPath(repo)}/releases/${id}`, opts);
1003
+ }
1004
+ delete(repo, id) {
1005
+ return this.c.delete(`${repoPath(repo)}/releases/${id}`);
1006
+ }
1007
+ async uploadAsset(repo, id, name, data, contentType = "application/octet-stream") {
1008
+ const body = typeof data === "string" || data instanceof Blob ? data : new Blob([new Uint8Array(data)]);
1009
+ const res = await this.c.raw("POST", qs(`${repoPath(repo)}/releases/${id}/assets`, { name }), body, { "Content-Type": contentType });
1010
+ return res.json();
1011
+ }
1012
+ downloadAsset(repo, id, assetId) {
1013
+ return this.c.bytes(`${repoPath(repo)}/releases/${id}/assets/${assetId}`);
1014
+ }
1015
+ deleteAsset(repo, id, assetId) {
1016
+ return this.c.delete(`${repoPath(repo)}/releases/${id}/assets/${assetId}`);
1017
+ }
1018
+ };
1019
+ var GitStatusesAPI = class {
1020
+ constructor(c) {
1021
+ this.c = c;
1022
+ }
1023
+ /** Report a check on a commit (what required checks in branch rules wait for). */
1024
+ create(repo, sha, status) {
1025
+ return this.c.post(`${repoPath(repo)}/statuses/${seg(sha)}`, status);
1026
+ }
1027
+ list(repo, ref) {
1028
+ return this.c.get(`${repoPath(repo)}/commits/${seg(ref)}/statuses`);
1029
+ }
1030
+ combined(repo, ref) {
1031
+ return this.c.get(`${repoPath(repo)}/commits/${seg(ref)}/status`);
1032
+ }
1033
+ };
1034
+ var GitNotificationsAPI = class {
1035
+ constructor(c) {
1036
+ this.c = c;
1037
+ }
1038
+ list(opts = {}) {
1039
+ return this.c.get(qs("/api/notifications", { all: opts.all ? 1 : void 0 }));
1040
+ }
1041
+ markRead(ids) {
1042
+ return this.c.post("/api/notifications/read", ids?.length ? { ids } : { all: true });
1043
+ }
1044
+ };
1045
+ var GitTokensAPI = class {
1046
+ constructor(c) {
1047
+ this.c = c;
1048
+ }
1049
+ list() {
1050
+ return this.c.get("/api/user/tokens");
1051
+ }
1052
+ /** A Lizard Git token (lzg_...), optionally limited to repos, refs, paths, or read-only. The secret is in `token`, once. */
1053
+ create(opts) {
1054
+ return this.c.post("/api/user/tokens", opts);
1055
+ }
1056
+ delete(id) {
1057
+ return this.c.delete(`/api/user/tokens/${id}`);
1058
+ }
1059
+ };
1060
+ var GitAPI = class {
1061
+ constructor(config, url) {
1062
+ const c = this.client = new GitClient(config, url);
1063
+ this.repos = new GitReposAPI(c);
1064
+ this.issues = new GitIssuesOnlyAPI(c);
1065
+ this.pulls = new GitPullsAPI(c);
1066
+ this.actions = new GitActionsAPI(c);
1067
+ this.collaborators = new GitCollaboratorsAPI(c);
1068
+ this.webhooks = new GitWebhooksAPI(c);
1069
+ this.branchRules = new GitBranchRulesAPI(c);
1070
+ this.labels = new GitLabelsAPI(c);
1071
+ this.releases = new GitReleasesAPI(c);
1072
+ this.statuses = new GitStatusesAPI(c);
1073
+ this.notifications = new GitNotificationsAPI(c);
1074
+ this.tokens = new GitTokensAPI(c);
1075
+ }
1076
+ /** Who the key signs in as on Lizard Git. */
1077
+ me() {
1078
+ return this.client.get("/api/session");
1079
+ }
1080
+ user(login) {
1081
+ return this.client.get(`/api/users/${seg(login)}`);
1082
+ }
1083
+ };
1084
+
538
1085
  // src/project.ts
539
1086
  var import_node_crypto = require("crypto");
540
1087
  var projectIdCache = /* @__PURE__ */ new Map();
@@ -1414,8 +1961,8 @@ var ProjectsAPI = class {
1414
1961
  }
1415
1962
  /** List all projects the API key can access. */
1416
1963
  list(opts) {
1417
- const qs = opts?.workspaceId ? `?workspaceId=${opts.workspaceId}` : "";
1418
- return this.client.get(`/api/projects${qs}`);
1964
+ const qs2 = opts?.workspaceId ? `?workspaceId=${opts.workspaceId}` : "";
1965
+ return this.client.get(`/api/projects${qs2}`);
1419
1966
  }
1420
1967
  /** Get a project by ID. */
1421
1968
  get(id) {
@@ -1808,8 +2355,8 @@ var WorkspacesAPI = class {
1808
2355
  * volumes and snapshots first.
1809
2356
  */
1810
2357
  delete(id, opts) {
1811
- const qs = opts?.force ? "" : "?requireEmpty=true";
1812
- return this.client.delete(`/api/workspaces/${id}${qs}`);
2358
+ const qs2 = opts?.force ? "" : "?requireEmpty=true";
2359
+ return this.client.delete(`/api/workspaces/${id}${qs2}`);
1813
2360
  }
1814
2361
  /**
1815
2362
  * Find a workspace by name, slug, or id, or `null` if there is no such workspace.
@@ -2091,6 +2638,7 @@ var Lizard = class {
2091
2638
  this.metrics = new MetricsAPI(platform);
2092
2639
  this.storage = new StorageAPI(platform);
2093
2640
  this.github = new GitHubAPI(platform);
2641
+ this.git = new GitAPI(this.config, opts.gitUrl);
2094
2642
  }
2095
2643
  // ── Sandbox convenience methods (backward-compatible) ──────────────────
2096
2644
  /** Resolve the client's project reference to a stable project ID (cached). */
@@ -2703,10 +3251,26 @@ var LizardCLI = class {
2703
3251
  CodeSandbox,
2704
3252
  ConfigApplyError,
2705
3253
  ConflictError,
3254
+ DEFAULT_GIT_URL,
2706
3255
  DeployHandle,
2707
3256
  Desktop,
2708
3257
  DomainsAPI,
3258
+ GitAPI,
3259
+ GitActionsAPI,
3260
+ GitBranchRulesAPI,
3261
+ GitClient,
3262
+ GitCollaboratorsAPI,
2709
3263
  GitHubAPI,
3264
+ GitIssuesOnlyAPI,
3265
+ GitLabelsAPI,
3266
+ GitNotificationsAPI,
3267
+ GitPullsAPI,
3268
+ GitReleasesAPI,
3269
+ GitReposAPI,
3270
+ GitSecretsAPI,
3271
+ GitStatusesAPI,
3272
+ GitTokensAPI,
3273
+ GitWebhooksAPI,
2710
3274
  LimitExceededError,
2711
3275
  Lizard,
2712
3276
  LizardCLI,