@dropthis/cli 0.34.0 → 0.36.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.
@@ -286,6 +286,28 @@ whatever `retryable` the API returns (the SDK never overrides it). `publish()` a
286
286
  `updateContent()` are always safe to retry — each derives a stable idempotency key, so a
287
287
  retried publish never creates a duplicate drop.
288
288
 
289
+ ### Plan gates & quotas
290
+
291
+ A feature your plan doesn't include (`feature_not_in_plan`) or a limit you've hit (`quota_exceeded`)
292
+ comes back as a typed, **non-retryable** error — don't retry it, surface the upgrade path instead.
293
+ `isPlanGate(error)` covers both; the narrower `isFeatureNotInPlan` / `isQuotaExceeded` are exported too.
294
+
295
+ ```typescript
296
+ import { isPlanGate } from "@dropthis/node";
297
+
298
+ const { error } = await dropthis.drops.publish("./dist", { password: "hunter2" });
299
+ if (isPlanGate(error)) {
300
+ // error.feature ("passwordProtect"), error.requiredPlan ("pro"), error.upgradeUrl
301
+ // — on quota_exceeded also: error.limit, error.usage, error.requested
302
+ console.log(`Needs ${error.requiredPlan}: ${error.upgradeUrl}`);
303
+ }
304
+ ```
305
+
306
+ Pre-flight instead of failing: `account.get().data.entitlements` carries the capability matrix +
307
+ numeric limits, so you can check a gate or size a publish before attempting it. A `403`
308
+ `insufficient_scope` is different — that's a [scope](#capability-scopes) gap on the credential, not a
309
+ plan gate; fix it by minting/re-logging with the needed scope, not by upgrading.
310
+
289
311
  ## Configuration
290
312
 
291
313
  ```typescript
@@ -304,6 +326,11 @@ You can also pass just the API key as a string:
304
326
  const dropthis = new Dropthis("sk_...");
305
327
  ```
306
328
 
329
+ **Getting a key.** Grab an `sk_…` key from the [console](https://app.dropthis.app) Credentials page,
330
+ or mint one programmatically after an OTP login (`auth.verifyEmailOtp()` → `apiKeys.create()`). The
331
+ credential is sent as `Authorization: Bearer <key>`; an `at_…` session token works in the same slot.
332
+ With no explicit `apiKey`, the client reads `DROPTHIS_API_KEY` from the environment.
333
+
307
334
  ## Resources
308
335
 
309
336
  ### drops
@@ -392,12 +419,39 @@ await dropthis.apiKeys.create({
392
419
  workspace: "prod-team",
393
420
  });
394
421
 
422
+ // Key that can create + manage teams (not just publish) — pass a scope bundle
423
+ await dropthis.apiKeys.create({ label: "Team bot", scopes: ["team"] });
424
+
395
425
  await dropthis.apiKeys.list();
396
426
  await dropthis.apiKeys.delete("key_abc123"); // 204 No Content — data is null
397
427
  ```
398
428
 
399
429
  `KeyType` is `"delegated" | "service"`. A `delegated` key acts on behalf of the owning account and can switch workspaces server-side via `workspaces.use()`; a `service` key is pinned to one workspace and is intended for CI/automation where a stable target is needed.
400
430
 
431
+ #### Capability scopes
432
+
433
+ Every credential carries a set of **capability scopes** that decide what it may do (ADR 0068). You
434
+ request them as **bundles**; `apiKeys.create({ scopes })` mints the requested set **intersected with
435
+ your own** (downscope-only — you can never grant a key more than you hold).
436
+
437
+ | Bundle | Grants | Use it for |
438
+ | --- | --- | --- |
439
+ | `publish` *(default)* | publish drops, read your own context, mint downscope-only keys | a normal API key / `dropthis login` |
440
+ | `team` | `publish` + create/rename workspaces + invite/manage members | building or running a team |
441
+ | `team-admin` | `team` + delete workspaces, remove members, change roles | the irreversible team-admin tier |
442
+ | `service` | `publish` minus key-minting, workspace-pinned | CI/automation |
443
+
444
+ Omit `scopes` for the default `publish` bundle. A plain key **cannot** do team management — call a
445
+ team op (`workspaces.create()`, members, invitations) with a publish-only key and the API returns
446
+ `403 insufficient_scope`; mint or re-login with the `team` bundle to fix it. Fine-grained scopes
447
+ (e.g. `members:admin`) may be passed individually; the created key's `data.scopes` echoes the
448
+ granted set.
449
+
450
+ ```typescript
451
+ const { data } = await dropthis.apiKeys.create({ label: "Team bot", scopes: ["team"] });
452
+ console.log(data.scopes); // the granted set (requested ∩ yours)
453
+ ```
454
+
401
455
  ### account
402
456
 
403
457
  ```typescript
@@ -512,6 +566,37 @@ const { data } = await dropthis.drops.publish("./dist");
512
566
 
513
567
  `DropthisEdge` honours the same `workspace` constructor option and publish option.
514
568
 
569
+ #### Team management (workspaces, members, invitations)
570
+
571
+ A **team** workspace is shared by multiple accounts. Creating and managing one needs a `team`-scoped
572
+ credential (see [Capability scopes](#capability-scopes)) — a default publish key gets
573
+ `403 insufficient_scope`. Drops published while a team workspace is active land there and route on its
574
+ shared custom domain automatically.
575
+
576
+ ```typescript
577
+ // Create a team (caller becomes its owner) and switch to it
578
+ const { data: team } = await dropthis.workspaces.create({ name: "Acme" });
579
+ await dropthis.workspaces.use(team.slug);
580
+
581
+ // Invite + manage members
582
+ await dropthis.members.invite(team.id, { email: "teammate@acme.com", role: "member" });
583
+ const { data: roster } = await dropthis.members.list(team.id);
584
+ await dropthis.members.updateRole(team.id, "acc_123", { role: "admin" }); // needs members:admin
585
+ await dropthis.members.remove(team.id, "acc_123"); // remove a member, or your own id to leave
586
+
587
+ // Rename / delete the workspace
588
+ await dropthis.workspaces.rename(team.id, { name: "Acme Inc" });
589
+ await dropthis.workspaces.delete(team.id); // owner only, needs workspaces:admin
590
+
591
+ // Invitee side — accept an invite (joins + switches active workspace)
592
+ const { data: pending } = await dropthis.invitations.list();
593
+ await dropthis.invitations.accept({ token: "inv_raw_token_from_email" });
594
+ await dropthis.invitations.acceptById({ invitationId: "inv_abc123" }); // agent path: no token, just be the invited email
595
+ ```
596
+
597
+ `role` is `"admin" | "member"` for invites; `updateRole` also accepts `"owner"` to transfer ownership
598
+ (owner-only, enforced server-side).
599
+
515
600
  ## Pricing tiers
516
601
 
517
602
  - **Free** — drops expire after 30 days, 5 MB per drop, 500 MB active storage, dropthis badge.
@@ -246,7 +246,8 @@ function buildStagedRequest(files, options, entry) {
246
246
  sourceUrl: file.sourceUrl,
247
247
  ...file.contentType ? { contentType: file.contentType } : {},
248
248
  ...file.sizeBytes !== void 0 ? { sizeBytes: file.sizeBytes } : {},
249
- ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
249
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {},
250
+ ...file.transform ? { transform: file.transform } : {}
250
251
  }
251
252
  ) : {
252
253
  path: normalizeManifestPath(file.path),
@@ -333,7 +334,8 @@ function prepareBundleFile(file) {
333
334
  sourceUrl: file.sourceUrl,
334
335
  ...file.contentType ? { contentType: file.contentType } : {},
335
336
  ...file.sizeBytes !== void 0 ? { sizeBytes: file.sizeBytes } : {},
336
- ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
337
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {},
338
+ ...file.transform ? { transform: file.transform } : {}
337
339
  };
338
340
  }
339
341
  const bytes = fileBytes(file);
@@ -877,6 +879,69 @@ function updateBody(options) {
877
879
  };
878
880
  }
879
881
 
882
+ // src/resources/invitations.ts
883
+ var InvitationsResource = class {
884
+ constructor(transport) {
885
+ this.transport = transport;
886
+ }
887
+ transport;
888
+ /** List the calling account's own pending invitations. */
889
+ list() {
890
+ return this.transport.request("GET", "/invitations");
891
+ }
892
+ /** Accept by the raw single-use token from the invite email. Joins + switches active workspace. */
893
+ accept(input) {
894
+ return this.transport.request("POST", "/invitations/accept", {
895
+ body: input
896
+ });
897
+ }
898
+ /** Accept by invitation id, once authenticated as the invited email — the agent path, no token. */
899
+ acceptById(input) {
900
+ return this.transport.request("POST", "/invitations/accept-by-id", {
901
+ body: input
902
+ });
903
+ }
904
+ };
905
+
906
+ // src/resources/members.ts
907
+ var MembersResource = class {
908
+ constructor(transport) {
909
+ this.transport = transport;
910
+ }
911
+ transport;
912
+ /** List a workspace's members (any member, `members:read`). */
913
+ list(workspaceId) {
914
+ return this.transport.request(
915
+ "GET",
916
+ `/workspaces/${encodeURIComponent(workspaceId)}/members`
917
+ );
918
+ }
919
+ /** Invite an email to the workspace (owner/admin, `members:write`). */
920
+ invite(workspaceId, input) {
921
+ return this.transport.request(
922
+ "POST",
923
+ `/workspaces/${encodeURIComponent(workspaceId)}/invitations`,
924
+ { body: input }
925
+ );
926
+ }
927
+ /** Change a member's role (`members:admin`, owner-only-touches-owner enforced server-side). */
928
+ updateRole(workspaceId, accountId, input) {
929
+ return this.transport.request(
930
+ "PATCH",
931
+ `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(accountId)}`,
932
+ { body: input }
933
+ );
934
+ }
935
+ /** Remove a member, or leave the workspace (your own id). Removing others needs `members:admin`;
936
+ * leaving needs `members:write`. 204 — data is null. */
937
+ remove(workspaceId, accountId) {
938
+ return this.transport.request(
939
+ "DELETE",
940
+ `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(accountId)}`
941
+ );
942
+ }
943
+ };
944
+
880
945
  // src/resources/workspaces.ts
881
946
  var WorkspacesResource = class {
882
947
  constructor(transport) {
@@ -958,7 +1023,7 @@ function toSnakeCase(value) {
958
1023
 
959
1024
  // src/transport.ts
960
1025
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
961
- var SDK_VERSION = "0.29.0";
1026
+ var SDK_VERSION = true ? "0.32.0" : "0.0.0-dev";
962
1027
  var Transport = class {
963
1028
  apiKey;
964
1029
  baseUrl;
@@ -1215,6 +1280,8 @@ var DropthisEdge = class {
1215
1280
  deploymentsResource;
1216
1281
  domainsResource;
1217
1282
  workspacesResource;
1283
+ membersResource;
1284
+ invitationsResource;
1218
1285
  constructor(options = {}) {
1219
1286
  this.transport = new Transport(options);
1220
1287
  this.defaultWorkspace = typeof options === "string" ? void 0 : options.workspace;
@@ -1258,6 +1325,18 @@ var DropthisEdge = class {
1258
1325
  this.workspacesResource = new WorkspacesResource(this.transport);
1259
1326
  return this.workspacesResource;
1260
1327
  }
1328
+ /** Team membership management (ADR 0068; needs a team / team-admin scoped credential). */
1329
+ get members() {
1330
+ if (!this.membersResource)
1331
+ this.membersResource = new MembersResource(this.transport);
1332
+ return this.membersResource;
1333
+ }
1334
+ /** Invitee-side invitations: list the caller's pending invites, accept by token or id. */
1335
+ get invitations() {
1336
+ if (!this.invitationsResource)
1337
+ this.invitationsResource = new InvitationsResource(this.transport);
1338
+ return this.invitationsResource;
1339
+ }
1261
1340
  };
1262
1341
  // Annotate the CommonJS export names for ESM import in node:
1263
1342
  0 && (module.exports = {