@shipstatic/ship 2.0.0-beta.9 → 2.0.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
@@ -129,7 +129,7 @@ ship ping
129
129
 
130
130
  ```typescript
131
131
  ship.account.get() // → whoami
132
- ship.ping() // → boolean
132
+ ship.ping() // → { timestamp } (server clock; reachability is the absence of a throw)
133
133
  ship.getLimits() // → platform plan limits (cached)
134
134
  ```
135
135
 
@@ -139,6 +139,8 @@ ship.getLimits() // → platform plan limits (cached)
139
139
 
140
140
  The `-q` flag outputs only the resource identifier — perfect for piping and scripting:
141
141
 
142
+ `ship tokens create -q` is the one exception: it prints the token **secret**, which is shown once and never again.
143
+
142
144
  ```bash
143
145
  # Deploy and link domain in one pipe
144
146
  ship ./dist -q | ship domains set www.example.com
@@ -318,7 +320,15 @@ The **CLI** (`ship`) resolves its token in this order:
318
320
 
319
321
  1. CLI flag: `--token`
320
322
  2. Environment variable: `SHIP_TOKEN`
321
- 3. Config files: `.shiprc` or `package.json` `"ship"` key (run `ship config` to create one)
323
+ 3. Config file: `~/.shiprc` (run `ship config` to create one)
324
+
325
+ `--config <file>` reads any path you name instead of `~/.shiprc`, which is how per-environment
326
+ configs work (`ship --config dev.shiprc ...`). The file is strict JSON; an empty one means "no
327
+ config".
328
+
329
+ **No repository file is ever read.** A `.shiprc` or `package.json` `"ship"` key in your working
330
+ directory is ignored — cloning a repo can never change which account you deploy to, or which
331
+ host your token is sent to.
322
332
 
323
333
  The **SDK** (`new Ship(...)`) resolves its token in this order:
324
334
 
package/SKILL.md CHANGED
@@ -223,6 +223,8 @@ Every command supports three modes:
223
223
  | `--json` | JSON on stdout | Parsing programmatically |
224
224
  | `-q` | Identifier only | Piping between commands |
225
225
 
226
+ `-q` prints the resource identifier — except `tokens create -q`, which prints the token **secret** (shown once, never again).
227
+
226
228
  Errors go to stderr in all modes. Exit 0 = success, 1 = error.
227
229
 
228
230
  List commands return `{"<resource>s": [...], "cursor": null}`. A non-null `cursor` means more pages remain — pass it back with `--cursor` to continue, and size pages with `--limit`. There is no total; a count is an aggregate over a collection, not a property of one page. `domains list` text mode omits status — use `--json` to see `pending` vs `success`.
package/dist/browser.d.ts CHANGED
@@ -12,6 +12,28 @@ declare const DeploymentStatus: {
12
12
  readonly DELETING: "deleting";
13
13
  };
14
14
  type DeploymentStatusType = (typeof DeploymentStatus)[keyof typeof DeploymentStatus];
15
+ /**
16
+ * Which client made a deployment — the origin-tracking vocabulary.
17
+ *
18
+ * A closed set with many authors: the CLI, the SDK, the dashboard, both MCP
19
+ * transports, the GitHub Action, the n8n node and the VS Code extension each
20
+ * name themselves here. It lived in the API's config until 2026-08-06, where
21
+ * being server-side made it unenforceable in the one direction that matters —
22
+ * every client wrote a bare string, and a value outside the set was **silently
23
+ * dropped** by the server, so a typo did not fail anywhere. It stopped
24
+ * recording where deploys came from and said nothing.
25
+ */
26
+ declare const DeploymentVia: {
27
+ readonly WEB: "web";
28
+ readonly SDK: "sdk";
29
+ readonly CLI: "cli";
30
+ readonly MCP: "mcp";
31
+ readonly GIT: "git";
32
+ readonly N8N: "n8n";
33
+ readonly GPT: "gpt";
34
+ readonly VSC: "vsc";
35
+ };
36
+ type DeploymentViaType = (typeof DeploymentVia)[keyof typeof DeploymentVia];
15
37
  /**
16
38
  * Core deployment object - used in both API responses and SDK
17
39
  */
@@ -32,7 +54,15 @@ interface Deployment {
32
54
  readonly password: boolean;
33
55
  /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
34
56
  labels: string[];
35
- /** The client/tool used to create this deployment (e.g., 'web', 'sdk', 'cli'), null if unknown */
57
+ /**
58
+ * The client/tool that created this deployment, null if unknown.
59
+ *
60
+ * Deliberately wider than {@link DeploymentViaType}: this is stored data,
61
+ * and rows predate the vocabulary being closed. Narrowing the ENTITY would
62
+ * be a claim about every row already in the database; narrowing the
63
+ * REQUEST option ({@link DeploymentUploadOptions.via}) is a claim about
64
+ * what a client may send, which is ours to make.
65
+ */
36
66
  readonly via: string | null;
37
67
  /** Unix timestamp (seconds) when deployment was created */
38
68
  readonly created: number;
@@ -49,62 +79,6 @@ interface DeploymentCreateResponse extends Deployment {
49
79
  /** Claim URL for public deployments. Present when deployed without credentials. */
50
80
  readonly claim?: string;
51
81
  }
52
- /**
53
- * Every path the public API answers on, declared once.
54
- *
55
- * The URL surface was written out in four places — the API's mounts, the
56
- * SDK's client, the dashboard's client, and the post-deploy smoke — so a
57
- * rename meant finding all four. The first three now read this table.
58
- *
59
- * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
60
- * five of its nine paths are `/admin/*`, which this table excludes by
61
- * design, and splitting one list between a registry and literals reads worse
62
- * than keeping it uniform.
63
- *
64
- * **What this guarantees, exactly.** Collection paths are mounted from here,
65
- * so producer and consumer cannot diverge. Item paths are declared here and
66
- * consumed by clients, but the API spells them relative to their mount
67
- * (`/:deployment/config`), so the table does not *generate* them — it is
68
- * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
69
- * any entry names a path no route answers. Some entries have no client yet
70
- * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
71
- * deliberately does not reach); the fence is what keeps those honest rather
72
- * than merely asserted.
73
- *
74
- * **The operator surface is deliberately absent.** `/admin/*` paths belong
75
- * to `web/my`, for the same reason its row types do: this package is
76
- * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
77
- * types"). A path here is a promise to every npm consumer; `/admin` is a
78
- * promise to one dashboard.
79
- *
80
- * Item paths are functions rather than templates so the key is interpolated
81
- * in one place, encoded the same way by every caller.
82
- */
83
- declare const API_PATHS: {
84
- readonly DEPLOYMENTS: "/deployments";
85
- readonly DEPLOYMENT: (deployment: string) => string;
86
- readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
87
- readonly DOMAINS: "/domains";
88
- readonly DOMAIN: (domain: string) => string;
89
- readonly DOMAIN_VERIFY: (domain: string) => string;
90
- readonly DOMAIN_DNS: (domain: string) => string;
91
- readonly DOMAIN_RECORDS: (domain: string) => string;
92
- readonly DOMAIN_SHARE: (domain: string) => string;
93
- readonly DOMAIN_PROPAGATION: (domain: string) => string;
94
- readonly DOMAINS_VALIDATE: "/domains/validate";
95
- readonly TOKENS: "/tokens";
96
- readonly TOKEN: (token: string) => string;
97
- readonly ACCOUNT: "/account";
98
- readonly ACCOUNT_KEY: "/account/key";
99
- readonly ACCOUNT_CLAIM: "/account/claim";
100
- readonly ACTIVITIES: "/activities";
101
- readonly LABELS: "/labels";
102
- readonly LIMITS: "/limits";
103
- readonly PING: "/ping";
104
- readonly SETUP: "/setup";
105
- readonly SPA_CHECK: "/spa-check";
106
- readonly UPLOAD: "/upload";
107
- };
108
82
  /**
109
83
  * The half of a list response that is identical on every list.
110
84
  *
@@ -123,6 +97,28 @@ interface ListResponse {
123
97
  /** Opaque cursor from this page; `null` on the last page. */
124
98
  cursor: string | null;
125
99
  }
100
+ /**
101
+ * Pagination options for every list endpoint. The response's `cursor` feeds
102
+ * the next request; a `null` cursor means the last page. Omitting both
103
+ * returns the server's default first page.
104
+ *
105
+ * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
106
+ * carries the entire has-more signal, so no redundant boolean, and no
107
+ * `total`. **A count is an aggregate over a collection, not a property of a
108
+ * page:** including one makes every read pay for a full scan it did not ask
109
+ * for, which is precisely the cost keyset pagination exists to avoid.
110
+ *
111
+ * Counts therefore live on the summary resource that owns them —
112
+ * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
113
+ * platform-wide ones. Ask for a count when you want a count; ask for a page
114
+ * when you want a page.
115
+ */
116
+ interface ListOptions {
117
+ /** Maximum number of items to return in one page. */
118
+ limit?: number;
119
+ /** Opaque cursor from the previous page's response. */
120
+ cursor?: string;
121
+ }
126
122
  /**
127
123
  * Response for listing deployments
128
124
  */
@@ -324,10 +320,33 @@ interface DomainRecordsResponse {
324
320
  * API would reject the same value the same way.
325
321
  */
326
322
  declare const IDEMPOTENCY_KEY_CONSTRAINTS: {
323
+ /**
324
+ * HTTP header name. Here for the same reason {@link CALLER.HEADER} is: a
325
+ * wire header has two ends, and the package that owns the value's format
326
+ * is the only place both ends can read its name from.
327
+ */
328
+ readonly HEADER: "Idempotency-Key";
327
329
  readonly MAX_LENGTH: 256;
328
330
  /** How long a stored 201 stays replayable. */
329
331
  readonly WINDOW_SECONDS: number;
330
332
  };
333
+ /**
334
+ * Normalize a `via` value from any transport — trimmed, lowercased, and a
335
+ * member of {@link DeploymentVia}, or `undefined`.
336
+ *
337
+ * A format rule by this package's own test: a client can decide offline
338
+ * whether a value is well-formed, and the API reaches the same verdict on the
339
+ * same input. It lived server-side until 2026-08-06, which meant clients could
340
+ * only learn their label was unusable by noticing analytics had gone quiet.
341
+ *
342
+ * **Not knowing your `via` is not an error** — an unrecognized value yields
343
+ * `undefined` rather than throwing, because origin tracking is telemetry and a
344
+ * deploy must never fail over it. A caller that has an honest default should
345
+ * prefer it (`normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI`): the
346
+ * deploy really did come from the CLI, so recording that beats recording
347
+ * nothing.
348
+ */
349
+ declare function normalizeVia(value: unknown): DeploymentViaType | undefined;
331
350
  /**
332
351
  * Validate an idempotency key, returning the trimmed value or `undefined`
333
352
  * when none was supplied. Throws {@link ShipError.validation} when the value
@@ -570,6 +589,97 @@ interface AccountOverrides {
570
589
  /** Override for maximum total deployment size in bytes */
571
590
  totalSize?: number;
572
591
  }
592
+ /**
593
+ * Every path the public API answers on, declared once.
594
+ *
595
+ * The URL surface was written out in four places — the API's mounts, the
596
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
597
+ * rename meant finding all four. The first three now read this table.
598
+ *
599
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
600
+ * five of its nine paths are `/admin/*`, which this table excludes by
601
+ * design, and splitting one list between a registry and literals reads worse
602
+ * than keeping it uniform.
603
+ *
604
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
605
+ * so producer and consumer cannot diverge. Item paths are declared here and
606
+ * consumed by clients, but the API spells them relative to their mount
607
+ * (`/:deployment/config`), so the table does not *generate* them — it is
608
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
609
+ * any entry names a path no route answers. Some entries have no client yet
610
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
611
+ * deliberately does not reach); the fence is what keeps those honest rather
612
+ * than merely asserted.
613
+ *
614
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
615
+ * to `web/my`, for the same reason its row types do: this package is
616
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
617
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
618
+ * promise to one dashboard.
619
+ *
620
+ * Item paths are functions rather than templates so the key is interpolated
621
+ * in one place, encoded the same way by every caller.
622
+ */
623
+ declare const API_PATHS: {
624
+ readonly DEPLOYMENTS: "/deployments";
625
+ readonly DEPLOYMENT: (deployment: string) => string;
626
+ readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
627
+ readonly DOMAINS: "/domains";
628
+ readonly DOMAIN: (domain: string) => string;
629
+ readonly DOMAIN_VERIFY: (domain: string) => string;
630
+ readonly DOMAIN_DNS: (domain: string) => string;
631
+ readonly DOMAIN_RECORDS: (domain: string) => string;
632
+ readonly DOMAIN_SHARE: (domain: string) => string;
633
+ readonly DOMAIN_PROPAGATION: (domain: string) => string;
634
+ readonly DOMAINS_VALIDATE: "/domains/validate";
635
+ readonly TOKENS: "/tokens";
636
+ readonly TOKEN: (token: string) => string;
637
+ readonly ACCOUNT: "/account";
638
+ readonly ACCOUNT_KEY: "/account/key";
639
+ readonly ACCOUNT_CLAIM: "/account/claim";
640
+ readonly ACTIVITIES: "/activities";
641
+ readonly LABELS: "/labels";
642
+ readonly LIMITS: "/limits";
643
+ readonly PING: "/ping";
644
+ readonly SETUP: "/setup";
645
+ readonly SPA_CHECK: "/spa-check";
646
+ readonly UPLOAD: "/upload";
647
+ };
648
+ /**
649
+ * The deploy request's multipart field names — the other half of the wire
650
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
651
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
652
+ *
653
+ * Declared once because the body has three independent WRITERS — the SDK's
654
+ * Node and browser body builders, and the n8n community node's hand-rolled
655
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
656
+ * and fences its restated copy instead) — and until this export every writer
657
+ * restated the strings the API parses, with nothing comparing them.
658
+ *
659
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
660
+ * other field is single. The `@internal` flags are serialized as the literal
661
+ * string `'true'` and belong to first-party surfaces only.
662
+ */
663
+ declare const DEPLOY_FIELDS: {
664
+ /** One entry per file — read with `getAll`. */
665
+ readonly FILES: "files[]";
666
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
667
+ readonly CHECKSUMS: "checksums";
668
+ /** JSON array of label strings. */
669
+ readonly LABELS: "labels";
670
+ /** The deploying surface's {@link DeploymentVia} member. */
671
+ readonly VIA: "via";
672
+ /** Plaintext password — the API hashes it server-side. */
673
+ readonly PASSWORD: "password";
674
+ /** @internal Server-processing flag — first-party `/upload` only. */
675
+ readonly BUILD: "build";
676
+ /** @internal Server-processing flag — first-party `/upload` only. */
677
+ readonly PRERENDER: "prerender";
678
+ /** @internal Server-processing flag — first-party `/upload` only. */
679
+ readonly SPA: "spa";
680
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
681
+ readonly CAPTCHA: "captcha";
682
+ };
573
683
  /**
574
684
  * All possible error types in the ShipStatic platform.
575
685
  *
@@ -602,6 +712,17 @@ declare const ErrorType: {
602
712
  readonly Business: "business_logic_error";
603
713
  /** API server error (500). Generic server-side fault. */
604
714
  readonly Api: "internal_server_error";
715
+ /**
716
+ * The platform is closed for maintenance (503). A deliberate operator
717
+ * state, not a fault — nothing errored; the API is refusing work on
718
+ * purpose, and deployed sites keep serving throughout.
719
+ *
720
+ * Distinct from `Api` at 503, which the platform already uses for a
721
+ * dependency that failed (moderation unavailable). A consumer has to tell
722
+ * "we closed the door" from "something broke": the two get opposite words
723
+ * and opposite retry behaviour.
724
+ */
725
+ readonly Maintenance: "maintenance";
605
726
  /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
606
727
  readonly Network: "network_error";
607
728
  /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
@@ -668,7 +789,8 @@ declare class ShipError extends Error {
668
789
  * Routing:
669
790
  * - Already a `ShipError` → returned as-is (caller's intent preserved)
670
791
  * - `AbortError` → `ShipError.cancelled(...)`
671
- * - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
792
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
793
+ * for what each runtime offers as evidence
672
794
  * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
673
795
  * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
674
796
  *
@@ -701,6 +823,16 @@ declare class ShipError extends Error {
701
823
  static file(message: string, details?: unknown): ShipError;
702
824
  static config(message: string, details?: unknown): ShipError;
703
825
  static api(message: string, status?: number, details?: unknown): ShipError;
826
+ /**
827
+ * The platform is closed for maintenance (503).
828
+ *
829
+ * `message` is REQUIRED and has no default here. The API is the only
830
+ * producer of that sentence, and a default in this file would be a second
831
+ * owner of one fact — see CLAUDE.md, "The Constellation Law" (stopping
832
+ * rule). It is also the one factory whose status is fixed rather than
833
+ * defaulted: a maintenance refusal is 503 or it is not this error.
834
+ */
835
+ static maintenance(message: string, details?: unknown): ShipError;
704
836
  /**
705
837
  * The caller is at fault — by HTTP's own definition of a 4xx, or by a type
706
838
  * that is client-attributable without ever having a status (`Config`,
@@ -775,6 +907,28 @@ declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
775
907
  * isBlockedExtension('README') // false
776
908
  */
777
909
  declare function isBlockedExtension(filename: string): boolean;
910
+ /**
911
+ * The `accept` attribute value for a browser file picker offering web files.
912
+ *
913
+ * **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
914
+ * gate and the only thing that decides what may be hosted; this constant
915
+ * decides what a *file dialog* shows first. The two are not two halves of one
916
+ * policy, and this one must never be consulted to accept or reject a file.
917
+ *
918
+ * The distinction is structural, not stylistic. `accept` can express only an
919
+ * allowlist, while the platform's rule is a blocklist — so this list is
920
+ * necessarily *narrower* than what the platform hosts, and reading it as
921
+ * authority would reject files the platform serves happily. It is also not
922
+ * enforcement in the browser's own terms: every file dialog offers an
923
+ * all-files escape, and **drag-and-drop ignores `accept` entirely**. The
924
+ * dropzone and the picker must reach the same verdict on the same files, and
925
+ * they do — because the verdict is `validateFiles`, downstream of both.
926
+ *
927
+ * Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
928
+ * `tests/validation-constants.test.ts` fence the invariant that matters: the
929
+ * picker must never offer a file the platform will refuse.
930
+ */
931
+ declare const WEB_FILE_ACCEPT: string;
778
932
  /**
779
933
  * Characters that are unsafe in filenames for static hosting.
780
934
  *
@@ -947,6 +1101,26 @@ declare const SPA_DEFAULT_CONFIG: {
947
1101
  readonly destination: "/index.html";
948
1102
  }];
949
1103
  };
1104
+ /**
1105
+ * The `/spa-check` pre-flight's client-side envelope: which file is the
1106
+ * check's subject, and how large it may be before a client skips the call.
1107
+ *
1108
+ * One fact with three holders until this export — the API's config declared
1109
+ * the cap, the SDK's `checkSPA` hardcoded `100 * 1024`, and prose restated
1110
+ * "100KB". `INDEX_FILE` is the selection rule (the file whose content rides
1111
+ * `SPACheckRequest.index`), restated by every client that builds the request.
1112
+ *
1113
+ * Neither member is a validation boundary: a client over the cap simply
1114
+ * skips the pre-flight, because the server answers an oversized index
1115
+ * `isSPA: false` anyway. A consumer that cannot import this (n8n) needs no
1116
+ * size copy at all — outcome parity is the server's, not the client's.
1117
+ */
1118
+ declare const SPA_CHECK_CONSTRAINTS: {
1119
+ /** The file whose content is the check's subject. */
1120
+ readonly INDEX_FILE: "index.html";
1121
+ /** Skip the pre-flight above this size — the server would answer false. */
1122
+ readonly MAX_INDEX_BYTES: number;
1123
+ };
950
1124
  /**
951
1125
  * Assert that a ship.json file is *syntactically* loadable. Syntax only —
952
1126
  * never schema.
@@ -1071,6 +1245,50 @@ interface StaticFile {
1071
1245
  }
1072
1246
  /** Default API URL if not otherwise configured. */
1073
1247
  declare const DEFAULT_API = "https://api.shipstatic.com";
1248
+ /**
1249
+ * The Node SDK's ambient configuration pair — the ONLY environment variables
1250
+ * the SDK reads, and therefore the COMPLETE list an embedding host must
1251
+ * scrub (per `npm/ship`'s strict-isolation contract, scrubbing is the host's
1252
+ * job, not the SDK's). A host that derives its scrub from this object's
1253
+ * values — as the VS Code extension's child-process env block does — picks
1254
+ * up a grown contract at the next pin bump instead of by remembered prose.
1255
+ *
1256
+ * Browser builds read no environment at all, and the CLI-only variables
1257
+ * (`SHIP_PASSWORD`, `SHIP_VIA`) are deliberately NOT here: they are the
1258
+ * CLI's operational levers, not the SDK's ambient contract — see
1259
+ * `npm/ship/CLAUDE.md`, "CLI-only env vars".
1260
+ */
1261
+ declare const SHIP_ENV: {
1262
+ /** The one credential slot — any platform token. */
1263
+ readonly TOKEN: "SHIP_TOKEN";
1264
+ /** The API endpoint override. */
1265
+ readonly API_URL: "SHIP_API_URL";
1266
+ };
1267
+ /**
1268
+ * Where a human creates an API key — the console deep link quoted by every
1269
+ * surface that teaches authentication (the CLI's config wizard, the VS Code
1270
+ * and n8n listings, the n8n rate-limit hint and credential copy). Written
1271
+ * out in five files across three repos until this export.
1272
+ *
1273
+ * Production-branded by design: published artifacts name the product, never
1274
+ * an environment (root `CLAUDE.md`, "Environment-Aware URLs").
1275
+ */
1276
+ declare const MY_API_KEY_URL = "https://my.shipstatic.com/api-key";
1277
+ /**
1278
+ * How long an anonymous deployment lives before it expires.
1279
+ *
1280
+ * The lifetime of the public tier, and one fact with several readers. The API
1281
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1282
+ * same window — a live site with a dead claim link is a coherence bug, so the
1283
+ * two are one constant rather than two that agree. Both MCP transports quote
1284
+ * the duration in prose an agent reads, and derive it from here rather than
1285
+ * writing it out, which they did in eight places until this export existed.
1286
+ *
1287
+ * Seconds, spelled in the name: this platform has both second- and
1288
+ * millisecond-valued durations, and the pair is only safe when each says which
1289
+ * it is.
1290
+ */
1291
+ declare const PUBLIC_DEPLOYMENT_TTL_SECONDS: number;
1074
1292
  /**
1075
1293
  * Universal deploy input — the union of every shape the SDK accepts.
1076
1294
  *
@@ -1089,8 +1307,12 @@ type DeployInput = File[] | string | string[];
1089
1307
  interface DeploymentUploadOptions {
1090
1308
  /** Optional labels for categorization and filtering */
1091
1309
  labels?: string[];
1092
- /** Client identifier (e.g., 'cli', 'sdk', 'web') */
1093
- via?: string;
1310
+ /**
1311
+ * Which client is making this deploy. Closed, because the server silently
1312
+ * ignores anything outside the set — so an unchecked string turned a typo
1313
+ * into missing analytics rather than an error. See {@link DeploymentVia}.
1314
+ */
1315
+ via?: DeploymentViaType;
1094
1316
  /**
1095
1317
  * Optional password that protects this deployment.
1096
1318
  *
@@ -1122,36 +1344,14 @@ interface DeploymentUploadOptions {
1122
1344
  *
1123
1345
  * **Agents are the audience.** A human notices a duplicate; an automated
1124
1346
  * retry does not. Pick a key that identifies the ATTEMPT — a run id, a
1125
- * commit sha, a uuid minted before the first try — never one that varies
1126
- * per attempt, which would defeat the point.
1347
+ * commit sha, a uuid minted before the first try — never one minted fresh
1348
+ * on each retry, which would defeat the point.
1127
1349
  *
1128
1350
  * The replay is per-caller, and it stores successes only: a failed deploy
1129
1351
  * retries fresh under the same key.
1130
1352
  */
1131
1353
  idempotencyKey?: string;
1132
1354
  }
1133
- /**
1134
- * Pagination options for every list endpoint. The response's `cursor` feeds
1135
- * the next request; a `null` cursor means the last page. Omitting both
1136
- * returns the server's default first page.
1137
- *
1138
- * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
1139
- * carries the entire has-more signal, so no redundant boolean, and no
1140
- * `total`. **A count is an aggregate over a collection, not a property of a
1141
- * page:** including one makes every read pay for a full scan it did not ask
1142
- * for, which is precisely the cost keyset pagination exists to avoid.
1143
- *
1144
- * Counts therefore live on the summary resource that owns them —
1145
- * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
1146
- * platform-wide ones. Ask for a count when you want a count; ask for a page
1147
- * when you want a page.
1148
- */
1149
- interface ListOptions {
1150
- /** Maximum number of items to return in one page. */
1151
- limit?: number;
1152
- /** Opaque cursor from the previous page's response. */
1153
- cursor?: string;
1154
- }
1155
1355
  /**
1156
1356
  * What a caller may change on an existing deployment.
1157
1357
  *
@@ -1569,8 +1769,13 @@ interface DeployBodyContext {
1569
1769
  * `LABEL_CONSTRAINTS` (length and pattern, lowercased+trimmed).
1570
1770
  */
1571
1771
  labels?: string[];
1572
- /** Client identifier (`cli`, `sdk`, `web`). */
1573
- via?: string;
1772
+ /**
1773
+ * Which client is deploying — the same closed vocabulary the public option
1774
+ * carries, not a second `string`. This context receives an already-narrowed
1775
+ * value and passed it on widened, which made the narrowing stop one seam
1776
+ * short of the wire.
1777
+ */
1778
+ via?: DeploymentViaType;
1574
1779
  /**
1575
1780
  * Optional plaintext password to protect the deployment.
1576
1781
  * Length: `PASSWORD_CONSTRAINTS.MIN_LENGTH` to `PASSWORD_CONSTRAINTS.MAX_LENGTH`
@@ -2238,4 +2443,4 @@ declare class Ship extends Ship$1 {
2238
2443
  protected getDeployBodyCreator(): DeployBodyCreator;
2239
2444
  }
2240
2445
 
2241
- export { API_KEY, API_PATHS, AUTH_BASE_PATH, type Account, type AccountDeleteResponse, type AccountGetResponse, type AccountKeyResponse, type AccountOverrides, AccountPlan, type AccountPlanType, type AccountResource, type AccountUsage, type Activity, type ActivityEvent, type ActivityListResponse, type ActivityMeta, type ApiDeployOptions, ApiHttp, type ApiHttpOptions, AuthMethod, type AuthMethodType, BLOCKED_EXTENSIONS, type BillingCancelResponse, type BillingStatus, CALLER, type CheckoutSession, DEFAULT_API, DEPLOYMENT_CONFIG_FILENAME, DEPLOY_TOKEN, type DeployBody, type DeployBodyContext, type DeployBodyCreator, type DeployFile, type DeployInput, type Deployment, type DeploymentCreateResponse, type DeploymentDeleteResponse, type DeploymentListResponse, type DeploymentOptions, type DeploymentResource, type DeploymentResourceContext, type DeploymentSetOptions, DeploymentStatus, type DeploymentStatusType, type DeploymentUploadOptions, type DnsLookup, type DnsProvider, type DnsRecord, type DnsRecordType, type Domain, type DomainDeleteResponse, type DomainDnsResponse, type DomainListResponse, type DomainRecordsResponse, type DomainResource, type DomainSetOptions, type DomainSetResult, type DomainShareResponse, DomainStatus, type DomainStatusType, type DomainValidateResponse, type DomainVerifyResponse, type ErrorResponse, ErrorType, type ExecutionEnvironment, FileValidationStatus as FILE_VALIDATION_STATUS, type Fetch, type FileValidationResult, FileValidationStatus, type FileValidationStatusType, IDEMPOTENCY_KEY_CONSTRAINTS, JUNK_DIRECTORIES, LABEL_CONSTRAINTS, LABEL_PATTERN, type LabelsResponse, type ListOptions, type ListResponse, type MD5Result, OAuthScope, type OAuthScopeType, PASSWORD_CONSTRAINTS, type PingResponse, type PlatformLimits, type ResourceContext, type SPACheckDebug, type SPACheckRequest, type SPACheckResponse, SPA_DEFAULT_CONFIG, type SetupInstructionsResponse, Ship, type ShipClientOptions, ShipError, type ShipEvents, type StaticFile, type Token, type TokenCreateOptions, type TokenCreateResponse, type TokenDeleteResponse, TokenKind, type TokenKindType, type TokenListResponse, type TokenProvider, type TokenResource, UNBUILT_PROJECT_MARKERS, UNSAFE_FILENAME_CHARS, type UploadedFile, type UserVisibleActivityEvent, type ValidatableFile, type ValidationIssue, __setTestEnvironment, allValidFilesReady, assertShipJsonSyntax, calculateMD5, classifyToken, createAccountResource, createDeploymentResource, createDomainResource, createTokenResource, Ship as default, deserializeLabels, extractSubdomain, filterJunk, formatFileSize, generateDeploymentUrl, generateDomainUrl, getENV, getValidFiles, hasUnbuiltMarker, hasUnsafeChars, isBlockedExtension, isCustomDomain, isDeployment, isPlatformDomain, isShipError, optimizeDeployPaths, pluralize, processFilesForBrowser, serializeLabels, validateApiKey, validateApiUrl, validateCaller, validateDeployFile, validateDeployPath, validateDeployToken, validateFileName, validateFiles, validateIdempotencyKey, validatePassword, validateToken };
2446
+ export { API_KEY, API_PATHS, AUTH_BASE_PATH, type Account, type AccountDeleteResponse, type AccountGetResponse, type AccountKeyResponse, type AccountOverrides, AccountPlan, type AccountPlanType, type AccountResource, type AccountUsage, type Activity, type ActivityEvent, type ActivityListResponse, type ActivityMeta, type ApiDeployOptions, ApiHttp, type ApiHttpOptions, AuthMethod, type AuthMethodType, BLOCKED_EXTENSIONS, type BillingCancelResponse, type BillingStatus, CALLER, type CheckoutSession, DEFAULT_API, DEPLOYMENT_CONFIG_FILENAME, DEPLOY_FIELDS, DEPLOY_TOKEN, type DeployBody, type DeployBodyContext, type DeployBodyCreator, type DeployFile, type DeployInput, type Deployment, type DeploymentCreateResponse, type DeploymentDeleteResponse, type DeploymentListResponse, type DeploymentOptions, type DeploymentResource, type DeploymentResourceContext, type DeploymentSetOptions, DeploymentStatus, type DeploymentStatusType, type DeploymentUploadOptions, DeploymentVia, type DeploymentViaType, type DnsLookup, type DnsProvider, type DnsRecord, type DnsRecordType, type Domain, type DomainDeleteResponse, type DomainDnsResponse, type DomainListResponse, type DomainRecordsResponse, type DomainResource, type DomainSetOptions, type DomainSetResult, type DomainShareResponse, DomainStatus, type DomainStatusType, type DomainValidateResponse, type DomainVerifyResponse, type ErrorResponse, ErrorType, type ExecutionEnvironment, FileValidationStatus as FILE_VALIDATION_STATUS, type Fetch, type FileValidationResult, FileValidationStatus, type FileValidationStatusType, IDEMPOTENCY_KEY_CONSTRAINTS, JUNK_DIRECTORIES, LABEL_CONSTRAINTS, LABEL_PATTERN, type LabelsResponse, type ListOptions, type ListResponse, type MD5Result, MY_API_KEY_URL, OAuthScope, type OAuthScopeType, PASSWORD_CONSTRAINTS, PUBLIC_DEPLOYMENT_TTL_SECONDS, type PingResponse, type PlatformLimits, type ResourceContext, SHIP_ENV, type SPACheckDebug, type SPACheckRequest, type SPACheckResponse, SPA_CHECK_CONSTRAINTS, SPA_DEFAULT_CONFIG, type SetupInstructionsResponse, Ship, type ShipClientOptions, ShipError, type ShipEvents, type StaticFile, type Token, type TokenCreateOptions, type TokenCreateResponse, type TokenDeleteResponse, TokenKind, type TokenKindType, type TokenListResponse, type TokenProvider, type TokenResource, UNBUILT_PROJECT_MARKERS, UNSAFE_FILENAME_CHARS, type UploadedFile, type UserVisibleActivityEvent, type ValidatableFile, type ValidationIssue, WEB_FILE_ACCEPT, __setTestEnvironment, allValidFilesReady, assertShipJsonSyntax, calculateMD5, classifyToken, createAccountResource, createDeploymentResource, createDomainResource, createTokenResource, Ship as default, deserializeLabels, extractSubdomain, filterJunk, formatFileSize, generateDeploymentUrl, generateDomainUrl, getENV, getValidFiles, hasUnbuiltMarker, hasUnsafeChars, isBlockedExtension, isCustomDomain, isDeployment, isPlatformDomain, isShipError, normalizeVia, optimizeDeployPaths, pluralize, processFilesForBrowser, serializeLabels, validateApiKey, validateApiUrl, validateCaller, validateDeployFile, validateDeployPath, validateDeployToken, validateFileName, validateFiles, validateIdempotencyKey, validatePassword, validateToken };