@dropthis/cli 0.31.0 → 0.33.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.
package/dist/cli.cjs CHANGED
@@ -305,6 +305,16 @@ function exitCodeFor(code) {
305
305
  if (code === "verify_timeout") return 7;
306
306
  return 1;
307
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
+ };
308
318
 
309
319
  // src/program.ts
310
320
  var import_commander = require("commander");
@@ -324,7 +334,8 @@ async function resolveCredential(input) {
324
334
  ...stored.keyId ? { keyId: stored.keyId } : {},
325
335
  ...stored.keyLast4 ? { keyLast4: stored.keyLast4 } : {},
326
336
  ...stored.accountId ? { accountId: stored.accountId } : {},
327
- ...stored.email ? { email: stored.email } : {}
337
+ ...stored.email ? { email: stored.email } : {},
338
+ ...stored.scopes?.length ? { scopes: stored.scopes } : {}
328
339
  };
329
340
  }
330
341
  async function requireCredential(input) {
@@ -673,14 +684,55 @@ async function runApiKeysCreate(input, deps) {
673
684
  } catch {
674
685
  return writeAuthError(deps);
675
686
  }
676
- 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);
677
722
  if (result.error) return writeApiError(deps, result.error);
678
723
  const data = result.data;
679
724
  const pairs = [];
680
725
  if (data.id) pairs.push(["ID", String(data.id)]);
681
726
  if (data.key) pairs.push(["Key", String(data.key)]);
682
727
  if (data.keyLast4) pairs.push(["Last 4", String(data.keyLast4)]);
728
+ if (data.label) pairs.push(["Label", String(data.label)]);
683
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
+ }
684
736
  return { exitCode: 0 };
685
737
  }
686
738
  async function runApiKeysList(_input, deps) {
@@ -845,7 +897,10 @@ var COMMAND_ANNOTATIONS = {
845
897
  },
846
898
  "api-keys create": {
847
899
  auth: "required",
848
- 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
+ ]
849
904
  },
850
905
  "api-keys list": {
851
906
  auth: "required",
@@ -870,17 +925,56 @@ var COMMAND_ANNOTATIONS = {
870
925
  "dropthis workspace use ws_team123"
871
926
  ]
872
927
  },
873
- keys: {
928
+ "workspace create": {
929
+ auth: "required",
930
+ examples: ['dropthis workspace create "Acme" --slug acme --json']
931
+ },
932
+ "workspace rename": {
933
+ auth: "required",
934
+ examples: ['dropthis workspace rename ws_team123 --name "Acme Inc" --json']
935
+ },
936
+ "workspace delete": {
874
937
  auth: "required",
875
- examples: ["dropthis keys create --service --workspace acme --json"]
938
+ examples: ["dropthis workspace delete ws_team123 --yes --json"]
876
939
  },
877
- "keys create": {
940
+ members: {
941
+ auth: "required",
942
+ examples: ["dropthis members list ws_team123 --json"]
943
+ },
944
+ "members list": {
945
+ auth: "required",
946
+ examples: ["dropthis members list ws_team123 --json"]
947
+ },
948
+ "members invite": {
878
949
  auth: "required",
879
950
  examples: [
880
- "dropthis keys create --json",
881
- "dropthis keys create --service --workspace acme --json"
951
+ "dropthis members invite ws_team123 --email teammate@acme.com --role member --json"
882
952
  ]
883
953
  },
954
+ "members role": {
955
+ auth: "required",
956
+ examples: ["dropthis members role ws_team123 acc_123 --role admin --json"]
957
+ },
958
+ "members remove": {
959
+ auth: "required",
960
+ examples: ["dropthis members remove ws_team123 acc_123 --yes --json"]
961
+ },
962
+ invitations: {
963
+ auth: "required",
964
+ examples: ["dropthis invitations --json"]
965
+ },
966
+ "invitations list": {
967
+ auth: "required",
968
+ examples: ["dropthis invitations list --json"]
969
+ },
970
+ "invitations accept": {
971
+ auth: "required",
972
+ examples: ["dropthis invitations accept --token inv_tok_abc --json"]
973
+ },
974
+ "invitations accept-by-id": {
975
+ auth: "required",
976
+ examples: ["dropthis invitations accept-by-id inv_123 --json"]
977
+ },
884
978
  login: {
885
979
  examples: [
886
980
  "dropthis login",
@@ -908,7 +1002,9 @@ var COMMAND_ANNOTATIONS = {
908
1002
  auth: "required",
909
1003
  examples: ["dropthis account delete --yes --json"]
910
1004
  },
911
- doctor: { examples: ["dropthis doctor --json"] },
1005
+ doctor: {
1006
+ examples: ["dropthis doctor --json", "dropthis doctor --online --json"]
1007
+ },
912
1008
  upgrade: { examples: ["dropthis upgrade", "dropthis upgrade --json"] },
913
1009
  commands: { examples: ["dropthis commands --json"] }
914
1010
  };
@@ -935,6 +1031,12 @@ function toEntry(cmd, path) {
935
1031
  function buildCatalog(program) {
936
1032
  return program.commands.map((cmd) => toEntry(cmd, []));
937
1033
  }
1034
+ function buildGlobalOptions(program) {
1035
+ return program.options.map((o) => ({
1036
+ flags: o.flags,
1037
+ description: o.description
1038
+ }));
1039
+ }
938
1040
 
939
1041
  // src/commands/commands.ts
940
1042
  async function runCommands(_input, deps) {
@@ -942,7 +1044,9 @@ async function runCommands(_input, deps) {
942
1044
  `${JSON.stringify({
943
1045
  ok: true,
944
1046
  output: "JSON by default in CI, pipes, non-TTY, --json, or --quiet.",
945
- commands: buildCatalog(deps.program)
1047
+ commands: buildCatalog(deps.program),
1048
+ global_options: buildGlobalOptions(deps.program),
1049
+ exit_codes: EXIT_CODES
946
1050
  })}
947
1051
  `
948
1052
  );
@@ -1032,10 +1136,76 @@ function spreadData(data) {
1032
1136
  }
1033
1137
 
1034
1138
  // src/version.ts
1035
- var CLI_VERSION = true ? "0.31.0" : "0.0.0-dev";
1139
+ var CLI_VERSION = true ? "0.33.1" : "0.0.0-dev";
1036
1140
 
1037
1141
  // src/commands/doctor.ts
1038
- async function runDoctor(_input, deps) {
1142
+ async function onlineChecks(deps, hasCredential) {
1143
+ if (!hasCredential) {
1144
+ return {
1145
+ checks: [
1146
+ {
1147
+ name: "auth",
1148
+ status: "fail",
1149
+ detail: "No credential found \u2014 run `dropthis login` or set DROPTHIS_API_KEY."
1150
+ }
1151
+ ],
1152
+ exitCode: exitCodeFor("auth_error")
1153
+ };
1154
+ }
1155
+ const account = await deps.client.account.get();
1156
+ if (account.error) {
1157
+ const code = cliErrorCodeFor(account.error);
1158
+ const isAuth = code === "auth_error";
1159
+ return {
1160
+ checks: [
1161
+ {
1162
+ name: "api_reachable",
1163
+ status: isAuth ? "ok" : "fail",
1164
+ detail: isAuth ? "reached the API" : `could not reach the API: ${account.error.message}`
1165
+ },
1166
+ {
1167
+ name: "auth",
1168
+ status: isAuth ? "fail" : "warn",
1169
+ detail: isAuth ? "credential rejected \u2014 re-authenticate with `dropthis login`" : account.error.message
1170
+ }
1171
+ ],
1172
+ exitCode: exitCodeFor(code)
1173
+ };
1174
+ }
1175
+ const data = account.data;
1176
+ const checks = [
1177
+ { name: "api_reachable", status: "ok", detail: "reached the API" },
1178
+ { name: "auth", status: "ok", detail: "credential accepted" }
1179
+ ];
1180
+ const ws = data?.workspace;
1181
+ checks.push(
1182
+ ws?.name ? {
1183
+ name: "workspace",
1184
+ status: "ok",
1185
+ detail: `active: ${String(ws.name)} (${String(ws.kind)})`
1186
+ } : {
1187
+ name: "workspace",
1188
+ status: "warn",
1189
+ detail: "no active workspace resolved \u2014 pass --workspace or run `dropthis workspace use`"
1190
+ }
1191
+ );
1192
+ if (data?.plan) {
1193
+ checks.push({ name: "plan", status: "ok", detail: String(data.plan) });
1194
+ }
1195
+ const domains = await deps.client.domains.list();
1196
+ if (!domains.error) {
1197
+ const list = domains.data?.domains ?? [];
1198
+ const live = list.filter((d) => d.status === "live").length;
1199
+ const pending = list.length - live;
1200
+ checks.push({
1201
+ name: "domains",
1202
+ status: pending > 0 ? "warn" : "ok",
1203
+ detail: `${list.length} connected, ${live} live${pending > 0 ? `, ${pending} pending verification` : ""}`
1204
+ });
1205
+ }
1206
+ return { checks, exitCode: 0 };
1207
+ }
1208
+ async function runDoctor(input, deps) {
1039
1209
  const stored = await deps.store.read();
1040
1210
  const credential = await resolveCredential({
1041
1211
  env: deps.env,
@@ -1048,13 +1218,30 @@ async function runDoctor(_input, deps) {
1048
1218
  ["Auth", authSource],
1049
1219
  ["Storage", storageBackend]
1050
1220
  ];
1221
+ if (!input.online) {
1222
+ writeKv(deps, pairs, {
1223
+ ok: true,
1224
+ version: CLI_VERSION,
1225
+ auth: { source: authSource },
1226
+ storage: { backend: storageBackend }
1227
+ });
1228
+ return { exitCode: 0 };
1229
+ }
1230
+ const { checks, exitCode } = await onlineChecks(
1231
+ deps,
1232
+ credential !== void 0
1233
+ );
1234
+ for (const c of checks) {
1235
+ pairs.push([c.name, `${c.status} \u2014 ${c.detail}`]);
1236
+ }
1051
1237
  writeKv(deps, pairs, {
1052
- ok: true,
1238
+ ok: exitCode === 0,
1053
1239
  version: CLI_VERSION,
1054
1240
  auth: { source: authSource },
1055
- storage: { backend: storageBackend }
1241
+ storage: { backend: storageBackend },
1242
+ checks
1056
1243
  });
1057
- return { exitCode: 0 };
1244
+ return { exitCode };
1058
1245
  }
1059
1246
 
1060
1247
  // src/commands/domains.ts
@@ -1635,8 +1822,8 @@ async function runDropsDelete(target, input, deps) {
1635
1822
  return { exitCode: 0 };
1636
1823
  }
1637
1824
 
1638
- // src/commands/keys.ts
1639
- async function runKeysCreate(input, deps) {
1825
+ // src/commands/invitations.ts
1826
+ async function runInvitationsList(_input, deps) {
1640
1827
  try {
1641
1828
  await requireCredential({
1642
1829
  ...deps.apiKey ? { apiKey: deps.apiKey } : {},
@@ -1646,45 +1833,67 @@ async function runKeysCreate(input, deps) {
1646
1833
  } catch {
1647
1834
  return writeAuthError(deps);
1648
1835
  }
1649
- const keyType = input.type ?? "delegated";
1650
- if (input.workspace && keyType !== "service") {
1651
- return writeError(
1652
- deps,
1653
- "invalid_usage",
1654
- "--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."
1655
- );
1656
- }
1657
- if (keyType === "service" && !input.workspace) {
1658
- return writeError(
1659
- deps,
1660
- "invalid_usage",
1661
- "Service keys must be pinned to a workspace.",
1662
- "Run: dropthis workspace list to see slugs, then: dropthis keys create --service --workspace <slug>"
1663
- );
1836
+ const result = await deps.client.invitations.list();
1837
+ if (result.error) return writeApiError(deps, result.error);
1838
+ const raw = unwrapListData(result.data, "invitations");
1839
+ const items = Array.isArray(raw) ? raw : [];
1840
+ if (deps.outputMode === "human") {
1841
+ if (items.length === 0) {
1842
+ writeEmpty(deps, "No pending invitations.", {
1843
+ ok: true,
1844
+ invitations: []
1845
+ });
1846
+ } else {
1847
+ for (const inv of items) {
1848
+ deps.stdout(`${inv.email} ${inv.role} ${inv.status} ${inv.id}
1849
+ `);
1850
+ }
1851
+ }
1852
+ } else {
1853
+ writeJson(deps, { ok: true, invitations: items });
1664
1854
  }
1665
- const label = input.label ?? (keyType === "service" ? `Service key${input.workspace ? ` (${input.workspace})` : ""}` : "CLI key");
1666
- const createInput = {
1667
- label,
1668
- type: keyType
1669
- };
1670
- if (keyType === "service" && input.workspace) {
1671
- createInput.workspace = input.workspace;
1855
+ return { exitCode: 0 };
1856
+ }
1857
+ async function runInvitationsAccept(input, deps) {
1858
+ try {
1859
+ await requireCredential({
1860
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
1861
+ env: deps.env,
1862
+ store: deps.store
1863
+ });
1864
+ } catch {
1865
+ return writeAuthError(deps);
1672
1866
  }
1673
- const result = await deps.client.apiKeys.create(createInput);
1867
+ const result = await deps.client.invitations.accept({ token: input.token });
1674
1868
  if (result.error) return writeApiError(deps, result.error);
1675
- const data = result.data;
1676
- const pairs = [];
1677
- if (data.id) pairs.push(["ID", String(data.id)]);
1678
- if (data.key) pairs.push(["Key", String(data.key)]);
1679
- if (data.keyLast4) pairs.push(["Last 4", String(data.keyLast4)]);
1680
- if (data.label) pairs.push(["Label", String(data.label)]);
1681
- writeKv(deps, pairs, { ok: true, api_key: result.data });
1682
- if (deps.outputMode === "human") {
1683
- deps.stderr(
1684
- `${hint("Store this key now \u2014 it will not be shown again.")}
1685
- `
1686
- );
1869
+ const ws = result.data;
1870
+ writeHumanOrJson(
1871
+ deps,
1872
+ success(`Joined ${ws.name} (${ws.slug}) \u2014 now your active workspace.`),
1873
+ { ok: true, workspace: ws }
1874
+ );
1875
+ return { exitCode: 0 };
1876
+ }
1877
+ async function runInvitationsAcceptById(input, deps) {
1878
+ try {
1879
+ await requireCredential({
1880
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
1881
+ env: deps.env,
1882
+ store: deps.store
1883
+ });
1884
+ } catch {
1885
+ return writeAuthError(deps);
1687
1886
  }
1887
+ const result = await deps.client.invitations.acceptById({
1888
+ invitationId: input.invitationId
1889
+ });
1890
+ if (result.error) return writeApiError(deps, result.error);
1891
+ const ws = result.data;
1892
+ writeHumanOrJson(
1893
+ deps,
1894
+ success(`Joined ${ws.name} (${ws.slug}) \u2014 now your active workspace.`),
1895
+ { ok: true, workspace: ws }
1896
+ );
1688
1897
  return { exitCode: 0 };
1689
1898
  }
1690
1899
 
@@ -1747,7 +1956,8 @@ async function runLoginInteractive(deps) {
1747
1956
  const authedClient = deps.createClient(session.data.token);
1748
1957
  const apiKey = await authedClient.apiKeys.create({
1749
1958
  label: "CLI",
1750
- type: "delegated"
1959
+ type: "delegated",
1960
+ ...deps.scope ? { scopes: [deps.scope] } : {}
1751
1961
  });
1752
1962
  if (apiKey.error) {
1753
1963
  prompts4.cancel(apiKey.error.message);
@@ -1759,6 +1969,7 @@ async function runLoginInteractive(deps) {
1759
1969
  keyLast4: apiKey.data.keyLast4,
1760
1970
  accountId: apiKey.data.accountId ?? session.data.accountId,
1761
1971
  email,
1972
+ ...apiKey.data.scopes ? { scopes: apiKey.data.scopes } : {},
1762
1973
  storage: "secure"
1763
1974
  });
1764
1975
  prompts4.outro("Logged in successfully.");
@@ -1793,7 +2004,10 @@ async function runLoginVerify(input, deps) {
1793
2004
  const authedClient = deps.createClient(session.data.token);
1794
2005
  const apiKey = await authedClient.apiKeys.create({
1795
2006
  label: "CLI",
1796
- type: "delegated"
2007
+ type: "delegated",
2008
+ // A single bundle name (e.g. "team") requests a capability-scoped key; the
2009
+ // server returns the resolved scope list on the mint response.
2010
+ ...input.scope ? { scopes: [input.scope] } : {}
1797
2011
  });
1798
2012
  if (apiKey.error) {
1799
2013
  return writeApiError(deps, apiKey.error);
@@ -1804,6 +2018,7 @@ async function runLoginVerify(input, deps) {
1804
2018
  keyLast4: apiKey.data.keyLast4,
1805
2019
  accountId: apiKey.data.accountId ?? session.data.accountId,
1806
2020
  email: input.email,
2021
+ ...apiKey.data.scopes ? { scopes: apiKey.data.scopes } : {},
1807
2022
  storage: "secure"
1808
2023
  });
1809
2024
  writeHumanOrJson(deps, success(`Logged in as ${input.email}.`), {
@@ -1832,6 +2047,115 @@ async function runLogout(input, deps) {
1832
2047
  return { exitCode: 0 };
1833
2048
  }
1834
2049
 
2050
+ // src/commands/members.ts
2051
+ var prompts5 = __toESM(require("@clack/prompts"), 1);
2052
+ async function runMembersList(workspaceId, _input, deps) {
2053
+ try {
2054
+ await requireCredential({
2055
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2056
+ env: deps.env,
2057
+ store: deps.store
2058
+ });
2059
+ } catch {
2060
+ return writeAuthError(deps);
2061
+ }
2062
+ const result = await deps.client.members.list(workspaceId);
2063
+ if (result.error) return writeApiError(deps, result.error);
2064
+ const raw = unwrapListData(result.data, "members");
2065
+ const items = Array.isArray(raw) ? raw : [];
2066
+ if (deps.outputMode === "human") {
2067
+ if (items.length === 0) {
2068
+ writeEmpty(deps, "No members.", { ok: true, members: [] });
2069
+ } else {
2070
+ for (const m of items) {
2071
+ const who = m.email || m.accountId;
2072
+ const you = m.isYou ? "(you)" : "";
2073
+ deps.stdout(`${who} ${m.role} ${you}
2074
+ `);
2075
+ }
2076
+ }
2077
+ } else {
2078
+ writeJson(deps, { ok: true, members: items });
2079
+ }
2080
+ return { exitCode: 0 };
2081
+ }
2082
+ async function runMembersInvite(workspaceId, input, deps) {
2083
+ try {
2084
+ await requireCredential({
2085
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2086
+ env: deps.env,
2087
+ store: deps.store
2088
+ });
2089
+ } catch {
2090
+ return writeAuthError(deps);
2091
+ }
2092
+ const role = input.role ?? "member";
2093
+ const result = await deps.client.members.invite(workspaceId, {
2094
+ email: input.email,
2095
+ role
2096
+ });
2097
+ if (result.error) return writeApiError(deps, result.error);
2098
+ writeHumanOrJson(deps, success(`Invited ${input.email} as ${role}`), {
2099
+ ok: true,
2100
+ invitation: result.data
2101
+ });
2102
+ return { exitCode: 0 };
2103
+ }
2104
+ async function runMembersRole(workspaceId, accountId, input, deps) {
2105
+ try {
2106
+ await requireCredential({
2107
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2108
+ env: deps.env,
2109
+ store: deps.store
2110
+ });
2111
+ } catch {
2112
+ return writeAuthError(deps);
2113
+ }
2114
+ const result = await deps.client.members.updateRole(workspaceId, accountId, {
2115
+ role: input.role
2116
+ });
2117
+ if (result.error) return writeApiError(deps, result.error);
2118
+ writeHumanOrJson(deps, success(`Set ${accountId} to ${input.role}`), {
2119
+ ok: true,
2120
+ member: result.data
2121
+ });
2122
+ return { exitCode: 0 };
2123
+ }
2124
+ async function runMembersRemove(workspaceId, accountId, input, deps) {
2125
+ if (!input.yes && input.interactive === false) {
2126
+ return writeError(
2127
+ deps,
2128
+ "invalid_usage",
2129
+ "Pass --yes to remove in non-interactive mode."
2130
+ );
2131
+ }
2132
+ if (!input.yes && input.interactive !== false) {
2133
+ const confirmed = await prompts5.confirm({
2134
+ message: `Remove ${accountId} from workspace ${workspaceId}?`
2135
+ });
2136
+ if (prompts5.isCancel(confirmed) || !confirmed) {
2137
+ return { exitCode: 0 };
2138
+ }
2139
+ }
2140
+ try {
2141
+ await requireCredential({
2142
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
2143
+ env: deps.env,
2144
+ store: deps.store
2145
+ });
2146
+ } catch {
2147
+ return writeAuthError(deps);
2148
+ }
2149
+ const result = await deps.client.members.remove(workspaceId, accountId);
2150
+ if (result?.error) return writeApiError(deps, result.error);
2151
+ writeHumanOrJson(deps, success(`Removed ${accountId}`), {
2152
+ ok: true,
2153
+ removed: true,
2154
+ account_id: accountId
2155
+ });
2156
+ return { exitCode: 0 };
2157
+ }
2158
+
1835
2159
  // src/drop-operations.ts
1836
2160
  var import_node_crypto = require("crypto");
1837
2161
  var import_promises = require("fs/promises");
@@ -1968,6 +2292,9 @@ async function parseDropOptions(raw) {
1968
2292
  if (raw.noindex && raw.index) {
1969
2293
  throw new Error("Use either --noindex or --index, not both.");
1970
2294
  }
2295
+ if (raw.expiresAt && raw.noExpires) {
2296
+ throw new Error("Use either --expires-at or --no-expires, not both.");
2297
+ }
1971
2298
  if (raw.shared && raw.domain) {
1972
2299
  throw new Error("Use either --shared or --domain <hostname>, not both.");
1973
2300
  }
@@ -1996,6 +2323,7 @@ async function parseDropOptions(raw) {
1996
2323
  ...raw.noindex ? { noindex: true } : {},
1997
2324
  ...raw.index ? { noindex: false } : {},
1998
2325
  ...raw.expiresAt ? { expiresAt: raw.expiresAt } : {},
2326
+ ...raw.noExpires ? { expiresAt: null } : {},
1999
2327
  ...metadata ? { metadata } : {},
2000
2328
  ...raw.entry ? { entry: raw.entry } : {},
2001
2329
  ...raw.contentType ? { contentType: raw.contentType } : {},
@@ -2197,7 +2525,9 @@ async function runPull(target, input, deps) {
2197
2525
  const dropId = resolved.dropId;
2198
2526
  const defaultDirName = resolved.slug || resolved.dropId;
2199
2527
  const spin = shouldSpin(deps.outputMode) ? createSpinner("Pulling content\u2026", deps.stderr) : void 0;
2200
- const manifestResult = await deps.client.drops.getContent(dropId);
2528
+ const deploymentId = input.deployment;
2529
+ const manifestOptions = deploymentId ? { deploymentId } : void 0;
2530
+ const manifestResult = manifestOptions ? await deps.client.drops.getContent(dropId, manifestOptions) : await deps.client.drops.getContent(dropId);
2201
2531
  if (manifestResult.error) {
2202
2532
  spin?.fail();
2203
2533
  return writeApiError(deps, manifestResult.error);
@@ -2221,7 +2551,10 @@ async function runPull(target, input, deps) {
2221
2551
  try {
2222
2552
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
2223
2553
  for (const { path, dest } of destinations) {
2224
- const fileResult = await deps.client.drops.getContent(dropId, { path });
2554
+ const fileResult = await deps.client.drops.getContent(dropId, {
2555
+ path,
2556
+ ...deploymentId ? { deploymentId } : {}
2557
+ });
2225
2558
  if (fileResult.error) {
2226
2559
  spin?.fail();
2227
2560
  return writeApiError(deps, {
@@ -2408,9 +2741,9 @@ async function handleDryRun(dropId, input, raw, deps) {
2408
2741
  }
2409
2742
 
2410
2743
  // src/commands/update-settings.ts
2411
- var prompts5 = __toESM(require("@clack/prompts"), 1);
2744
+ var prompts6 = __toESM(require("@clack/prompts"), 1);
2412
2745
  var NO_SETTINGS_MESSAGE = "Nothing to update. Provide at least one settings option.";
2413
- 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.";
2746
+ 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.";
2414
2747
  async function runUpdateSettings(target, raw, deps) {
2415
2748
  const apiKey = raw.apiKey ?? deps.apiKey;
2416
2749
  try {
@@ -2440,7 +2773,7 @@ async function runUpdateSettings(target, raw, deps) {
2440
2773
  if (shouldPrompt(deps)) {
2441
2774
  const prompted = await promptForSettings(
2442
2775
  dropId,
2443
- deps.prompts ?? prompts5
2776
+ deps.prompts ?? prompts6
2444
2777
  );
2445
2778
  if (!prompted) return { exitCode: 0 };
2446
2779
  parsed = { ...parsed, ...prompted };
@@ -2629,8 +2962,10 @@ async function handleDryRun2(dropId, raw, deps) {
2629
2962
  if (options.visibility) fields.visibility = options.visibility;
2630
2963
  if (options.password !== void 0) fields.password = options.password;
2631
2964
  if (options.noindex !== void 0) fields.noindex = options.noindex;
2632
- if (options.expiresAt) fields.expires_at = options.expiresAt;
2965
+ if (options.expiresAt !== void 0) fields.expires_at = options.expiresAt;
2633
2966
  if (options.metadata) fields.metadata = options.metadata;
2967
+ if (options.domain) fields.domain = options.domain;
2968
+ if (options.slug) fields.slug = options.slug;
2634
2969
  deps.stdout(
2635
2970
  `${JSON.stringify({ ok: true, dryRun: true, kind: "settings_update", target: dropId, fields }, null, 2)}
2636
2971
  `
@@ -2778,6 +3113,8 @@ async function runWhoami(_input, deps) {
2778
3113
  pairs.push(["Source", credential.source]);
2779
3114
  if (credential.accountId ?? account.id)
2780
3115
  pairs.push(["Account", String(credential.accountId ?? account.id)]);
3116
+ if (credential.scopes?.length)
3117
+ pairs.push(["Scopes", credential.scopes.join(", ")]);
2781
3118
  const jsonEnvelope = {
2782
3119
  ok: true,
2783
3120
  authenticated: true,
@@ -2786,13 +3123,15 @@ async function runWhoami(_input, deps) {
2786
3123
  ...credential.keyId ? { key_id: credential.keyId } : {},
2787
3124
  ...last4 ? { key_last4: last4 } : {},
2788
3125
  ...credential.accountId ?? account.id ? { account_id: String(credential.accountId ?? account.id) } : {},
2789
- ...credential.email ?? account.email ? { email: String(credential.email ?? account.email) } : {}
3126
+ ...credential.email ?? account.email ? { email: String(credential.email ?? account.email) } : {},
3127
+ ...credential.scopes?.length ? { scopes: credential.scopes } : {}
2790
3128
  };
2791
3129
  writeKv(deps, pairs, jsonEnvelope);
2792
3130
  return { exitCode: 0 };
2793
3131
  }
2794
3132
 
2795
3133
  // src/commands/workspace.ts
3134
+ var prompts7 = __toESM(require("@clack/prompts"), 1);
2796
3135
  async function runWorkspaceList(_input, deps) {
2797
3136
  try {
2798
3137
  await requireCredential({
@@ -2845,6 +3184,104 @@ async function runWorkspaceUse(slugOrId, _input, deps) {
2845
3184
  );
2846
3185
  return { exitCode: 0 };
2847
3186
  }
3187
+ async function runWorkspaceCreate(input, deps) {
3188
+ try {
3189
+ await requireCredential({
3190
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3191
+ env: deps.env,
3192
+ store: deps.store
3193
+ });
3194
+ } catch {
3195
+ return writeAuthError(deps);
3196
+ }
3197
+ const createInput = { name: input.name };
3198
+ if (input.slug !== void 0) createInput.slug = input.slug;
3199
+ const result = await deps.client.workspaces.create(createInput);
3200
+ if (result.error) return writeApiError(deps, result.error);
3201
+ const ws = result.data;
3202
+ writeHumanOrJson(
3203
+ deps,
3204
+ success(`Created workspace: ${ws.name} (${ws.slug})`),
3205
+ {
3206
+ ok: true,
3207
+ workspace: ws
3208
+ }
3209
+ );
3210
+ if (ws.creatorCanReach === false && deps.outputMode === "human") {
3211
+ deps.stderr(
3212
+ `${hint("This key can't reach the new workspace \u2014 re-authenticate (dropthis login) to pick it up.")}
3213
+ `
3214
+ );
3215
+ }
3216
+ return { exitCode: 0 };
3217
+ }
3218
+ async function runWorkspaceRename(workspaceId, input, deps) {
3219
+ if (input.name === void 0 && input.slug === void 0) {
3220
+ return writeError(
3221
+ deps,
3222
+ "invalid_usage",
3223
+ "Provide --name and/or --slug to rename."
3224
+ );
3225
+ }
3226
+ try {
3227
+ await requireCredential({
3228
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3229
+ env: deps.env,
3230
+ store: deps.store
3231
+ });
3232
+ } catch {
3233
+ return writeAuthError(deps);
3234
+ }
3235
+ const renameInput = {};
3236
+ if (input.name !== void 0) renameInput.name = input.name;
3237
+ if (input.slug !== void 0) renameInput.slug = input.slug;
3238
+ const result = await deps.client.workspaces.rename(workspaceId, renameInput);
3239
+ if (result.error) return writeApiError(deps, result.error);
3240
+ const ws = result.data;
3241
+ writeHumanOrJson(
3242
+ deps,
3243
+ success(`Renamed workspace: ${ws.name} (${ws.slug})`),
3244
+ {
3245
+ ok: true,
3246
+ workspace: ws
3247
+ }
3248
+ );
3249
+ return { exitCode: 0 };
3250
+ }
3251
+ async function runWorkspaceDelete(workspaceId, input, deps) {
3252
+ if (!input.yes && input.interactive === false) {
3253
+ return writeError(
3254
+ deps,
3255
+ "invalid_usage",
3256
+ "Pass --yes to delete in non-interactive mode."
3257
+ );
3258
+ }
3259
+ if (!input.yes && input.interactive !== false) {
3260
+ const confirmed = await prompts7.confirm({
3261
+ message: `Delete workspace ${workspaceId}?`
3262
+ });
3263
+ if (prompts7.isCancel(confirmed) || !confirmed) {
3264
+ return { exitCode: 0 };
3265
+ }
3266
+ }
3267
+ try {
3268
+ await requireCredential({
3269
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
3270
+ env: deps.env,
3271
+ store: deps.store
3272
+ });
3273
+ } catch {
3274
+ return writeAuthError(deps);
3275
+ }
3276
+ const result = await deps.client.workspaces.delete(workspaceId);
3277
+ if (result?.error) return writeApiError(deps, result.error);
3278
+ writeHumanOrJson(deps, success(`Deleted workspace ${workspaceId}`), {
3279
+ ok: true,
3280
+ deleted: true,
3281
+ id: workspaceId
3282
+ });
3283
+ return { exitCode: 0 };
3284
+ }
2848
3285
 
2849
3286
  // src/context.ts
2850
3287
  var import_node2 = require("@dropthis/node");
@@ -3170,7 +3607,8 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3170
3607
  return;
3171
3608
  }
3172
3609
  }
3173
- if (deps.outputMode === "human" && deps.interactive && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
3610
+ const publishWasExplicit = (program.rawArgs ?? []).includes("publish");
3611
+ if (!publishWasExplicit && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
3174
3612
  const suggestion = suggestCommand(
3175
3613
  publishInput,
3176
3614
  program.commands.map((c) => c.name())
@@ -3184,13 +3622,15 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3184
3622
  ).exitCode;
3185
3623
  return;
3186
3624
  }
3187
- const prompts6 = await import("@clack/prompts");
3188
- const confirmed = await prompts6.confirm({
3189
- message: `Publish '${publishInput}' as inline text?`,
3190
- initialValue: false
3191
- });
3192
- if (prompts6.isCancel(confirmed) || !confirmed) {
3193
- return;
3625
+ if (deps.outputMode === "human" && deps.interactive) {
3626
+ const prompts8 = await import("@clack/prompts");
3627
+ const confirmed = await prompts8.confirm({
3628
+ message: `Publish '${publishInput}' as inline text?`,
3629
+ initialValue: false
3630
+ });
3631
+ if (prompts8.isCancel(confirmed) || !confirmed) {
3632
+ return;
3633
+ }
3194
3634
  }
3195
3635
  }
3196
3636
  let dropOpts;
@@ -3281,10 +3721,19 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3281
3721
  );
3282
3722
  program.command("update-settings <dropId>").description(
3283
3723
  "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."
3284
- ).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(
3724
+ ).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(
3285
3725
  "--metadata <json>",
3286
3726
  `Attach JSON key-value pairs, e.g. '{"source":"ci"}'`
3287
3727
  ).option("--metadata-file <path>", "Read metadata JSON from a file").option(
3728
+ "--domain <hostname>",
3729
+ "Move this drop to a connected custom domain (must be live \u2014 see: dropthis domains list)."
3730
+ ).option(
3731
+ "--shared",
3732
+ "Unmount this drop back to the shared dropthis pool. Cannot combine with --domain."
3733
+ ).option(
3734
+ "--slug <vanity-slug>",
3735
+ "Rename the vanity slug on a path-mode custom domain (1\u201363 lowercase letters/digits/hyphens). 409 on conflict \u2014 does not auto-suffix."
3736
+ ).option(
3288
3737
  "--if-revision <n>",
3289
3738
  "Fail if current revision doesn't match (optimistic lock)",
3290
3739
  parseInteger
@@ -3316,7 +3765,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3316
3765
  );
3317
3766
  program.command("pull <target>").description(
3318
3767
  "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."
3319
- ).option("-o, --output <dir>", "Output directory (default: ./<slug-or-id>)").option("--json", "Force JSON output").addHelpText(
3768
+ ).option("-o, --output <dir>", "Output directory (default: ./<slug-or-id>)").option("--deployment <deploymentId>", "Download a specific deployment").option("--json", "Force JSON output").addHelpText(
3320
3769
  "after",
3321
3770
  examplesBlock([
3322
3771
  "dropthis pull drop_abc123 -o ./site # download the drop's files",
@@ -3452,7 +3901,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3452
3901
  process.exitCode = result.exitCode;
3453
3902
  });
3454
3903
  domains.command("verify <hostname>").description(
3455
- "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."
3904
+ "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."
3456
3905
  ).option("--wait", "Poll until live (or failed/timeout)").option(
3457
3906
  "--timeout <secs>",
3458
3907
  "Max seconds to wait (default: 300)",
@@ -3508,11 +3957,35 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3508
3957
  }
3509
3958
  );
3510
3959
  const apiKeys = program.command("api-keys").description("Manage API keys");
3511
- apiKeys.command("create").description("Create an API key").option("--label <label>", "API key label", "CLI").option("--json", "Force JSON output").action(async (commandOptions) => {
3512
- const deps = await commandDeps(program, options, store, commandOptions);
3513
- const result = await runApiKeysCreate(commandOptions, deps);
3514
- process.exitCode = result.exitCode;
3515
- });
3960
+ apiKeys.command("create").description(
3961
+ "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."
3962
+ ).option("--label <label>", "API key label").option(
3963
+ "--service",
3964
+ "Mint a pinned service key for CI/automation (requires --workspace to pin it)"
3965
+ ).option(
3966
+ "--workspace <slugOrId>",
3967
+ "Pin this service key to the given workspace slug or id (only valid with --service)"
3968
+ ).option(
3969
+ "--allowed-workspace <slugOrId>",
3970
+ "Allow a delegated key to access the given workspace slug or id",
3971
+ collect,
3972
+ []
3973
+ ).option("--json", "Force JSON output").action(
3974
+ async (commandOptions) => {
3975
+ const deps = await commandDeps(program, options, store, commandOptions);
3976
+ const result = await runApiKeysCreate(
3977
+ {
3978
+ ...commandOptions.label !== void 0 ? { label: commandOptions.label } : {},
3979
+ type: commandOptions.service ? "service" : "delegated",
3980
+ ...commandOptions.workspace !== void 0 ? { workspace: commandOptions.workspace } : {},
3981
+ ...commandOptions.allowedWorkspace?.length ? { allowedWorkspaces: commandOptions.allowedWorkspace } : {},
3982
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3983
+ },
3984
+ deps
3985
+ );
3986
+ process.exitCode = result.exitCode;
3987
+ }
3988
+ );
3516
3989
  apiKeys.command("list").description("List API keys").option("--json", "Force JSON output").action(async (commandOptions) => {
3517
3990
  const deps = await commandDeps(program, options, store, commandOptions);
3518
3991
  const result = await runApiKeysList(commandOptions, deps);
@@ -3549,25 +4022,121 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3549
4022
  const result = await runWorkspaceUse(slugOrId, commandOptions, deps);
3550
4023
  process.exitCode = result.exitCode;
3551
4024
  });
3552
- const keys = program.command("keys").description(
3553
- "Mint API keys (delegated login keys or pinned CI service keys)"
4025
+ workspace.command("create <name>").description("Create a new team workspace").option("--slug <slug>", "Vanity slug for the workspace").option("--json", "Force JSON output").action(
4026
+ async (name, commandOptions) => {
4027
+ const deps = await commandDeps(program, options, store, commandOptions);
4028
+ const result = await runWorkspaceCreate(
4029
+ {
4030
+ name,
4031
+ ...commandOptions.slug !== void 0 ? { slug: commandOptions.slug } : {},
4032
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4033
+ },
4034
+ deps
4035
+ );
4036
+ process.exitCode = result.exitCode;
4037
+ }
3554
4038
  );
3555
- keys.command("create").description(
3556
- "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."
3557
- ).option("--label <label>", "Key label").option(
3558
- "--service",
3559
- "Mint a pinned service key for CI/automation (requires --workspace to pin it)"
3560
- ).option(
3561
- "--workspace <slugOrId>",
3562
- "Pin this service key to the given workspace slug or id (only valid with --service)"
3563
- ).option("--json", "Force JSON output").action(
3564
- async (commandOptions) => {
4039
+ 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(
4040
+ async (workspaceId, commandOptions) => {
3565
4041
  const deps = await commandDeps(program, options, store, commandOptions);
3566
- const result = await runKeysCreate(
4042
+ const result = await runWorkspaceRename(
4043
+ workspaceId,
3567
4044
  {
3568
- ...commandOptions.label !== void 0 ? { label: commandOptions.label } : {},
3569
- type: commandOptions.service ? "service" : "delegated",
3570
- ...commandOptions.workspace !== void 0 ? { workspace: commandOptions.workspace } : {},
4045
+ ...commandOptions.name !== void 0 ? { name: commandOptions.name } : {},
4046
+ ...commandOptions.slug !== void 0 ? { slug: commandOptions.slug } : {},
4047
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4048
+ },
4049
+ deps
4050
+ );
4051
+ process.exitCode = result.exitCode;
4052
+ }
4053
+ );
4054
+ workspace.command("delete <workspaceId>").description("Delete a workspace").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(
4055
+ async (workspaceId, commandOptions) => {
4056
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4057
+ const global = globalOptions(program, commandOptions);
4058
+ const deps = await commandDeps(program, options, store, commandOptions);
4059
+ const result = await runWorkspaceDelete(
4060
+ workspaceId,
4061
+ {
4062
+ ...commandOptions,
4063
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
4064
+ },
4065
+ deps
4066
+ );
4067
+ process.exitCode = result.exitCode;
4068
+ }
4069
+ );
4070
+ const members = program.command("members").description("Manage team members");
4071
+ members.command("list <workspaceId>").description("List members of a workspace").option("--json", "Force JSON output").action(async (workspaceId, commandOptions) => {
4072
+ const deps = await commandDeps(program, options, store, commandOptions);
4073
+ const result = await runMembersList(workspaceId, commandOptions, deps);
4074
+ process.exitCode = result.exitCode;
4075
+ });
4076
+ 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(
4077
+ async (workspaceId, commandOptions) => {
4078
+ const deps = await commandDeps(program, options, store, commandOptions);
4079
+ const result = await runMembersInvite(
4080
+ workspaceId,
4081
+ {
4082
+ email: commandOptions.email,
4083
+ ...commandOptions.role !== void 0 ? { role: commandOptions.role } : {},
4084
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4085
+ },
4086
+ deps
4087
+ );
4088
+ process.exitCode = result.exitCode;
4089
+ }
4090
+ );
4091
+ 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(
4092
+ async (workspaceId, accountId, commandOptions) => {
4093
+ const deps = await commandDeps(program, options, store, commandOptions);
4094
+ const result = await runMembersRole(
4095
+ workspaceId,
4096
+ accountId,
4097
+ {
4098
+ role: commandOptions.role,
4099
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4100
+ },
4101
+ deps
4102
+ );
4103
+ process.exitCode = result.exitCode;
4104
+ }
4105
+ );
4106
+ members.command("remove <workspaceId> <accountId>").description("Remove a member from a workspace").option("--yes", "Confirm removal").option("--json", "Force JSON output").action(
4107
+ async (workspaceId, accountId, commandOptions) => {
4108
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4109
+ const global = globalOptions(program, commandOptions);
4110
+ const deps = await commandDeps(program, options, store, commandOptions);
4111
+ const result = await runMembersRemove(
4112
+ workspaceId,
4113
+ accountId,
4114
+ {
4115
+ ...commandOptions,
4116
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
4117
+ },
4118
+ deps
4119
+ );
4120
+ process.exitCode = result.exitCode;
4121
+ }
4122
+ );
4123
+ const invitations = program.command("invitations").description("Manage your workspace invitations");
4124
+ invitations.command("list", { isDefault: true }).description("List your pending workspace invitations").option("--json", "Force JSON output").action(async (commandOptions) => {
4125
+ const deps = await commandDeps(program, options, store, commandOptions);
4126
+ const result = await runInvitationsList(commandOptions, deps);
4127
+ process.exitCode = result.exitCode;
4128
+ });
4129
+ invitations.command("accept").description("Accept a workspace invitation by token").requiredOption("--token <token>", "Invitation token").option("--json", "Force JSON output").action(async (commandOptions) => {
4130
+ const deps = await commandDeps(program, options, store, commandOptions);
4131
+ const result = await runInvitationsAccept(commandOptions, deps);
4132
+ process.exitCode = result.exitCode;
4133
+ });
4134
+ invitations.command("accept-by-id <invitationId>").description("Accept a workspace invitation by its id").option("--json", "Force JSON output").action(
4135
+ async (invitationId, commandOptions) => {
4136
+ const deps = await commandDeps(program, options, store, commandOptions);
4137
+ const result = await runInvitationsAcceptById(
4138
+ {
4139
+ invitationId,
3571
4140
  ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3572
4141
  },
3573
4142
  deps
@@ -3575,7 +4144,10 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3575
4144
  process.exitCode = result.exitCode;
3576
4145
  }
3577
4146
  );
3578
- 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(
4147
+ program.command("login").description("Authenticate with email OTP").option("--email <email>", "Email address").option("--otp <otp>", "One-time passcode").option(
4148
+ "--scope <scope>",
4149
+ "Capability scope to request for the minted key (e.g. team)"
4150
+ ).option("--json", "Force JSON output").action(
3579
4151
  async (commandOptions) => {
3580
4152
  if (!commandOptions.email || !commandOptions.otp) {
3581
4153
  const deps2 = await commandDeps(program, options, store, commandOptions);
@@ -3593,7 +4165,8 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3593
4165
  const context = createContext({ global: global2 });
3594
4166
  const result2 = await runLoginInteractive({
3595
4167
  ...deps2,
3596
- createClient: (apiKey) => context.createClient(apiKey)
4168
+ createClient: (apiKey) => context.createClient(apiKey),
4169
+ ...commandOptions.scope ? { scope: commandOptions.scope } : {}
3597
4170
  });
3598
4171
  process.exitCode = result2.exitCode;
3599
4172
  return;
@@ -3605,6 +4178,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3605
4178
  {
3606
4179
  email: commandOptions.email,
3607
4180
  otp: commandOptions.otp,
4181
+ ...commandOptions.scope ? { scope: commandOptions.scope } : {},
3608
4182
  ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3609
4183
  },
3610
4184
  {
@@ -3621,7 +4195,10 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3621
4195
  const result = await runLoginRequest(commandOptions, deps);
3622
4196
  process.exitCode = result.exitCode;
3623
4197
  });
3624
- 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(
4198
+ login?.command("verify").description("Verify email OTP and store an API key").requiredOption("--email <email>", "Email address").requiredOption("--otp <otp>", "One-time passcode").option(
4199
+ "--scope <scope>",
4200
+ "Capability scope to request for the minted key (e.g. team)"
4201
+ ).option("--json", "Force JSON output").action(
3625
4202
  async (commandOptions) => {
3626
4203
  const deps = await commandDeps(program, options, store, commandOptions);
3627
4204
  const global = globalOptions(program, commandOptions);
@@ -3659,17 +4236,30 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3659
4236
  const result = await runAccountGet(commandOptions, deps);
3660
4237
  process.exitCode = result.exitCode;
3661
4238
  });
3662
- account.command("update").description("Update account display name").requiredOption("--display-name <name>", "Set the display name").option("--json", "Force JSON output").action(async (commandOptions) => {
3663
- const deps = await commandDeps(program, options, store, commandOptions);
3664
- const result = await runAccountUpdate(
3665
- {
3666
- displayName: commandOptions.displayName,
3667
- ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
3668
- },
3669
- deps
3670
- );
3671
- process.exitCode = result.exitCode;
3672
- });
4239
+ 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(
4240
+ async (commandOptions) => {
4241
+ if (commandOptions.displayName === void 0 && !commandOptions.clearDisplayName || commandOptions.displayName !== void 0 && commandOptions.clearDisplayName) {
4242
+ const writer = outputWriter(program, options, commandOptions);
4243
+ process.exitCode = writeError(
4244
+ writer,
4245
+ "invalid_usage",
4246
+ "Use either --display-name <name> or --clear-display-name."
4247
+ ).exitCode;
4248
+ return;
4249
+ }
4250
+ const displayName = commandOptions.clearDisplayName ? null : commandOptions.displayName;
4251
+ if (displayName === void 0) return;
4252
+ const deps = await commandDeps(program, options, store, commandOptions);
4253
+ const result = await runAccountUpdate(
4254
+ {
4255
+ displayName,
4256
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4257
+ },
4258
+ deps
4259
+ );
4260
+ process.exitCode = result.exitCode;
4261
+ }
4262
+ );
3673
4263
  account.command("delete").description("Delete your account").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(async (commandOptions) => {
3674
4264
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
3675
4265
  const global = globalOptions(program, commandOptions);
@@ -3683,7 +4273,10 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3683
4273
  );
3684
4274
  process.exitCode = result.exitCode;
3685
4275
  });
3686
- program.command("doctor").description("Report CLI diagnostics").option("--json", "Force JSON output").action(async (commandOptions) => {
4276
+ program.command("doctor").description("Report CLI diagnostics").option("--json", "Force JSON output").option(
4277
+ "--online",
4278
+ "Run a network preflight (auth + active workspace + custom-domain status) \u2014 exits 3 on auth failure, 5 on a network failure"
4279
+ ).action(async (commandOptions) => {
3687
4280
  const deps = await commandDeps(
3688
4281
  program,
3689
4282
  options,
@@ -3818,6 +4411,7 @@ function toDropOptions(options) {
3818
4411
  ...options.noindex ? { noindex: true } : {},
3819
4412
  ...options.index ? { index: true } : {},
3820
4413
  ...options.expiresAt ? { expiresAt: options.expiresAt } : {},
4414
+ ...options.expires === false || options.noExpires ? { noExpires: true } : {},
3821
4415
  ...options.metadata ? { metadata: options.metadata } : {},
3822
4416
  ...options.metadataFile ? { metadataFile: options.metadataFile } : {},
3823
4417
  ...options.entry ? { entry: options.entry } : {},