@dropthis/cli 0.33.1 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,12 +39,30 @@ dropthis login verify --email you@example.com --otp 123456
39
39
 
40
40
  > A failed verify prints `Your code has expired` (`otp_expired`) or `That code is incorrect` (`otp_invalid`) and exits non-zero.
41
41
 
42
- Or pass both flags directly:
42
+ ### Scopes
43
+
44
+ Keys are minted with a **capability bundle**. The default for `dropthis login` is **publish** — enough to publish drops and read your own context, nothing else. To manage a team you need a wider bundle, requested with `--scope` on `login verify`:
45
+
46
+ | Bundle | Grants | Use it for |
47
+ |--------|--------|------------|
48
+ | `publish` *(default)* | publish + read your own account/workspaces + mint downscoped keys | publishing, CI |
49
+ | `team` | publish + create/rename workspaces, invite/manage members | running a team |
50
+ | `team-admin` | team + delete workspaces, remove members, change roles | irreversible team admin |
51
+ | `service` | publish without key-minting (workspace-pinned) | automation / CI that can't mint keys |
52
+
53
+ ```bash
54
+ # Mint a team-scoped key
55
+ dropthis login verify --email you@example.com --otp 123456 --scope team
56
+ ```
57
+
58
+ Minting is **downscope-only** — a minted key can never have more scopes than the key that minted it. Check what your current key carries:
43
59
 
44
60
  ```bash
45
- dropthis login --email you@example.com --otp 123456
61
+ dropthis whoami --json # echoes the key's scopes
46
62
  ```
47
63
 
64
+ If a command returns `insufficient_scope` (exit 1, HTTP 403), your key lacks a scope that route needs. Fix it by re-logging in with the right bundle (`dropthis login verify … --scope team`) or, via the SDK, `apiKeys.create({ scopes: ["team"] })`.
65
+
48
66
  Credentials resolve in this order:
49
67
 
50
68
  1. `--api-key sk_...` flag
@@ -232,6 +250,42 @@ dropthis pull drop_abc123 -o ./site
232
250
 
233
251
  Pull fetches the current deployment's file manifest and writes every file into the output directory. It is owner-side read-back via the API — it works regardless of any viewer password. Custom-domain URLs are not resolvable yet; use the `drop_…` id instead.
234
252
 
253
+ ## Teams & workspaces
254
+
255
+ Publishes land in a **workspace**. Everyone has a personal workspace; teams add shared ones. List the workspaces you belong to and switch the active one (delegated keys):
256
+
257
+ ```bash
258
+ dropthis workspace list
259
+ dropthis workspace use acme # by slug or ws_… id
260
+ ```
261
+
262
+ Creating and managing team workspaces needs a `team`-scoped key (see [Scopes](#scopes)):
263
+
264
+ ```bash
265
+ dropthis workspace create "Acme" --slug acme # create a team workspace
266
+ dropthis workspace rename ws_team123 --name "Acme Inc"
267
+ dropthis workspace delete ws_team123 --yes # team-admin scope
268
+ ```
269
+
270
+ Manage who's in a workspace:
271
+
272
+ ```bash
273
+ dropthis members list ws_team123
274
+ dropthis members invite ws_team123 --email teammate@acme.com --role member # --role admin|member
275
+ dropthis members role ws_team123 acc_123 --role admin # --role owner|admin|member
276
+ dropthis members remove ws_team123 acc_123 --yes
277
+ ```
278
+
279
+ Accept an invitation you received:
280
+
281
+ ```bash
282
+ dropthis invitations list # pending invitations for your email
283
+ dropthis invitations accept --token inv_tok_abc # accept by invite token
284
+ dropthis invitations accept-by-id inv_123 # or by invitation id
285
+ ```
286
+
287
+ To publish into a specific workspace for one call, pass `--workspace <slugOrId>` on `publish` (delegated keys only).
288
+
235
289
  ## Commands
236
290
 
237
291
  ```bash
@@ -259,6 +313,18 @@ dropthis domains remove <host> --yes # Disconnect a domain
259
313
 
260
314
  dropthis workspace list # List workspaces you belong to
261
315
  dropthis workspace use <slug|id> # Switch active workspace (delegated keys)
316
+ dropthis workspace create <name> --slug <s> # Create a team workspace (team scope)
317
+ dropthis workspace rename <ws> --name <name> # Rename a team workspace (team scope)
318
+ dropthis workspace delete <ws> --yes # Delete a team workspace (team-admin scope)
319
+
320
+ dropthis members list <ws> # List a workspace's members
321
+ dropthis members invite <ws> --email <e> --role <admin|member> # Invite someone (team scope)
322
+ dropthis members role <ws> <account-id> --role <owner|admin|member> # Change a member's role
323
+ dropthis members remove <ws> <account-id> --yes # Remove a member (team-admin scope)
324
+
325
+ dropthis invitations list # List pending invitations for your email
326
+ dropthis invitations accept --token <token> # Accept an invitation by token
327
+ dropthis invitations accept-by-id <inv-id> # Accept an invitation by id
262
328
 
263
329
  dropthis login # Authenticate with email OTP
264
330
  dropthis login request --email <email> # Request OTP (non-interactive)
@@ -290,6 +356,7 @@ export DROPTHIS_API_KEY=sk_live_... # API key (same as --api-key)
290
356
  export DROPTHIS_API_URL=https://... # Override API base URL (same as --api-url)
291
357
  export DROPTHIS_NON_INTERACTIVE=1 # Disable interactive prompts (same as --no-interactive)
292
358
  export DROPTHIS_NO_UPDATE_NOTIFIER=1 # Disable the startup update notice (also auto-disabled in CI, non-TTY, --json/--quiet)
359
+ export NO_COLOR=1 # Disable ANSI color in human output (also auto-disabled when not a TTY)
293
360
  ```
294
361
 
295
362
  ```bash
@@ -319,6 +386,13 @@ dropthis publish ./dist --url
319
386
 
320
387
  This table — plus the global flags (`--api-key`, `--api-url`, `--json`, `--quiet`, `--no-interactive`) — is machine-readable from `dropthis commands --json` (the `exit_codes` and `global_options` fields), so an agent can discover the whole surface without scraping docs.
321
388
 
389
+ Some failures share exit 1 but carry a distinct error `code` in the JSON envelope:
390
+
391
+ | Error code | HTTP | Meaning & fix |
392
+ |------------|------|----------------|
393
+ | `insufficient_scope` | 403 | The key lacks a scope this route needs. Re-login with the bundle (`dropthis login verify … --scope team`), or mint a key with it via the SDK. See [Scopes](#scopes). |
394
+ | `quota_exceeded` | 403/413 | A plan limit was hit. Check `dropthis account --json` (`entitlements.limits` vs `usage`) or upgrade. |
395
+
322
396
  **JSON output shape:**
323
397
 
324
398
  ```json
@@ -353,6 +427,8 @@ dropthis doctor --online --json
353
427
 
354
428
  It hits `GET /v1/account` (token valid? which workspace resolves? which plan?) and lists custom-domain verification status as a `checks` array. It exits **3** on an auth failure and **5** on a network failure, so a script can branch on the exit code.
355
429
 
430
+ For a pre-publish **quota** check, `dropthis account --json` returns `entitlements.limits` (e.g. `maxStorageBytes`, `maxCustomHostnames`, `seatLimit`) alongside your current `usage` — read it before a large publish to avoid a `quota_exceeded` failure.
431
+
356
432
  ## Pricing tiers
357
433
 
358
434
  dropthis has four tiers — **Free**, **Keep**, **Pro**, and **Business**. Free drops carry the dropthis badge and expire after 30 days; paid tiers add permanence, larger limits, custom domains, passwords, and analytics. Run `dropthis account` to see your active tier and its exact caps, or visit https://dropthis.app/pricing for the current plans.
package/dist/cli.cjs CHANGED
@@ -1032,7 +1032,7 @@ function buildCatalog(program) {
1032
1032
  return program.commands.map((cmd) => toEntry(cmd, []));
1033
1033
  }
1034
1034
  function buildGlobalOptions(program) {
1035
- return program.options.map((o) => ({
1035
+ return program.options.filter((o) => o.long !== "--version" && o.long !== "--help").map((o) => ({
1036
1036
  flags: o.flags,
1037
1037
  description: o.description
1038
1038
  }));
@@ -1136,7 +1136,7 @@ function spreadData(data) {
1136
1136
  }
1137
1137
 
1138
1138
  // src/version.ts
1139
- var CLI_VERSION = true ? "0.33.1" : "0.0.0-dev";
1139
+ var CLI_VERSION = true ? "0.34.0" : "0.0.0-dev";
1140
1140
 
1141
1141
  // src/commands/doctor.ts
1142
1142
  async function onlineChecks(deps, hasCredential) {
@@ -1956,8 +1956,7 @@ async function runLoginInteractive(deps) {
1956
1956
  const authedClient = deps.createClient(session.data.token);
1957
1957
  const apiKey = await authedClient.apiKeys.create({
1958
1958
  label: "CLI",
1959
- type: "delegated",
1960
- ...deps.scope ? { scopes: [deps.scope] } : {}
1959
+ type: "delegated"
1961
1960
  });
1962
1961
  if (apiKey.error) {
1963
1962
  prompts4.cancel(apiKey.error.message);
@@ -4073,7 +4072,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4073
4072
  const result = await runMembersList(workspaceId, commandOptions, deps);
4074
4073
  process.exitCode = result.exitCode;
4075
4074
  });
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(
4075
+ members.command("invite <workspaceId>").description("Invite someone to a workspace by email").requiredOption("--email <email>", "Email address to invite").option(
4076
+ "--role <role>",
4077
+ "Role to grant (admin or member)",
4078
+ parseInvitableRole,
4079
+ "member"
4080
+ ).option("--json", "Force JSON output").action(
4077
4081
  async (workspaceId, commandOptions) => {
4078
4082
  const deps = await commandDeps(program, options, store, commandOptions);
4079
4083
  const result = await runMembersInvite(
@@ -4088,7 +4092,11 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4088
4092
  process.exitCode = result.exitCode;
4089
4093
  }
4090
4094
  );
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(
4095
+ members.command("role <workspaceId> <accountId>").description("Change a member's role").requiredOption(
4096
+ "--role <role>",
4097
+ "New role (owner, admin, or member)",
4098
+ parseWorkspaceRole
4099
+ ).option("--json", "Force JSON output").action(
4092
4100
  async (workspaceId, accountId, commandOptions) => {
4093
4101
  const deps = await commandDeps(program, options, store, commandOptions);
4094
4102
  const result = await runMembersRole(
@@ -4144,51 +4152,26 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4144
4152
  process.exitCode = result.exitCode;
4145
4153
  }
4146
4154
  );
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(
4151
- async (commandOptions) => {
4152
- if (!commandOptions.email || !commandOptions.otp) {
4153
- const deps2 = await commandDeps(program, options, store, commandOptions);
4154
- const global2 = globalOptions(program, commandOptions);
4155
- const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4156
- if (!isInteractive(stdinIsTTY, deps2.env, global2)) {
4157
- process.exitCode = writeError(
4158
- deps2,
4159
- "invalid_usage",
4160
- "dropthis login is interactive and needs a terminal.",
4161
- "Set DROPTHIS_API_KEY, or run: dropthis login request --email <email>, then dropthis login verify --email <email> --otp <code>."
4162
- ).exitCode;
4163
- return;
4164
- }
4165
- const context = createContext({ global: global2 });
4166
- const result2 = await runLoginInteractive({
4167
- ...deps2,
4168
- createClient: (apiKey) => context.createClient(apiKey),
4169
- ...commandOptions.scope ? { scope: commandOptions.scope } : {}
4170
- });
4171
- process.exitCode = result2.exitCode;
4172
- return;
4173
- }
4174
- const deps = await commandDeps(program, options, store, commandOptions);
4175
- const global = globalOptions(program, commandOptions);
4176
- const verifyContext = createContext({ global });
4177
- const result = await runLoginVerify(
4178
- {
4179
- email: commandOptions.email,
4180
- otp: commandOptions.otp,
4181
- ...commandOptions.scope ? { scope: commandOptions.scope } : {},
4182
- ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
4183
- },
4184
- {
4185
- ...deps,
4186
- createClient: (apiKey) => verifyContext.createClient(apiKey)
4187
- }
4188
- );
4189
- process.exitCode = result.exitCode;
4155
+ program.command("login").description("Authenticate with email OTP (interactive)").option("--json", "Force JSON output").action(async (commandOptions) => {
4156
+ const deps = await commandDeps(program, options, store, commandOptions);
4157
+ const global = globalOptions(program, commandOptions);
4158
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4159
+ if (!isInteractive(stdinIsTTY, deps.env, global)) {
4160
+ process.exitCode = writeError(
4161
+ deps,
4162
+ "invalid_usage",
4163
+ "dropthis login is interactive and needs a terminal.",
4164
+ "Set DROPTHIS_API_KEY, or run: dropthis login request --email <email>, then dropthis login verify --email <email> --otp <code>."
4165
+ ).exitCode;
4166
+ return;
4190
4167
  }
4191
- );
4168
+ const context = createContext({ global });
4169
+ const result = await runLoginInteractive({
4170
+ ...deps,
4171
+ createClient: (apiKey) => context.createClient(apiKey)
4172
+ });
4173
+ process.exitCode = result.exitCode;
4174
+ });
4192
4175
  const login = program.commands.find((command) => command.name() === "login");
4193
4176
  login?.command("request").description("Request an email OTP").requiredOption("--email <email>", "Email address").option("--json", "Force JSON output").action(async (commandOptions) => {
4194
4177
  const deps = await commandDeps(program, options, store, commandOptions);
@@ -4441,6 +4424,18 @@ function parseMode(value) {
4441
4424
  }
4442
4425
  return value;
4443
4426
  }
4427
+ function parseInvitableRole(value) {
4428
+ if (value !== "admin" && value !== "member") {
4429
+ throw new import_commander.InvalidArgumentError('Use "admin" or "member".');
4430
+ }
4431
+ return value;
4432
+ }
4433
+ function parseWorkspaceRole(value) {
4434
+ if (value !== "owner" && value !== "admin" && value !== "member") {
4435
+ throw new import_commander.InvalidArgumentError('Use "owner", "admin", or "member".');
4436
+ }
4437
+ return value;
4438
+ }
4444
4439
  function collect(value, previous) {
4445
4440
  return [...previous, value];
4446
4441
  }