@dropthis/cli 0.30.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -206,6 +206,13 @@ function apiErrorEnvelope(error2) {
206
206
  ...error2.requestId ? { request_id: error2.requestId } : {},
207
207
  ...error2.suggestion ? { suggestion: error2.suggestion } : {},
208
208
  ...error2.retryable !== void 0 && error2.retryable !== null ? { retryable: error2.retryable } : {},
209
+ ...error2.feature ? { feature: error2.feature } : {},
210
+ ...error2.currentPlan ? { current_plan: error2.currentPlan } : {},
211
+ ...error2.requiredPlan ? { required_plan: error2.requiredPlan } : {},
212
+ ...error2.upgradeUrl ? { upgrade_url: error2.upgradeUrl } : {},
213
+ ...typeof error2.limit === "number" ? { limit: error2.limit } : {},
214
+ ...typeof error2.used === "number" ? { used: error2.used } : {},
215
+ ...typeof error2.requested === "number" ? { requested: error2.requested } : {},
209
216
  ...failures.length ? { failures } : {},
210
217
  next_action: nextActionForApiError(error2)
211
218
  }
@@ -298,6 +305,16 @@ function exitCodeFor(code) {
298
305
  if (code === "verify_timeout") return 7;
299
306
  return 1;
300
307
  }
308
+ var EXIT_CODES = {
309
+ "0": "success",
310
+ "1": "api_error \u2014 the server returned an error (see the error envelope)",
311
+ "2": "invalid_usage \u2014 bad arguments, bad options, or an unknown command",
312
+ "3": "auth_error \u2014 missing or invalid credential; re-authenticate (dropthis login)",
313
+ "4": "local_input_error \u2014 a local file or path could not be read",
314
+ "5": "network_error \u2014 could not reach the API; safe to retry",
315
+ "6": "verify_pending \u2014 a domain is not verified yet; retry later (domains verify)",
316
+ "7": "verify_timeout \u2014 domain verification timed out while waiting (--wait)"
317
+ };
301
318
 
302
319
  // src/program.ts
303
320
  var import_commander = require("commander");
@@ -317,7 +334,8 @@ async function resolveCredential(input) {
317
334
  ...stored.keyId ? { keyId: stored.keyId } : {},
318
335
  ...stored.keyLast4 ? { keyLast4: stored.keyLast4 } : {},
319
336
  ...stored.accountId ? { accountId: stored.accountId } : {},
320
- ...stored.email ? { email: stored.email } : {}
337
+ ...stored.email ? { email: stored.email } : {},
338
+ ...stored.scopes?.length ? { scopes: stored.scopes } : {}
321
339
  };
322
340
  }
323
341
  async function requireCredential(input) {
@@ -473,6 +491,14 @@ function writeApiError(deps, details) {
473
491
  deps.stderr(` ${f.path}: ${f.reason}
474
492
  `);
475
493
  }
494
+ if (details.code === "feature_not_in_plan" || details.code === "quota_exceeded") {
495
+ if (details.currentPlan && details.requiredPlan) {
496
+ deps.stderr(
497
+ `${hint(`Plan: ${details.currentPlan} \u2192 needs ${details.requiredPlan}`)}
498
+ `
499
+ );
500
+ }
501
+ }
476
502
  const nextAction = nextActionForApiError(details, "human");
477
503
  if (nextAction) deps.stderr(`${hint(nextAction)}
478
504
  `);
@@ -546,7 +572,8 @@ async function runAccountGet(_input, deps) {
546
572
  pairs.push(["Workspace", `${String(ws.name)} (${String(ws.kind)})${role}`]);
547
573
  }
548
574
  const usage = data?.usage;
549
- const limits = data?.limits;
575
+ const entitlements = data?.entitlements;
576
+ const limits = entitlements?.limits;
550
577
  if (typeof usage?.storageUsedBytes === "number") {
551
578
  if (typeof limits?.maxStorageBytes === "number") {
552
579
  pairs.push([
@@ -563,6 +590,9 @@ async function runAccountGet(_input, deps) {
563
590
  `${usage.customDomainsUsed} of ${limits.maxCustomHostnames}`
564
591
  ]);
565
592
  }
593
+ if (typeof usage?.seatsUsed === "number" && typeof limits?.seatLimit === "number") {
594
+ pairs.push(["Seats", `${usage.seatsUsed} of ${limits.seatLimit}`]);
595
+ }
566
596
  writeKv(deps, pairs, { ok: true, account: result.data });
567
597
  if (typeof data?.upgradeUrl === "string") {
568
598
  deps.stderr(`${hint(`Upgrade: ${data.upgradeUrl}`)}
@@ -654,14 +684,55 @@ async function runApiKeysCreate(input, deps) {
654
684
  } catch {
655
685
  return writeAuthError(deps);
656
686
  }
657
- const result = await deps.client.apiKeys.create({ label: input.label });
687
+ const keyType = input.type ?? "delegated";
688
+ if (input.workspace && keyType !== "service") {
689
+ return writeError(
690
+ deps,
691
+ "invalid_usage",
692
+ "--workspace is only valid with --service (it pins a CI service key to one workspace). A delegated login key is account-scoped and switchable \u2014 use `dropthis workspace use <slug>` to change its active workspace."
693
+ );
694
+ }
695
+ if (input.allowedWorkspaces?.length && keyType === "service") {
696
+ return writeError(
697
+ deps,
698
+ "invalid_usage",
699
+ "--allowed-workspace is only valid for delegated keys; do not combine it with --service."
700
+ );
701
+ }
702
+ if (keyType === "service" && !input.workspace) {
703
+ return writeError(
704
+ deps,
705
+ "invalid_usage",
706
+ "Service keys must be pinned to a workspace.",
707
+ "Run: dropthis workspace list to see slugs, then: dropthis api-keys create --service --workspace <slug>"
708
+ );
709
+ }
710
+ const label = input.label ?? (keyType === "service" ? `Service key${input.workspace ? ` (${input.workspace})` : ""}` : "CLI key");
711
+ const createInput = {
712
+ label,
713
+ type: keyType
714
+ };
715
+ if (keyType === "service" && input.workspace) {
716
+ createInput.workspace = input.workspace;
717
+ }
718
+ if (input.allowedWorkspaces?.length) {
719
+ createInput.allowedWorkspaces = input.allowedWorkspaces;
720
+ }
721
+ const result = await deps.client.apiKeys.create(createInput);
658
722
  if (result.error) return writeApiError(deps, result.error);
659
723
  const data = result.data;
660
724
  const pairs = [];
661
725
  if (data.id) pairs.push(["ID", String(data.id)]);
662
726
  if (data.key) pairs.push(["Key", String(data.key)]);
663
727
  if (data.keyLast4) pairs.push(["Last 4", String(data.keyLast4)]);
728
+ if (data.label) pairs.push(["Label", String(data.label)]);
664
729
  writeKv(deps, pairs, { ok: true, api_key: result.data });
730
+ if (deps.outputMode === "human") {
731
+ deps.stderr(
732
+ `${hint("Store this key now \u2014 it will not be shown again.")}
733
+ `
734
+ );
735
+ }
665
736
  return { exitCode: 0 };
666
737
  }
667
738
  async function runApiKeysList(_input, deps) {
@@ -826,7 +897,10 @@ var COMMAND_ANNOTATIONS = {
826
897
  },
827
898
  "api-keys create": {
828
899
  auth: "required",
829
- examples: ["dropthis api-keys create --label CI --json"]
900
+ examples: [
901
+ "dropthis api-keys create --label CI --json",
902
+ "dropthis api-keys create --service --workspace acme --json"
903
+ ]
830
904
  },
831
905
  "api-keys list": {
832
906
  auth: "required",
@@ -851,17 +925,6 @@ var COMMAND_ANNOTATIONS = {
851
925
  "dropthis workspace use ws_team123"
852
926
  ]
853
927
  },
854
- keys: {
855
- auth: "required",
856
- examples: ["dropthis keys create --service --workspace acme --json"]
857
- },
858
- "keys create": {
859
- auth: "required",
860
- examples: [
861
- "dropthis keys create --json",
862
- "dropthis keys create --service --workspace acme --json"
863
- ]
864
- },
865
928
  login: {
866
929
  examples: [
867
930
  "dropthis login",
@@ -889,7 +952,9 @@ var COMMAND_ANNOTATIONS = {
889
952
  auth: "required",
890
953
  examples: ["dropthis account delete --yes --json"]
891
954
  },
892
- doctor: { examples: ["dropthis doctor --json"] },
955
+ doctor: {
956
+ examples: ["dropthis doctor --json", "dropthis doctor --online --json"]
957
+ },
893
958
  upgrade: { examples: ["dropthis upgrade", "dropthis upgrade --json"] },
894
959
  commands: { examples: ["dropthis commands --json"] }
895
960
  };
@@ -916,6 +981,12 @@ function toEntry(cmd, path) {
916
981
  function buildCatalog(program) {
917
982
  return program.commands.map((cmd) => toEntry(cmd, []));
918
983
  }
984
+ function buildGlobalOptions(program) {
985
+ return program.options.map((o) => ({
986
+ flags: o.flags,
987
+ description: o.description
988
+ }));
989
+ }
919
990
 
920
991
  // src/commands/commands.ts
921
992
  async function runCommands(_input, deps) {
@@ -923,7 +994,9 @@ async function runCommands(_input, deps) {
923
994
  `${JSON.stringify({
924
995
  ok: true,
925
996
  output: "JSON by default in CI, pipes, non-TTY, --json, or --quiet.",
926
- commands: buildCatalog(deps.program)
997
+ commands: buildCatalog(deps.program),
998
+ global_options: buildGlobalOptions(deps.program),
999
+ exit_codes: EXIT_CODES
927
1000
  })}
928
1001
  `
929
1002
  );
@@ -1013,10 +1086,76 @@ function spreadData(data) {
1013
1086
  }
1014
1087
 
1015
1088
  // src/version.ts
1016
- var CLI_VERSION = true ? "0.30.1" : "0.0.0-dev";
1089
+ var CLI_VERSION = true ? "0.33.0" : "0.0.0-dev";
1017
1090
 
1018
1091
  // src/commands/doctor.ts
1019
- async function runDoctor(_input, deps) {
1092
+ async function onlineChecks(deps, hasCredential) {
1093
+ if (!hasCredential) {
1094
+ return {
1095
+ checks: [
1096
+ {
1097
+ name: "auth",
1098
+ status: "fail",
1099
+ detail: "No credential found \u2014 run `dropthis login` or set DROPTHIS_API_KEY."
1100
+ }
1101
+ ],
1102
+ exitCode: exitCodeFor("auth_error")
1103
+ };
1104
+ }
1105
+ const account = await deps.client.account.get();
1106
+ if (account.error) {
1107
+ const code = cliErrorCodeFor(account.error);
1108
+ const isAuth = code === "auth_error";
1109
+ return {
1110
+ checks: [
1111
+ {
1112
+ name: "api_reachable",
1113
+ status: isAuth ? "ok" : "fail",
1114
+ detail: isAuth ? "reached the API" : `could not reach the API: ${account.error.message}`
1115
+ },
1116
+ {
1117
+ name: "auth",
1118
+ status: isAuth ? "fail" : "warn",
1119
+ detail: isAuth ? "credential rejected \u2014 re-authenticate with `dropthis login`" : account.error.message
1120
+ }
1121
+ ],
1122
+ exitCode: exitCodeFor(code)
1123
+ };
1124
+ }
1125
+ const data = account.data;
1126
+ const checks = [
1127
+ { name: "api_reachable", status: "ok", detail: "reached the API" },
1128
+ { name: "auth", status: "ok", detail: "credential accepted" }
1129
+ ];
1130
+ const ws = data?.workspace;
1131
+ checks.push(
1132
+ ws?.name ? {
1133
+ name: "workspace",
1134
+ status: "ok",
1135
+ detail: `active: ${String(ws.name)} (${String(ws.kind)})`
1136
+ } : {
1137
+ name: "workspace",
1138
+ status: "warn",
1139
+ detail: "no active workspace resolved \u2014 pass --workspace or run `dropthis workspace use`"
1140
+ }
1141
+ );
1142
+ if (data?.plan) {
1143
+ checks.push({ name: "plan", status: "ok", detail: String(data.plan) });
1144
+ }
1145
+ const domains = await deps.client.domains.list();
1146
+ if (!domains.error) {
1147
+ const list = domains.data?.domains ?? [];
1148
+ const live = list.filter((d) => d.status === "live").length;
1149
+ const pending = list.length - live;
1150
+ checks.push({
1151
+ name: "domains",
1152
+ status: pending > 0 ? "warn" : "ok",
1153
+ detail: `${list.length} connected, ${live} live${pending > 0 ? `, ${pending} pending verification` : ""}`
1154
+ });
1155
+ }
1156
+ return { checks, exitCode: 0 };
1157
+ }
1158
+ async function runDoctor(input, deps) {
1020
1159
  const stored = await deps.store.read();
1021
1160
  const credential = await resolveCredential({
1022
1161
  env: deps.env,
@@ -1029,13 +1168,30 @@ async function runDoctor(_input, deps) {
1029
1168
  ["Auth", authSource],
1030
1169
  ["Storage", storageBackend]
1031
1170
  ];
1171
+ if (!input.online) {
1172
+ writeKv(deps, pairs, {
1173
+ ok: true,
1174
+ version: CLI_VERSION,
1175
+ auth: { source: authSource },
1176
+ storage: { backend: storageBackend }
1177
+ });
1178
+ return { exitCode: 0 };
1179
+ }
1180
+ const { checks, exitCode } = await onlineChecks(
1181
+ deps,
1182
+ credential !== void 0
1183
+ );
1184
+ for (const c of checks) {
1185
+ pairs.push([c.name, `${c.status} \u2014 ${c.detail}`]);
1186
+ }
1032
1187
  writeKv(deps, pairs, {
1033
- ok: true,
1188
+ ok: exitCode === 0,
1034
1189
  version: CLI_VERSION,
1035
1190
  auth: { source: authSource },
1036
- storage: { backend: storageBackend }
1191
+ storage: { backend: storageBackend },
1192
+ checks
1037
1193
  });
1038
- return { exitCode: 0 };
1194
+ return { exitCode };
1039
1195
  }
1040
1196
 
1041
1197
  // src/commands/domains.ts
@@ -1616,8 +1772,8 @@ async function runDropsDelete(target, input, deps) {
1616
1772
  return { exitCode: 0 };
1617
1773
  }
1618
1774
 
1619
- // src/commands/keys.ts
1620
- async function runKeysCreate(input, deps) {
1775
+ // src/commands/invitations.ts
1776
+ async function runInvitationsList(_input, deps) {
1621
1777
  try {
1622
1778
  await requireCredential({
1623
1779
  ...deps.apiKey ? { apiKey: deps.apiKey } : {},
@@ -1627,45 +1783,67 @@ async function runKeysCreate(input, deps) {
1627
1783
  } catch {
1628
1784
  return writeAuthError(deps);
1629
1785
  }
1630
- const keyType = input.type ?? "delegated";
1631
- if (input.workspace && keyType !== "service") {
1632
- return writeError(
1633
- deps,
1634
- "invalid_usage",
1635
- "--workspace is only valid with --service (it pins a CI service key to one workspace). A delegated login key is account-scoped and switchable \u2014 use `dropthis workspace use <slug>` to change its active workspace."
1636
- );
1637
- }
1638
- if (keyType === "service" && !input.workspace) {
1639
- return writeError(
1640
- deps,
1641
- "invalid_usage",
1642
- "Service keys must be pinned to a workspace.",
1643
- "Run: dropthis workspace list to see slugs, then: dropthis keys create --service --workspace <slug>"
1644
- );
1786
+ const result = await deps.client.invitations.list();
1787
+ if (result.error) return writeApiError(deps, result.error);
1788
+ const raw = unwrapListData(result.data, "invitations");
1789
+ const items = Array.isArray(raw) ? raw : [];
1790
+ if (deps.outputMode === "human") {
1791
+ if (items.length === 0) {
1792
+ writeEmpty(deps, "No pending invitations.", {
1793
+ ok: true,
1794
+ invitations: []
1795
+ });
1796
+ } else {
1797
+ for (const inv of items) {
1798
+ deps.stdout(`${inv.email} ${inv.role} ${inv.status} ${inv.id}
1799
+ `);
1800
+ }
1801
+ }
1802
+ } else {
1803
+ writeJson(deps, { ok: true, invitations: items });
1645
1804
  }
1646
- const label = input.label ?? (keyType === "service" ? `Service key${input.workspace ? ` (${input.workspace})` : ""}` : "CLI key");
1647
- const createInput = {
1648
- label,
1649
- type: keyType
1650
- };
1651
- if (keyType === "service" && input.workspace) {
1652
- createInput.workspace = input.workspace;
1805
+ return { exitCode: 0 };
1806
+ }
1807
+ async function runInvitationsAccept(input, deps) {
1808
+ try {
1809
+ await requireCredential({
1810
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
1811
+ env: deps.env,
1812
+ store: deps.store
1813
+ });
1814
+ } catch {
1815
+ return writeAuthError(deps);
1653
1816
  }
1654
- const result = await deps.client.apiKeys.create(createInput);
1817
+ const result = await deps.client.invitations.accept({ token: input.token });
1655
1818
  if (result.error) return writeApiError(deps, result.error);
1656
- const data = result.data;
1657
- const pairs = [];
1658
- if (data.id) pairs.push(["ID", String(data.id)]);
1659
- if (data.key) pairs.push(["Key", String(data.key)]);
1660
- if (data.keyLast4) pairs.push(["Last 4", String(data.keyLast4)]);
1661
- if (data.label) pairs.push(["Label", String(data.label)]);
1662
- writeKv(deps, pairs, { ok: true, api_key: result.data });
1663
- if (deps.outputMode === "human") {
1664
- deps.stderr(
1665
- `${hint("Store this key now \u2014 it will not be shown again.")}
1666
- `
1667
- );
1819
+ const ws = result.data;
1820
+ writeHumanOrJson(
1821
+ deps,
1822
+ success(`Joined ${ws.name} (${ws.slug}) \u2014 now your active workspace.`),
1823
+ { ok: true, workspace: ws }
1824
+ );
1825
+ return { exitCode: 0 };
1826
+ }
1827
+ async function runInvitationsAcceptById(input, deps) {
1828
+ try {
1829
+ await requireCredential({
1830
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
1831
+ env: deps.env,
1832
+ store: deps.store
1833
+ });
1834
+ } catch {
1835
+ return writeAuthError(deps);
1668
1836
  }
1837
+ const result = await deps.client.invitations.acceptById({
1838
+ invitationId: input.invitationId
1839
+ });
1840
+ if (result.error) return writeApiError(deps, result.error);
1841
+ const ws = result.data;
1842
+ writeHumanOrJson(
1843
+ deps,
1844
+ success(`Joined ${ws.name} (${ws.slug}) \u2014 now your active workspace.`),
1845
+ { ok: true, workspace: ws }
1846
+ );
1669
1847
  return { exitCode: 0 };
1670
1848
  }
1671
1849
 
@@ -1728,7 +1906,8 @@ async function runLoginInteractive(deps) {
1728
1906
  const authedClient = deps.createClient(session.data.token);
1729
1907
  const apiKey = await authedClient.apiKeys.create({
1730
1908
  label: "CLI",
1731
- type: "delegated"
1909
+ type: "delegated",
1910
+ ...deps.scope ? { scopes: [deps.scope] } : {}
1732
1911
  });
1733
1912
  if (apiKey.error) {
1734
1913
  prompts4.cancel(apiKey.error.message);
@@ -1740,6 +1919,7 @@ async function runLoginInteractive(deps) {
1740
1919
  keyLast4: apiKey.data.keyLast4,
1741
1920
  accountId: apiKey.data.accountId ?? session.data.accountId,
1742
1921
  email,
1922
+ ...apiKey.data.scopes ? { scopes: apiKey.data.scopes } : {},
1743
1923
  storage: "secure"
1744
1924
  });
1745
1925
  prompts4.outro("Logged in successfully.");
@@ -1774,7 +1954,10 @@ async function runLoginVerify(input, deps) {
1774
1954
  const authedClient = deps.createClient(session.data.token);
1775
1955
  const apiKey = await authedClient.apiKeys.create({
1776
1956
  label: "CLI",
1777
- type: "delegated"
1957
+ type: "delegated",
1958
+ // A single bundle name (e.g. "team") requests a capability-scoped key; the
1959
+ // server returns the resolved scope list on the mint response.
1960
+ ...input.scope ? { scopes: [input.scope] } : {}
1778
1961
  });
1779
1962
  if (apiKey.error) {
1780
1963
  return writeApiError(deps, apiKey.error);
@@ -1785,6 +1968,7 @@ async function runLoginVerify(input, deps) {
1785
1968
  keyLast4: apiKey.data.keyLast4,
1786
1969
  accountId: apiKey.data.accountId ?? session.data.accountId,
1787
1970
  email: input.email,
1971
+ ...apiKey.data.scopes ? { scopes: apiKey.data.scopes } : {},
1788
1972
  storage: "secure"
1789
1973
  });
1790
1974
  writeHumanOrJson(deps, success(`Logged in as ${input.email}.`), {
@@ -1813,6 +1997,115 @@ async function runLogout(input, deps) {
1813
1997
  return { exitCode: 0 };
1814
1998
  }
1815
1999
 
2000
+ // src/commands/members.ts
2001
+ var prompts5 = __toESM(require("@clack/prompts"), 1);
2002
+ async function runMembersList(workspaceId, _input, deps) {
2003
+ try {
2004
+ await requireCredential({
2005
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2006
+ env: deps.env,
2007
+ store: deps.store
2008
+ });
2009
+ } catch {
2010
+ return writeAuthError(deps);
2011
+ }
2012
+ const result = await deps.client.members.list(workspaceId);
2013
+ if (result.error) return writeApiError(deps, result.error);
2014
+ const raw = unwrapListData(result.data, "members");
2015
+ const items = Array.isArray(raw) ? raw : [];
2016
+ if (deps.outputMode === "human") {
2017
+ if (items.length === 0) {
2018
+ writeEmpty(deps, "No members.", { ok: true, members: [] });
2019
+ } else {
2020
+ for (const m of items) {
2021
+ const who = m.email || m.accountId;
2022
+ const you = m.isYou ? "(you)" : "";
2023
+ deps.stdout(`${who} ${m.role} ${you}
2024
+ `);
2025
+ }
2026
+ }
2027
+ } else {
2028
+ writeJson(deps, { ok: true, members: items });
2029
+ }
2030
+ return { exitCode: 0 };
2031
+ }
2032
+ async function runMembersInvite(workspaceId, input, deps) {
2033
+ try {
2034
+ await requireCredential({
2035
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2036
+ env: deps.env,
2037
+ store: deps.store
2038
+ });
2039
+ } catch {
2040
+ return writeAuthError(deps);
2041
+ }
2042
+ const role = input.role ?? "member";
2043
+ const result = await deps.client.members.invite(workspaceId, {
2044
+ email: input.email,
2045
+ role
2046
+ });
2047
+ if (result.error) return writeApiError(deps, result.error);
2048
+ writeHumanOrJson(deps, success(`Invited ${input.email} as ${role}`), {
2049
+ ok: true,
2050
+ invitation: result.data
2051
+ });
2052
+ return { exitCode: 0 };
2053
+ }
2054
+ async function runMembersRole(workspaceId, accountId, input, deps) {
2055
+ try {
2056
+ await requireCredential({
2057
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2058
+ env: deps.env,
2059
+ store: deps.store
2060
+ });
2061
+ } catch {
2062
+ return writeAuthError(deps);
2063
+ }
2064
+ const result = await deps.client.members.updateRole(workspaceId, accountId, {
2065
+ role: input.role
2066
+ });
2067
+ if (result.error) return writeApiError(deps, result.error);
2068
+ writeHumanOrJson(deps, success(`Set ${accountId} to ${input.role}`), {
2069
+ ok: true,
2070
+ member: result.data
2071
+ });
2072
+ return { exitCode: 0 };
2073
+ }
2074
+ async function runMembersRemove(workspaceId, accountId, input, deps) {
2075
+ if (!input.yes && input.interactive === false) {
2076
+ return writeError(
2077
+ deps,
2078
+ "invalid_usage",
2079
+ "Pass --yes to remove in non-interactive mode."
2080
+ );
2081
+ }
2082
+ if (!input.yes && input.interactive !== false) {
2083
+ const confirmed = await prompts5.confirm({
2084
+ message: `Remove ${accountId} from workspace ${workspaceId}?`
2085
+ });
2086
+ if (prompts5.isCancel(confirmed) || !confirmed) {
2087
+ return { exitCode: 0 };
2088
+ }
2089
+ }
2090
+ try {
2091
+ await requireCredential({
2092
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2093
+ env: deps.env,
2094
+ store: deps.store
2095
+ });
2096
+ } catch {
2097
+ return writeAuthError(deps);
2098
+ }
2099
+ const result = await deps.client.members.remove(workspaceId, accountId);
2100
+ if (result?.error) return writeApiError(deps, result.error);
2101
+ writeHumanOrJson(deps, success(`Removed ${accountId}`), {
2102
+ ok: true,
2103
+ removed: true,
2104
+ account_id: accountId
2105
+ });
2106
+ return { exitCode: 0 };
2107
+ }
2108
+
1816
2109
  // src/drop-operations.ts
1817
2110
  var import_node_crypto = require("crypto");
1818
2111
  var import_promises = require("fs/promises");
@@ -1949,6 +2242,9 @@ async function parseDropOptions(raw) {
1949
2242
  if (raw.noindex && raw.index) {
1950
2243
  throw new Error("Use either --noindex or --index, not both.");
1951
2244
  }
2245
+ if (raw.expiresAt && raw.noExpires) {
2246
+ throw new Error("Use either --expires-at or --no-expires, not both.");
2247
+ }
1952
2248
  if (raw.shared && raw.domain) {
1953
2249
  throw new Error("Use either --shared or --domain <hostname>, not both.");
1954
2250
  }
@@ -1977,6 +2273,7 @@ async function parseDropOptions(raw) {
1977
2273
  ...raw.noindex ? { noindex: true } : {},
1978
2274
  ...raw.index ? { noindex: false } : {},
1979
2275
  ...raw.expiresAt ? { expiresAt: raw.expiresAt } : {},
2276
+ ...raw.noExpires ? { expiresAt: null } : {},
1980
2277
  ...metadata ? { metadata } : {},
1981
2278
  ...raw.entry ? { entry: raw.entry } : {},
1982
2279
  ...raw.contentType ? { contentType: raw.contentType } : {},
@@ -2178,7 +2475,9 @@ async function runPull(target, input, deps) {
2178
2475
  const dropId = resolved.dropId;
2179
2476
  const defaultDirName = resolved.slug || resolved.dropId;
2180
2477
  const spin = shouldSpin(deps.outputMode) ? createSpinner("Pulling content\u2026", deps.stderr) : void 0;
2181
- const manifestResult = await deps.client.drops.getContent(dropId);
2478
+ const deploymentId = input.deployment;
2479
+ const manifestOptions = deploymentId ? { deploymentId } : void 0;
2480
+ const manifestResult = manifestOptions ? await deps.client.drops.getContent(dropId, manifestOptions) : await deps.client.drops.getContent(dropId);
2182
2481
  if (manifestResult.error) {
2183
2482
  spin?.fail();
2184
2483
  return writeApiError(deps, manifestResult.error);
@@ -2202,7 +2501,10 @@ async function runPull(target, input, deps) {
2202
2501
  try {
2203
2502
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
2204
2503
  for (const { path, dest } of destinations) {
2205
- const fileResult = await deps.client.drops.getContent(dropId, { path });
2504
+ const fileResult = await deps.client.drops.getContent(dropId, {
2505
+ path,
2506
+ ...deploymentId ? { deploymentId } : {}
2507
+ });
2206
2508
  if (fileResult.error) {
2207
2509
  spin?.fail();
2208
2510
  return writeApiError(deps, {
@@ -2389,9 +2691,9 @@ async function handleDryRun(dropId, input, raw, deps) {
2389
2691
  }
2390
2692
 
2391
2693
  // src/commands/update-settings.ts
2392
- var prompts5 = __toESM(require("@clack/prompts"), 1);
2694
+ var prompts6 = __toESM(require("@clack/prompts"), 1);
2393
2695
  var NO_SETTINGS_MESSAGE = "Nothing to update. Provide at least one settings option.";
2394
- var NO_SETTINGS_NEXT_ACTION = "Run dropthis update-settings --help, or retry with one of: --title, --visibility, --password, --no-password, --noindex, --index, --expires-at, --metadata, or --metadata-file. To replace the content at the URL instead, use dropthis update-content <dropId> ./dist.";
2696
+ var NO_SETTINGS_NEXT_ACTION = "Run dropthis update-settings --help, or retry with one of: --title, --visibility, --password, --no-password, --noindex, --index, --expires-at, --no-expires, --domain, --shared, --slug, --metadata, or --metadata-file. To replace the content at the URL instead, use dropthis update-content <dropId> ./dist.";
2395
2697
  async function runUpdateSettings(target, raw, deps) {
2396
2698
  const apiKey = raw.apiKey ?? deps.apiKey;
2397
2699
  try {
@@ -2421,7 +2723,7 @@ async function runUpdateSettings(target, raw, deps) {
2421
2723
  if (shouldPrompt(deps)) {
2422
2724
  const prompted = await promptForSettings(
2423
2725
  dropId,
2424
- deps.prompts ?? prompts5
2726
+ deps.prompts ?? prompts6
2425
2727
  );
2426
2728
  if (!prompted) return { exitCode: 0 };
2427
2729
  parsed = { ...parsed, ...prompted };
@@ -2610,8 +2912,10 @@ async function handleDryRun2(dropId, raw, deps) {
2610
2912
  if (options.visibility) fields.visibility = options.visibility;
2611
2913
  if (options.password !== void 0) fields.password = options.password;
2612
2914
  if (options.noindex !== void 0) fields.noindex = options.noindex;
2613
- if (options.expiresAt) fields.expires_at = options.expiresAt;
2915
+ if (options.expiresAt !== void 0) fields.expires_at = options.expiresAt;
2614
2916
  if (options.metadata) fields.metadata = options.metadata;
2917
+ if (options.domain) fields.domain = options.domain;
2918
+ if (options.slug) fields.slug = options.slug;
2615
2919
  deps.stdout(
2616
2920
  `${JSON.stringify({ ok: true, dryRun: true, kind: "settings_update", target: dropId, fields }, null, 2)}
2617
2921
  `
@@ -2759,6 +3063,8 @@ async function runWhoami(_input, deps) {
2759
3063
  pairs.push(["Source", credential.source]);
2760
3064
  if (credential.accountId ?? account.id)
2761
3065
  pairs.push(["Account", String(credential.accountId ?? account.id)]);
3066
+ if (credential.scopes?.length)
3067
+ pairs.push(["Scopes", credential.scopes.join(", ")]);
2762
3068
  const jsonEnvelope = {
2763
3069
  ok: true,
2764
3070
  authenticated: true,
@@ -2767,13 +3073,15 @@ async function runWhoami(_input, deps) {
2767
3073
  ...credential.keyId ? { key_id: credential.keyId } : {},
2768
3074
  ...last4 ? { key_last4: last4 } : {},
2769
3075
  ...credential.accountId ?? account.id ? { account_id: String(credential.accountId ?? account.id) } : {},
2770
- ...credential.email ?? account.email ? { email: String(credential.email ?? account.email) } : {}
3076
+ ...credential.email ?? account.email ? { email: String(credential.email ?? account.email) } : {},
3077
+ ...credential.scopes?.length ? { scopes: credential.scopes } : {}
2771
3078
  };
2772
3079
  writeKv(deps, pairs, jsonEnvelope);
2773
3080
  return { exitCode: 0 };
2774
3081
  }
2775
3082
 
2776
3083
  // src/commands/workspace.ts
3084
+ var prompts7 = __toESM(require("@clack/prompts"), 1);
2777
3085
  async function runWorkspaceList(_input, deps) {
2778
3086
  try {
2779
3087
  await requireCredential({
@@ -2826,6 +3134,104 @@ async function runWorkspaceUse(slugOrId, _input, deps) {
2826
3134
  );
2827
3135
  return { exitCode: 0 };
2828
3136
  }
3137
+ async function runWorkspaceCreate(input, deps) {
3138
+ try {
3139
+ await requireCredential({
3140
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3141
+ env: deps.env,
3142
+ store: deps.store
3143
+ });
3144
+ } catch {
3145
+ return writeAuthError(deps);
3146
+ }
3147
+ const createInput = { name: input.name };
3148
+ if (input.slug !== void 0) createInput.slug = input.slug;
3149
+ const result = await deps.client.workspaces.create(createInput);
3150
+ if (result.error) return writeApiError(deps, result.error);
3151
+ const ws = result.data;
3152
+ writeHumanOrJson(
3153
+ deps,
3154
+ success(`Created workspace: ${ws.name} (${ws.slug})`),
3155
+ {
3156
+ ok: true,
3157
+ workspace: ws
3158
+ }
3159
+ );
3160
+ if (ws.creatorCanReach === false && deps.outputMode === "human") {
3161
+ deps.stderr(
3162
+ `${hint("This key can't reach the new workspace \u2014 re-authenticate (dropthis login) to pick it up.")}
3163
+ `
3164
+ );
3165
+ }
3166
+ return { exitCode: 0 };
3167
+ }
3168
+ async function runWorkspaceRename(workspaceId, input, deps) {
3169
+ if (input.name === void 0 && input.slug === void 0) {
3170
+ return writeError(
3171
+ deps,
3172
+ "invalid_usage",
3173
+ "Provide --name and/or --slug to rename."
3174
+ );
3175
+ }
3176
+ try {
3177
+ await requireCredential({
3178
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3179
+ env: deps.env,
3180
+ store: deps.store
3181
+ });
3182
+ } catch {
3183
+ return writeAuthError(deps);
3184
+ }
3185
+ const renameInput = {};
3186
+ if (input.name !== void 0) renameInput.name = input.name;
3187
+ if (input.slug !== void 0) renameInput.slug = input.slug;
3188
+ const result = await deps.client.workspaces.rename(workspaceId, renameInput);
3189
+ if (result.error) return writeApiError(deps, result.error);
3190
+ const ws = result.data;
3191
+ writeHumanOrJson(
3192
+ deps,
3193
+ success(`Renamed workspace: ${ws.name} (${ws.slug})`),
3194
+ {
3195
+ ok: true,
3196
+ workspace: ws
3197
+ }
3198
+ );
3199
+ return { exitCode: 0 };
3200
+ }
3201
+ async function runWorkspaceDelete(workspaceId, input, deps) {
3202
+ if (!input.yes && input.interactive === false) {
3203
+ return writeError(
3204
+ deps,
3205
+ "invalid_usage",
3206
+ "Pass --yes to delete in non-interactive mode."
3207
+ );
3208
+ }
3209
+ if (!input.yes && input.interactive !== false) {
3210
+ const confirmed = await prompts7.confirm({
3211
+ message: `Delete workspace ${workspaceId}?`
3212
+ });
3213
+ if (prompts7.isCancel(confirmed) || !confirmed) {
3214
+ return { exitCode: 0 };
3215
+ }
3216
+ }
3217
+ try {
3218
+ await requireCredential({
3219
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3220
+ env: deps.env,
3221
+ store: deps.store
3222
+ });
3223
+ } catch {
3224
+ return writeAuthError(deps);
3225
+ }
3226
+ const result = await deps.client.workspaces.delete(workspaceId);
3227
+ if (result?.error) return writeApiError(deps, result.error);
3228
+ writeHumanOrJson(deps, success(`Deleted workspace ${workspaceId}`), {
3229
+ ok: true,
3230
+ deleted: true,
3231
+ id: workspaceId
3232
+ });
3233
+ return { exitCode: 0 };
3234
+ }
2829
3235
 
2830
3236
  // src/context.ts
2831
3237
  var import_node2 = require("@dropthis/node");
@@ -3151,7 +3557,8 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3151
3557
  return;
3152
3558
  }
3153
3559
  }
3154
- if (deps.outputMode === "human" && deps.interactive && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
3560
+ const publishWasExplicit = (program.rawArgs ?? []).includes("publish");
3561
+ if (!publishWasExplicit && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
3155
3562
  const suggestion = suggestCommand(
3156
3563
  publishInput,
3157
3564
  program.commands.map((c) => c.name())
@@ -3165,13 +3572,15 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3165
3572
  ).exitCode;
3166
3573
  return;
3167
3574
  }
3168
- const prompts6 = await import("@clack/prompts");
3169
- const confirmed = await prompts6.confirm({
3170
- message: `Publish '${publishInput}' as inline text?`,
3171
- initialValue: false
3172
- });
3173
- if (prompts6.isCancel(confirmed) || !confirmed) {
3174
- return;
3575
+ if (deps.outputMode === "human" && deps.interactive) {
3576
+ const prompts8 = await import("@clack/prompts");
3577
+ const confirmed = await prompts8.confirm({
3578
+ message: `Publish '${publishInput}' as inline text?`,
3579
+ initialValue: false
3580
+ });
3581
+ if (prompts8.isCancel(confirmed) || !confirmed) {
3582
+ return;
3583
+ }
3175
3584
  }
3176
3585
  }
3177
3586
  let dropOpts;
@@ -3262,10 +3671,19 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3262
3671
  );
3263
3672
  program.command("update-settings <dropId>").description(
3264
3673
  "Change a drop's title, visibility, password, expiry, or metadata (pass the full drop_\u2026 id, not the slug).\nContent stays unchanged \u2014 use update-content to replace the files at the URL."
3265
- ).option("--title <title>", "Change title").option("--visibility <v>", "Set to public or unlisted").option("--password <password>", "Require password to view").option("--no-password", "Remove password protection").option("--noindex", "Prevent search-engine indexing").option("--index", "Allow search-engine indexing").option("--expires-at <datetime>", "Auto-delete after this ISO 8601 date").option(
3674
+ ).option("--title <title>", "Change title").option("--visibility <v>", "Set to public or unlisted").option("--password <password>", "Require password to view").option("--no-password", "Remove password protection").option("--noindex", "Prevent search-engine indexing").option("--index", "Allow search-engine indexing").option("--expires-at <datetime>", "Auto-delete after this ISO 8601 date").option("--no-expires", "Clear the auto-delete expiry").option(
3266
3675
  "--metadata <json>",
3267
3676
  `Attach JSON key-value pairs, e.g. '{"source":"ci"}'`
3268
3677
  ).option("--metadata-file <path>", "Read metadata JSON from a file").option(
3678
+ "--domain <hostname>",
3679
+ "Move this drop to a connected custom domain (must be live \u2014 see: dropthis domains list)."
3680
+ ).option(
3681
+ "--shared",
3682
+ "Unmount this drop back to the shared dropthis pool. Cannot combine with --domain."
3683
+ ).option(
3684
+ "--slug <vanity-slug>",
3685
+ "Rename the vanity slug on a path-mode custom domain (1\u201363 lowercase letters/digits/hyphens). 409 on conflict \u2014 does not auto-suffix."
3686
+ ).option(
3269
3687
  "--if-revision <n>",
3270
3688
  "Fail if current revision doesn't match (optimistic lock)",
3271
3689
  parseInteger
@@ -3297,7 +3715,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3297
3715
  );
3298
3716
  program.command("pull <target>").description(
3299
3717
  "Download a drop's files into a local directory (accepts the drop_\u2026 id, the drop URL, or its slug).\nFetches the current deployment's file manifest and writes every file. Pull, edit, then update-content to ship the changes back to the same URL \u2014 also the rollback path."
3300
- ).option("-o, --output <dir>", "Output directory (default: ./<slug-or-id>)").option("--json", "Force JSON output").addHelpText(
3718
+ ).option("-o, --output <dir>", "Output directory (default: ./<slug-or-id>)").option("--deployment <deploymentId>", "Download a specific deployment").option("--json", "Force JSON output").addHelpText(
3301
3719
  "after",
3302
3720
  examplesBlock([
3303
3721
  "dropthis pull drop_abc123 -o ./site # download the drop's files",
@@ -3433,7 +3851,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3433
3851
  process.exitCode = result.exitCode;
3434
3852
  });
3435
3853
  domains.command("verify <hostname>").description(
3436
- "Trigger a DNS verification check. With --wait, polls until live or failed.\nUse after adding the DNS records from: dropthis domains connect\n\nExit codes: 0=live, 1=failed, 6=still pending (retry). Use --wait to poll automatically."
3854
+ "Trigger a DNS verification check. With --wait, polls until live or failed.\nUse after adding the DNS records from: dropthis domains connect\n\nExit codes (one-shot): 0=live, 1=failed, 6=still pending (retry). With --wait: 0=live, 1=failed, 7=timed out."
3437
3855
  ).option("--wait", "Poll until live (or failed/timeout)").option(
3438
3856
  "--timeout <secs>",
3439
3857
  "Max seconds to wait (default: 300)",
@@ -3489,11 +3907,35 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3489
3907
  }
3490
3908
  );
3491
3909
  const apiKeys = program.command("api-keys").description("Manage API keys");
3492
- apiKeys.command("create").description("Create an API key").option("--label <label>", "API key label", "CLI").option("--json", "Force JSON output").action(async (commandOptions) => {
3493
- const deps = await commandDeps(program, options, store, commandOptions);
3494
- const result = await runApiKeysCreate(commandOptions, deps);
3495
- process.exitCode = result.exitCode;
3496
- });
3910
+ apiKeys.command("create").description(
3911
+ "Create an API key. Without --service, mints a delegated account-scoped key (default for login). With --service --workspace <ws>, mints a CI key pinned to that workspace."
3912
+ ).option("--label <label>", "API key label").option(
3913
+ "--service",
3914
+ "Mint a pinned service key for CI/automation (requires --workspace to pin it)"
3915
+ ).option(
3916
+ "--workspace <slugOrId>",
3917
+ "Pin this service key to the given workspace slug or id (only valid with --service)"
3918
+ ).option(
3919
+ "--allowed-workspace <slugOrId>",
3920
+ "Allow a delegated key to access the given workspace slug or id",
3921
+ collect,
3922
+ []
3923
+ ).option("--json", "Force JSON output").action(
3924
+ async (commandOptions) => {
3925
+ const deps = await commandDeps(program, options, store, commandOptions);
3926
+ const result = await runApiKeysCreate(
3927
+ {
3928
+ ...commandOptions.label !== void 0 ? { label: commandOptions.label } : {},
3929
+ type: commandOptions.service ? "service" : "delegated",
3930
+ ...commandOptions.workspace !== void 0 ? { workspace: commandOptions.workspace } : {},
3931
+ ...commandOptions.allowedWorkspace?.length ? { allowedWorkspaces: commandOptions.allowedWorkspace } : {},
3932
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3933
+ },
3934
+ deps
3935
+ );
3936
+ process.exitCode = result.exitCode;
3937
+ }
3938
+ );
3497
3939
  apiKeys.command("list").description("List API keys").option("--json", "Force JSON output").action(async (commandOptions) => {
3498
3940
  const deps = await commandDeps(program, options, store, commandOptions);
3499
3941
  const result = await runApiKeysList(commandOptions, deps);
@@ -3530,25 +3972,80 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3530
3972
  const result = await runWorkspaceUse(slugOrId, commandOptions, deps);
3531
3973
  process.exitCode = result.exitCode;
3532
3974
  });
3533
- const keys = program.command("keys").description(
3534
- "Mint API keys (delegated login keys or pinned CI service keys)"
3975
+ workspace.command("create <name>").description("Create a new team workspace").option("--slug <slug>", "Vanity slug for the workspace").option("--json", "Force JSON output").action(
3976
+ async (name, commandOptions) => {
3977
+ const deps = await commandDeps(program, options, store, commandOptions);
3978
+ const result = await runWorkspaceCreate(
3979
+ {
3980
+ name,
3981
+ ...commandOptions.slug !== void 0 ? { slug: commandOptions.slug } : {},
3982
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3983
+ },
3984
+ deps
3985
+ );
3986
+ process.exitCode = result.exitCode;
3987
+ }
3535
3988
  );
3536
- keys.command("create").description(
3537
- "Create an API key. Without --service, mints a delegated account-scoped key (default for login). With --service --workspace <ws>, mints a CI key pinned to that workspace."
3538
- ).option("--label <label>", "Key label").option(
3539
- "--service",
3540
- "Mint a pinned service key for CI/automation (requires --workspace to pin it)"
3541
- ).option(
3542
- "--workspace <slugOrId>",
3543
- "Pin this service key to the given workspace slug or id (only valid with --service)"
3544
- ).option("--json", "Force JSON output").action(
3545
- async (commandOptions) => {
3989
+ workspace.command("rename <workspaceId>").description("Rename a workspace or change its vanity slug").option("--name <name>", "New workspace name").option("--slug <slug>", "New vanity slug").option("--json", "Force JSON output").action(
3990
+ async (workspaceId, commandOptions) => {
3546
3991
  const deps = await commandDeps(program, options, store, commandOptions);
3547
- const result = await runKeysCreate(
3992
+ const result = await runWorkspaceRename(
3993
+ workspaceId,
3548
3994
  {
3549
- ...commandOptions.label !== void 0 ? { label: commandOptions.label } : {},
3550
- type: commandOptions.service ? "service" : "delegated",
3551
- ...commandOptions.workspace !== void 0 ? { workspace: commandOptions.workspace } : {},
3995
+ ...commandOptions.name !== void 0 ? { name: commandOptions.name } : {},
3996
+ ...commandOptions.slug !== void 0 ? { slug: commandOptions.slug } : {},
3997
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3998
+ },
3999
+ deps
4000
+ );
4001
+ process.exitCode = result.exitCode;
4002
+ }
4003
+ );
4004
+ workspace.command("delete <workspaceId>").description("Delete a workspace").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(
4005
+ async (workspaceId, commandOptions) => {
4006
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4007
+ const global = globalOptions(program, commandOptions);
4008
+ const deps = await commandDeps(program, options, store, commandOptions);
4009
+ const result = await runWorkspaceDelete(
4010
+ workspaceId,
4011
+ {
4012
+ ...commandOptions,
4013
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
4014
+ },
4015
+ deps
4016
+ );
4017
+ process.exitCode = result.exitCode;
4018
+ }
4019
+ );
4020
+ const members = program.command("members").description("Manage team members");
4021
+ members.command("list <workspaceId>").description("List members of a workspace").option("--json", "Force JSON output").action(async (workspaceId, commandOptions) => {
4022
+ const deps = await commandDeps(program, options, store, commandOptions);
4023
+ const result = await runMembersList(workspaceId, commandOptions, deps);
4024
+ process.exitCode = result.exitCode;
4025
+ });
4026
+ members.command("invite <workspaceId>").description("Invite someone to a workspace by email").requiredOption("--email <email>", "Email address to invite").option("--role <role>", "Role to grant (admin or member)", "member").option("--json", "Force JSON output").action(
4027
+ async (workspaceId, commandOptions) => {
4028
+ const deps = await commandDeps(program, options, store, commandOptions);
4029
+ const result = await runMembersInvite(
4030
+ workspaceId,
4031
+ {
4032
+ email: commandOptions.email,
4033
+ ...commandOptions.role !== void 0 ? { role: commandOptions.role } : {},
4034
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4035
+ },
4036
+ deps
4037
+ );
4038
+ process.exitCode = result.exitCode;
4039
+ }
4040
+ );
4041
+ members.command("role <workspaceId> <accountId>").description("Change a member's role").requiredOption("--role <role>", "New role (owner, admin, or member)").option("--json", "Force JSON output").action(
4042
+ async (workspaceId, accountId, commandOptions) => {
4043
+ const deps = await commandDeps(program, options, store, commandOptions);
4044
+ const result = await runMembersRole(
4045
+ workspaceId,
4046
+ accountId,
4047
+ {
4048
+ role: commandOptions.role,
3552
4049
  ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3553
4050
  },
3554
4051
  deps
@@ -3556,7 +4053,51 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3556
4053
  process.exitCode = result.exitCode;
3557
4054
  }
3558
4055
  );
3559
- program.command("login").description("Authenticate with email OTP").option("--email <email>", "Email address").option("--otp <otp>", "One-time passcode").option("--json", "Force JSON output").action(
4056
+ members.command("remove <workspaceId> <accountId>").description("Remove a member from a workspace").option("--yes", "Confirm removal").option("--json", "Force JSON output").action(
4057
+ async (workspaceId, accountId, commandOptions) => {
4058
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4059
+ const global = globalOptions(program, commandOptions);
4060
+ const deps = await commandDeps(program, options, store, commandOptions);
4061
+ const result = await runMembersRemove(
4062
+ workspaceId,
4063
+ accountId,
4064
+ {
4065
+ ...commandOptions,
4066
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
4067
+ },
4068
+ deps
4069
+ );
4070
+ process.exitCode = result.exitCode;
4071
+ }
4072
+ );
4073
+ const invitations = program.command("invitations").description("Manage your workspace invitations");
4074
+ invitations.command("list", { isDefault: true }).description("List your pending workspace invitations").option("--json", "Force JSON output").action(async (commandOptions) => {
4075
+ const deps = await commandDeps(program, options, store, commandOptions);
4076
+ const result = await runInvitationsList(commandOptions, deps);
4077
+ process.exitCode = result.exitCode;
4078
+ });
4079
+ invitations.command("accept").description("Accept a workspace invitation by token").requiredOption("--token <token>", "Invitation token").option("--json", "Force JSON output").action(async (commandOptions) => {
4080
+ const deps = await commandDeps(program, options, store, commandOptions);
4081
+ const result = await runInvitationsAccept(commandOptions, deps);
4082
+ process.exitCode = result.exitCode;
4083
+ });
4084
+ invitations.command("accept-by-id <invitationId>").description("Accept a workspace invitation by its id").option("--json", "Force JSON output").action(
4085
+ async (invitationId, commandOptions) => {
4086
+ const deps = await commandDeps(program, options, store, commandOptions);
4087
+ const result = await runInvitationsAcceptById(
4088
+ {
4089
+ invitationId,
4090
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4091
+ },
4092
+ deps
4093
+ );
4094
+ process.exitCode = result.exitCode;
4095
+ }
4096
+ );
4097
+ program.command("login").description("Authenticate with email OTP").option("--email <email>", "Email address").option("--otp <otp>", "One-time passcode").option(
4098
+ "--scope <scope>",
4099
+ "Capability scope to request for the minted key (e.g. team)"
4100
+ ).option("--json", "Force JSON output").action(
3560
4101
  async (commandOptions) => {
3561
4102
  if (!commandOptions.email || !commandOptions.otp) {
3562
4103
  const deps2 = await commandDeps(program, options, store, commandOptions);
@@ -3574,7 +4115,8 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3574
4115
  const context = createContext({ global: global2 });
3575
4116
  const result2 = await runLoginInteractive({
3576
4117
  ...deps2,
3577
- createClient: (apiKey) => context.createClient(apiKey)
4118
+ createClient: (apiKey) => context.createClient(apiKey),
4119
+ ...commandOptions.scope ? { scope: commandOptions.scope } : {}
3578
4120
  });
3579
4121
  process.exitCode = result2.exitCode;
3580
4122
  return;
@@ -3586,6 +4128,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3586
4128
  {
3587
4129
  email: commandOptions.email,
3588
4130
  otp: commandOptions.otp,
4131
+ ...commandOptions.scope ? { scope: commandOptions.scope } : {},
3589
4132
  ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3590
4133
  },
3591
4134
  {
@@ -3602,7 +4145,10 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3602
4145
  const result = await runLoginRequest(commandOptions, deps);
3603
4146
  process.exitCode = result.exitCode;
3604
4147
  });
3605
- login?.command("verify").description("Verify email OTP and store an API key").requiredOption("--email <email>", "Email address").requiredOption("--otp <otp>", "One-time passcode").option("--json", "Force JSON output").action(
4148
+ login?.command("verify").description("Verify email OTP and store an API key").requiredOption("--email <email>", "Email address").requiredOption("--otp <otp>", "One-time passcode").option(
4149
+ "--scope <scope>",
4150
+ "Capability scope to request for the minted key (e.g. team)"
4151
+ ).option("--json", "Force JSON output").action(
3606
4152
  async (commandOptions) => {
3607
4153
  const deps = await commandDeps(program, options, store, commandOptions);
3608
4154
  const global = globalOptions(program, commandOptions);
@@ -3640,17 +4186,30 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3640
4186
  const result = await runAccountGet(commandOptions, deps);
3641
4187
  process.exitCode = result.exitCode;
3642
4188
  });
3643
- account.command("update").description("Update account display name").requiredOption("--display-name <name>", "Set the display name").option("--json", "Force JSON output").action(async (commandOptions) => {
3644
- const deps = await commandDeps(program, options, store, commandOptions);
3645
- const result = await runAccountUpdate(
3646
- {
3647
- displayName: commandOptions.displayName,
3648
- ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3649
- },
3650
- deps
3651
- );
3652
- process.exitCode = result.exitCode;
3653
- });
4189
+ account.command("update").description("Update account display name").option("--display-name <name>", "Set the display name").option("--clear-display-name", "Clear the display name").option("--json", "Force JSON output").action(
4190
+ async (commandOptions) => {
4191
+ if (commandOptions.displayName === void 0 && !commandOptions.clearDisplayName || commandOptions.displayName !== void 0 && commandOptions.clearDisplayName) {
4192
+ const writer = outputWriter(program, options, commandOptions);
4193
+ process.exitCode = writeError(
4194
+ writer,
4195
+ "invalid_usage",
4196
+ "Use either --display-name <name> or --clear-display-name."
4197
+ ).exitCode;
4198
+ return;
4199
+ }
4200
+ const displayName = commandOptions.clearDisplayName ? null : commandOptions.displayName;
4201
+ if (displayName === void 0) return;
4202
+ const deps = await commandDeps(program, options, store, commandOptions);
4203
+ const result = await runAccountUpdate(
4204
+ {
4205
+ displayName,
4206
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4207
+ },
4208
+ deps
4209
+ );
4210
+ process.exitCode = result.exitCode;
4211
+ }
4212
+ );
3654
4213
  account.command("delete").description("Delete your account").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(async (commandOptions) => {
3655
4214
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
3656
4215
  const global = globalOptions(program, commandOptions);
@@ -3664,7 +4223,10 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3664
4223
  );
3665
4224
  process.exitCode = result.exitCode;
3666
4225
  });
3667
- program.command("doctor").description("Report CLI diagnostics").option("--json", "Force JSON output").action(async (commandOptions) => {
4226
+ program.command("doctor").description("Report CLI diagnostics").option("--json", "Force JSON output").option(
4227
+ "--online",
4228
+ "Run a network preflight (auth + active workspace + custom-domain status) \u2014 exits 3 on auth failure, 5 on a network failure"
4229
+ ).action(async (commandOptions) => {
3668
4230
  const deps = await commandDeps(
3669
4231
  program,
3670
4232
  options,
@@ -3799,6 +4361,7 @@ function toDropOptions(options) {
3799
4361
  ...options.noindex ? { noindex: true } : {},
3800
4362
  ...options.index ? { index: true } : {},
3801
4363
  ...options.expiresAt ? { expiresAt: options.expiresAt } : {},
4364
+ ...options.expires === false || options.noExpires ? { noExpires: true } : {},
3802
4365
  ...options.metadata ? { metadata: options.metadata } : {},
3803
4366
  ...options.metadataFile ? { metadataFile: options.metadataFile } : {},
3804
4367
  ...options.entry ? { entry: options.entry } : {},