deploro 0.4.0 → 0.5.1

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.
Files changed (2) hide show
  1. package/dist/index.js +417 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -379,6 +379,24 @@ async function authUsersCommand() {
379
379
  }))
380
380
  );
381
381
  }
382
+ async function authBanCommand(id, opts) {
383
+ requireConfig();
384
+ await confirmOrDie(`Ban user ${id}?`, opts.yes);
385
+ const expiresAt = opts.days ? new Date(Date.now() + Number(opts.days) * 24 * 60 * 60 * 1e3).toISOString() : void 0;
386
+ await apiRequest(`/api/auth/users/${encodeURIComponent(id)}/ban`, {
387
+ method: "PATCH",
388
+ body: JSON.stringify({ banned: true, reason: opts.reason, expiresAt })
389
+ });
390
+ console.log(`\u2713 User ${id} banned${opts.days ? ` for ${opts.days} day(s)` : ""}`);
391
+ }
392
+ async function authUnbanCommand(id) {
393
+ requireConfig();
394
+ await apiRequest(`/api/auth/users/${encodeURIComponent(id)}/ban`, {
395
+ method: "PATCH",
396
+ body: JSON.stringify({ banned: false })
397
+ });
398
+ console.log(`\u2713 User ${id} unbanned`);
399
+ }
382
400
  async function authInviteCommand(email, opts) {
383
401
  requireConfig();
384
402
  const role = opts.admin ? "admin" : "member";
@@ -540,6 +558,11 @@ async function authProviderEnableCommand(provider, enabled) {
540
558
  { method: "PUT", body: JSON.stringify({ enabled }) }
541
559
  );
542
560
  console.log(`\u2713 ${provider} ${enabled ? "enabled" : "disabled"}`);
561
+ if (provider === "email_password" && enabled) {
562
+ console.log(
563
+ " Your app can now call /auth/:slug/email-password/{signup,login,request-reset,reset} directly \u2014\n see the dashboard's Auth page for the exact copyable URLs."
564
+ );
565
+ }
543
566
  }
544
567
  async function authProviderConfigureCommand(provider, opts) {
545
568
  const config = requireProject();
@@ -617,12 +640,355 @@ async function authStatsCommand() {
617
640
  }
618
641
  }
619
642
 
643
+ // src/commands/github.ts
644
+ async function githubStatusCommand() {
645
+ requireConfig();
646
+ const data = await apiRequestNoProject("/api/github/status");
647
+ if (!data.configured) {
648
+ console.log("GitHub OAuth is not configured on this platform (operator hasn't set GITHUB_APP_CLIENT_ID/SECRET).");
649
+ return;
650
+ }
651
+ console.log(data.connected ? `\u2713 Connected as ${data.login}` : "Not connected. Run: deploro github connect");
652
+ }
653
+ async function githubConnectCommand() {
654
+ requireConfig();
655
+ const data = await apiRequestNoProject("/api/github/oauth/start");
656
+ console.log("Open this URL in a browser to connect your GitHub account:\n");
657
+ console.log(` ${data.url}
658
+ `);
659
+ console.log("After approving, run 'deploro github status' to confirm.");
660
+ }
661
+ async function githubReposCommand() {
662
+ requireConfig();
663
+ const data = await apiRequestNoProject(
664
+ "/api/github/repos"
665
+ );
666
+ if (!data.configured) {
667
+ console.log("GitHub OAuth is not configured on this platform.");
668
+ return;
669
+ }
670
+ if (!data.connected) {
671
+ console.log("Not connected. Run: deploro github connect");
672
+ return;
673
+ }
674
+ if (data.error) {
675
+ console.error(data.error);
676
+ return;
677
+ }
678
+ table(
679
+ data.repos.map((r) => ({
680
+ repo: r.full_name,
681
+ branch: r.default_branch,
682
+ visibility: r.private ? "private" : "public"
683
+ }))
684
+ );
685
+ console.log("\nLink one to the active project with: deploro repo link <owner/repo>");
686
+ }
687
+ async function githubDisconnectCommand(opts) {
688
+ requireConfig();
689
+ await confirmOrDie(
690
+ "Disconnect your GitHub account? Projects with a repo already linked keep deploying \u2014 this only removes your stored OAuth token, so you won't be able to link new repos until you reconnect.",
691
+ opts.yes
692
+ );
693
+ await apiRequestNoProject("/api/github/disconnect", { method: "POST" });
694
+ console.log("\u2713 GitHub account disconnected");
695
+ }
696
+ async function repoStatusCommand() {
697
+ const config = requireProject();
698
+ const data = await apiRequest(
699
+ `/api/projects/${config.projectId}/github`
700
+ );
701
+ if (!data.connection) {
702
+ console.log("No repo linked. Run: deploro repo link <owner/repo>");
703
+ return;
704
+ }
705
+ console.log(`Repo : ${data.connection.github_owner}/${data.connection.github_repo}`);
706
+ console.log(`Branch : ${data.connection.branch}`);
707
+ console.log(`Webhook: ${data.connection.webhook_id ? "auto-created on GitHub" : "(not created \u2014 deploys may not trigger)"}`);
708
+ }
709
+ async function repoLinkCommand(ownerRepo, opts) {
710
+ const config = requireProject();
711
+ const [owner, repo2] = ownerRepo.split("/");
712
+ if (!owner || !repo2) {
713
+ console.error('Expected "owner/repo", e.g. "acme/my-app"');
714
+ process.exit(1);
715
+ }
716
+ const data = await apiRequest(
717
+ `/api/projects/${config.projectId}/github`,
718
+ { method: "POST", body: JSON.stringify({ owner, repo: repo2, branch: opts.branch }) }
719
+ );
720
+ console.log(`\u2713 Linked ${data.connection.owner}/${data.connection.repo} (branch: ${data.connection.branch})`);
721
+ console.log(
722
+ data.connection.webhook_created ? " Webhook created on GitHub \u2014 pushes will now auto-deploy." : " Webhook already existed on GitHub \u2014 reusing it."
723
+ );
724
+ }
725
+ async function repoUnlinkCommand(opts) {
726
+ const config = requireProject();
727
+ await confirmOrDie("Unlink this project's repo and remove its GitHub webhook?", opts.yes);
728
+ await apiRequest(`/api/projects/${config.projectId}/github`, { method: "DELETE" });
729
+ console.log("\u2713 Repo unlinked");
730
+ }
731
+ async function repoAutoDeployCommand(state) {
732
+ const config = requireProject();
733
+ const enabled = state === "on" || state === "true" || state === "enable";
734
+ const disabled = state === "off" || state === "false" || state === "disable";
735
+ if (!enabled && !disabled) {
736
+ console.error('Expected "on" or "off"');
737
+ process.exit(1);
738
+ }
739
+ await apiRequest(`/api/projects/${config.projectId}`, {
740
+ method: "PATCH",
741
+ body: JSON.stringify({ auto_deploy_enabled: enabled })
742
+ });
743
+ console.log(`\u2713 Automatic deployment ${enabled ? "enabled" : "disabled"}`);
744
+ }
745
+
746
+ // src/commands/domains.ts
747
+ async function domainsListCommand() {
748
+ const config = requireProject();
749
+ const data = await apiRequest(`/api/projects/${config.projectId}/domains`);
750
+ table(
751
+ data.domains.map((d) => ({
752
+ hostname: d.hostname,
753
+ status: d.status,
754
+ ssl: d.ssl_status,
755
+ verified: d.verified_at ? "yes" : "no"
756
+ }))
757
+ );
758
+ }
759
+ async function domainsAddCommand(hostname) {
760
+ const config = requireProject();
761
+ const data = await apiRequest(`/api/projects/${config.projectId}/domains`, {
762
+ method: "POST",
763
+ body: JSON.stringify({ hostname })
764
+ });
765
+ console.log(`\u2713 Domain added: ${data.domain.hostname} (id: ${data.domain.id})
766
+ `);
767
+ console.log("Add these DNS records at your registrar/DNS provider, then run 'deploro domains verify " + data.domain.id + "':\n");
768
+ const { cname, txt } = data.dns_instructions;
769
+ console.log(` ${cname.type} ${cname.name} \u2192 ${cname.value}`);
770
+ console.log(` ${txt.type} ${txt.name} \u2192 ${txt.value}`);
771
+ }
772
+ async function domainsVerifyCommand(idOrHostname) {
773
+ const config = requireProject();
774
+ const id = await resolveDomainId(config.projectId, idOrHostname);
775
+ const data = await apiRequest(
776
+ `/api/projects/${config.projectId}/domains/${id}/verify`,
777
+ { method: "POST" }
778
+ );
779
+ console.log(data.verified ? `\u2713 ${data.message}` : `\u2717 ${data.message}`);
780
+ }
781
+ async function domainsStatusCommand(idOrHostname) {
782
+ const config = requireProject();
783
+ const id = await resolveDomainId(config.projectId, idOrHostname);
784
+ const data = await apiRequest(
785
+ `/api/projects/${config.projectId}/domains/${id}/status`
786
+ );
787
+ console.log(`Status: ${data.status}`);
788
+ console.log(`SSL : ${data.ssl_status}`);
789
+ }
790
+ async function domainsRemoveCommand(idOrHostname, opts) {
791
+ const config = requireProject();
792
+ const id = await resolveDomainId(config.projectId, idOrHostname);
793
+ await confirmOrDie(`Remove domain "${idOrHostname}"? Traffic to it will stop resolving.`, opts.yes);
794
+ await apiRequest(`/api/projects/${config.projectId}/domains/${id}`, { method: "DELETE" });
795
+ console.log("\u2713 Domain removed");
796
+ }
797
+ async function resolveDomainId(projectId, idOrHostname) {
798
+ const data = await apiRequest(`/api/projects/${projectId}/domains`);
799
+ const match = data.domains.find((d) => d.id === idOrHostname || d.hostname === idOrHostname.toLowerCase());
800
+ if (!match) {
801
+ console.error(`No domain matches "${idOrHostname}". Run: deploro domains list`);
802
+ process.exit(1);
803
+ }
804
+ return match.id;
805
+ }
806
+
807
+ // src/commands/storage.ts
808
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
809
+ import { basename } from "path";
810
+ function authHeaders(config) {
811
+ return { Authorization: `Bearer ${config.accessToken}` };
812
+ }
813
+ async function throwOnError(res) {
814
+ if (res.ok) return;
815
+ const body = await res.json().catch(() => ({ error: res.statusText }));
816
+ throw new Error(`${res.status} ${body.error ?? res.statusText}`);
817
+ }
818
+ async function storageListCommand() {
819
+ const config = requireProject();
820
+ const res = await fetch(`${config.baseUrl}/api/projects/${config.projectId}/storage`, {
821
+ headers: authHeaders(config)
822
+ });
823
+ await throwOnError(res);
824
+ const data = await res.json();
825
+ table(
826
+ data.objects.map((o) => ({
827
+ key: o.key,
828
+ size: `${(o.size / 1024).toFixed(1)} KB`,
829
+ type: o.contentType,
830
+ uploaded: new Date(o.uploaded).toLocaleString()
831
+ }))
832
+ );
833
+ if (data.truncated) console.log("\n(more objects exist \u2014 pagination isn't wired up here yet, use the dashboard for the full list)");
834
+ }
835
+ async function storageUploadCommand(filePath, opts) {
836
+ const config = requireProject();
837
+ const bytes = readFileSync2(filePath);
838
+ const filename = opts.key ?? basename(filePath);
839
+ const form = new FormData();
840
+ form.append("file", new Blob([bytes]), filename);
841
+ const res = await fetch(`${config.baseUrl}/api/projects/${config.projectId}/storage/upload`, {
842
+ method: "POST",
843
+ headers: authHeaders(config),
844
+ body: form
845
+ });
846
+ await throwOnError(res);
847
+ const data = await res.json();
848
+ console.log(`\u2713 Uploaded as ${data.key} (${data.size} bytes, ${data.contentType})`);
849
+ }
850
+ async function storageDownloadCommand(key, opts) {
851
+ const config = requireProject();
852
+ const res = await fetch(
853
+ `${config.baseUrl}/api/projects/${config.projectId}/storage/download?key=${encodeURIComponent(key)}`,
854
+ { headers: authHeaders(config) }
855
+ );
856
+ await throwOnError(res);
857
+ const bytes = new Uint8Array(await res.arrayBuffer());
858
+ const outPath = opts.out ?? basename(key);
859
+ writeFileSync2(outPath, bytes);
860
+ console.log(`\u2713 Saved to ${outPath} (${bytes.length} bytes)`);
861
+ }
862
+ async function storageDeleteCommand(key, opts) {
863
+ const config = requireProject();
864
+ await confirmOrDie(`Delete "${key}" from storage?`, opts.yes);
865
+ const res = await fetch(
866
+ `${config.baseUrl}/api/projects/${config.projectId}/storage?key=${encodeURIComponent(key)}`,
867
+ { method: "DELETE", headers: authHeaders(config) }
868
+ );
869
+ await throwOnError(res);
870
+ console.log(`\u2713 Deleted ${key}`);
871
+ }
872
+
873
+ // src/commands/webhooks.ts
874
+ var KNOWN_EVENTS = [
875
+ "db.row.insert",
876
+ "db.row.update",
877
+ "db.row.delete",
878
+ "deployment.started",
879
+ "deployment.success",
880
+ "deployment.failed",
881
+ "test"
882
+ ];
883
+ async function webhooksListCommand() {
884
+ const config = requireProject();
885
+ const data = await apiRequest(`/api/projects/${config.projectId}/webhooks`);
886
+ table(
887
+ data.webhooks.map((w) => ({
888
+ id: w.id,
889
+ url: w.url,
890
+ events: w.events.join(","),
891
+ enabled: w.enabled,
892
+ deliveries: w.delivery_count,
893
+ lastStatus: w.last_status_code ?? "-"
894
+ }))
895
+ );
896
+ }
897
+ async function webhooksCreateCommand(url, opts) {
898
+ const config = requireProject();
899
+ const events = opts.events ? opts.events.split(",").map((e) => e.trim()).filter(Boolean) : ["test"];
900
+ const data = await apiRequest(`/api/projects/${config.projectId}/webhooks`, {
901
+ method: "POST",
902
+ body: JSON.stringify({ url, events, secret: opts.secret })
903
+ });
904
+ console.log(`\u2713 Webhook created: ${data.webhook.id}`);
905
+ console.log(` Events: ${data.webhook.events.join(", ")}`);
906
+ if (!opts.events) {
907
+ console.log(` (no --events passed \u2014 defaulted to "test". Known events: ${KNOWN_EVENTS.join(", ")})`);
908
+ }
909
+ }
910
+ async function webhooksUpdateCommand(id, opts) {
911
+ const config = requireProject();
912
+ const body = {};
913
+ if (opts.url) body.url = opts.url;
914
+ if (opts.events) body.events = opts.events.split(",").map((e) => e.trim()).filter(Boolean);
915
+ if (opts.secret) body.secret = opts.secret;
916
+ if (opts.enable) body.enabled = true;
917
+ if (opts.disable) body.enabled = false;
918
+ if (Object.keys(body).length === 0) {
919
+ console.error("Nothing to update \u2014 pass --url, --events, --secret, --enable, or --disable");
920
+ process.exit(1);
921
+ }
922
+ await apiRequest(`/api/projects/${config.projectId}/webhooks/${id}`, {
923
+ method: "PATCH",
924
+ body: JSON.stringify(body)
925
+ });
926
+ console.log(`\u2713 Webhook ${id} updated`);
927
+ }
928
+ async function webhooksDeleteCommand(id, opts) {
929
+ const config = requireProject();
930
+ await confirmOrDie(`Delete webhook ${id}?`, opts.yes);
931
+ await apiRequest(`/api/projects/${config.projectId}/webhooks/${id}`, { method: "DELETE" });
932
+ console.log(`\u2713 Webhook ${id} deleted`);
933
+ }
934
+ async function webhooksTestCommand(id) {
935
+ const config = requireProject();
936
+ await apiRequest(`/api/projects/${config.projectId}/webhooks/${id}/test`, { method: "POST" });
937
+ console.log("\u2713 Test payload dispatched \u2014 check 'deploro webhooks deliveries' for the result.");
938
+ }
939
+ async function webhooksDeliveriesCommand(id) {
940
+ const config = requireProject();
941
+ const data = await apiRequest(
942
+ `/api/projects/${config.projectId}/webhooks/${id}/deliveries`
943
+ );
944
+ table(
945
+ data.deliveries.map((d) => ({
946
+ event: d.event,
947
+ status: d.status_code ?? "-",
948
+ ms: d.duration_ms ?? "-",
949
+ error: d.error ?? "",
950
+ at: new Date(d.delivered_at).toLocaleString()
951
+ }))
952
+ );
953
+ }
954
+
955
+ // src/commands/team.ts
956
+ async function teamListCommand() {
957
+ const config = requireProject();
958
+ const data = await apiRequest(`/api/projects/${config.projectId}/members`);
959
+ table(
960
+ data.members.map((m) => ({
961
+ id: m.id,
962
+ name: m.name,
963
+ email: m.email,
964
+ role: m.role,
965
+ joined: new Date(m.joined_at).toLocaleDateString()
966
+ }))
967
+ );
968
+ }
969
+ async function teamInviteCommand(email, opts) {
970
+ const config = requireProject();
971
+ const role = opts.admin ? "admin" : "member";
972
+ await apiRequest(`/api/projects/${config.projectId}/members`, {
973
+ method: "POST",
974
+ body: JSON.stringify({ email, role })
975
+ });
976
+ console.log(`\u2713 Added ${email} to the project as ${role}`);
977
+ console.log(" (they must already have a Deploro account \u2014 use 'deploro auth invite' first if not)");
978
+ }
979
+ async function teamRemoveCommand(memberId, opts) {
980
+ const config = requireProject();
981
+ await confirmOrDie(`Remove member ${memberId} from this project?`, opts.yes);
982
+ await apiRequest(`/api/projects/${config.projectId}/members/${memberId}`, { method: "DELETE" });
983
+ console.log(`\u2713 Member ${memberId} removed`);
984
+ }
985
+
620
986
  // src/commands/migrate.ts
621
- import { readFileSync as readFileSync2 } from "fs";
987
+ import { readFileSync as readFileSync3 } from "fs";
622
988
  function readSqlOpt(inline, file, label) {
623
989
  if (file) {
624
990
  try {
625
- return readFileSync2(file, "utf-8");
991
+ return readFileSync3(file, "utf-8");
626
992
  } catch (err) {
627
993
  console.error(`Could not read --${label}-file "${file}": ${err instanceof Error ? err.message : String(err)}`);
628
994
  process.exit(1);
@@ -717,9 +1083,7 @@ async function migrateDeleteCommand(idOrName, opts) {
717
1083
  // src/commands/hosting.ts
718
1084
  async function hostingStatusCommand() {
719
1085
  requireConfig();
720
- const data = await apiRequest(
721
- "/api/hosting/status"
722
- );
1086
+ const data = await apiRequest("/api/hosting/status");
723
1087
  console.log("\n\u2500\u2500 WORKER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
724
1088
  if (data.worker) {
725
1089
  console.log(` Script : ${data.worker.name}`);
@@ -749,6 +1113,10 @@ async function hostingStatusCommand() {
749
1113
  } else {
750
1114
  console.log(" (no VPS info)");
751
1115
  }
1116
+ if (data.untrackedContainers && data.untrackedContainers.length > 0) {
1117
+ console.log("\n \u26A0 Untracked containers (didn't come from a Deployer-issued deploy):");
1118
+ for (const name of data.untrackedContainers) console.log(` - ${name}`);
1119
+ }
752
1120
  console.log("");
753
1121
  }
754
1122
  async function hostingSetEnvCommand(key, value) {
@@ -894,7 +1262,7 @@ async function deployCommand() {
894
1262
  async function deployStatusCommand() {
895
1263
  const config = requireProject();
896
1264
  const data = await apiRequest(
897
- `/api/projects/${config.projectId}/deployments`
1265
+ `/api/projects/${config.projectId}/deployments/all`
898
1266
  );
899
1267
  const latest = data.deployments[0];
900
1268
  if (!latest) {
@@ -904,6 +1272,7 @@ async function deployStatusCommand() {
904
1272
  console.log(`
905
1273
  \u2500\u2500 LATEST DEPLOYMENT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
906
1274
  console.log(` ID : ${latest.id}`);
1275
+ console.log(` Target : ${latest.target ?? "worker"}`);
907
1276
  console.log(` Status : ${latest.status}`);
908
1277
  console.log(` Branch : ${latest.git_branch ?? "(none)"}`);
909
1278
  console.log(` Started : ${new Date(latest.created_at).toLocaleString()}`);
@@ -915,7 +1284,7 @@ async function deployLogsCommand(deploymentId) {
915
1284
  let id = deploymentId;
916
1285
  if (!id) {
917
1286
  const list = await apiRequest(
918
- `/api/projects/${config.projectId}/deployments`
1287
+ `/api/projects/${config.projectId}/deployments/all`
919
1288
  );
920
1289
  id = list.deployments[0]?.id;
921
1290
  if (!id) {
@@ -929,6 +1298,7 @@ async function deployLogsCommand(deploymentId) {
929
1298
  const d = data.deployment;
930
1299
  console.log(`
931
1300
  \u2500\u2500 DEPLOYMENT ${d.id} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
1301
+ console.log(` Target : ${d.target ?? "worker"}`);
932
1302
  console.log(` Status : ${d.status}`);
933
1303
  console.log(
934
1304
  ` Commit : ${d.git_branch ?? "(none)"}${d.git_sha ? ` @ ${String(d.git_sha).slice(0, 7)}` : ""}`
@@ -1136,6 +1506,10 @@ async function vpsStatusCommand() {
1136
1506
  console.log(data.postgres ? ` ${data.postgres.running ? "\u25CF" : "\u25CB"} ${data.postgres.status}` : " (not provisioned)");
1137
1507
  console.log("\n\u2500\u2500 RAW REDIS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1138
1508
  console.log(data.redis ? ` ${data.redis.running ? "\u25CF" : "\u25CB"} ${data.redis.status}` : " (not provisioned)");
1509
+ console.log("\n\u2500\u2500 WEB \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1510
+ console.log(
1511
+ data.web_url ? ` ${data.web_url} (nginx proxy on the VPS, TLS via Let's Encrypt)` : " (none detected \u2014 compose must publish an HTTP port, then redeploy)"
1512
+ );
1139
1513
  console.log("");
1140
1514
  }
1141
1515
  async function vpsLogsCommand(service, opts) {
@@ -1154,7 +1528,7 @@ program.name("deploro").description("Deploro developer platform CLI").version(pk
1154
1528
  program.command("login").description("Log in to Deploro").option("--url <url>", "Worker base URL").option("--token <token>", "Personal access token from the Deploro dashboard").action(loginCommand);
1155
1529
  program.command("logout").description("Log out and clear local credentials").action(logoutCommand);
1156
1530
  program.command("projects").description("List projects").action(projectsListCommand);
1157
- program.command("create <name>").description("Create a new project (and set it active)").option("--slug <slug>", "Project slug (derived from name if omitted)").option("--repo <url>", "GitHub repo URL to link for deployments").option("--branch <branch>", "Git branch to deploy from").action(projectsCreateCommand);
1531
+ program.command("create <name>").description("Create a new project (and set it active)").option("--slug <slug>", "Project slug (derived from name if omitted)").option("--repo <url>", "Public repo URL to link for deployments (no embedded tokens/credentials \u2014 for a private repo or auto-deploy on push, use 'deploro github connect' + 'deploro repo link' instead)").option("--branch <branch>", "Git branch to deploy from").action(projectsCreateCommand);
1158
1532
  program.command("use <project>").description("Set the active project (accepts id, slug, or name)").action(useProjectCommand);
1159
1533
  program.command("delete <project>").description("Delete a project and its database (accepts id, slug, or name)").option("-y, --yes", "Skip confirmation prompt").action(projectsDeleteCommand);
1160
1534
  var db = program.command("db").description("Database commands");
@@ -1178,6 +1552,8 @@ var auth = program.command("auth").description("Auth management commands");
1178
1552
  auth.command("whoami").description("Show the current identity, and \u2014 for a token login \u2014 exactly what it's scoped to").action(authWhoamiCommand);
1179
1553
  auth.command("users").description("List platform users").action(authUsersCommand);
1180
1554
  auth.command("invite <email>").description("Invite a user by email").option("--admin", "Grant admin role").action(authInviteCommand);
1555
+ auth.command("ban <id>").description("Ban a platform user (blocks sign-in immediately, no session revocation needed)").option("--reason <text>", "Ban reason").option("--days <n>", "Expire the ban after n days (omit for indefinite)").option("-y, --yes", "Skip confirmation prompt").action(authBanCommand);
1556
+ auth.command("unban <id>").description("Lift a ban").action(authUnbanCommand);
1181
1557
  auth.command("site-url [url]").description("Get or set the active project's Auth Site URL (where end-user confirmation-email links redirect back to)").option("--clear", "Clear the Site URL \u2014 confirmation links fall back to the project's own deployment URL").action(authSiteUrlCommand);
1182
1558
  auth.command("stats").description("Show Auth-as-a-Service stats for the active project's end users").action(authStatsCommand);
1183
1559
  var authProviders = auth.command("providers").description("Manage the active project's end-user sign-in providers");
@@ -1202,7 +1578,7 @@ var deploy = program.command("deploy").description("Deployment commands");
1202
1578
  deploy.command("trigger").description("Trigger a manual deployment").action(deployCommand);
1203
1579
  deploy.command("status").description("Show latest deployment status").action(deployStatusCommand);
1204
1580
  deploy.command("logs [deployment-id]").description("Show logs for a deployment (defaults to latest)").action(deployLogsCommand);
1205
- var vps = program.command("vps").description("VPS compute hosting commands (raw Postgres/Redis, custom docker-compose deploys)");
1581
+ var vps = program.command("vps").description("VPS compute hosting (raw Postgres/Redis, custom docker-compose deploys) \u2014 grandfathered projects only, closed to new projects");
1206
1582
  var vpsDatabase = vps.command("database").description("Raw Postgres (dedicated container, real connection string \u2014 for drizzle-kit, transactions, etc.)");
1207
1583
  vpsDatabase.command("create").description("Provision raw Postgres (idempotent; connection string shown once)").action(vpsDatabaseCreateCommand);
1208
1584
  vpsDatabase.command("info").description("Show raw Postgres connection info (no password)").action(vpsDatabaseInfoCommand);
@@ -1222,6 +1598,38 @@ vps.command("deployments").description("List VPS deploy history").action(vpsDepl
1222
1598
  vps.command("deploy-status [deployment-id]").description("Show a VPS deployment's status and logs (defaults to latest)").action(vpsDeployStatusCommand);
1223
1599
  vps.command("status").description("Show running compute services + raw Postgres/Redis container status").action(vpsStatusCommand);
1224
1600
  vps.command("logs [service]").description("Tail logs for a compute service (or 'postgres'/'redis')").option("--tail <n>", "Number of lines", "200").action(vpsLogsCommand);
1601
+ var github = program.command("github").description("GitHub account connection (OAuth)");
1602
+ github.command("status").description("Show whether your GitHub account is connected").action(githubStatusCommand);
1603
+ github.command("connect").description("Print the URL to open in a browser to connect GitHub").action(githubConnectCommand);
1604
+ github.command("repos").description("List repos your connected GitHub account can access").action(githubReposCommand);
1605
+ github.command("disconnect").description("Remove your stored GitHub OAuth token").option("-y, --yes", "Skip confirmation prompt").action(githubDisconnectCommand);
1606
+ var repo = program.command("repo").description("Active project's connected repo (auto-deploy from GitHub)");
1607
+ repo.command("status").description("Show the active project's linked repo").action(repoStatusCommand);
1608
+ repo.command("link <owner/repo>").description("Link a repo (requires 'deploro github connect' first)").option("--branch <branch>", "Branch to deploy from (default: main)").action(repoLinkCommand);
1609
+ repo.command("unlink").description("Unlink the repo and remove its GitHub webhook").option("-y, --yes", "Skip confirmation prompt").action(repoUnlinkCommand);
1610
+ repo.command("auto-deploy <on|off>").description("Toggle automatic deployment on push").action(repoAutoDeployCommand);
1611
+ var domains = program.command("domains").description("Custom domains for the active project");
1612
+ domains.command("list").description("List custom domains").action(domainsListCommand);
1613
+ domains.command("add <hostname>").description("Add a domain \u2014 prints the CNAME + TXT records to create").action(domainsAddCommand);
1614
+ domains.command("verify <id-or-hostname>").description("Verify DNS ownership and register SSL").action(domainsVerifyCommand);
1615
+ domains.command("status <id-or-hostname>").description("Poll domain verification/SSL status").action(domainsStatusCommand);
1616
+ domains.command("remove <id-or-hostname>").description("Remove a domain").option("-y, --yes", "Skip confirmation prompt").action(domainsRemoveCommand);
1617
+ var storage = program.command("storage").description("R2 file storage for the active project");
1618
+ storage.command("list").description("List stored files").action(storageListCommand);
1619
+ storage.command("upload <file>").description("Upload a local file").option("--key <name>", "Stored file name (defaults to the local file's name)").action(storageUploadCommand);
1620
+ storage.command("download <key>").description("Download a stored file").option("--out <path>", "Local path to save to (defaults to the file's name)").action(storageDownloadCommand);
1621
+ storage.command("delete <key>").description("Delete a stored file").option("-y, --yes", "Skip confirmation prompt").action(storageDeleteCommand);
1622
+ var webhooks = program.command("webhooks").description("Outbound webhooks for the active project");
1623
+ webhooks.command("list").description("List webhooks").action(webhooksListCommand);
1624
+ webhooks.command("create <url>").description("Create a webhook").option("--events <list>", "Comma-separated event names (default: test)").option("--secret <secret>", "HMAC signing secret").action(webhooksCreateCommand);
1625
+ webhooks.command("update <id>").description("Update a webhook").option("--url <url>").option("--events <list>", "Comma-separated event names").option("--secret <secret>").option("--enable").option("--disable").action(webhooksUpdateCommand);
1626
+ webhooks.command("delete <id>").description("Delete a webhook").option("-y, --yes", "Skip confirmation prompt").action(webhooksDeleteCommand);
1627
+ webhooks.command("test <id>").description("Fire a test payload").action(webhooksTestCommand);
1628
+ webhooks.command("deliveries <id>").description("Show the last 30 delivery attempts").action(webhooksDeliveriesCommand);
1629
+ var team = program.command("team").description("Manage the active project's team members");
1630
+ team.command("list").description("List project members").action(teamListCommand);
1631
+ team.command("invite <email>").description("Add an existing Deploro user to this project").option("--admin", "Grant project admin role").action(teamInviteCommand);
1632
+ team.command("remove <member-id>").description("Remove a member from this project").option("-y, --yes", "Skip confirmation prompt").action(teamRemoveCommand);
1225
1633
  program.parseAsync().catch((err) => {
1226
1634
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1227
1635
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploro",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Command-line interface for the Deploro developer platform — manage projects, databases, deployments, and hosting from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {