@spacefast/common 0.0.13 → 0.0.18

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 (154) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +149 -44
  14. package/dist/contracts/access.js +94 -8
  15. package/dist/contracts/activity.js +5 -1
  16. package/dist/contracts/api-keys.js +9 -9
  17. package/dist/contracts/archives.js +11 -11
  18. package/dist/contracts/beta.js +1 -1
  19. package/dist/contracts/builds.js +14 -14
  20. package/dist/contracts/channels.js +2 -2
  21. package/dist/contracts/comments.d.ts +86 -15
  22. package/dist/contracts/comments.js +116 -20
  23. package/dist/contracts/common.d.ts +12 -12
  24. package/dist/contracts/common.js +33 -30
  25. package/dist/contracts/continuation.js +5 -5
  26. package/dist/contracts/data-source-routes.js +1 -1
  27. package/dist/contracts/device-auth.d.ts +6 -6
  28. package/dist/contracts/device-auth.js +12 -12
  29. package/dist/contracts/docs.d.ts +10 -10
  30. package/dist/contracts/domains.js +39 -33
  31. package/dist/contracts/enums.d.ts +2 -10
  32. package/dist/contracts/enums.js +0 -2
  33. package/dist/contracts/error-code-meta.d.ts +116 -47
  34. package/dist/contracts/error-code-meta.js +37 -16
  35. package/dist/contracts/error-codes.d.ts +7 -4
  36. package/dist/contracts/error-codes.js +46 -21
  37. package/dist/contracts/events.d.ts +10 -3
  38. package/dist/contracts/events.js +33 -5
  39. package/dist/contracts/execution.d.ts +26 -0
  40. package/dist/contracts/execution.js +21 -0
  41. package/dist/contracts/feature-lifecycle.d.ts +21 -24
  42. package/dist/contracts/feature-lifecycle.js +28 -26
  43. package/dist/contracts/features.js +4 -4
  44. package/dist/contracts/functions.d.ts +8 -140
  45. package/dist/contracts/functions.js +9 -19
  46. package/dist/contracts/git.d.ts +1 -1
  47. package/dist/contracts/grants.d.ts +96 -79
  48. package/dist/contracts/grants.js +130 -43
  49. package/dist/contracts/ids.d.ts +2 -2
  50. package/dist/contracts/ids.js +2 -4
  51. package/dist/contracts/internal.js +2 -2
  52. package/dist/contracts/mcp.d.ts +17 -556
  53. package/dist/contracts/mcp.js +9 -103
  54. package/dist/contracts/me.js +3 -4
  55. package/dist/contracts/notifications.d.ts +108 -0
  56. package/dist/contracts/notifications.js +54 -0
  57. package/dist/contracts/oauth-resources.d.ts +18 -1
  58. package/dist/contracts/oauth-resources.js +22 -2
  59. package/dist/contracts/operations.d.ts +2 -50
  60. package/dist/contracts/operations.js +0 -19
  61. package/dist/contracts/pages.d.ts +8 -4
  62. package/dist/contracts/pages.js +16 -3
  63. package/dist/contracts/platform.js +10 -8
  64. package/dist/contracts/problem-document.d.ts +14 -0
  65. package/dist/contracts/problem-document.js +35 -0
  66. package/dist/contracts/push-new.d.ts +21 -6
  67. package/dist/contracts/push-new.js +3 -7
  68. package/dist/contracts/resources.d.ts +8 -7
  69. package/dist/contracts/resources.js +9 -6
  70. package/dist/contracts/runtime-api.d.ts +162 -92
  71. package/dist/contracts/runtime-api.js +99 -27
  72. package/dist/contracts/runtime-app.d.ts +41 -11
  73. package/dist/contracts/runtime-app.js +22 -1
  74. package/dist/contracts/runtime-db.d.ts +424 -0
  75. package/dist/contracts/runtime-db.js +108 -0
  76. package/dist/contracts/runtime-storage.d.ts +120 -0
  77. package/dist/contracts/runtime-storage.js +40 -0
  78. package/dist/contracts/sf-config-v1.d.ts +49 -1
  79. package/dist/contracts/sf-config-v1.js +2 -2
  80. package/dist/contracts/sites.d.ts +1 -0
  81. package/dist/contracts/sites.js +20 -12
  82. package/dist/contracts/space-config.d.ts +62 -22
  83. package/dist/contracts/space-config.js +173 -47
  84. package/dist/contracts/spaces.d.ts +79 -45
  85. package/dist/contracts/spaces.js +66 -50
  86. package/dist/contracts/superadmin-spaces.d.ts +13 -2
  87. package/dist/contracts/superadmin-spaces.js +15 -0
  88. package/dist/contracts/superadmin-tenants.js +4 -4
  89. package/dist/contracts/superadmin.d.ts +4 -0
  90. package/dist/contracts/superadmin.js +14 -1
  91. package/dist/contracts/tags.d.ts +17 -17
  92. package/dist/contracts/tags.js +1 -1
  93. package/dist/contracts/teams.js +25 -16
  94. package/dist/contracts/theme-json.js +1 -1
  95. package/dist/contracts/transfers.js +4 -4
  96. package/dist/contracts/variables.js +3 -3
  97. package/dist/contracts/zero.d.ts +204 -338
  98. package/dist/contracts/zero.js +94 -98
  99. package/dist/dashboard-paths/index.d.ts +16 -1
  100. package/dist/dashboard-paths/index.js +18 -0
  101. package/dist/docs/agent-handoff-document.d.ts +2 -2
  102. package/dist/docs/agent-handoff-document.js +8 -7
  103. package/dist/docs/agent-prose.d.ts +22 -3
  104. package/dist/docs/agent-prose.js +221 -51
  105. package/dist/docs/agent-setup.d.ts +13 -10
  106. package/dist/docs/agent-setup.js +279 -53
  107. package/dist/docs/agent-solutions.d.ts +6 -6
  108. package/dist/docs/agent-solutions.js +23 -21
  109. package/dist/docs/catalog.d.ts +29 -74
  110. package/dist/docs/catalog.js +65 -92
  111. package/dist/docs/error-docs.js +293 -221
  112. package/dist/docs/skill-distribution.d.ts +2 -0
  113. package/dist/docs/skill-distribution.js +7 -4
  114. package/dist/docs/skills.d.ts +546 -0
  115. package/dist/docs/skills.js +340 -0
  116. package/dist/utils/browser-credential.d.ts +19 -0
  117. package/dist/utils/browser-credential.js +62 -0
  118. package/dist/utils/comment-avatars.d.ts +7 -0
  119. package/dist/utils/comment-avatars.js +42 -0
  120. package/dist/utils/content-type.d.ts +3 -3
  121. package/dist/utils/content-type.js +38 -61
  122. package/dist/utils/domain-diagnostics.d.ts +5 -5
  123. package/dist/utils/domain-diagnostics.js +21 -10
  124. package/dist/utils/local-space-state.d.ts +78 -4
  125. package/dist/utils/local-space-state.js +383 -82
  126. package/dist/utils/one-shot-replay.d.ts +14 -0
  127. package/dist/utils/one-shot-replay.js +21 -0
  128. package/dist/utils/page-preview.d.ts +7 -0
  129. package/dist/utils/page-preview.js +9 -0
  130. package/dist/utils/pages.d.ts +2 -11
  131. package/dist/utils/pages.js +58 -14
  132. package/dist/utils/publish-form-data.d.ts +22 -0
  133. package/dist/utils/publish-form-data.js +26 -0
  134. package/dist/utils/publish-policy.js +20 -4
  135. package/dist/utils/query-keys.d.ts +7 -4
  136. package/dist/utils/query-keys.js +13 -4
  137. package/dist/utils/runtime-paths.d.ts +3 -0
  138. package/dist/utils/runtime-paths.js +7 -0
  139. package/dist/utils/secure-local-file.d.ts +73 -0
  140. package/dist/utils/secure-local-file.js +1373 -0
  141. package/dist/utils/space-config.d.ts +13 -4
  142. package/dist/utils/space-config.js +123 -15
  143. package/dist/utils/storage-policy.d.ts +20 -0
  144. package/dist/utils/storage-policy.fixtures.json +174 -0
  145. package/dist/utils/storage-policy.js +193 -0
  146. package/dist/utils/upload-session.d.ts +12 -1
  147. package/dist/utils/upload-session.js +37 -2
  148. package/dist/utils/version-file-browser.d.ts +13 -0
  149. package/dist/utils/version-file-browser.js +59 -0
  150. package/package.json +19 -1
  151. package/dist/contracts/mounts.d.ts +0 -142
  152. package/dist/contracts/mounts.js +0 -56
  153. package/dist/utils/asset-fingerprint.d.ts +0 -64
  154. package/dist/utils/asset-fingerprint.js +0 -620
@@ -0,0 +1,340 @@
1
+ import { DOCS_BASE_URL } from "../config/domains.js";
2
+ const BOTH_RUNTIMES = ["local", "hosted"];
3
+ /** Canonical agent workflows shared by MCP tools, prompts, resources, and recipe docs. */
4
+ export const SPACEFAST_SKILLS = [
5
+ {
6
+ name: "create-and-publish-site",
7
+ title: "Create and publish a polished website",
8
+ description: "Turn a brief into a responsive static website, review the result, publish it to Spacefast, and verify the live version.",
9
+ whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.",
10
+ keywords: ["create", "website", "design", "build", "responsive", "launch", "publish"],
11
+ runtimes: BOTH_RUNTIMES,
12
+ tools: ["publish", "execute"],
13
+ docs: ["quickstart", "publishing"],
14
+ instructions: [
15
+ "Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.",
16
+ "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.",
17
+ "Build a focused, accessible static experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.",
18
+ "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.",
19
+ "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.",
20
+ "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions.",
21
+ ],
22
+ verification: [
23
+ "Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.",
24
+ "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success.",
25
+ ],
26
+ runtimeNotes: {
27
+ local: "On-Device can work directly inside the configured local workspace root and publish a local build path.",
28
+ hosted: "Hosted has no filesystem. Generate small files in the client and publish them inline, or use catalog manifest and import operations for larger artifacts.",
29
+ },
30
+ prompt: true,
31
+ recipe: {
32
+ slug: "recipes/create-and-publish-site",
33
+ tier: "full",
34
+ url: `${DOCS_BASE_URL}/getting-started/agents`,
35
+ },
36
+ },
37
+ {
38
+ name: "publish-and-verify",
39
+ title: "Publish and verify",
40
+ description: "Publish an existing static build, confirm the new version is actually live, and return the useful URLs without leaking credentials.",
41
+ whenToUse: "Use when the artifact already exists and the user wants it published or updated.",
42
+ keywords: ["publish", "deploy", "live", "verify", "version", "claim"],
43
+ runtimes: BOTH_RUNTIMES,
44
+ tools: ["publish", "status", "diagnose"],
45
+ docs: ["publishing", "workflow/publish-receipt", "workflow/secret-handling"],
46
+ instructions: [
47
+ "Identify the actual publish root or build output. Run the real build first when the source tree is not itself the static artifact.",
48
+ "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.",
49
+ "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics.",
50
+ ],
51
+ verification: [
52
+ "Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.",
53
+ "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof.",
54
+ ],
55
+ runtimeNotes: {
56
+ local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.",
57
+ hosted: "Publish inline files, or use catalog manifest and import operations for larger artifacts. Hosted has no filesystem.",
58
+ },
59
+ prompt: true,
60
+ recipe: {
61
+ slug: "recipes/agent-publish",
62
+ tier: "full",
63
+ url: `${DOCS_BASE_URL}/publishing/anonymous`,
64
+ },
65
+ },
66
+ {
67
+ name: "ci-github-actions",
68
+ title: "Publish from GitHub Actions",
69
+ description: "Create a repository-aware GitHub Actions workflow that builds and publishes with a masked Spacefast token.",
70
+ whenToUse: "Use when pushes or manual workflow runs should publish automatically from GitHub.",
71
+ keywords: ["github", "actions", "ci", "push", "automatic", "workflow"],
72
+ runtimes: BOTH_RUNTIMES,
73
+ tools: ["execute"],
74
+ docs: ["builds", "publishing", "workflow/secret-handling"],
75
+ instructions: [
76
+ "Inspect the repository's package manager, lockfile, build command, runtime version, output directory, and existing workflows before writing YAML.",
77
+ "Create a least-privilege workflow triggered by the intended branch and `workflow_dispatch`: check out the repository, install with the locked dependency command, build, and run `npx -y spacefast@<pinned-version> publish <output> --json`.",
78
+ "Store the credential as a masked `SPACEFAST_TOKEN` repository or environment secret. Never echo it or upload generated auth files, claim credentials, or upload credentials as artifacts.",
79
+ "Use the repository's real package manager and output directory; do not copy a generic npm/dist example over a Bun, pnpm, yarn, or custom build.",
80
+ ],
81
+ verification: [
82
+ "Validate the workflow syntax and run it on an explicit test branch or manual dispatch.",
83
+ "Inspect the publish receipt and live URL; a green build step alone is not serving proof.",
84
+ ],
85
+ prompt: true,
86
+ recipe: {
87
+ slug: "recipes/github-deploys",
88
+ tier: "full",
89
+ url: `${DOCS_BASE_URL}/publishing/git`,
90
+ },
91
+ },
92
+ {
93
+ name: "preview-channels",
94
+ title: "Preview changes safely",
95
+ description: "Publish branch or pull-request work to an isolated private preview and share it explicitly without touching production.",
96
+ whenToUse: "Use when reviewers need a realistic preview before a change goes live.",
97
+ keywords: ["preview", "channel", "branch", "pull request", "review", "private"],
98
+ runtimes: BOTH_RUNTIMES,
99
+ tools: ["publish", "status", "execute"],
100
+ docs: ["builds", "access-control"],
101
+ instructions: [
102
+ "Publish the branch through a connected repository or to an explicit non-production channel so the live production channel is unchanged.",
103
+ "Keep the preview private by default. Use an Open URL for the owner's browser or create the narrowest scoped Link needed for reviewers.",
104
+ "Label the preview with the branch, change, or pull request it represents and report what is intentionally out of scope.",
105
+ ],
106
+ verification: [
107
+ "Confirm the preview serves the expected version and production still points at its previous version.",
108
+ "Test the reviewer link in a signed-out context and revoke it when review is complete.",
109
+ ],
110
+ recipe: {
111
+ slug: "recipes/preview-channels",
112
+ tier: "full",
113
+ url: `${DOCS_BASE_URL}/publishing/git`,
114
+ },
115
+ },
116
+ {
117
+ name: "claim-flow",
118
+ title: "Claim an anonymous space",
119
+ description: "Attach an anonymous space to an account or team before expiry while keeping every claim credential in server or local custody.",
120
+ whenToUse: "Use after an anonymous publish returns a claim URL and expiry.",
121
+ keywords: ["claim", "anonymous", "ownership", "team", "continuation", "credential"],
122
+ runtimes: BOTH_RUNTIMES,
123
+ tools: ["execute", "publish"],
124
+ docs: ["anonymous-publish", "workflow/continue-after-claim", "workflow/secret-handling"],
125
+ instructions: [
126
+ "Return the claim URL and expiry to the user, never the raw claim credential. On-Device stores that credential in `.spacefast/state.json` and reuses it without returning it to the model.",
127
+ "When authenticated On-Device credentials are available, use the catalog `claim` operation with only `teamId` and the expected `spaceId`; otherwise let the user open the claim URL in their browser.",
128
+ "Publish again from On-Device after the browser claim. `publish` automatically exchanges the locally held claim credential once, persists the durable space-scoped credential, and retries without exposing either secret.",
129
+ ],
130
+ verification: [
131
+ "Confirm the space no longer has a claim expiry and appears under the intended account or team.",
132
+ "Publish again from the same local root or inspect the space through `execute` to verify durable access works without asking for a raw token.",
133
+ ],
134
+ prompt: true,
135
+ },
136
+ {
137
+ name: "rollback-safely",
138
+ title: "Roll back safely",
139
+ description: "Promote a known-good immutable version without rebuilding, then prove the live URL serves it.",
140
+ whenToUse: "Use when the current live version is broken and a prior healthy version is known or discoverable.",
141
+ keywords: ["rollback", "revert", "version", "incident", "restore"],
142
+ runtimes: BOTH_RUNTIMES,
143
+ tools: ["status", "rollback", "execute", "diagnose"],
144
+ docs: ["versions-and-rollback"],
145
+ instructions: [
146
+ "Inspect recent immutable versions and current status. Compare the current live version with the candidate using the catalog `versions` and `diff` operations.",
147
+ "Call `rollback` with the selected `versionId`. Rollback is additive and keeps later immutable versions available.",
148
+ "If promotion returns an operation that is still settling, poll the catalog `operation_status` operation until it is done instead of assuming success.",
149
+ ],
150
+ verification: [
151
+ "Call `status` and confirm the live pointer references the selected version.",
152
+ "Open the live URL and verify the failing path or behavior is restored; use `diagnose` if it is not.",
153
+ ],
154
+ prompt: true,
155
+ },
156
+ {
157
+ name: "set-up-custom-domain",
158
+ title: "Set up a custom domain",
159
+ description: "Attach a domain, provide the exact DNS records, and wait for HTTPS serving proof before reporting it live.",
160
+ whenToUse: "Use when the user owns a domain they want a Spacefast space to serve.",
161
+ keywords: ["domain", "dns", "tls", "https", "cname", "custom"],
162
+ runtimes: BOTH_RUNTIMES,
163
+ tools: ["execute"],
164
+ docs: ["custom-domains", "recipes/migrate-dns"],
165
+ instructions: [
166
+ "Add the domain with `sf domains add <domain>` or catalog operation `domains_add`; preserve the exact record values Spacefast returns.",
167
+ "Give the user those DNS records to create at their provider. Do not claim this MCP changed third-party DNS unless it actually has an authorized provider integration.",
168
+ "Keep the existing production hostname available during the cutover and avoid deleting old records until the new path is proven.",
169
+ ],
170
+ verification: [
171
+ "Run `sf domains check` or catalog operation `domains_check` until DNS and certificate state are ready.",
172
+ "Open the final custom-domain HTTPS URL and verify representative content before reporting it live.",
173
+ ],
174
+ prompt: true,
175
+ recipe: {
176
+ slug: "recipes/custom-domain",
177
+ tier: "full",
178
+ url: "https://spacefast.com/help/domains",
179
+ },
180
+ },
181
+ {
182
+ name: "redirects",
183
+ title: "Ship redirects with a version",
184
+ description: "Add redirect rules to the publish artifact so routing changes are versioned and roll back with the content.",
185
+ whenToUse: "Use for moved pages, canonical host paths, and intentional permanent or temporary redirects.",
186
+ keywords: ["redirect", "rewrite", "routing", "301", "302", "path"],
187
+ runtimes: BOTH_RUNTIMES,
188
+ tools: ["publish", "execute"],
189
+ docs: ["redirects"],
190
+ instructions: [
191
+ "Inspect existing routing rules, then add the smallest non-conflicting rules to `_redirects` at the publish root.",
192
+ "Choose temporary or permanent status deliberately, preserve path/query semantics where required, and avoid redirect loops or rules that shadow real assets.",
193
+ "Publish the redirect file as part of the same immutable version as the related content change.",
194
+ ],
195
+ verification: [
196
+ "Request representative old and new paths and confirm the exact status and Location header.",
197
+ "Verify the final target serves successfully and unrelated assets are not redirected.",
198
+ ],
199
+ recipe: {
200
+ slug: "recipes/redirects",
201
+ tier: "reference",
202
+ url: "https://spacefast.com/help/redirects",
203
+ },
204
+ },
205
+ {
206
+ name: "migrate-dns",
207
+ title: "Migrate DNS without avoidable downtime",
208
+ description: "Pre-stage a custom domain, prove certificate readiness, and cut traffic over with a reversible DNS change.",
209
+ whenToUse: "Use when an existing production domain must move to Spacefast.",
210
+ keywords: ["dns", "migrate", "cutover", "domain", "downtime", "certificate"],
211
+ runtimes: BOTH_RUNTIMES,
212
+ tools: ["execute"],
213
+ docs: ["custom-domains"],
214
+ instructions: [
215
+ "Inventory the current authoritative DNS records and lower TTL ahead of the cutover when the provider and timing allow it.",
216
+ "Add the domain to Spacefast and pre-stage every record it returns while preserving a working rollback path.",
217
+ "Cut over only after the Spacefast endpoint, expected content, and TLS readiness can be validated. Keep the previous origin available until propagation is complete.",
218
+ ],
219
+ verification: [
220
+ "Check resolution from more than one resolver and confirm the certificate matches the final hostname.",
221
+ "Open the public HTTPS URL, verify representative content, and monitor for old-origin traffic before removing rollback records.",
222
+ ],
223
+ recipe: {
224
+ slug: "recipes/migrate-dns",
225
+ tier: "reference",
226
+ url: "https://spacefast.com/help/domains",
227
+ },
228
+ },
229
+ {
230
+ name: "debug-a-failed-build",
231
+ title: "Debug a failed build",
232
+ description: "Use ranked diagnostics and real logs to find why a publish failed or stalled, fix it, and re-verify.",
233
+ whenToUse: "Use when a build, finalize, activation, or live serving step fails or stalls.",
234
+ keywords: ["debug", "failed", "build", "logs", "diagnose", "stalled"],
235
+ runtimes: BOTH_RUNTIMES,
236
+ tools: ["execute"],
237
+ docs: ["troubleshooting"],
238
+ instructions: [
239
+ "Use `execute` to find and call the catalog diagnostics operation for the space and optional version. Read its ranked findings, locations, error codes, fix hints, and docs links instead of guessing from a generic status.",
240
+ "Use the catalog `build_logs` operation for publish/finalize detail, runtime-log operations for serving errors, and version operations to compare with the last ready version.",
241
+ "Apply the smallest fix tied to the evidence, rebuild, and publish again. Follow the problem document's `type` URL for any referenced error code.",
242
+ ],
243
+ verification: [
244
+ "Re-run the relevant diagnostics or status operation through `execute` and confirm the new version reaches the intended live state.",
245
+ "Open the affected live path and verify the concrete failure no longer reproduces.",
246
+ ],
247
+ prompt: true,
248
+ },
249
+ {
250
+ name: "manage-access",
251
+ title: "Manage access",
252
+ description: "Create narrowly scoped Grants, People, and Links, then verify a concrete request without leaking credentials.",
253
+ whenToUse: "Use when a space or path must be made public, team-only, or shared with specific people or machines.",
254
+ keywords: ["access", "private", "public", "people", "links", "grant", "share"],
255
+ runtimes: BOTH_RUNTIMES,
256
+ tools: ["execute", "whoami"],
257
+ docs: ["access-control"],
258
+ instructions: [
259
+ "Inspect current Grants with `sf share` or the catalog `grants_list` operation and identify one explicit access intent. Anonymous spaces start Private.",
260
+ "Apply the narrowest suitable Public or Team Grant, People invitation, scoped Link/password, or machine/external-identity Grant. Keep resource scopes explicit.",
261
+ "Show newly created Link or machine secrets only to the requesting user. Never put credentials, claim credentials, or API keys in logs or committed files.",
262
+ ],
263
+ verification: [
264
+ "Use `sf share check --as public --path /docs` or the catalog `grants_check` operation for a representative request.",
265
+ "Test a new Link in the intended signed-out or recipient context and confirm unrelated resources remain inaccessible.",
266
+ ],
267
+ prompt: true,
268
+ },
269
+ ];
270
+ export const SPACEFAST_PROMPT_SKILLS = SPACEFAST_SKILLS.filter((skill) => "prompt" in skill && skill.prompt);
271
+ export const SPACEFAST_RECIPE_SKILLS = SPACEFAST_SKILLS.filter((skill) => "recipe" in skill);
272
+ export function findSpacefastSkill(name) {
273
+ const normalized = name.trim().toLowerCase();
274
+ return SPACEFAST_SKILLS.find((skill) => skill.name === normalized);
275
+ }
276
+ export function searchSpacefastSkills(query = "", limit = 5) {
277
+ const normalized = query.trim().toLowerCase();
278
+ const boundedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(10, Math.trunc(limit))) : 5;
279
+ if (!normalized)
280
+ return SPACEFAST_SKILLS.slice(0, boundedLimit);
281
+ const tokens = normalized.split(/\s+/).filter(Boolean);
282
+ return SPACEFAST_SKILLS.map((skill) => {
283
+ const name = skill.name.toLowerCase();
284
+ const title = skill.title.toLowerCase();
285
+ const keywords = skill.keywords.map((candidate) => candidate.toLowerCase());
286
+ const body = [skill.description, skill.whenToUse, ...skill.docs].join(" ").toLowerCase();
287
+ let score = skill.name === normalized ? 100 : 0;
288
+ for (const token of tokens) {
289
+ let tokenScore = 0;
290
+ if (name === token)
291
+ tokenScore = 30;
292
+ else if (name.includes(token))
293
+ tokenScore = 20;
294
+ if (title.includes(token))
295
+ tokenScore = Math.max(tokenScore, 15);
296
+ if (keywords.some((candidate) => candidate.includes(token))) {
297
+ tokenScore = Math.max(tokenScore, 10);
298
+ }
299
+ if (body.includes(token))
300
+ tokenScore = Math.max(tokenScore, 5);
301
+ if (tokenScore === 0)
302
+ return { skill, score: 0 };
303
+ score += tokenScore;
304
+ }
305
+ return { skill, score };
306
+ })
307
+ .filter((match) => match.score > 0)
308
+ .toSorted((left, right) => right.score - left.score || left.skill.name.localeCompare(right.skill.name))
309
+ .slice(0, boundedLimit)
310
+ .map((match) => match.skill);
311
+ }
312
+ export function renderSpacefastSkill(skillOrName, runtime) {
313
+ const skill = typeof skillOrName === "string" ? findSpacefastSkill(skillOrName) : skillOrName;
314
+ if (!skill) {
315
+ const requestedName = typeof skillOrName === "string" ? skillOrName : skillOrName.name;
316
+ throw new Error(`Unknown Spacefast skill: ${requestedName}`);
317
+ }
318
+ const runtimeNote = skill.runtimeNotes?.[runtime];
319
+ return [
320
+ `# ${skill.title}`,
321
+ "",
322
+ skill.description,
323
+ "",
324
+ `**Use when:** ${skill.whenToUse}`,
325
+ ...(runtimeNote
326
+ ? ["", `**${runtime === "local" ? "On-Device" : "Hosted"}:** ${runtimeNote}`]
327
+ : []),
328
+ "",
329
+ "## Workflow",
330
+ "",
331
+ ...skill.instructions.map((step, index) => `${index + 1}. ${step}`),
332
+ "",
333
+ "## Verify",
334
+ "",
335
+ ...skill.verification.map((step) => `- ${step}`),
336
+ "",
337
+ `**Tools:** ${skill.tools.map((tool) => `\`${tool}\``).join(", ") || "none"}`,
338
+ `**Docs:** ${skill.docs.map((doc) => `\`${doc}\``).join(", ") || "none"}`,
339
+ ].join("\n");
340
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Builds a browser handoff URL around a short-lived, one-use exchange token.
3
+ *
4
+ * The durable credential never appears in this URL. The access origin consumes
5
+ * the final path segment once and immediately redirects browsers to a clean
6
+ * path-scoped session. `publicId` is used only by agent handoffs, whose clean
7
+ * static document remains `/agent/<id>`.
8
+ */
9
+ export declare function createBrowserCredentialUrl(input: {
10
+ origin: string;
11
+ path: string;
12
+ publicId?: string;
13
+ credential: string;
14
+ }): string;
15
+ /** Parses a one-use browser handoff URL and reconstructs its clean public path. */
16
+ export declare function parseBrowserCredentialUrl(value: string): {
17
+ credential: string;
18
+ requestUrl: string;
19
+ };
@@ -0,0 +1,62 @@
1
+ const browserHandoffSegmentPattern = /^[A-Za-z0-9_-]{16,512}$/;
2
+ const browserHandoffPublicIdPattern = /^[A-Za-z0-9_-]{1,512}$/;
3
+ function requireBrowserHandoffSegment(value) {
4
+ if (!browserHandoffSegmentPattern.test(value)) {
5
+ throw new TypeError("Browser handoff token must be an opaque URL-safe value.");
6
+ }
7
+ return value;
8
+ }
9
+ /**
10
+ * Builds a browser handoff URL around a short-lived, one-use exchange token.
11
+ *
12
+ * The durable credential never appears in this URL. The access origin consumes
13
+ * the final path segment once and immediately redirects browsers to a clean
14
+ * path-scoped session. `publicId` is used only by agent handoffs, whose clean
15
+ * static document remains `/agent/<id>`.
16
+ */
17
+ export function createBrowserCredentialUrl(input) {
18
+ const origin = new URL(input.origin);
19
+ if ((origin.protocol !== "https:" && origin.protocol !== "http:") ||
20
+ origin.username ||
21
+ origin.password ||
22
+ origin.pathname !== "/" ||
23
+ origin.search ||
24
+ origin.hash) {
25
+ throw new TypeError("Browser handoff URLs require a clean HTTP(S) origin.");
26
+ }
27
+ const basePath = `/${input.path.split("/").filter(Boolean).join("/")}`;
28
+ if (basePath === "/" || input.path.includes("?") || input.path.includes("#")) {
29
+ throw new TypeError("Browser handoff URLs require a clean absolute path.");
30
+ }
31
+ const segments = [
32
+ ...(input.publicId === undefined
33
+ ? []
34
+ : [
35
+ browserHandoffPublicIdPattern.test(input.publicId)
36
+ ? input.publicId
37
+ : (() => {
38
+ throw new TypeError("Browser handoff public id must be URL-safe.");
39
+ })(),
40
+ ]),
41
+ requireBrowserHandoffSegment(input.credential),
42
+ ];
43
+ return new URL(`${basePath}/${segments.map(encodeURIComponent).join("/")}`, origin).toString();
44
+ }
45
+ /** Parses a one-use browser handoff URL and reconstructs its clean public path. */
46
+ export function parseBrowserCredentialUrl(value) {
47
+ const url = new URL(value);
48
+ if ((url.protocol !== "https:" && url.protocol !== "http:") ||
49
+ url.username ||
50
+ url.password ||
51
+ url.search ||
52
+ url.hash) {
53
+ throw new TypeError("Browser handoff URL is not a clean HTTP(S) URL.");
54
+ }
55
+ const segments = url.pathname.split("/").filter(Boolean);
56
+ const credential = segments.pop();
57
+ if (!credential || !browserHandoffSegmentPattern.test(credential) || segments.length === 0) {
58
+ throw new TypeError("Browser handoff URL does not carry a valid one-use token.");
59
+ }
60
+ url.pathname = `/${segments.map(encodeURIComponent).join("/")}`;
61
+ return { credential, requestUrl: url.toString() };
62
+ }
@@ -0,0 +1,7 @@
1
+ export declare const COMMENT_AVATAR_IMAGE_ORIGINS: readonly ["https://gravatar.com", "https://i0.wp.com"];
2
+ /**
3
+ * Keep Comments avatar requests on origins that can be named explicitly in
4
+ * the host page's CSP. Gravatar is already canonical; every other HTTP(S)
5
+ * profile image goes through the HTTPS WordPress.com image CDN.
6
+ */
7
+ export declare function normalizeCommentAvatarUrl(value: string): string | null;
@@ -0,0 +1,42 @@
1
+ export const COMMENT_AVATAR_IMAGE_ORIGINS = ["https://gravatar.com", "https://i0.wp.com"];
2
+ const CANONICAL_GRAVATAR_ORIGIN = COMMENT_AVATAR_IMAGE_ORIGINS[0];
3
+ const AVATAR_PROXY_ORIGIN = COMMENT_AVATAR_IMAGE_ORIGINS[1];
4
+ const MAX_AVATAR_URL_LENGTH = 2_048;
5
+ /**
6
+ * Keep Comments avatar requests on origins that can be named explicitly in
7
+ * the host page's CSP. Gravatar is already canonical; every other HTTP(S)
8
+ * profile image goes through the HTTPS WordPress.com image CDN.
9
+ */
10
+ export function normalizeCommentAvatarUrl(value) {
11
+ const trimmed = value.trim();
12
+ if (!trimmed || trimmed.length > MAX_AVATAR_URL_LENGTH)
13
+ return null;
14
+ try {
15
+ const source = new URL(trimmed);
16
+ if ((source.protocol !== "https:" && source.protocol !== "http:") ||
17
+ source.username !== "" ||
18
+ source.password !== "") {
19
+ return null;
20
+ }
21
+ if (source.origin === CANONICAL_GRAVATAR_ORIGIN || source.origin === AVATAR_PROXY_ORIGIN) {
22
+ return source.href.length <= MAX_AVATAR_URL_LENGTH ? source.href : null;
23
+ }
24
+ // Assign the proxy path instead of parsing `<host><path>` as a URL. A host
25
+ // with a non-default port contains a colon and would otherwise become a
26
+ // made-up scheme (`javascript:8443/...`, for example). Photon addresses an
27
+ // HTTPS source with `ssl=1`; without it, an HTTPS and HTTP source collapse
28
+ // to the same proxy URL. `source.host` deliberately retains an explicit
29
+ // non-default port.
30
+ const proxied = new URL(AVATAR_PROXY_ORIGIN);
31
+ proxied.pathname = `/${source.host}${source.pathname}`;
32
+ proxied.search = source.search;
33
+ if (source.protocol === "https:")
34
+ proxied.searchParams.set("ssl", "1");
35
+ if (proxied.protocol !== "https:" || proxied.origin !== AVATAR_PROXY_ORIGIN)
36
+ return null;
37
+ return proxied.href.length <= MAX_AVATAR_URL_LENGTH ? proxied.href : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
@@ -2,9 +2,9 @@
2
2
  * Map a file extension to a Content-Type header value. Returns
3
3
  * `application/octet-stream` when the extension is not recognized.
4
4
  *
5
- * This is the superset of the extensions handled by the control-plane
6
- * runtime service and the spacefast-cli uploader, so both sides agree on the
7
- * same content types when uploading and serving version files.
5
+ * MIME extension coverage comes from the maintained `mime` database. The
6
+ * small overrides above preserve intentional existing policy; this module
7
+ * does not carry a second partial extension registry.
8
8
  */
9
9
  export declare function contentTypeForPath(filePath: string): string;
10
10
  export declare function textTypeForPath(filePath: string): string | null;
@@ -1,71 +1,41 @@
1
+ import mime from "mime";
2
+ const CONTENT_TYPE_OVERRIDES = {
3
+ // Preserve the public uploader/runtime policy where the database's preferred
4
+ // server-oriented type differs from the established browser-facing type.
5
+ ".cjs": "text/javascript",
6
+ ".cts": "text/plain",
7
+ ".d.ts": "text/plain",
8
+ ".ico": "image/x-icon",
9
+ ".mts": "text/plain",
10
+ ".rs": "text/plain",
11
+ ".sh": "text/plain",
12
+ ".tgz": "application/gzip",
13
+ ".ts": "text/plain",
14
+ };
15
+ const UTF8_APPLICATION_TYPES = new Set([
16
+ "application/json",
17
+ "application/manifest+json",
18
+ "application/xml",
19
+ ]);
1
20
  /**
2
21
  * Map a file extension to a Content-Type header value. Returns
3
22
  * `application/octet-stream` when the extension is not recognized.
4
23
  *
5
- * This is the superset of the extensions handled by the control-plane
6
- * runtime service and the spacefast-cli uploader, so both sides agree on the
7
- * same content types when uploading and serving version files.
24
+ * MIME extension coverage comes from the maintained `mime` database. The
25
+ * small overrides above preserve intentional existing policy; this module
26
+ * does not carry a second partial extension registry.
8
27
  */
9
28
  export function contentTypeForPath(filePath) {
10
- const extensionIndex = filePath.lastIndexOf(".");
11
- const extension = extensionIndex >= 0 ? filePath.slice(extensionIndex).toLowerCase() : "";
12
- switch (extension) {
13
- case ".html":
14
- case ".htm":
15
- return "text/html; charset=utf-8";
16
- case ".css":
17
- return "text/css; charset=utf-8";
18
- case ".js":
19
- case ".mjs":
20
- case ".cjs":
21
- return "text/javascript; charset=utf-8";
22
- case ".json":
23
- case ".map":
24
- return "application/json; charset=utf-8";
25
- case ".webmanifest":
26
- return "application/manifest+json; charset=utf-8";
27
- case ".gz":
28
- case ".tgz":
29
- return "application/gzip";
30
- case ".txt":
31
- case ".sh":
32
- return "text/plain; charset=utf-8";
33
- case ".md":
34
- return "text/markdown; charset=utf-8";
35
- case ".csv":
36
- return "text/csv; charset=utf-8";
37
- case ".xml":
38
- return "application/xml; charset=utf-8";
39
- case ".svg":
40
- return "image/svg+xml";
41
- case ".png":
42
- return "image/png";
43
- case ".jpg":
44
- case ".jpeg":
45
- return "image/jpeg";
46
- case ".gif":
47
- return "image/gif";
48
- case ".webp":
49
- return "image/webp";
50
- case ".avif":
51
- return "image/avif";
52
- case ".ico":
53
- return "image/x-icon";
54
- case ".pdf":
55
- return "application/pdf";
56
- case ".woff":
57
- return "font/woff";
58
- case ".woff2":
59
- return "font/woff2";
60
- case ".ttf":
61
- return "font/ttf";
62
- case ".otf":
63
- return "font/otf";
64
- case ".wasm":
65
- return "application/wasm";
66
- default:
67
- return "application/octet-stream";
29
+ const normalizedPath = filePath.toLowerCase();
30
+ const override = Object.entries(CONTENT_TYPE_OVERRIDES).find(([suffix]) => normalizedPath.endsWith(suffix))?.[1];
31
+ const type = override ?? mime.getType(filePath);
32
+ if (!type) {
33
+ return "application/octet-stream";
68
34
  }
35
+ if (type.startsWith("text/") || UTF8_APPLICATION_TYPES.has(type)) {
36
+ return `${type}; charset=utf-8`;
37
+ }
38
+ return type;
69
39
  }
70
40
  function hasPrefix(bytes, prefix) {
71
41
  return prefix.every((value, index) => bytes[index] === value);
@@ -141,5 +111,12 @@ export function contentTypeForBytes(filePath, bytes) {
141
111
  if (textType && bytesLookTextual(bytes)) {
142
112
  return textType;
143
113
  }
114
+ // Container signatures such as ISO-BMFF, WebM, and Ogg are shared by audio
115
+ // and video variants. The extension is the reliable discriminator for the
116
+ // manifest declaration, while unknown binary extensions remain generic.
117
+ const pathType = contentTypeForPath(filePath);
118
+ if (pathType.startsWith("audio/") || pathType.startsWith("video/")) {
119
+ return pathType;
120
+ }
144
121
  return "application/octet-stream";
145
122
  }
@@ -1,15 +1,15 @@
1
1
  export type HostnameDiagnosticsForActions = {
2
2
  hostname: string;
3
- providerIps: string[];
4
- dnsInstructions: unknown[];
3
+ providerIps?: string[] | undefined;
4
+ dnsInstructions?: unknown[] | undefined;
5
5
  dns: {
6
6
  pointsToProvider: boolean;
7
7
  txtVerified: boolean | null;
8
8
  expectedTxtValue: string | null;
9
9
  records?: {
10
- a: string[];
11
- aaaa: string[];
12
- };
10
+ a?: string[] | undefined;
11
+ aaaa?: string[] | undefined;
12
+ } | undefined;
13
13
  };
14
14
  ssl: {
15
15
  status: string;