@spacefast/common 0.0.5 → 0.0.7

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.
Files changed (183) hide show
  1. package/brand-assets/spacefast-favicon.svg +16 -0
  2. package/brand-assets/spacefast-sf-full-bleed.svg +13 -0
  3. package/brand-assets/spacefast-wordmark.svg +5 -0
  4. package/dist/agents/connect-targets.d.ts +138 -0
  5. package/dist/agents/connect-targets.js +243 -0
  6. package/dist/brand-assets-build.d.ts +26 -0
  7. package/dist/brand-assets-build.js +81 -0
  8. package/dist/brand-assets.d.ts +42 -0
  9. package/dist/brand-assets.js +30 -0
  10. package/dist/brand.d.ts +1 -0
  11. package/dist/brand.js +5 -0
  12. package/dist/config/domains.d.ts +12 -6
  13. package/dist/config/domains.js +30 -38
  14. package/dist/contracts/access.d.ts +967 -124
  15. package/dist/contracts/access.js +804 -148
  16. package/dist/contracts/activity.d.ts +1 -1
  17. package/dist/contracts/activity.js +28 -2
  18. package/dist/contracts/annotations.d.ts +2 -4
  19. package/dist/contracts/api-keys.d.ts +126 -4
  20. package/dist/contracts/api-keys.js +74 -6
  21. package/dist/contracts/archives.d.ts +8 -2
  22. package/dist/contracts/archives.js +11 -4
  23. package/dist/contracts/billing.d.ts +0 -9
  24. package/dist/contracts/builds.d.ts +65 -21
  25. package/dist/contracts/builds.js +29 -0
  26. package/dist/contracts/common.d.ts +2 -0
  27. package/dist/contracts/common.js +4 -0
  28. package/dist/contracts/continuation.d.ts +0 -3
  29. package/dist/contracts/countries.d.ts +2 -0
  30. package/dist/contracts/countries.js +261 -0
  31. package/dist/contracts/deployments.d.ts +34 -34
  32. package/dist/contracts/device-auth.d.ts +4 -11
  33. package/dist/contracts/device-auth.js +4 -0
  34. package/dist/contracts/docs.d.ts +119 -0
  35. package/dist/contracts/docs.js +51 -0
  36. package/dist/contracts/domains.d.ts +96 -54
  37. package/dist/contracts/domains.js +26 -4
  38. package/dist/contracts/enums.d.ts +15 -16
  39. package/dist/contracts/enums.js +26 -14
  40. package/dist/contracts/error-code-meta.d.ts +184 -0
  41. package/dist/contracts/error-code-meta.js +124 -10
  42. package/dist/contracts/error-codes.d.ts +1 -1
  43. package/dist/contracts/error-codes.js +42 -0
  44. package/dist/contracts/events.d.ts +1 -1
  45. package/dist/contracts/feature-lifecycle-core.d.ts +36 -0
  46. package/dist/contracts/feature-lifecycle-core.js +220 -0
  47. package/dist/contracts/feature-lifecycle.d.ts +7 -0
  48. package/dist/contracts/feature-lifecycle.js +31 -0
  49. package/dist/contracts/features.d.ts +76 -6
  50. package/dist/contracts/features.js +178 -13
  51. package/dist/contracts/generated-feature-launch-entries.d.ts +2 -0
  52. package/dist/contracts/generated-feature-launch-entries.js +518 -0
  53. package/dist/contracts/git.d.ts +2 -8
  54. package/dist/contracts/ids.d.ts +9 -0
  55. package/dist/contracts/ids.js +13 -0
  56. package/dist/contracts/mcp.d.ts +8 -10
  57. package/dist/contracts/me.js +1 -1
  58. package/dist/contracts/oauth-resources.d.ts +19 -8
  59. package/dist/contracts/oauth-resources.js +44 -13
  60. package/dist/contracts/operations.d.ts +161 -1
  61. package/dist/contracts/operations.js +49 -1
  62. package/dist/contracts/platform.d.ts +6 -102
  63. package/dist/contracts/platform.js +6 -50
  64. package/dist/contracts/publishes.d.ts +130 -0
  65. package/dist/contracts/publishes.js +145 -0
  66. package/dist/contracts/push-new.d.ts +4 -4
  67. package/dist/contracts/quotas.d.ts +2 -0
  68. package/dist/contracts/quotas.js +8 -0
  69. package/dist/contracts/repository-connections.d.ts +9 -8
  70. package/dist/contracts/repository-connections.js +13 -0
  71. package/dist/contracts/resources.d.ts +99 -13
  72. package/dist/contracts/resources.js +36 -4
  73. package/dist/contracts/routes.d.ts +0 -2
  74. package/dist/contracts/runtime-api.d.ts +363 -39
  75. package/dist/contracts/runtime-api.js +276 -6
  76. package/dist/contracts/sf-config-v1.d.ts +75 -0
  77. package/dist/contracts/sf-config-v1.js +6 -0
  78. package/dist/contracts/sites.d.ts +19 -2
  79. package/dist/contracts/sites.js +21 -0
  80. package/dist/contracts/space-config.d.ts +201 -2
  81. package/dist/contracts/space-config.js +135 -5
  82. package/dist/contracts/spaces.d.ts +376 -196
  83. package/dist/contracts/spaces.js +62 -10
  84. package/dist/contracts/superadmin-emails.d.ts +4 -2
  85. package/dist/contracts/superadmin-emails.js +8 -0
  86. package/dist/contracts/superadmin-queues.d.ts +26 -26
  87. package/dist/contracts/superadmin-spaces.d.ts +172 -42
  88. package/dist/contracts/superadmin-tenants.d.ts +6 -6
  89. package/dist/contracts/superadmin-tenants.js +6 -11
  90. package/dist/contracts/superadmin.d.ts +6 -93
  91. package/dist/contracts/superadmin.js +6 -14
  92. package/dist/contracts/tags.d.ts +10 -9
  93. package/dist/contracts/tags.js +2 -1
  94. package/dist/contracts/teams.d.ts +26 -30
  95. package/dist/contracts/teams.js +11 -17
  96. package/dist/contracts/theme-json.d.ts +30 -0
  97. package/dist/contracts/theme-json.js +48 -0
  98. package/dist/contracts/variables.d.ts +0 -2
  99. package/dist/contracts/variables.js +0 -5
  100. package/dist/contracts/webhooks.d.ts +2 -0
  101. package/dist/contracts/webhooks.js +1 -1
  102. package/dist/contracts/zero.d.ts +5 -10
  103. package/dist/docs/agent-prose.d.ts +43 -3
  104. package/dist/docs/agent-prose.js +146 -7
  105. package/dist/docs/agent-setup.d.ts +51 -0
  106. package/dist/docs/agent-setup.js +211 -14
  107. package/dist/docs/agent-solutions.d.ts +5 -5
  108. package/dist/docs/agent-solutions.js +2 -2
  109. package/dist/docs/catalog.d.ts +650 -0
  110. package/dist/docs/catalog.js +758 -0
  111. package/dist/docs/error-docs.d.ts +0 -2
  112. package/dist/docs/error-docs.js +189 -35
  113. package/dist/docs/index-build.d.ts +17 -0
  114. package/dist/docs/index-build.js +24 -0
  115. package/dist/docs/search.d.ts +54 -0
  116. package/dist/docs/search.js +96 -0
  117. package/dist/docs/skill-distribution.d.ts +5 -3
  118. package/dist/docs/skill-distribution.js +8 -8
  119. package/dist/slug-policy/blocklist.d.ts +4 -0
  120. package/dist/slug-policy/blocklist.js +122 -0
  121. package/dist/slug-policy/index.d.ts +17 -0
  122. package/dist/slug-policy/index.js +86 -0
  123. package/dist/utils/access-match.d.ts +39 -0
  124. package/dist/utils/access-match.js +296 -0
  125. package/dist/utils/auth-redirect.d.ts +3 -1
  126. package/dist/utils/auth-redirect.js +21 -1
  127. package/dist/utils/build-settings.d.ts +6 -0
  128. package/dist/utils/build-settings.js +429 -46
  129. package/dist/utils/claim-token.d.ts +8 -0
  130. package/dist/utils/claim-token.js +23 -2
  131. package/dist/utils/concurrency.d.ts +1 -0
  132. package/dist/utils/concurrency.js +22 -0
  133. package/dist/utils/content-type.d.ts +1 -0
  134. package/dist/utils/content-type.js +1 -1
  135. package/dist/utils/credential-policy.d.ts +54 -0
  136. package/dist/utils/{access-policy.js → credential-policy.js} +28 -2
  137. package/dist/utils/email.d.ts +1 -0
  138. package/dist/utils/email.js +3 -0
  139. package/dist/utils/gate-theme.d.ts +61 -0
  140. package/dist/utils/gate-theme.js +217 -0
  141. package/dist/utils/git-repository.d.ts +0 -1
  142. package/dist/utils/git-repository.js +0 -3
  143. package/dist/utils/id-hints.d.ts +8 -0
  144. package/dist/utils/id-hints.js +61 -0
  145. package/dist/utils/local-space-state.d.ts +91 -0
  146. package/dist/utils/local-space-state.js +251 -0
  147. package/dist/utils/oauth-signed-query.d.ts +8 -0
  148. package/dist/utils/oauth-signed-query.js +26 -0
  149. package/dist/utils/publish-detection.d.ts +0 -8
  150. package/dist/utils/publish-detection.js +0 -93
  151. package/dist/utils/publish-policy.d.ts +6 -5
  152. package/dist/utils/publish-policy.js +71 -7
  153. package/dist/utils/query-keys.d.ts +16 -4
  154. package/dist/utils/query-keys.js +31 -4
  155. package/dist/utils/runtime-paths.d.ts +0 -1
  156. package/dist/utils/runtime-paths.js +0 -1
  157. package/dist/utils/runtime-upload-batch.d.ts +18 -0
  158. package/dist/utils/runtime-upload-batch.js +90 -0
  159. package/dist/utils/runtime-upload.d.ts +2 -0
  160. package/dist/utils/runtime-upload.js +5 -0
  161. package/dist/utils/sf-config-v1.d.ts +2 -0
  162. package/dist/utils/sf-config-v1.js +542 -0
  163. package/dist/utils/space-config.d.ts +4 -0
  164. package/dist/utils/space-config.js +10 -1
  165. package/dist/utils/space-theme.d.ts +2 -0
  166. package/dist/utils/space-theme.js +9 -0
  167. package/dist/utils/static-runtime-policy.d.ts +0 -21
  168. package/dist/utils/static-runtime-policy.js +19 -103
  169. package/dist/utils/upload-session.d.ts +1 -1
  170. package/dist/utils/wpcom-auth-redirects.d.ts +32 -2
  171. package/dist/utils/wpcom-auth-redirects.js +26 -10
  172. package/dist/vocabulary.d.ts +130 -30
  173. package/dist/vocabulary.js +77 -36
  174. package/package.json +35 -1
  175. package/dist/contracts/account.d.ts +0 -28
  176. package/dist/contracts/account.js +0 -54
  177. package/dist/contracts/intercom.d.ts +0 -6
  178. package/dist/contracts/intercom.js +0 -5
  179. package/dist/contracts/telemetry.d.ts +0 -2
  180. package/dist/contracts/telemetry.js +0 -1
  181. package/dist/utils/access-policy.d.ts +0 -39
  182. package/dist/utils/error-display.d.ts +0 -7
  183. package/dist/utils/error-display.js +0 -42
@@ -46,19 +46,17 @@ export const PLATFORM_MANAGED_RESPONSE_HEADERS = [
46
46
  export const NON_IMMUTABLE_EXTENSIONS = ["html", "htm", "php", "txt", "xml"];
47
47
  const PHP_LIKE_EXTENSION_SET = new Set(PHP_LIKE_EXTENSIONS);
48
48
  const NON_IMMUTABLE_EXTENSION_SET = new Set(NON_IMMUTABLE_EXTENSIONS);
49
- // Two real bundler content-hash shapes. (A) lowercase hex (git/webpack short
50
- // SHAs, many tools): >=7 chars of [0-9a-f]. (B) base64url (Vite/Rollup/esbuild/
51
- // Next): >=8 chars of [A-Za-z0-9_-]. Both forms are recognized; the entropy
52
- // guards in looksLikeBundlerFingerprint are what keep human names out.
53
- const HEX_FINGERPRINT = /^[0-9a-f]{7,}$/;
54
- const BASE64URL_FINGERPRINT = /^[A-Za-z0-9_-]{8,}$/;
55
- // Durable path-convention layer over the filename-entropy heuristic
56
- // (looksLikeBundlerFingerprint). These are reserved build-output directories that the
57
- // framework OWNS and fills exclusively with content-addressed ([contenthash]) output by
58
- // construction so every asset served from under them is safe to pin immutable for a
59
- // year regardless of how its individual filename reads. This recovers the real hashes the
60
- // filename heuristic conservatively MISSES (no-digit / single-case base64url draws like
61
- // `oton0Guf`, `DYyuCeEr`) without loosening the heuristic itself.
49
+ // Immutable caching is decided by DETERMINISTIC signals only reserved framework
50
+ // build directories below, plus user-declared `_headers` Cache-Control rules that
51
+ // override the baked default at serve time. There is deliberately NO filename-entropy
52
+ // heuristic: an 8-char base64url bundler hash is character-for-character
53
+ // indistinguishable from a short human name (D3GeoMap, McKayCV2), so any guess either
54
+ // pins a user's mutable file for a year (stale site nobody can fix — browsers have no
55
+ // bust channel) or silently downgrades real hashes to revalidate-every-load, which
56
+ // under the wp.cloud edge per-IP rate limiter turned dashboard page loads into 429
57
+ // storms (2026-07-06). Unrecognized files default to the safe direction:
58
+ // `max-age=0, must-revalidate` for browsers, long s-maxage + purge-on-publish at the
59
+ // edge.
62
60
  //
63
61
  // Each prefix is HIGH-CONFIDENCE guaranteed-hashed:
64
62
  // - `_next/static/` Next.js immutable build assets (chunks/css/media), all
@@ -71,9 +69,10 @@ const BASE64URL_FINGERPRINT = /^[A-Za-z0-9_-]{8,}$/;
71
69
  //
72
70
  // DELIBERATELY EXCLUDED: generic user-writable dirs (Vite's `assets/`, CRA/Gatsby's
73
71
  // `static/`). Users drop NON-hashed files there, so widening to them would create
74
- // year-long false positives. Those stay gated by the filename heuristic only. This layer
75
- // is purely ADDITIVE: it can only turn a heuristic-`false` into `true` for assets under a
76
- // guaranteed-hashed dir; it never flips a `true` to `false`.
72
+ // year-long false positives. Deployments of bundler output declare those dirs immutable
73
+ // themselves via a `_headers` rule (e.g. `/assets/*` `Cache-Control: public,
74
+ // max-age=31536000, immutable`), which the publisher a build we ran, an agent, a CI
75
+ // deploy — writes because it KNOWS the output is content-hashed.
77
76
  export const GUARANTEED_IMMUTABLE_PREFIXES = [
78
77
  "_next/static/",
79
78
  "_app/immutable/",
@@ -109,97 +108,14 @@ export function pathStem(filePath) {
109
108
  export function isPhpLikeStaticPath(filePath) {
110
109
  return PHP_LIKE_EXTENSION_SET.has(pathExtension(filePath));
111
110
  }
112
- /**
113
- * Classifies a candidate hash token — the final `.`/`-`-delimited tail of a
114
- * filename stem (see isImmutableAssetPath) — as a real bundler content hash.
115
- *
116
- * Immutable caching is a one-year pin, so the test is asymmetric on purpose: a
117
- * missed hash costs a cheap 304 revalidation, a false positive serves stale
118
- * content for a year. When a token is ambiguous we say no. The discriminator is
119
- * NOT character case — modern bundlers emit BOTH lowercase-hex and mixed-case
120
- * base64url hashes — it is entropy plus the absence of dictionary-word shape.
121
- *
122
- * IRREDUCIBLE LIMIT: an 8-char base64url token is the same size as a short
123
- * CamelCase-with-version-digit human name, and every default Vite/Rollup hash we
124
- * must accept carries only one digit (BzeDGMM3, 4A_iFExJ). So a token like
125
- * `D3GeoMap`, `McKayCV2`, or `v2-WebGL` is character-for-character indistinguishable
126
- * from a real hash and is accepted by clause B. No filename-only rule can separate
127
- * these without also rejecting the bundler hashes the spec mandates. The durable
128
- * fix is to gate immutable caching on path convention (Vite `assets/`, Next
129
- * `_next/static/`, SvelteKit `_app/immutable/`, …) rather than guessing from the
130
- * stem; this heuristic is the best-effort fallback when that context is absent.
131
- */
132
- export function looksLikeBundlerFingerprint(segment) {
133
- // (A) Lowercase-hex hash (git short-SHA, webpack/Vite/Gatsby `[contenthash]`):
134
- // a1b2c3d, 3f9a2b7e, d41d8cd98f00b204e980. Always require >=1 hex letter and
135
- // >=1 digit so all-letter words (deadbeef) and digit-only runs (1234567) are out.
136
- if (HEX_FINGERPRINT.test(segment) && /[a-f]/.test(segment) && /[0-9]/.test(segment)) {
137
- // A long all-hex token is unambiguous: no human authors a 16+ char hex name, so
138
- // length alone is entropy proof and the anti-word guards would only lose real
139
- // hashes (most 16/20-char contenthashes contain an incidental 4-run).
140
- if (segment.length >= 16) {
141
- return true;
142
- }
143
- // Short hex (7-15) overlaps with hex-spelled words/years/colors, so require >=2
144
- // digits and forbid a run of 4+ hex letters (deadbeef, cafe, face, decade) or
145
- // 4+ digits (2024, 1234) — low-entropy human false positives.
146
- if ((segment.match(/[0-9]/g) ?? []).length >= 2 &&
147
- !/[a-f]{4}/.test(segment) &&
148
- !/[0-9]{4}/.test(segment)) {
149
- return true;
150
- }
151
- }
152
- // (B) Base64url hash (Vite/Rollup/esbuild/Next): BzeDGMM3, 4A_iFExJ, P1Tx-Cyu,
153
- // Z-cBR0Xb, 9-ygPMte. Random base64url draws — but immutable is a one-year pin, so
154
- // against the human names that mimic this shape (acronym data/UI names, version
155
- // and fiscal labels, dates, resolutions) we demand the full BALANCED-entropy
156
- // signature of a real hash, and accept the safe cost of missing the minority of
157
- // genuine hashes that fall short (a cheap 304):
158
- // - >=2 lowercase AND >=2 uppercase letters. Real base64url hashes carry several
159
- // of each; acronym-dominant human names (USMap-AKHIORWA, Map-USA_CA_TX,
160
- // MyCSS2DUI) have at most one lowercase token and are rejected here. This also
161
- // conservatively drops single-case and no-/one-lowercase hashes (DYyuCeEr,
162
- // ChJ_j-JJ) — the safe miss direction.
163
- // - at least one digit. ~1/4 of base64url hashes lack one and become cheap 304s,
164
- // but requiring it removes the all-letter acronym names that are otherwise
165
- // indistinguishable from a hash (USMap-AKHIORWA has no digit).
166
- // - no run of 3+ lowercase letters: a dictionary-word body (MyComponent
167
- // "omponent", LoginOAuth2 "ogin", HTTPClient2 "lient"). A packed hash breaks
168
- // lowercase up with caps/digits.
169
- // - no run of 2+ digits: clustered digits are human years/resolutions/sequences/
170
- // dates (2024, 1080, Win10, 01); a hash scatters single digits between letters.
171
- // <8 chars (Widget2, API2, v2) never qualifies via the regex length floor.
172
- if (BASE64URL_FINGERPRINT.test(segment) &&
173
- (segment.match(/[a-z]/g) ?? []).length >= 2 &&
174
- (segment.match(/[A-Z]/g) ?? []).length >= 2 &&
175
- /[0-9]/.test(segment) &&
176
- !/[a-z]{3}/.test(segment) &&
177
- !/[0-9]{2}/.test(segment)) {
178
- return true;
179
- }
180
- return false;
181
- }
182
111
  export function isImmutableAssetPath(filePath) {
183
112
  if (NON_IMMUTABLE_EXTENSION_SET.has(pathExtension(filePath))) {
184
113
  return false;
185
114
  }
186
- // Path-convention layer (additive): anything under a guaranteed-content-hashed
187
- // framework build dir is immutable regardless of filename. The extension gate above
188
- // still applies, so an `index.html` accidentally under `_next/static/` is never pinned.
189
- if (isUnderGuaranteedImmutablePrefix(filePath)) {
190
- return true;
191
- }
192
- // Fallback for assets OUTSIDE those dirs: the filename-entropy heuristic.
193
- const stem = pathStem(filePath);
194
- for (let index = 0; index < stem.length; index += 1) {
195
- if (index > 0 && stem[index - 1] !== "." && stem[index - 1] !== "-") {
196
- continue;
197
- }
198
- if (looksLikeBundlerFingerprint(stem.slice(index))) {
199
- return true;
200
- }
201
- }
202
- return false;
115
+ // Path-convention layer: anything under a guaranteed-content-hashed framework build
116
+ // dir is immutable regardless of filename. The extension gate above still applies,
117
+ // so an `index.html` accidentally under `_next/static/` is never pinned.
118
+ return isUnderGuaranteedImmutablePrefix(filePath);
203
119
  }
204
120
  /** Forced inert serving policy for a committed static file. PHP-like files are never executable. */
205
121
  export function runtimeFileSafety(filePath) {
@@ -28,7 +28,7 @@ export declare function executeUploadSession(input: {
28
28
  session: UploadSession;
29
29
  targets?: readonly UploadTarget[] | undefined;
30
30
  concurrency?: number | undefined;
31
- fetch?: typeof fetch | undefined;
31
+ fetch?: ((...args: Parameters<typeof fetch>) => ReturnType<typeof fetch>) | undefined;
32
32
  prepareTarget?: ((target: UploadTarget) => Promise<UploadTargetBody> | UploadTargetBody) | undefined;
33
33
  onEvent?: ((event: UploadSessionEvent) => void) | undefined;
34
34
  }): Promise<void>;
@@ -1,11 +1,41 @@
1
1
  export type LoginIntentSearch = {
2
2
  direct?: boolean | undefined;
3
3
  returnTo?: string | undefined;
4
+ /** Route straight to WordPress.com's signup page instead of its login page. */
5
+ signup?: boolean | undefined;
4
6
  };
7
+ /**
8
+ * Optional query params WordPress.com's OAuth2 authorize endpoint understands
9
+ * (wpcom `wp-content/mu-plugins/oauth2/oauth2.php`). All are cosmetic/flow
10
+ * hints — dropping them never breaks the OAuth handshake.
11
+ */
12
+ export type WpcomAuthorizeExtras = {
13
+ /** Prefills the email field on the WP.com login screen (`user_email`). */
14
+ userEmail?: string | undefined;
15
+ /** Localizes the WP.com login/consent/signup screens (`locale`). */
16
+ locale?: string | undefined;
17
+ /**
18
+ * `implicit=false`: WP.com signs the user out of WordPress.com and shows a
19
+ * fresh login instead of silently reusing the current WP.com session. The
20
+ * only way to let a user switch WP.com accounts — without it, sign-out on
21
+ * our side loops them straight back into the same account.
22
+ */
23
+ switchAccount?: boolean | undefined;
24
+ /** `new-user=1`: routes straight to the WP.com signup page. */
25
+ signup?: boolean | undefined;
26
+ };
27
+ export declare function decorateWpcomAuthorizeUrl(authorizeUrl: string, extras?: WpcomAuthorizeExtras): string;
5
28
  export declare function buildDashboardLoginPath(input: LoginIntentSearch): string;
6
- export declare function buildWpcomAuthStartPath(input: LoginIntentSearch): string;
29
+ export declare function buildWpcomAuthStartPath(input: LoginIntentSearch & {
30
+ /**
31
+ * A signed OAuth authorize query (Better Auth's login-page redirect).
32
+ * Carried through the WordPress.com round trip so the auth server can
33
+ * resume the authorization flow the moment the session exists. Opaque
34
+ * here — the auth server verifies its signature.
35
+ */
36
+ oauthQuery?: string | undefined;
37
+ }): string;
7
38
  export declare function buildPostAuthPath(input: LoginIntentSearch): string;
8
- export declare function buildWpcomLoginUrl(authorizeUrl: string): string;
9
39
  export declare function buildWpcomSignInOptions(input: LoginIntentSearch, dashboardOrigin: string): {
10
40
  readonly provider: "wpcom";
11
41
  readonly callbackURL: string;
@@ -1,4 +1,24 @@
1
1
  import { sanitizeDashboardReturnTo } from "@spacefast/common/utils/auth-redirect";
2
+ export function decorateWpcomAuthorizeUrl(authorizeUrl, extras = {}) {
3
+ const url = new URL(authorizeUrl);
4
+ const email = extras.userEmail?.trim();
5
+ if (email && email.includes("@") && email.length <= 254) {
6
+ url.searchParams.set("user_email", email);
7
+ }
8
+ // WP.com validates locales against its own list and falls back to English,
9
+ // so a light shape check is enough here. English is the default — omit it.
10
+ const locale = extras.locale?.trim().toLowerCase().replace("_", "-");
11
+ if (locale && /^[a-z]{2}(-[a-z]{2})?$/.test(locale) && locale !== "en") {
12
+ url.searchParams.set("locale", locale);
13
+ }
14
+ if (extras.switchAccount) {
15
+ url.searchParams.set("implicit", "false");
16
+ }
17
+ if (extras.signup) {
18
+ url.searchParams.set("new-user", "1");
19
+ }
20
+ return url.toString();
21
+ }
2
22
  export function buildDashboardLoginPath(input) {
3
23
  const url = new URL("/sign-in", "http://dashboard.local");
4
24
  if (input.direct) {
@@ -16,6 +36,12 @@ export function buildWpcomAuthStartPath(input) {
16
36
  if (returnTo) {
17
37
  url.searchParams.set("returnTo", returnTo);
18
38
  }
39
+ if (input.signup) {
40
+ url.searchParams.set("signup", "1");
41
+ }
42
+ if (input.oauthQuery) {
43
+ url.searchParams.set("oauth_query", input.oauthQuery);
44
+ }
19
45
  return `${url.pathname}${url.search}`;
20
46
  }
21
47
  export function buildPostAuthPath(input) {
@@ -27,16 +53,6 @@ export function buildPostAuthPath(input) {
27
53
  }
28
54
  return `${url.pathname}${url.search}`;
29
55
  }
30
- export function buildWpcomLoginUrl(authorizeUrl) {
31
- const authUrl = new URL(authorizeUrl);
32
- const loginUrl = new URL("https://wordpress.com/log-in");
33
- const clientId = authUrl.searchParams.get("client_id");
34
- if (clientId) {
35
- loginUrl.searchParams.set("client_id", clientId);
36
- }
37
- loginUrl.searchParams.set("redirect_to", authUrl.toString());
38
- return loginUrl.toString();
39
- }
40
56
  export function buildWpcomSignInOptions(input, dashboardOrigin) {
41
57
  const errorCallbackUrl = new URL(buildDashboardLoginPath(input), dashboardOrigin);
42
58
  errorCallbackUrl.searchParams.set("claim_error", "login");
@@ -56,7 +56,7 @@ export declare const CONCEPTS: {
56
56
  readonly canonicalNoun: "version";
57
57
  readonly verb: "publish";
58
58
  readonly aliases: readonly [];
59
- readonly deprecated: readonly ["deployment"];
59
+ readonly externalTerms: readonly ["deployment"];
60
60
  readonly description: string;
61
61
  };
62
62
  readonly channel: {
@@ -133,7 +133,7 @@ export declare const EXTERNAL_TERM_ALLOWLIST: readonly [{
133
133
  readonly concept: "publish";
134
134
  readonly allowedContexts: readonly ["github-integration", "platform-api"];
135
135
  readonly replacement: "publish";
136
- readonly reason: "Push-deploys and the deploy status come from the GitHub integration.";
136
+ readonly reason: string;
137
137
  }, {
138
138
  readonly term: "site";
139
139
  readonly concept: "space";
@@ -148,6 +148,44 @@ export declare const EXTERNAL_TERM_ALLOWLIST: readonly [{
148
148
  readonly replacement: "infra";
149
149
  readonly reason: string;
150
150
  }];
151
+ /**
152
+ * AMENDMENT (DECIDED 2026-07-02): the deploy vocabulary stays, as documented,
153
+ * first-class ALIASES of the canonical vocabulary — `deploy` is an alias of
154
+ * `publish` (the verb) and `deployments` is an alias of `versions` (the noun).
155
+ * The canonical lifecycle nouns are unchanged: version / channel / build.
156
+ *
157
+ * This relaxes the earlier total noun ban to alias-with-disambiguation:
158
+ *
159
+ * - `sf deploy` / `sf deployments` stay listed and documented; every surface
160
+ * that presents them names the canonical command first and marks the
161
+ * deploy form as an alias.
162
+ * - Wherever the alias is introduced, attach DEPLOY_ALIAS_DISAMBIGUATION
163
+ * (the one sanctioned disambiguation sentence) so readers land on the
164
+ * canonical nouns.
165
+ * - A bare "deployment" noun in FRESH prose is still discouraged — write
166
+ * "version" (the thing) or "publish" (the act) unless deliberately using
167
+ * the alias, in which case disambiguate.
168
+ */
169
+ export interface DeployAlias {
170
+ /** The deploy-vocabulary word being sanctioned. */
171
+ readonly alias: string;
172
+ /** The canonical word it is an alias of. */
173
+ readonly canonical: string;
174
+ }
175
+ export declare const DEPLOY_ALIASES: readonly [{
176
+ readonly alias: "deploy";
177
+ readonly canonical: "publish";
178
+ }, {
179
+ readonly alias: "deployments";
180
+ readonly canonical: "versions";
181
+ }];
182
+ /**
183
+ * THE sanctioned disambiguation copy. Surfaces that present the deploy
184
+ * vocabulary (CLI --help, handbook/reference docs, generated skill docs) carry
185
+ * this sentence verbatim next to the alias, with the canonical command named
186
+ * first.
187
+ */
188
+ export declare const DEPLOY_ALIAS_DISAMBIGUATION: string;
151
189
  /** An internal provider name and how it must be referred to in public copy. */
152
190
  export interface ProviderHygieneRule {
153
191
  /** Internal/vendor name that must not appear on public surfaces. */
@@ -187,34 +225,11 @@ export declare const PROVIDER_HYGIENE: {
187
225
  readonly reason: "Internal git/CI dependency; never surfaced.";
188
226
  };
189
227
  };
190
- export type ProviderHygieneId = keyof typeof PROVIDER_HYGIENE;
191
228
  /**
192
- * Canonical MCP tool naming. Tools are exposed under a namespaced canonical id
193
- * (`spacefast_<verb>`) plus a bare alias (`<verb>`) for clients where per-server
194
- * namespacing is already guaranteed.
195
- *
196
- * This replaces the current inverted form (`execute_spacefast`,
197
- * `resume_spacefast`) and the un-namespaced verbs (`publish`, `prepare_publish`).
198
- */
199
- export declare const MCP_TOOL_NAMING: {
200
- /** Prefix that makes a tool id unambiguous across servers. */
201
- readonly namespacePrefix: "spacefast_";
202
- /** Canonical, namespaced tool id for a verb (e.g. "publish" → "spacefast_publish"). */
203
- readonly canonical: (verb: string) => string;
204
- /** Bare alias for a verb, valid only where per-server namespacing is guaranteed. */
205
- readonly alias: (verb: string) => string;
206
- /** Both names a verb should register under. */
207
- readonly names: (verb: string) => {
208
- readonly canonical: string;
209
- readonly alias: string;
210
- };
211
- };
212
- /**
213
- * Verbs that have a locked canonical MCP tool name. Generators should emit
214
- * `spacefast_<verb>` as the primary id and `<verb>` as the bare alias.
229
+ * Verbs that have a locked canonical MCP tool name. Generators emit the bare
230
+ * `<verb>` as the one and only tool id.
215
231
  */
216
- export declare const MCP_CANONICAL_VERBS: readonly ["publish", "execute", "prepare_publish", "resume_publish", "finalize_publish"];
217
- export type McpCanonicalVerb = (typeof MCP_CANONICAL_VERBS)[number];
232
+ export declare const MCP_CANONICAL_VERBS: readonly ["publish", "execute", "search", "resume", "prepare_publish", "resume_publish", "finalize_publish"];
218
233
  /**
219
234
  * Canonical CLI verb → accepted aliases. Reused across command groups so every
220
235
  * `list`/`remove`/`create` command exposes the same shorthands. A command file
@@ -226,5 +241,90 @@ export declare const CLI_ALIASES: {
226
241
  readonly create: readonly ["add"];
227
242
  };
228
243
  export type CliCanonicalVerb = keyof typeof CLI_ALIASES;
229
- /** Aliases registered for a canonical CLI verb (empty for unknown verbs). */
230
- export declare function cliAliasesFor(verb: CliCanonicalVerb): readonly string[];
244
+ /**
245
+ * Disclosure tiers gate how much of a surface (CLI help, `sf docs`) shows by
246
+ * default, so neither humans nor agents are overwhelmed:
247
+ *
248
+ * - `essential` — the handful of commands/docs a new user or agent needs to
249
+ * make a first publish and manage a space. The default everywhere.
250
+ * - `full` — the complete product CLI surface (everything not hidden).
251
+ * - `reference` — absolutely everything, including hidden/advanced commands.
252
+ *
253
+ * Tiers are cumulative for ranked surfaces (`reference` ⊇ `full` ⊇ `essential`);
254
+ * see {@link tierVisibleAt}. This is THE source of truth for tier ids + labels.
255
+ */
256
+ export type CliDisclosureTier = "essential" | "full" | "reference";
257
+ /** Display metadata for one disclosure tier. */
258
+ export interface CliDisclosureTierMeta {
259
+ readonly id: CliDisclosureTier;
260
+ /** Human label for headers (`Essential commands`, etc.). */
261
+ readonly label: string;
262
+ /** One-line description of what the tier reveals. */
263
+ readonly description: string;
264
+ /** Flag that opts into this tier (absent for the default `essential`). */
265
+ readonly flag?: string;
266
+ }
267
+ export declare const CLI_DISCLOSURE_TIERS: {
268
+ readonly essential: {
269
+ readonly id: "essential";
270
+ readonly label: "Essential";
271
+ readonly description: "The handful of commands a new user or agent needs to publish and manage a space.";
272
+ };
273
+ readonly full: {
274
+ readonly id: "full";
275
+ readonly label: "Full";
276
+ readonly description: "Every command in the product CLI.";
277
+ readonly flag: "--full";
278
+ };
279
+ readonly reference: {
280
+ readonly id: "reference";
281
+ readonly label: "Reference";
282
+ readonly description: "Everything, including hidden and advanced commands.";
283
+ readonly flag: "--all";
284
+ };
285
+ };
286
+ /** Tiers ordered narrowest-first; index doubles as the cumulative rank. */
287
+ export declare const CLI_DISCLOSURE_TIER_ORDER: readonly ["essential", "full", "reference"];
288
+ /**
289
+ * Does a surface tagged `surfaceTier` show when viewing at `viewTier`? Ranked
290
+ * surfaces are cumulative: an `essential` doc shows at every tier, a `full` doc
291
+ * shows at `full`/`reference`, a `reference` doc only at `reference`.
292
+ */
293
+ export declare function tierVisibleAt(viewTier: CliDisclosureTier, surfaceTier: CliDisclosureTier): boolean;
294
+ /** One essential CLI verb and the registered command id it resolves to. */
295
+ export interface CliEssentialCommand {
296
+ /** The verb a new user reaches for. */
297
+ readonly verb: string;
298
+ /** The registered CLI command id (some live under a topic, e.g. `spaces:claim`). */
299
+ readonly commandId: string;
300
+ }
301
+ /**
302
+ * The first-publish path: the essential verbs `sf help` (and `sf docs`) surface
303
+ * by default. Everything else is FULL (shown with `--full`) or REFERENCE
304
+ * (hidden/advanced, shown with `--all`). Ordered for display.
305
+ */
306
+ export declare const CLI_ESSENTIAL_COMMANDS: readonly [{
307
+ readonly verb: "publish";
308
+ readonly commandId: "publish";
309
+ }, {
310
+ readonly verb: "status";
311
+ readonly commandId: "status";
312
+ }, {
313
+ readonly verb: "versions";
314
+ readonly commandId: "versions";
315
+ }, {
316
+ readonly verb: "rollback";
317
+ readonly commandId: "rollback";
318
+ }, {
319
+ readonly verb: "claim";
320
+ readonly commandId: "spaces:claim";
321
+ }, {
322
+ readonly verb: "login";
323
+ readonly commandId: "login";
324
+ }, {
325
+ readonly verb: "init";
326
+ readonly commandId: "init";
327
+ }, {
328
+ readonly verb: "docs";
329
+ readonly commandId: "docs";
330
+ }];
@@ -11,10 +11,12 @@
11
11
  // - `space` is the primitive noun on every user surface. `site` survives only
12
12
  // as the platform-API term for the shared-pool host a space is placed on
13
13
  // (see contracts/sites.ts, internal-docs/platform.md "Site").
14
- // - `publish` is the verb. "deployment" is NOT a product noun — it means
15
- // "publishing a version" and is retired from user-facing surfaces (drop the
16
- // `dep_*` framing). It stays only inside the GitHub integration and the
17
- // platform API (see EXTERNAL_TERM_ALLOWLIST).
14
+ // - `publish` is the verb. AMENDED 2026-07-02: `deploy` / `deployments` are
15
+ // first-class ALIASES of `publish` / `versions` (see DEPLOY_ALIASES) they
16
+ // stay listed and documented, always with the sanctioned disambiguation
17
+ // line (DEPLOY_ALIAS_DISAMBIGUATION) and with the canonical command named
18
+ // first. A bare "deployment" noun in fresh prose is still discouraged (say
19
+ // "version"), and the `dep_*` id framing stays retired.
18
20
  // - `version` / `channel` / `build` are the lifecycle nouns.
19
21
  // - `API key` — never "access token".
20
22
  // - `Collab` is the feature; `comment` is the unit.
@@ -40,17 +42,19 @@ export const CONCEPTS = {
40
42
  aliases: ["go live"],
41
43
  externalTerms: ["deployment", "deploy"],
42
44
  deprecated: ["dep_", "dep_build_", "dep_version_"],
43
- description: "The verb for making a version live. 'Deployment' is not a product noun " +
44
- "(it just means publishing a version); allowed only in GitHub + platform API.",
45
+ description: "The verb for making a version live. 'Deploy' is a sanctioned first-class " +
46
+ "alias of publish (see DEPLOY_ALIASES); introduce it with the " +
47
+ "disambiguation line, and keep fresh prose on 'publish'.",
45
48
  },
46
49
  version: {
47
50
  id: "version",
48
51
  canonicalNoun: "version",
49
52
  verb: "publish",
50
53
  aliases: [],
51
- deprecated: ["deployment"],
54
+ externalTerms: ["deployment"],
52
55
  description: "An immutable snapshot of a space's content. Publishing promotes a version " +
53
- "to a channel; the thing once mislabeled a 'deployment' is a version.",
56
+ "to a channel; a 'deployment' is a sanctioned alias for a version (see " +
57
+ "DEPLOY_ALIASES), though fresh prose still says 'version'.",
54
58
  },
55
59
  channel: {
56
60
  id: "channel",
@@ -109,14 +113,18 @@ export const EXTERNAL_TERM_ALLOWLIST = [
109
113
  allowedContexts: ["github-integration", "platform-api"],
110
114
  replacement: "version / publish",
111
115
  reason: "GitHub speaks 'deployment'; the platform API keeps it for contract " +
112
- "stability. Everywhere else say 'publish a version'.",
116
+ "stability. Elsewhere it is a sanctioned alias of 'version' when paired " +
117
+ "with DEPLOY_ALIAS_DISAMBIGUATION (see DEPLOY_ALIASES); bare fresh prose " +
118
+ "still says 'publish a version'.",
113
119
  },
114
120
  {
115
121
  term: "deploy",
116
122
  concept: "publish",
117
123
  allowedContexts: ["github-integration", "platform-api"],
118
124
  replacement: "publish",
119
- reason: "Push-deploys and the deploy status come from the GitHub integration.",
125
+ reason: "Push-deploys and the deploy status come from the GitHub integration; " +
126
+ "'deploy' is also a sanctioned first-class alias of 'publish' when paired " +
127
+ "with DEPLOY_ALIAS_DISAMBIGUATION (see DEPLOY_ALIASES).",
120
128
  },
121
129
  {
122
130
  term: "site",
@@ -136,6 +144,18 @@ export const EXTERNAL_TERM_ALLOWLIST = [
136
144
  "the provider; general copy says 'infra'.",
137
145
  },
138
146
  ];
147
+ export const DEPLOY_ALIASES = [
148
+ { alias: "deploy", canonical: "publish" },
149
+ { alias: "deployments", canonical: "versions" },
150
+ ];
151
+ /**
152
+ * THE sanctioned disambiguation copy. Surfaces that present the deploy
153
+ * vocabulary (CLI --help, handbook/reference docs, generated skill docs) carry
154
+ * this sentence verbatim next to the alias, with the canonical command named
155
+ * first.
156
+ */
157
+ export const DEPLOY_ALIAS_DISAMBIGUATION = "Deploying is publishing: a deployment is a version, and the canonical " +
158
+ "nouns are version, channel, and build.";
139
159
  export const PROVIDER_HYGIENE = {
140
160
  e2b: {
141
161
  internalName: "E2B",
@@ -168,33 +188,14 @@ export const PROVIDER_HYGIENE = {
168
188
  /* MCP tool naming */
169
189
  /* -------------------------------------------------------------------------- */
170
190
  /**
171
- * Canonical MCP tool naming. Tools are exposed under a namespaced canonical id
172
- * (`spacefast_<verb>`) plus a bare alias (`<verb>`) for clients where per-server
173
- * namespacing is already guaranteed.
174
- *
175
- * This replaces the current inverted form (`execute_spacefast`,
176
- * `resume_spacefast`) and the un-namespaced verbs (`publish`, `prepare_publish`).
177
- */
178
- export const MCP_TOOL_NAMING = {
179
- /** Prefix that makes a tool id unambiguous across servers. */
180
- namespacePrefix: "spacefast_",
181
- /** Canonical, namespaced tool id for a verb (e.g. "publish" → "spacefast_publish"). */
182
- canonical: (verb) => `spacefast_${verb}`,
183
- /** Bare alias for a verb, valid only where per-server namespacing is guaranteed. */
184
- alias: (verb) => verb,
185
- /** Both names a verb should register under. */
186
- names: (verb) => ({
187
- canonical: `spacefast_${verb}`,
188
- alias: verb,
189
- }),
190
- };
191
- /**
192
- * Verbs that have a locked canonical MCP tool name. Generators should emit
193
- * `spacefast_<verb>` as the primary id and `<verb>` as the bare alias.
191
+ * Verbs that have a locked canonical MCP tool name. Generators emit the bare
192
+ * `<verb>` as the one and only tool id.
194
193
  */
195
194
  export const MCP_CANONICAL_VERBS = [
196
195
  "publish",
197
196
  "execute",
197
+ "search",
198
+ "resume",
198
199
  "prepare_publish",
199
200
  "resume_publish",
200
201
  "finalize_publish",
@@ -212,7 +213,47 @@ export const CLI_ALIASES = {
212
213
  remove: ["rm", "delete"],
213
214
  create: ["add"],
214
215
  };
215
- /** Aliases registered for a canonical CLI verb (empty for unknown verbs). */
216
- export function cliAliasesFor(verb) {
217
- return CLI_ALIASES[verb];
216
+ export const CLI_DISCLOSURE_TIERS = {
217
+ essential: {
218
+ id: "essential",
219
+ label: "Essential",
220
+ description: "The handful of commands a new user or agent needs to publish and manage a space.",
221
+ },
222
+ full: {
223
+ id: "full",
224
+ label: "Full",
225
+ description: "Every command in the product CLI.",
226
+ flag: "--full",
227
+ },
228
+ reference: {
229
+ id: "reference",
230
+ label: "Reference",
231
+ description: "Everything, including hidden and advanced commands.",
232
+ flag: "--all",
233
+ },
234
+ };
235
+ /** Tiers ordered narrowest-first; index doubles as the cumulative rank. */
236
+ export const CLI_DISCLOSURE_TIER_ORDER = ["essential", "full", "reference"];
237
+ /**
238
+ * Does a surface tagged `surfaceTier` show when viewing at `viewTier`? Ranked
239
+ * surfaces are cumulative: an `essential` doc shows at every tier, a `full` doc
240
+ * shows at `full`/`reference`, a `reference` doc only at `reference`.
241
+ */
242
+ export function tierVisibleAt(viewTier, surfaceTier) {
243
+ return (CLI_DISCLOSURE_TIER_ORDER.indexOf(surfaceTier) <= CLI_DISCLOSURE_TIER_ORDER.indexOf(viewTier));
218
244
  }
245
+ /**
246
+ * The first-publish path: the essential verbs `sf help` (and `sf docs`) surface
247
+ * by default. Everything else is FULL (shown with `--full`) or REFERENCE
248
+ * (hidden/advanced, shown with `--all`). Ordered for display.
249
+ */
250
+ export const CLI_ESSENTIAL_COMMANDS = [
251
+ { verb: "publish", commandId: "publish" },
252
+ { verb: "status", commandId: "status" },
253
+ { verb: "versions", commandId: "versions" },
254
+ { verb: "rollback", commandId: "rollback" },
255
+ { verb: "claim", commandId: "spaces:claim" },
256
+ { verb: "login", commandId: "login" },
257
+ { verb: "init", commandId: "init" },
258
+ { verb: "docs", commandId: "docs" },
259
+ ];