drop2run 0.9.0 → 0.10.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/README.md CHANGED
@@ -32,6 +32,9 @@ drop2run rm <site> --yes Delete a site and everything on it
32
32
  drop2run comments [site] Read the comments left on a site's pages
33
33
  drop2run comments reply <thread> <text> Answer a thread as the owner [--resolve]
34
34
  drop2run comments resolve|reopen <thread> Resolve a thread, or open it again
35
+ drop2run people [site] Who the site is shared with
36
+ drop2run invite <email>... [--role comment] Share it with people; each is emailed
37
+ drop2run uninvite <email> Take someone off the list
35
38
  drop2run token list List your access tokens
36
39
  drop2run whoami Check the token and whose it is
37
40
  drop2run where Show which token source is in use
@@ -40,6 +43,18 @@ drop2run --version Print the version
40
43
 
41
44
  `--json` on any command prints machine-readable output instead of text.
42
45
 
46
+ ## Sharing for review
47
+
48
+ ```bash
49
+ drop2run invite dana@example.com --role comment --site calm-cedar # emailed an invitation
50
+ drop2run set invite-only on --site calm-cedar # only the list gets in
51
+ drop2run set comments invited --site calm-cedar # the people invited to comment can
52
+ ```
53
+
54
+ `drop2run people` shows the list and `drop2run uninvite` takes someone off it. `set comments anyone` lets
55
+ anybody signed in comment instead, on a site you keep public or behind a password. Both are plan features,
56
+ refused with the reason where the plan lacks them.
57
+
43
58
  ## Comments
44
59
 
45
60
  On a plan that includes comments, people viewing a site can pin comments to its pages. `drop2run comments`
package/dist/index.js CHANGED
@@ -190,10 +190,7 @@ async function folderFromListing(credentials, siteId, subdomain) {
190
190
  }
191
191
  async function getSite(credentials, siteId) {
192
192
  const [site, invitedOnly] = await Promise.all([
193
- call(
194
- credentials,
195
- `sites/${encodeURIComponent(siteId)}`
196
- ),
193
+ call(credentials, `sites/${encodeURIComponent(siteId)}`),
197
194
  readInvitedOnly(credentials, siteId)
198
195
  ]);
199
196
  const folderId = site.folderId !== void 0 ? site.folderId : await folderFromListing(credentials, site.siteId, site.subdomain);
@@ -215,6 +212,8 @@ async function getSite(credentials, siteId) {
215
212
  expiresAt: site.expiresAt,
216
213
  expiryAction: actionOf(site.expiresAt, site.expiryAction ?? ""),
217
214
  scheduledExpiryAvailable: site.scheduledExpiryAvailable,
215
+ comments: audienceOf(site.commentAudience),
216
+ commentsAvailable: site.feedbackAvailable === true,
218
217
  folderId,
219
218
  deploys: site.deploys.map((deploy2) => ({
220
219
  deployId: deploy2.deployId,
@@ -239,9 +238,11 @@ async function updateSiteSettings(credentials, siteId, change) {
239
238
  if (change.expiryAction !== void 0) body.expiryAction = change.expiryAction;
240
239
  if (change.formsEnabled !== void 0) body.formsEnabled = change.formsEnabled;
241
240
  if (change.folderId !== void 0) body.folderId = change.folderId;
241
+ if (change.invitedOnly !== void 0) body.invitedOnly = change.invitedOnly;
242
+ if (change.comments !== void 0) body.commentAudience = API_AUDIENCE[change.comments];
242
243
  if (Object.keys(body).length === 0) throw new Error("Nothing to change — name a setting.");
243
244
  const wasInvitedOnly = change.password !== void 0 && change.password !== "" ? await readInvitedOnly(credentials, siteId) : null;
244
- const settings = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
245
+ const { commentAudience, ...settings } = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
245
246
  method: "PATCH",
246
247
  body: JSON.stringify(body)
247
248
  });
@@ -250,9 +251,19 @@ async function updateSiteSettings(credentials, siteId, change) {
250
251
  ...settings,
251
252
  expiryAction: actionOf(settings.expiresAt, settings.expiryAction ?? ""),
252
253
  invitedOnly,
254
+ comments: audienceOf(commentAudience),
253
255
  replacedInviteOnly: wasInvitedOnly === true && invitedOnly === false
254
256
  };
255
257
  }
258
+ const API_AUDIENCE = {
259
+ off: "Off",
260
+ invited: "Invited",
261
+ anyone: "Anyone"
262
+ };
263
+ function audienceOf(value) {
264
+ const lower = value?.toLowerCase();
265
+ return lower === "off" || lower === "invited" || lower === "anyone" ? lower : null;
266
+ }
256
267
  async function setSitePaused(credentials, siteId, action) {
257
268
  const state = await call(
258
269
  credentials,
@@ -371,6 +382,34 @@ async function setCommentResolved(credentials, siteId, threadId, resolved) {
371
382
  { method: "POST" }
372
383
  );
373
384
  }
385
+ const PEOPLE_PER_REQUEST = 50;
386
+ async function listPeople(credentials, siteId) {
387
+ const page = await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers?pageSize=${PEOPLE_PER_REQUEST}`);
388
+ return {
389
+ invitedOnly: page.invitedOnly,
390
+ total: page.total,
391
+ limit: page.limit,
392
+ people: page.viewers.map((row) => ({
393
+ email: row.email,
394
+ role: enumName(row.role),
395
+ owner: row.owner,
396
+ status: enumName(row.status)
397
+ }))
398
+ };
399
+ }
400
+ async function invitePeople(credentials, siteId, emails, role) {
401
+ return await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers`, {
402
+ method: "POST",
403
+ body: JSON.stringify({ emails, role: role === "comment" ? "Comment" : "View", notify: true })
404
+ });
405
+ }
406
+ async function removePerson(credentials, siteId, email) {
407
+ await call(
408
+ credentials,
409
+ `sites/${encodeURIComponent(siteId)}/viewers?email=${encodeURIComponent(email)}`,
410
+ { method: "DELETE" }
411
+ );
412
+ }
374
413
  const VERSIONS_LISTED = 10;
375
414
  function servingModeOf$1(site) {
376
415
  if (site.spaMode) return "spa";
@@ -399,6 +438,7 @@ function describeSite(site, folderPath) {
399
438
  ` mode ${servingModeOf$1(site)}${site.modeIsManual ? "" : " (detected on each publish)"}`,
400
439
  ` password ${site.passwordProtected ? "on" : "off"}${unavailable(site.passwordProtectionAvailable)}`,
401
440
  ` forms ${site.formsEnabled ? "on" : "off"}${unavailable(site.formsAvailable)}`,
441
+ ` comments ${site.comments ?? "unknown"}${unavailable(site.commentsAvailable)}`,
402
442
  ` takedown ${site.expiresAt === null ? "none" : `${site.expiresAt} (${site.expiryAction})`}${unavailable(site.scheduledExpiryAvailable)}`,
403
443
  ` folder ${site.folderId === null ? "(top level)" : folderPath ?? site.folderId}`,
404
444
  "",
@@ -874,13 +914,14 @@ const FILE_TYPES = [
874
914
  reader: true,
875
915
  editable: true
876
916
  },
877
- // Source the viewer highlights. A link straight to one of these hands over the file.
917
+ // Source the viewer highlights. Inside a documents site a link straight to one opens it in the
918
+ // reader; on an ordinary site, and with `?raw`, it hands over the file.
878
919
  {
879
920
  ext: ".css",
880
921
  mime: "text/css; charset=utf-8",
881
922
  kind: "code",
882
923
  document: false,
883
- reader: false,
924
+ reader: true,
884
925
  editable: true
885
926
  },
886
927
  {
@@ -888,7 +929,7 @@ const FILE_TYPES = [
888
929
  mime: "text/javascript; charset=utf-8",
889
930
  kind: "code",
890
931
  document: false,
891
- reader: false,
932
+ reader: true,
892
933
  editable: true
893
934
  },
894
935
  {
@@ -896,7 +937,7 @@ const FILE_TYPES = [
896
937
  mime: null,
897
938
  kind: "code",
898
939
  document: false,
899
- reader: false,
940
+ reader: true,
900
941
  editable: false
901
942
  },
902
943
  {
@@ -904,7 +945,7 @@ const FILE_TYPES = [
904
945
  mime: null,
905
946
  kind: "code",
906
947
  document: false,
907
- reader: false,
948
+ reader: true,
908
949
  editable: false
909
950
  },
910
951
  {
@@ -912,7 +953,7 @@ const FILE_TYPES = [
912
953
  mime: null,
913
954
  kind: "code",
914
955
  document: false,
915
- reader: false,
956
+ reader: true,
916
957
  editable: false
917
958
  },
918
959
  {
@@ -920,19 +961,18 @@ const FILE_TYPES = [
920
961
  mime: null,
921
962
  kind: "code",
922
963
  document: false,
923
- reader: false,
964
+ reader: true,
924
965
  editable: false
925
966
  },
926
967
  // The languages an assistant writes most, beyond the web's own. Same row as `.ts`: shown as source
927
- // when somebody opens one in the reader or publishes it on its own, handed over as a file when a
928
- // link names it directly, and no media type — adding one changes what a browser does with a
968
+ // in the reader, handed over as a file with `?raw` or on an ordinary site, and no media type — adding one changes what a browser does with a
929
969
  // published file, which I10 makes a decision of its own rather than a side effect of this list.
930
970
  {
931
971
  ext: ".py",
932
972
  mime: null,
933
973
  kind: "code",
934
974
  document: false,
935
- reader: false,
975
+ reader: true,
936
976
  editable: false
937
977
  },
938
978
  {
@@ -940,7 +980,7 @@ const FILE_TYPES = [
940
980
  mime: null,
941
981
  kind: "code",
942
982
  document: false,
943
- reader: false,
983
+ reader: true,
944
984
  editable: false
945
985
  },
946
986
  {
@@ -948,7 +988,7 @@ const FILE_TYPES = [
948
988
  mime: null,
949
989
  kind: "code",
950
990
  document: false,
951
- reader: false,
991
+ reader: true,
952
992
  editable: false
953
993
  },
954
994
  {
@@ -956,7 +996,7 @@ const FILE_TYPES = [
956
996
  mime: null,
957
997
  kind: "code",
958
998
  document: false,
959
- reader: false,
999
+ reader: true,
960
1000
  editable: false
961
1001
  },
962
1002
  {
@@ -964,7 +1004,7 @@ const FILE_TYPES = [
964
1004
  mime: null,
965
1005
  kind: "code",
966
1006
  document: false,
967
- reader: false,
1007
+ reader: true,
968
1008
  editable: false
969
1009
  },
970
1010
  {
@@ -972,7 +1012,7 @@ const FILE_TYPES = [
972
1012
  mime: null,
973
1013
  kind: "code",
974
1014
  document: false,
975
- reader: false,
1015
+ reader: true,
976
1016
  editable: false
977
1017
  },
978
1018
  {
@@ -980,7 +1020,7 @@ const FILE_TYPES = [
980
1020
  mime: null,
981
1021
  kind: "code",
982
1022
  document: false,
983
- reader: false,
1023
+ reader: true,
984
1024
  editable: false
985
1025
  },
986
1026
  {
@@ -988,7 +1028,7 @@ const FILE_TYPES = [
988
1028
  mime: null,
989
1029
  kind: "code",
990
1030
  document: false,
991
- reader: false,
1031
+ reader: true,
992
1032
  editable: false
993
1033
  },
994
1034
  {
@@ -996,7 +1036,7 @@ const FILE_TYPES = [
996
1036
  mime: null,
997
1037
  kind: "code",
998
1038
  document: false,
999
- reader: false,
1039
+ reader: true,
1000
1040
  editable: false
1001
1041
  },
1002
1042
  {
@@ -1004,7 +1044,7 @@ const FILE_TYPES = [
1004
1044
  mime: null,
1005
1045
  kind: "code",
1006
1046
  document: false,
1007
- reader: false,
1047
+ reader: true,
1008
1048
  editable: false
1009
1049
  },
1010
1050
  {
@@ -1012,7 +1052,7 @@ const FILE_TYPES = [
1012
1052
  mime: null,
1013
1053
  kind: "code",
1014
1054
  document: false,
1015
- reader: false,
1055
+ reader: true,
1016
1056
  editable: false
1017
1057
  },
1018
1058
  {
@@ -1020,7 +1060,7 @@ const FILE_TYPES = [
1020
1060
  mime: null,
1021
1061
  kind: "code",
1022
1062
  document: false,
1023
- reader: false,
1063
+ reader: true,
1024
1064
  editable: false
1025
1065
  },
1026
1066
  {
@@ -1028,7 +1068,7 @@ const FILE_TYPES = [
1028
1068
  mime: null,
1029
1069
  kind: "code",
1030
1070
  document: false,
1031
- reader: false,
1071
+ reader: true,
1032
1072
  editable: false
1033
1073
  },
1034
1074
  {
@@ -1036,7 +1076,7 @@ const FILE_TYPES = [
1036
1076
  mime: null,
1037
1077
  kind: "code",
1038
1078
  document: false,
1039
- reader: false,
1079
+ reader: true,
1040
1080
  editable: false
1041
1081
  },
1042
1082
  {
@@ -1044,7 +1084,7 @@ const FILE_TYPES = [
1044
1084
  mime: "application/xml",
1045
1085
  kind: "code",
1046
1086
  document: false,
1047
- reader: false,
1087
+ reader: true,
1048
1088
  editable: true
1049
1089
  },
1050
1090
  // Pictures, shown with an img element.
@@ -1115,25 +1155,25 @@ const FILE_TYPES = [
1115
1155
  reader: false,
1116
1156
  editable: false
1117
1157
  },
1118
- // A customer's own pages. Never rendered inside the reader.
1158
+ // A customer's own pages. Inside a documents site a navigation opens the reader, which frames the
1159
+ // page; `?raw` and every ordinary site serve the page itself.
1119
1160
  {
1120
1161
  ext: ".html",
1121
1162
  mime: "text/html; charset=utf-8",
1122
1163
  kind: "html",
1123
1164
  document: false,
1124
- reader: false,
1165
+ reader: true,
1125
1166
  editable: true
1126
1167
  },
1127
1168
  {
1128
- // ⚠️ No media type, and that is the state of the code rather than a decision: `TYPES` in
1129
- // `headers.ts` has never held `htm`, so a published `.htm` is served as octet-stream and
1130
- // downloads instead of rendering. Recorded here rather than fixed, because this table was
1131
- // introduced as a refactor and a refactor that changes what a URL answers is not one.
1169
+ // An HTML page in every respect the Worker decides by extension: media type, the revalidated
1170
+ // cache policy invariant I7 requires, and the badge's `isHtml`. Missing from `TYPES` it used to
1171
+ // download, which inside the reader meant a frame that downloaded instead of showing a page.
1132
1172
  ext: ".htm",
1133
- mime: null,
1173
+ mime: "text/html; charset=utf-8",
1134
1174
  kind: "html",
1135
1175
  document: false,
1136
- reader: false,
1176
+ reader: true,
1137
1177
  editable: true
1138
1178
  },
1139
1179
  // Everything below renders as nothing — the viewer offers a download — but still needs a media
@@ -2525,7 +2565,9 @@ const SETTINGS_USAGE = [
2525
2565
  " password Read from stdin, or typed at a hidden prompt",
2526
2566
  " forms on|off Whether its forms accept submissions",
2527
2567
  " expires <ISO 8601 instant> When it comes down; add --then delete to delete rather than pause",
2528
- " folder <path|id|root> Which folder it is filed in"
2568
+ " folder <path|id|root> Which folder it is filed in",
2569
+ " invite-only on|off Whether only the people on its list can open it",
2570
+ " comments off|invited|anyone Who may comment on its pages"
2529
2571
  ].join("\n");
2530
2572
  function settingChange(setting, values, then) {
2531
2573
  const value = setting === "name" ? values.join(" ").trim() : values[0];
@@ -2573,6 +2615,14 @@ function settingChange(setting, values, then) {
2573
2615
  }
2574
2616
  case "folder":
2575
2617
  return value ? { folder: value } : needs("Clients/Acme");
2618
+ case "invite-only":
2619
+ if (value === "on" || value === "off") return { change: { invitedOnly: value === "on" } };
2620
+ return { refusal: "`set invite-only` takes `on` or `off`." };
2621
+ case "comments":
2622
+ if (value === "off" || value === "invited" || value === "anyone") {
2623
+ return { change: { comments: value } };
2624
+ }
2625
+ return { refusal: "`set comments` takes `off`, `invited` or `anyone`." };
2576
2626
  default:
2577
2627
  return {
2578
2628
  refusal: `${setting === void 0 ? "Name a setting." : `\`${setting}\` is not a setting.`} Settings:
@@ -2650,9 +2700,14 @@ async function applied(credentials, siteId, subdomain, change) {
2650
2700
  ` access ${accessOf(settings)}`,
2651
2701
  ` mode ${servingModeOf$1(settings)}`,
2652
2702
  ` forms ${settings.formsEnabled ? "on" : "off"}`,
2703
+ ` comments ${settings.comments ?? "unknown"}`,
2653
2704
  ` takedown ${takedown}`,
2654
2705
  ...settings.replacedInviteOnly ? [
2655
- "The password replaced invite-only: the people on its list can no longer get in without it. The list is kept for if you switch back in the dashboard."
2706
+ "The password replaced invite-only: the people on its list can no longer get in without it. The list is kept; `drop2run set invite-only on` switches back."
2707
+ ] : [],
2708
+ // The one combination that reads as on and does nothing, which the API keeps on purpose.
2709
+ ...settings.comments === "invited" && settings.invitedOnly === false ? [
2710
+ "Comments are for invited people, but the site is not invite-only, so nobody can comment yet. Run `drop2run set invite-only on`, or `drop2run set comments anyone`."
2656
2711
  ] : [],
2657
2712
  settings.live ? "Reaching every edge takes up to about a minute." : "Nothing is published yet, so this applies from the first publish."
2658
2713
  ].join("\n"),
@@ -2678,6 +2733,72 @@ async function tokens() {
2678
2733
  return failure(error instanceof Error ? error.message : String(error));
2679
2734
  }
2680
2735
  }
2736
+ async function people(site) {
2737
+ const found = credentialsOr();
2738
+ if ("result" in found) return found.result;
2739
+ const target = siteOr(site, "drop2run people <subdomain>");
2740
+ if ("result" in target) return target.result;
2741
+ try {
2742
+ const summary = await findSite(found.credentials, target.named);
2743
+ const list2 = await listPeople(found.credentials, summary.siteId);
2744
+ const head = `${list2.total} of ${list2.limit} on ${summary.subdomain}'s list. ` + (list2.invitedOnly ? "Only they can open it." : "It is not invite-only, so anybody with the link can open it — `drop2run set invite-only on`.");
2745
+ const rows = list2.people.map(
2746
+ (person) => `${person.email} ${person.role}${person.owner ? ", owner" : ""} ${person.status}`
2747
+ );
2748
+ return { text: [head, ...rows].join("\n"), json: list2, code: 0 };
2749
+ } catch (error) {
2750
+ return failure(error instanceof Error ? error.message : String(error));
2751
+ }
2752
+ }
2753
+ async function invite(emails, site, role) {
2754
+ const found = credentialsOr();
2755
+ if ("result" in found) return found.result;
2756
+ if (emails.length === 0)
2757
+ return failure("Name at least one address: `drop2run invite dana@example.com`.");
2758
+ if (role !== void 0 && role !== "view" && role !== "comment") {
2759
+ return failure("`--role` is `view` or `comment`.");
2760
+ }
2761
+ const target = siteOr(site, "drop2run invite <email> --site <subdomain>");
2762
+ if ("result" in target) return target.result;
2763
+ try {
2764
+ const summary = await findSite(found.credentials, target.named);
2765
+ const invited = await invitePeople(
2766
+ found.credentials,
2767
+ summary.siteId,
2768
+ emails,
2769
+ role === "comment" ? "comment" : "view"
2770
+ );
2771
+ const list2 = await listPeople(found.credentials, summary.siteId);
2772
+ return {
2773
+ text: [
2774
+ invited.added.length === 0 ? `Nobody new on ${summary.subdomain}'s list.` : `Invited ${invited.added.join(", ")} to ${summary.subdomain} — each was emailed.`,
2775
+ ...invited.alreadyListed.length === 0 ? [] : [`Already on the list, not emailed again: ${invited.alreadyListed.join(", ")}.`],
2776
+ ...list2.invitedOnly ? [] : [
2777
+ "The site is not invite-only yet, so the list does not decide who opens it. `drop2run set invite-only on` does."
2778
+ ]
2779
+ ].join("\n"),
2780
+ json: invited,
2781
+ code: 0
2782
+ };
2783
+ } catch (error) {
2784
+ return failure(error instanceof Error ? error.message : String(error));
2785
+ }
2786
+ }
2787
+ async function uninvite(email, site) {
2788
+ const found = credentialsOr();
2789
+ if ("result" in found) return found.result;
2790
+ if (email === void 0)
2791
+ return failure("Name the address: `drop2run uninvite dana@example.com`.");
2792
+ const target = siteOr(site, "drop2run uninvite <email> --site <subdomain>");
2793
+ if ("result" in target) return target.result;
2794
+ try {
2795
+ const summary = await findSite(found.credentials, target.named);
2796
+ await removePerson(found.credentials, summary.siteId, email);
2797
+ return { text: `Removed ${email} from ${summary.subdomain}'s list.`, json: { email }, code: 0 };
2798
+ } catch (error) {
2799
+ return failure(error instanceof Error ? error.message : String(error));
2800
+ }
2801
+ }
2681
2802
  const COMMENTS_USAGE = [
2682
2803
  " drop2run comments [site] [--status open|resolved|all] [--path /page]",
2683
2804
  " drop2run comments reply <thread> <text> [--resolve] [--site X]",
@@ -2808,6 +2929,9 @@ const HELP = `drop2run — publish a static site from the command line
2808
2929
  drop2run resume [site] Put a paused site back on the air
2809
2930
  drop2run rollback <deployId> [--site X] Put an earlier version back live
2810
2931
  drop2run rm <site> --yes Delete a site and everything on it
2932
+ drop2run people [site] Who the site is shared with
2933
+ drop2run invite <email>... [--role comment] Share it with people; each is emailed
2934
+ drop2run uninvite <email> Take someone off the list
2811
2935
  drop2run comments [site] Read the comments left on a site's pages
2812
2936
  drop2run comments reply <thread> <text> Answer a thread as the owner [--resolve]
2813
2937
  drop2run comments resolve|reopen <thread> Resolve a thread, or open it again
@@ -2829,6 +2953,7 @@ Flags
2829
2953
  --status X Which comment threads: open (the default), resolved or all
2830
2954
  --path X Only the comments on one page, such as /pricing
2831
2955
  --resolve Resolve the thread with the reply (\`comments reply\` only)
2956
+ --role X What invited people may do: view (the default) or comment
2832
2957
 
2833
2958
  Comments
2834
2959
  \`comments reply\`, \`resolve\` and \`reopen\` act on --site, else the site in
@@ -2847,6 +2972,11 @@ Settings
2847
2972
  set forms on|off Whether its forms accept submissions
2848
2973
  set expires <ISO 8601> Take it down then, e.g. 2026-12-31T09:00:00Z
2849
2974
  set folder <path|id|root> Move it to a folder, or root for the top level
2975
+ set invite-only on|off Only the people on its list (\`invite\`) can open it;
2976
+ replaces a password
2977
+ set comments off|invited|anyone
2978
+ Who may comment: nobody, people invited to
2979
+ comment, or anybody signed in
2850
2980
 
2851
2981
  \`set\` and \`unset\` act on --site, else the site in drop2run.json. A
2852
2982
  setting your plan does not include is refused with the reason, and
@@ -2879,7 +3009,7 @@ function flagValue(args, flag) {
2879
3009
  const value = args[at + 1];
2880
3010
  return value === void 0 || value.startsWith("-") ? void 0 : value;
2881
3011
  }
2882
- const VALUE_FLAGS = ["--site", "--subdomain", "--folder", "--then", "--status", "--path"];
3012
+ const VALUE_FLAGS = ["--site", "--subdomain", "--folder", "--then", "--status", "--path", "--role"];
2883
3013
  function positionals(argv) {
2884
3014
  const found = [];
2885
3015
  for (let at = 0; at < argv.length; at++) {
@@ -2928,7 +3058,7 @@ async function run(commandLine) {
2928
3058
  const positional = [...positionals(argv), ...literal];
2929
3059
  const [command, ...rest] = positional;
2930
3060
  if (argv.includes("--version")) {
2931
- const { version } = await import("./package-LdzYr7IV.js").then(
3061
+ const { version } = await import("./package-CGI7Yt81.js").then(
2932
3062
  (module) => module.default
2933
3063
  );
2934
3064
  return { text: version, json: { version }, code: 0 };
@@ -2963,11 +3093,12 @@ async function run(commandLine) {
2963
3093
  status: flagValue(argv, "--status"),
2964
3094
  path: flagValue(argv, "--path"),
2965
3095
  resolve: argv.includes("--resolve")
2966
- }
3096
+ },
3097
+ flagValue(argv, "--role")
2967
3098
  );
2968
3099
  return json ? { ...result, text: JSON.stringify(result.json, null, 2) } : result;
2969
3100
  }
2970
- async function dispatch(command, rest, site, json, device, confirmed, newSite = {}, then = void 0, commentOptions = {}) {
3101
+ async function dispatch(command, rest, site, json, device, confirmed, newSite = {}, then = void 0, commentOptions = {}, role = void 0) {
2971
3102
  switch (command) {
2972
3103
  case "login": {
2973
3104
  const print = json ? () => {
@@ -3004,6 +3135,12 @@ async function dispatch(command, rest, site, json, device, confirmed, newSite =
3004
3135
  return await pauseOrResume(command, rest[0] ?? site);
3005
3136
  case "rollback":
3006
3137
  return await rollback(rest[0], site);
3138
+ case "people":
3139
+ return await people(rest[0] ?? site);
3140
+ case "invite":
3141
+ return await invite(rest, site, role);
3142
+ case "uninvite":
3143
+ return await uninvite(rest[0], site);
3007
3144
  case "rm":
3008
3145
  return await remove(rest[0] ?? site, confirmed);
3009
3146
  case "comments":
@@ -1,5 +1,5 @@
1
1
  const name = "drop2run";
2
- const version = "0.9.0";
2
+ const version = "0.10.0";
3
3
  const description = "Publish a page, a folder of documents or a built site to Drop2Run from the command line.";
4
4
  const license = "MIT";
5
5
  const type = "module";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drop2run",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Publish a page, a folder of documents or a built site to Drop2Run from the command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",