@dropthis/cli 0.16.0 → 0.18.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.
@@ -16,10 +16,14 @@ import { Dropthis } from "@dropthis/node";
16
16
  const dropthis = new Dropthis({ apiKey: "sk_..." });
17
17
  const { data, error } = await dropthis.drops.publish("<h1>Hello</h1>");
18
18
 
19
- console.log(data.url); // https://abc123.dropthis.app
19
+ console.log(data.url); // https://abc123.dropthis.app — branded human view (always badged)
20
20
  console.log(data.id); // drop_… — keep this; it's how you update or delete later
21
21
  ```
22
22
 
23
+ > **Sharing with an agent? Use `data.rawUrl`.** The canonical `url` always renders a
24
+ > branded human view; `rawUrl` is the drop's exact bytes (single-file drops only — `null`
25
+ > otherwise). See [Canonical URL vs raw bytes](#canonical-url-vs-raw-bytes-rawurl).
26
+
23
27
  > **Keep the `drop_…` id.** `publish()` never takes an id — every call creates a NEW drop.
24
28
  > To change something already published, pass the id from the publish response to
25
29
  > `drops.updateContent()` (the files at the URL) or `drops.updateSettings()` (title,
@@ -74,11 +78,14 @@ await dropthis.drops.updateSettings("drop_abc123", { title: "New title" });
74
78
 
75
79
  ### Resolve a URL back to its drop
76
80
 
77
- Lost the `drop_…` id? Resolve the drop's URL (or bare slug) back to the drop. The slug is
78
- parsed client-side from the first hostname label of a dropthis hostname
79
- (`<slug>.dropthis.app`, or `<slug>.` + your configured `baseUrl` host), then matched against
80
- your own drops via `GET /drops?slug=`. Custom-domain URLs are not resolvable yet — they are
81
- rejected client-side with an `invalid_drop_url` error, without hitting the API.
81
+ Lost the `drop_…` id? Resolve any public locator a drop URL, a custom-domain URL, or a bare
82
+ vanity/shared slug back to the drop. The target is sent to the server (`POST /drops/resolve`),
83
+ which owner-scopes and decomposes it; you get the full drop back, or `null` when nothing of yours
84
+ matches. Passing a `drop_…` id round-trips to an owner-scoped lookup.
85
+
86
+ > Persist the drop_… id. URLs, raw_url, and slugs are locators, not identifiers — a vanity slug is
87
+ > renameable and the pool host rotates, so a stored URL can drift; the id never moves. Treat
88
+ > drop_… as an opaque case-sensitive string.
82
89
 
83
90
  ```typescript
84
91
  const { data } = await dropthis.drops.resolve("https://my-report.dropthis.app/");
@@ -114,6 +121,36 @@ const old = await dropthis.drops.getContent("drop_abc123", {
114
121
  });
115
122
  ```
116
123
 
124
+ ### Canonical URL vs raw bytes (`rawUrl`)
125
+
126
+ Every drop has two faces. The canonical `data.url` **always serves a branded human
127
+ view** — that is how the dropthis badge is guaranteed without sniffing who's asking.
128
+ For a single non-HTML file (`renderMode: "file_viewer"`) that view is a branded
129
+ preview (image inline, text/markdown/JSON/CSV/code as escaped source, opaque binary as
130
+ a download); for a multi-file bundle with no HTML entry it's a branded index. An HTML
131
+ drop (`renderMode: "user_html"`) renders the page itself.
132
+
133
+ When you need the underlying file's **exact bytes** (e.g. one agent handing an artifact
134
+ to another), use `data.rawUrl` — the drop's bytes at their natural path under the mount.
135
+ It's populated **only for single-file (`file_viewer`) drops** and is `null` for
136
+ `user_html` and collections (the page is itself the artifact / per-file natural paths
137
+ come from the manifest). Hand `url` to humans and `rawUrl` to agents.
138
+
139
+ ```typescript
140
+ const { data } = await dropthis.drops.publish("./notes.md");
141
+
142
+ console.log(data.url); // https://abc123.dropthis.app/ ← branded preview (badge)
143
+ console.log(data.rawUrl); // https://abc123.dropthis.app/notes.md ← exact bytes for agents
144
+ console.log(data.renderMode); // "file_viewer"
145
+
146
+ // An HTML site or a multi-file collection has no single raw file:
147
+ // data.rawUrl === null (use drops.getContent() to pull bytes by path)
148
+ ```
149
+
150
+ To stream bytes through the SDK regardless of drop kind (and owner-only, so it works even
151
+ on password-protected drops), use [`drops.getContent()`](#read-back-what-a-drop-is-serving) —
152
+ that's the programmatic byte-fetch path; `rawUrl` is the public, shareable one.
153
+
117
154
  ### Safe concurrent edits with ifRevision
118
155
 
119
156
  Every drop response carries a `revision`. Pass it back as `ifRevision` on
@@ -246,7 +283,8 @@ const dropthis = new Dropthis("sk_...");
246
283
  await dropthis.drops.list({ limit: 20 });
247
284
  await dropthis.drops.list({ domain: "reports.example.com" }); // only drops mounted on a custom domain
248
285
  await dropthis.drops.get("drop_abc123");
249
- await dropthis.drops.resolve("https://my-report.dropthis.app/"); // URL/slug → drop (or null)
286
+ await dropthis.drops.resolve("https://my-report.dropthis.app/"); // any locator → drop (or null)
287
+ await dropthis.drops.resolve("drop_abc123"); // a bare id round-trips too
250
288
  await dropthis.drops.getContent("drop_abc123"); // manifest of served files
251
289
  await dropthis.drops.getContent("drop_abc123", { path: "index.html" }); // one file's bytes
252
290
  await dropthis.drops.updateSettings("drop_abc123", { title: "Updated" });
@@ -85,6 +85,17 @@ type DropResponse = {
85
85
  limitations: Limitations;
86
86
  /** Hostname of the custom domain this drop is mounted on; null for shared-pool drops. */
87
87
  domain: string | null;
88
+ /**
89
+ * Direct URL to the drop's raw bytes — the agent byte-fetch path. The canonical
90
+ * `url` always serves a branded human view (so the badge is guaranteed); `rawUrl`
91
+ * serves the underlying file's exact bytes at its natural path under the mount
92
+ * (ADR 0061). Populated only for single-file (`renderMode: "file_viewer"`) drops
93
+ * (= canonical URL + the entry filename); `null` for `user_html` drops (the page
94
+ * IS the artifact) and collections (per-file natural paths come from the manifest —
95
+ * see {@link DeploymentContentManifest}). Hand `url` to humans and `rawUrl` to agents.
96
+ * To stream bytes through the SDK regardless of drop kind, use `drops.getContent()`.
97
+ */
98
+ rawUrl: string | null;
88
99
  };
89
100
  type DropDeploymentResponse = {
90
101
  id: string;
@@ -617,14 +628,7 @@ declare class CursorPage<T> implements ListPage<T> {
617
628
  declare class DropsResource<TInput = PublishInput> {
618
629
  private readonly transport;
619
630
  private readonly resolveInput;
620
- private parentHosts?;
621
631
  constructor(transport: Transport, resolveInput: PublishInputResolver<TInput>);
622
- /**
623
- * Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
624
- * viewer domain plus the configured baseUrl's host (covers staging/self-hosted
625
- * setups where drops are served under the API's own domain).
626
- */
627
- private allowedParentHosts;
628
632
  /**
629
633
  * Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
630
634
  * Use to publish / share / post / put online / make public a report, dashboard, site, or file.
@@ -634,6 +638,12 @@ declare class DropsResource<TInput = PublishInput> {
634
638
  *
635
639
  * Mount target: `options.domain` accepts a connected custom hostname, or `SHARED_POOL`
636
640
  * (`"shared"`) to publish to the shared pool even when the account has a default domain.
641
+ *
642
+ * Two URLs come back on the response: `url` is the canonical, **always-branded** human
643
+ * view (badge guaranteed, no client detection); `rawUrl` is the drop's exact bytes at
644
+ * their natural path — hand it to other agents. `rawUrl` is populated only for single
645
+ * non-HTML files (`renderMode: "file_viewer"`) and is `null` for HTML drops and
646
+ * collections. To stream bytes through the SDK for any drop kind, use {@link getContent}.
637
647
  */
638
648
  publish(input: TInput, options?: PublishOptions): Promise<DropthisResult<DropResponse>>;
639
649
  /**
@@ -653,15 +663,17 @@ declare class DropsResource<TInput = PublishInput> {
653
663
  /** Fetch one drop by its `drop_…` id (not the slug/URL). GET /drops/{id}. */
654
664
  get(dropId: string): Promise<DropthisResult<DropResponse>>;
655
665
  /**
656
- * Resolve a drop URL (or bare slug) back to the drop the way to recover a lost
657
- * `drop_…` id. The slug is parsed client-side from the first hostname label of a
658
- * dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
659
- * the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
660
- * Returns the drop, or `data: null` when no drop of yours has that slug.
661
- * Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
662
- * not resolvable yet.
666
+ * Resolve a public locator (a drop URL, a custom-domain URL, or a bare vanity/shared
667
+ * slug) back to the drop the way to recover a lost `drop_…` id. Sends the raw target
668
+ * to the server (POST /drops/resolve), which owner-scopes and decomposes it. Returns the
669
+ * full drop, or `data: null` when nothing of yours matches. A `drop_…` id passed as the
670
+ * target round-trips to an owner-scoped id lookup (null instead of 404).
671
+ *
672
+ * Persist the drop_… id. URLs, raw_url, and slugs are locators, not identifiers — a vanity
673
+ * slug is renameable and the pool host rotates, so a stored URL can drift; the id never
674
+ * moves. Treat drop_… as an opaque case-sensitive string.
663
675
  */
664
- resolve(urlOrSlug: string): Promise<DropthisResult<DropResponse | null>>;
676
+ resolve(target: string): Promise<DropthisResult<DropResponse | null>>;
665
677
  /**
666
678
  * Read back what a drop is serving (owner-only; works regardless of any viewer
667
679
  * password). By default returns the JSON manifest of the CURRENT deployment's files;
@@ -85,6 +85,17 @@ type DropResponse = {
85
85
  limitations: Limitations;
86
86
  /** Hostname of the custom domain this drop is mounted on; null for shared-pool drops. */
87
87
  domain: string | null;
88
+ /**
89
+ * Direct URL to the drop's raw bytes — the agent byte-fetch path. The canonical
90
+ * `url` always serves a branded human view (so the badge is guaranteed); `rawUrl`
91
+ * serves the underlying file's exact bytes at its natural path under the mount
92
+ * (ADR 0061). Populated only for single-file (`renderMode: "file_viewer"`) drops
93
+ * (= canonical URL + the entry filename); `null` for `user_html` drops (the page
94
+ * IS the artifact) and collections (per-file natural paths come from the manifest —
95
+ * see {@link DeploymentContentManifest}). Hand `url` to humans and `rawUrl` to agents.
96
+ * To stream bytes through the SDK regardless of drop kind, use `drops.getContent()`.
97
+ */
98
+ rawUrl: string | null;
88
99
  };
89
100
  type DropDeploymentResponse = {
90
101
  id: string;
@@ -617,14 +628,7 @@ declare class CursorPage<T> implements ListPage<T> {
617
628
  declare class DropsResource<TInput = PublishInput> {
618
629
  private readonly transport;
619
630
  private readonly resolveInput;
620
- private parentHosts?;
621
631
  constructor(transport: Transport, resolveInput: PublishInputResolver<TInput>);
622
- /**
623
- * Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
624
- * viewer domain plus the configured baseUrl's host (covers staging/self-hosted
625
- * setups where drops are served under the API's own domain).
626
- */
627
- private allowedParentHosts;
628
632
  /**
629
633
  * Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
630
634
  * Use to publish / share / post / put online / make public a report, dashboard, site, or file.
@@ -634,6 +638,12 @@ declare class DropsResource<TInput = PublishInput> {
634
638
  *
635
639
  * Mount target: `options.domain` accepts a connected custom hostname, or `SHARED_POOL`
636
640
  * (`"shared"`) to publish to the shared pool even when the account has a default domain.
641
+ *
642
+ * Two URLs come back on the response: `url` is the canonical, **always-branded** human
643
+ * view (badge guaranteed, no client detection); `rawUrl` is the drop's exact bytes at
644
+ * their natural path — hand it to other agents. `rawUrl` is populated only for single
645
+ * non-HTML files (`renderMode: "file_viewer"`) and is `null` for HTML drops and
646
+ * collections. To stream bytes through the SDK for any drop kind, use {@link getContent}.
637
647
  */
638
648
  publish(input: TInput, options?: PublishOptions): Promise<DropthisResult<DropResponse>>;
639
649
  /**
@@ -653,15 +663,17 @@ declare class DropsResource<TInput = PublishInput> {
653
663
  /** Fetch one drop by its `drop_…` id (not the slug/URL). GET /drops/{id}. */
654
664
  get(dropId: string): Promise<DropthisResult<DropResponse>>;
655
665
  /**
656
- * Resolve a drop URL (or bare slug) back to the drop the way to recover a lost
657
- * `drop_…` id. The slug is parsed client-side from the first hostname label of a
658
- * dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
659
- * the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
660
- * Returns the drop, or `data: null` when no drop of yours has that slug.
661
- * Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
662
- * not resolvable yet.
666
+ * Resolve a public locator (a drop URL, a custom-domain URL, or a bare vanity/shared
667
+ * slug) back to the drop the way to recover a lost `drop_…` id. Sends the raw target
668
+ * to the server (POST /drops/resolve), which owner-scopes and decomposes it. Returns the
669
+ * full drop, or `data: null` when nothing of yours matches. A `drop_…` id passed as the
670
+ * target round-trips to an owner-scoped id lookup (null instead of 404).
671
+ *
672
+ * Persist the drop_… id. URLs, raw_url, and slugs are locators, not identifiers — a vanity
673
+ * slug is renameable and the pool host rotates, so a stored URL can drift; the id never
674
+ * moves. Treat drop_… as an opaque case-sensitive string.
663
675
  */
664
- resolve(urlOrSlug: string): Promise<DropthisResult<DropResponse | null>>;
676
+ resolve(target: string): Promise<DropthisResult<DropResponse | null>>;
665
677
  /**
666
678
  * Read back what a drop is serving (owner-only; works regardless of any viewer
667
679
  * password). By default returns the JSON manifest of the CURRENT deployment's files;
@@ -604,23 +604,6 @@ var DropsResource = class {
604
604
  }
605
605
  transport;
606
606
  resolveInput;
607
- parentHosts;
608
- /**
609
- * Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
610
- * viewer domain plus the configured baseUrl's host (covers staging/self-hosted
611
- * setups where drops are served under the API's own domain).
612
- */
613
- allowedParentHosts() {
614
- if (!this.parentHosts) {
615
- const hosts = /* @__PURE__ */ new Set([CANONICAL_VIEWER_HOST]);
616
- try {
617
- hosts.add(new URL(this.transport.baseUrl).hostname.replace(/\.$/, ""));
618
- } catch {
619
- }
620
- this.parentHosts = hosts;
621
- }
622
- return this.parentHosts;
623
- }
624
607
  /**
625
608
  * Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
626
609
  * Use to publish / share / post / put online / make public a report, dashboard, site, or file.
@@ -630,6 +613,12 @@ var DropsResource = class {
630
613
  *
631
614
  * Mount target: `options.domain` accepts a connected custom hostname, or `SHARED_POOL`
632
615
  * (`"shared"`) to publish to the shared pool even when the account has a default domain.
616
+ *
617
+ * Two URLs come back on the response: `url` is the canonical, **always-branded** human
618
+ * view (badge guaranteed, no client detection); `rawUrl` is the drop's exact bytes at
619
+ * their natural path — hand it to other agents. `rawUrl` is populated only for single
620
+ * non-HTML files (`renderMode: "file_viewer"`) and is `null` for HTML drops and
621
+ * collections. To stream bytes through the SDK for any drop kind, use {@link getContent}.
633
622
  */
634
623
  async publish(input, options = {}) {
635
624
  let prepared;
@@ -684,31 +673,25 @@ var DropsResource = class {
684
673
  );
685
674
  }
686
675
  /**
687
- * Resolve a drop URL (or bare slug) back to the drop the way to recover a lost
688
- * `drop_…` id. The slug is parsed client-side from the first hostname label of a
689
- * dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
690
- * the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
691
- * Returns the drop, or `data: null` when no drop of yours has that slug.
692
- * Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
693
- * not resolvable yet.
676
+ * Resolve a public locator (a drop URL, a custom-domain URL, or a bare vanity/shared
677
+ * slug) back to the drop the way to recover a lost `drop_…` id. Sends the raw target
678
+ * to the server (POST /drops/resolve), which owner-scopes and decomposes it. Returns the
679
+ * full drop, or `data: null` when nothing of yours matches. A `drop_…` id passed as the
680
+ * target round-trips to an owner-scoped id lookup (null instead of 404).
681
+ *
682
+ * Persist the drop_… id. URLs, raw_url, and slugs are locators, not identifiers — a vanity
683
+ * slug is renameable and the pool host rotates, so a stored URL can drift; the id never
684
+ * moves. Treat drop_… as an opaque case-sensitive string.
694
685
  */
695
- async resolve(urlOrSlug) {
696
- const slug = parseSlug(urlOrSlug, this.allowedParentHosts());
697
- if (slug === null) {
698
- return createErrorResult(
699
- "invalid_drop_url",
700
- `Could not resolve a drop slug from "${urlOrSlug}". Pass a canonical drop URL like https://<slug>.dropthis.app/ or the bare slug \u2014 custom-domain URLs cannot be resolved yet.`,
701
- null
702
- );
703
- }
704
- const result = await this.transport.request("GET", "/drops", { params: { slug } });
686
+ async resolve(target) {
687
+ const result = await this.transport.request(
688
+ "POST",
689
+ "/drops/resolve",
690
+ { body: { target } }
691
+ );
705
692
  if (result.error)
706
693
  return { data: null, error: result.error, headers: result.headers };
707
- return {
708
- data: result.data.drops[0] ?? null,
709
- error: null,
710
- headers: result.headers
711
- };
694
+ return { data: result.data.data, error: null, headers: result.headers };
712
695
  }
713
696
  async getContent(dropId, options = {}) {
714
697
  const base = options.deploymentId !== void 0 ? `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(options.deploymentId)}/content` : `/drops/${encodeURIComponent(dropId)}/content`;
@@ -767,25 +750,6 @@ var DropsResource = class {
767
750
  );
768
751
  }
769
752
  };
770
- var CANONICAL_VIEWER_HOST = "dropthis.app";
771
- function parseSlug(input, allowedParents) {
772
- const value = input.trim();
773
- if (!value) return null;
774
- if (!/[./]/.test(value)) return value;
775
- const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
776
- let hostname;
777
- try {
778
- hostname = new URL(withScheme).hostname;
779
- } catch {
780
- return null;
781
- }
782
- hostname = hostname.replace(/\.$/, "");
783
- const dot = hostname.indexOf(".");
784
- if (dot <= 0) return null;
785
- const slug = hostname.slice(0, dot);
786
- const parent = hostname.slice(dot + 1);
787
- return allowedParents.has(parent) ? slug : null;
788
- }
789
753
  var SETTINGS = [
790
754
  "title",
791
755
  "visibility",
@@ -840,7 +804,7 @@ function toSnakeCase(value) {
840
804
 
841
805
  // src/transport.ts
842
806
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
843
- var SDK_VERSION = "0.15.0";
807
+ var SDK_VERSION = "0.17.0";
844
808
  var Transport = class {
845
809
  apiKey;
846
810
  baseUrl;