@spacefast/common 0.0.2

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 (202) hide show
  1. package/README.md +42 -0
  2. package/dist/brand.d.ts +15 -0
  3. package/dist/brand.js +24 -0
  4. package/dist/config/domains.d.ts +30 -0
  5. package/dist/config/domains.js +75 -0
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +3 -0
  8. package/dist/contracts/abuse.d.ts +93 -0
  9. package/dist/contracts/abuse.js +50 -0
  10. package/dist/contracts/access.d.ts +409 -0
  11. package/dist/contracts/access.js +396 -0
  12. package/dist/contracts/account.d.ts +28 -0
  13. package/dist/contracts/account.js +54 -0
  14. package/dist/contracts/activity.d.ts +118 -0
  15. package/dist/contracts/activity.js +180 -0
  16. package/dist/contracts/analytics.d.ts +56 -0
  17. package/dist/contracts/analytics.js +91 -0
  18. package/dist/contracts/annotations.d.ts +327 -0
  19. package/dist/contracts/annotations.js +234 -0
  20. package/dist/contracts/api-keys.d.ts +517 -0
  21. package/dist/contracts/api-keys.js +235 -0
  22. package/dist/contracts/archives.d.ts +332 -0
  23. package/dist/contracts/archives.js +120 -0
  24. package/dist/contracts/auth.d.ts +86 -0
  25. package/dist/contracts/auth.js +82 -0
  26. package/dist/contracts/beta.d.ts +106 -0
  27. package/dist/contracts/beta.js +104 -0
  28. package/dist/contracts/billing.d.ts +328 -0
  29. package/dist/contracts/billing.js +203 -0
  30. package/dist/contracts/builds.d.ts +504 -0
  31. package/dist/contracts/builds.js +248 -0
  32. package/dist/contracts/channels.d.ts +49 -0
  33. package/dist/contracts/channels.js +51 -0
  34. package/dist/contracts/common.d.ts +238 -0
  35. package/dist/contracts/common.js +175 -0
  36. package/dist/contracts/deployments.d.ts +574 -0
  37. package/dist/contracts/deployments.js +97 -0
  38. package/dist/contracts/device-auth.d.ts +194 -0
  39. package/dist/contracts/device-auth.js +162 -0
  40. package/dist/contracts/domains.d.ts +6115 -0
  41. package/dist/contracts/domains.js +1435 -0
  42. package/dist/contracts/enums.d.ts +196 -0
  43. package/dist/contracts/enums.js +211 -0
  44. package/dist/contracts/error-code-meta.d.ts +1560 -0
  45. package/dist/contracts/error-code-meta.js +400 -0
  46. package/dist/contracts/error-codes.d.ts +13 -0
  47. package/dist/contracts/error-codes.js +416 -0
  48. package/dist/contracts/events.d.ts +398 -0
  49. package/dist/contracts/events.js +320 -0
  50. package/dist/contracts/fast-domain-search.d.ts +19 -0
  51. package/dist/contracts/fast-domain-search.js +22 -0
  52. package/dist/contracts/features.d.ts +240 -0
  53. package/dist/contracts/features.js +406 -0
  54. package/dist/contracts/git.d.ts +288 -0
  55. package/dist/contracts/git.js +116 -0
  56. package/dist/contracts/ids.d.ts +35 -0
  57. package/dist/contracts/ids.js +45 -0
  58. package/dist/contracts/intercom.d.ts +6 -0
  59. package/dist/contracts/intercom.js +5 -0
  60. package/dist/contracts/internal.d.ts +103 -0
  61. package/dist/contracts/internal.js +92 -0
  62. package/dist/contracts/mcp.d.ts +788 -0
  63. package/dist/contracts/mcp.js +161 -0
  64. package/dist/contracts/me.d.ts +81 -0
  65. package/dist/contracts/me.js +104 -0
  66. package/dist/contracts/oauth-resources.d.ts +30 -0
  67. package/dist/contracts/oauth-resources.js +61 -0
  68. package/dist/contracts/operations.d.ts +54 -0
  69. package/dist/contracts/operations.js +14 -0
  70. package/dist/contracts/plan-policy.d.ts +85 -0
  71. package/dist/contracts/plan-policy.js +75 -0
  72. package/dist/contracts/platform.d.ts +359 -0
  73. package/dist/contracts/platform.js +216 -0
  74. package/dist/contracts/push-new.d.ts +156 -0
  75. package/dist/contracts/push-new.js +52 -0
  76. package/dist/contracts/quotas.d.ts +16 -0
  77. package/dist/contracts/quotas.js +36 -0
  78. package/dist/contracts/repository-connections.d.ts +231 -0
  79. package/dist/contracts/repository-connections.js +106 -0
  80. package/dist/contracts/resources.d.ts +529 -0
  81. package/dist/contracts/resources.js +209 -0
  82. package/dist/contracts/routes.d.ts +45 -0
  83. package/dist/contracts/routes.js +31 -0
  84. package/dist/contracts/runtime-api.d.ts +449 -0
  85. package/dist/contracts/runtime-api.js +513 -0
  86. package/dist/contracts/sites.d.ts +393 -0
  87. package/dist/contracts/sites.js +121 -0
  88. package/dist/contracts/slugs.d.ts +31 -0
  89. package/dist/contracts/slugs.js +47 -0
  90. package/dist/contracts/space-config.d.ts +216 -0
  91. package/dist/contracts/space-config.js +237 -0
  92. package/dist/contracts/space-theme.d.ts +25 -0
  93. package/dist/contracts/space-theme.js +49 -0
  94. package/dist/contracts/spaces.d.ts +2760 -0
  95. package/dist/contracts/spaces.js +589 -0
  96. package/dist/contracts/superadmin-activity.d.ts +205 -0
  97. package/dist/contracts/superadmin-activity.js +107 -0
  98. package/dist/contracts/superadmin-emails.d.ts +212 -0
  99. package/dist/contracts/superadmin-emails.js +112 -0
  100. package/dist/contracts/superadmin-queues.d.ts +414 -0
  101. package/dist/contracts/superadmin-queues.js +202 -0
  102. package/dist/contracts/superadmin-runtime.d.ts +27 -0
  103. package/dist/contracts/superadmin-runtime.js +38 -0
  104. package/dist/contracts/superadmin-spaces.d.ts +429 -0
  105. package/dist/contracts/superadmin-spaces.js +82 -0
  106. package/dist/contracts/superadmin-tenants.d.ts +180 -0
  107. package/dist/contracts/superadmin-tenants.js +117 -0
  108. package/dist/contracts/superadmin.d.ts +654 -0
  109. package/dist/contracts/superadmin.js +309 -0
  110. package/dist/contracts/tags.d.ts +1140 -0
  111. package/dist/contracts/tags.js +1050 -0
  112. package/dist/contracts/teams.d.ts +233 -0
  113. package/dist/contracts/teams.js +174 -0
  114. package/dist/contracts/telemetry.d.ts +2 -0
  115. package/dist/contracts/telemetry.js +1 -0
  116. package/dist/contracts/transfers.d.ts +105 -0
  117. package/dist/contracts/transfers.js +48 -0
  118. package/dist/contracts/variables.d.ts +189 -0
  119. package/dist/contracts/variables.js +82 -0
  120. package/dist/contracts/webhooks.d.ts +159 -0
  121. package/dist/contracts/webhooks.js +59 -0
  122. package/dist/contracts/zero.d.ts +1538 -0
  123. package/dist/contracts/zero.js +755 -0
  124. package/dist/docs/agent-prose.d.ts +35 -0
  125. package/dist/docs/agent-prose.js +153 -0
  126. package/dist/docs/agent-setup.d.ts +6 -0
  127. package/dist/docs/agent-setup.js +164 -0
  128. package/dist/docs/agent-solutions.d.ts +110 -0
  129. package/dist/docs/agent-solutions.js +181 -0
  130. package/dist/docs/error-docs.d.ts +12 -0
  131. package/dist/docs/error-docs.js +1601 -0
  132. package/dist/docs/skill-distribution.d.ts +12 -0
  133. package/dist/docs/skill-distribution.js +18 -0
  134. package/dist/domain-exploration.d.ts +108 -0
  135. package/dist/domain-exploration.js +488 -0
  136. package/dist/domain-normalize.d.ts +1 -0
  137. package/dist/domain-normalize.js +14 -0
  138. package/dist/domain-zones.d.ts +1 -0
  139. package/dist/domain-zones.js +5272 -0
  140. package/dist/index.d.ts +1 -0
  141. package/dist/index.js +1 -0
  142. package/dist/utils/access-policy.d.ts +39 -0
  143. package/dist/utils/access-policy.js +232 -0
  144. package/dist/utils/api.d.ts +4 -0
  145. package/dist/utils/api.js +13 -0
  146. package/dist/utils/auth-redirect.d.ts +1 -0
  147. package/dist/utils/auth-redirect.js +48 -0
  148. package/dist/utils/auth.d.ts +5 -0
  149. package/dist/utils/auth.js +13 -0
  150. package/dist/utils/build-settings.d.ts +49 -0
  151. package/dist/utils/build-settings.js +366 -0
  152. package/dist/utils/claim-token.d.ts +1 -0
  153. package/dist/utils/claim-token.js +9 -0
  154. package/dist/utils/collections.d.ts +1 -0
  155. package/dist/utils/collections.js +5 -0
  156. package/dist/utils/concurrency.d.ts +1 -0
  157. package/dist/utils/concurrency.js +18 -0
  158. package/dist/utils/content-type.d.ts +10 -0
  159. package/dist/utils/content-type.js +145 -0
  160. package/dist/utils/error-display.d.ts +7 -0
  161. package/dist/utils/error-display.js +42 -0
  162. package/dist/utils/error.d.ts +1 -0
  163. package/dist/utils/error.js +3 -0
  164. package/dist/utils/generate-space-name.d.ts +14 -0
  165. package/dist/utils/generate-space-name.js +95 -0
  166. package/dist/utils/git-repository.d.ts +5 -0
  167. package/dist/utils/git-repository.js +99 -0
  168. package/dist/utils/guards.d.ts +1 -0
  169. package/dist/utils/guards.js +3 -0
  170. package/dist/utils/publish-detection.d.ts +10 -0
  171. package/dist/utils/publish-detection.js +118 -0
  172. package/dist/utils/publish-policy.d.ts +69 -0
  173. package/dist/utils/publish-policy.js +429 -0
  174. package/dist/utils/query-client.d.ts +7 -0
  175. package/dist/utils/query-client.js +21 -0
  176. package/dist/utils/query-keys.d.ts +75 -0
  177. package/dist/utils/query-keys.js +78 -0
  178. package/dist/utils/runtime-paths.d.ts +7 -0
  179. package/dist/utils/runtime-paths.js +28 -0
  180. package/dist/utils/runtime-upload.d.ts +19 -0
  181. package/dist/utils/runtime-upload.js +27 -0
  182. package/dist/utils/slug.d.ts +5 -0
  183. package/dist/utils/slug.js +13 -0
  184. package/dist/utils/space-config.d.ts +75 -0
  185. package/dist/utils/space-config.js +415 -0
  186. package/dist/utils/space-theme.d.ts +16 -0
  187. package/dist/utils/space-theme.js +89 -0
  188. package/dist/utils/static-runtime-policy.d.ts +17 -0
  189. package/dist/utils/static-runtime-policy.js +103 -0
  190. package/dist/utils/upload-session.d.ts +34 -0
  191. package/dist/utils/upload-session.js +69 -0
  192. package/dist/utils/urls.d.ts +2 -0
  193. package/dist/utils/urls.js +7 -0
  194. package/dist/utils/version-path.d.ts +6 -0
  195. package/dist/utils/version-path.js +51 -0
  196. package/dist/utils/wpcom-auth-redirects.d.ts +14 -0
  197. package/dist/utils/wpcom-auth-redirects.js +49 -0
  198. package/dist/utils/zero-realtime-signature.d.ts +12 -0
  199. package/dist/utils/zero-realtime-signature.js +45 -0
  200. package/dist/vocabulary.d.ts +230 -0
  201. package/dist/vocabulary.js +218 -0
  202. package/package.json +58 -0
@@ -0,0 +1,35 @@
1
+ export declare const ARCHIVE_SECRET_GLOBS: readonly [".env*", ".npmrc", ".netrc", "*/.git/*", "*/.stattic/*", "*/.ssh/*", "*/.aws/*", "*/.kube/*", "*/.docker/*", "credentials.json", "*.pem", "*.key", "*.p12", "*.pfx", "*.crt", "*id_rsa*"];
2
+ /**
3
+ * The credential rules every surface must repeat. Treat tokens as secrets, keep
4
+ * them out of logs and transcripts, and never echo them back.
5
+ */
6
+ export declare const SECRET_RULES_PROSE: string;
7
+ /**
8
+ * The zero-install publish sequence: one curl for a file, a secret-preflighted
9
+ * zip for a folder. This is the load-bearing rung 0 — an agent that stops here
10
+ * must still succeed.
11
+ */
12
+ export declare const PUBLISH_SEQUENCE_PROSE: string;
13
+ /**
14
+ * What the JSON publish receipt contains and how to read it. The receipt is the
15
+ * source of truth; never invent a separate success signal.
16
+ */
17
+ export declare const RECEIPT_SHAPE_PROSE: string;
18
+ /**
19
+ * The large/cloud-agent path. This keeps file bytes out of MCP JSON and chat
20
+ * transcripts while still giving agents a way to upload arbitrary artifacts.
21
+ */
22
+ export declare const SIGNED_UPLOAD_PROSE: string;
23
+ /**
24
+ * Named fragments for the skill-template generator. The template carries
25
+ * `{{prose.secretRules}}`, `{{prose.publishSequence}}`,
26
+ * `{{prose.receiptShape}}`, and `{{prose.signedUpload}}` placeholders;
27
+ * generate-skill.mjs substitutes these.
28
+ */
29
+ export declare const AGENT_PROSE: {
30
+ readonly secretRules: string;
31
+ readonly publishSequence: string;
32
+ readonly receiptShape: string;
33
+ readonly signedUpload: string;
34
+ };
35
+ export type AgentProseKey = keyof typeof AGENT_PROSE;
@@ -0,0 +1,153 @@
1
+ import { BRAND } from "../brand.js";
2
+ // Single home for the agent-facing prose that was triplicated across the three
3
+ // surfaces that teach an agent how to publish:
4
+ //
5
+ // - packages/cli/skill.template.md (the SKILL.md router + copies)
6
+ // - packages/common/src/docs/agent-setup.ts (the /ai and /ai.txt page body)
7
+ // - apps/www/src/lib/llmo.ts (the agent-card / discovery prose)
8
+ //
9
+ // Each surface re-stated the publish sequence, the secret rules, and the receipt
10
+ // shape in its own words, so they drifted. These fragments are the one source of
11
+ // truth. They are plain Markdown strings (no surface-specific framing) so any
12
+ // surface can splice them in: the skill template substitutes the `{{prose.*}}`
13
+ // placeholders during generation, and the TS surfaces import the strings.
14
+ //
15
+ // Keep these grounded in the real API receipt and the real /v1/publish flow. If
16
+ // the receipt shape or secret handling changes, change it here once.
17
+ const API_ORIGIN = BRAND.apiOrigin;
18
+ const PUBLISH_ENDPOINT = `${API_ORIGIN}/v1/publish`;
19
+ // Centralized secret/credential patterns excluded from a publish archive. The
20
+ // `find` preflight and the `zip -x` exclusions are derived from the same list so
21
+ // they can never disagree, and the no-install / skill copies stay identical.
22
+ export const ARCHIVE_SECRET_GLOBS = [
23
+ ".env*",
24
+ ".npmrc",
25
+ ".netrc",
26
+ "*/.git/*",
27
+ "*/.stattic/*",
28
+ "*/.ssh/*",
29
+ "*/.aws/*",
30
+ "*/.kube/*",
31
+ "*/.docker/*",
32
+ "credentials.json",
33
+ "*.pem",
34
+ "*.key",
35
+ "*.p12",
36
+ "*.pfx",
37
+ "*.crt",
38
+ "*id_rsa*",
39
+ ];
40
+ function findExpr(globs) {
41
+ return globs
42
+ .map((glob) => (glob.includes("/") ? `-path "${glob}"` : `-name "${glob}"`))
43
+ .join(" -o ");
44
+ }
45
+ function zipExcludeExpr(globs) {
46
+ // zip uses relative patterns: a leading "*/" is just a directory match, so
47
+ // collapse it to the directory glob the archive root sees.
48
+ return globs.map((glob) => `"${glob.replace(/^\*\//, "")}"`).join(" ");
49
+ }
50
+ /**
51
+ * The credential rules every surface must repeat. Treat tokens as secrets, keep
52
+ * them out of logs and transcripts, and never echo them back.
53
+ */
54
+ export const SECRET_RULES_PROSE = [
55
+ "Treat claim tokens, upload tokens, device codes, and API keys as credentials. Do not print",
56
+ "them, paste them into chat, commit them, archive them, or include them in shared logs. Avoid",
57
+ "`set -x`, persistent shell history, CI log echoing, and command transcripts when secrets are",
58
+ "present. On shared hosts, prefer one-off environment variables or restrictive temporary files,",
59
+ "clear them after use, and avoid durable auth unless the user explicitly asks.",
60
+ ].join("\n");
61
+ /**
62
+ * The zero-install publish sequence: one curl for a file, a secret-preflighted
63
+ * zip for a folder. This is the load-bearing rung 0 — an agent that stops here
64
+ * must still succeed.
65
+ */
66
+ export const PUBLISH_SEQUENCE_PROSE = [
67
+ "Publish a single file:",
68
+ "",
69
+ "```bash",
70
+ `curl -sS -F "files=@index.html" ${PUBLISH_ENDPOINT}`,
71
+ "```",
72
+ "",
73
+ "Publish a folder by zipping only the intended public output root. Do not archive a repository",
74
+ "root or `.` blindly. Before zipping, confirm the publish root contains no secrets, source",
75
+ "control, auth state, or unrelated build/cache folders:",
76
+ "",
77
+ "```bash",
78
+ `find {publish-root} \\( ${findExpr(ARCHIVE_SECRET_GLOBS)} \\) -print`,
79
+ `(cd {publish-root} && zip -r ../spacefast-site.zip . -x ${zipExcludeExpr(ARCHIVE_SECRET_GLOBS)} "*.zip" "*.tar" "*.tgz")`,
80
+ `curl -sS -F archive=@spacefast-site.zip ${PUBLISH_ENDPOINT}`,
81
+ "```",
82
+ "",
83
+ "If the preflight prints credential-like files, stop and choose a narrower generated output",
84
+ "directory or remove those files from the publish root before uploading.",
85
+ ].join("\n");
86
+ /**
87
+ * What the JSON publish receipt contains and how to read it. The receipt is the
88
+ * source of truth; never invent a separate success signal.
89
+ */
90
+ export const RECEIPT_SHAPE_PROSE = [
91
+ "The JSON receipt is the source of truth:",
92
+ "",
93
+ "- `data.space.liveUrl` — the live URL.",
94
+ "- `data.version.immutableUrl` — the permanent, frozen version URL when available; share it for reviews.",
95
+ "- `data.shareBlurb` — a paste-ready one-liner with the URL and claim nudge; relay it verbatim when present.",
96
+ "- `data.upload` — signed upload instructions for manifest publishes when file bytes were not sent",
97
+ " with the request. Send each `data.upload.targets[]` instruction exactly as returned, treat",
98
+ " authorization headers and upload tokens as secrets, then finalize.",
99
+ "- `data.claim.url` and `data.claim.expiresAt` — the space is anonymous and expires unless claimed;",
100
+ " always show the user the claim link and the deadline so the space becomes theirs.",
101
+ "- `data.claim.token` — a secret capability. Save it only when needed to update the space later, send it as",
102
+ " `Authorization: Bearer <claim-token>` with the same `spaceId`, and never print it back to the user.",
103
+ "- `data.links.inspect`, `resume`, `finalize`, and `promote` — follow-up API paths.",
104
+ "- `error.code`, `error.docsUrl`, and `error.hint` — stable error code plus exact recovery docs and a next step.",
105
+ "",
106
+ "If no files changed, the API may omit `upload`; report the receipt and diagnostics rather than",
107
+ "inventing a separate success signal.",
108
+ ].join("\n");
109
+ /**
110
+ * The large/cloud-agent path. This keeps file bytes out of MCP JSON and chat
111
+ * transcripts while still giving agents a way to upload arbitrary artifacts.
112
+ */
113
+ export const SIGNED_UPLOAD_PROSE = [
114
+ "Use signed upload sessions when the artifact is large, the client is cloud-hosted, or inline/base64",
115
+ "content would be awkward. Start with a manifest instead of file bytes:",
116
+ "",
117
+ "```bash",
118
+ `curl -sS ${PUBLISH_ENDPOINT} \\`,
119
+ ' -H "content-type: application/json" \\',
120
+ ' -d \'{"files":[{"path":"index.html","size":1234,"contentType":"text/html","sha256":"...","sourceUrl":"https://example.com/index.html"}]}\'',
121
+ "```",
122
+ "",
123
+ "For each `data.upload.targets[]`, send `target.method` to `target.url` with `target.headers`.",
124
+ 'If `target.body.kind` is `url`, send JSON `{ "url": target.body.url }`; otherwise send the',
125
+ "local file bytes for `target.path`. Then call `data.links.finalize` and share the final receipt.",
126
+ "If the session expires or only a page of files is returned, follow `data.links.resume`.",
127
+ "",
128
+ "For importing an archive that already lives somewhere else, prefer the import-from-link API:",
129
+ "",
130
+ "```bash",
131
+ "curl -sS https://api.spacefast.com/v1/spaces/{spaceId}/imports \\",
132
+ ' -H "content-type: application/json" \\',
133
+ ' -H "Authorization: Bearer $SPACEFAST_TOKEN" \\',
134
+ ' -d \'{"source":{"kind":"url","url":"https://example.com/site.zip"}}\'',
135
+ "```",
136
+ "",
137
+ "If the archive is only local to the agent, create an upload-sourced import, PUT the archive to",
138
+ "`{archiveUpload.baseUrl}/{archiveUpload.files[0]}` with the returned bearer token, then finalize",
139
+ "the import. If the token expires, fetch/resume the import for fresh `archiveUpload` instructions.",
140
+ "Do not base64 large archives into chat or MCP tool arguments.",
141
+ ].join("\n");
142
+ /**
143
+ * Named fragments for the skill-template generator. The template carries
144
+ * `{{prose.secretRules}}`, `{{prose.publishSequence}}`,
145
+ * `{{prose.receiptShape}}`, and `{{prose.signedUpload}}` placeholders;
146
+ * generate-skill.mjs substitutes these.
147
+ */
148
+ export const AGENT_PROSE = {
149
+ secretRules: SECRET_RULES_PROSE,
150
+ publishSequence: PUBLISH_SEQUENCE_PROSE,
151
+ receiptShape: RECEIPT_SHAPE_PROSE,
152
+ signedUpload: SIGNED_UPLOAD_PROSE,
153
+ };
@@ -0,0 +1,6 @@
1
+ export declare const agentSetupUrl: string;
2
+ export declare const agentSetupTextUrl: string;
3
+ export declare const agentSetupHostLabel: string;
4
+ export declare const agentSetupPrompt: string;
5
+ export declare function renderAgentSetupTemplate(value: string): string;
6
+ export declare function aiInstructionsText(): string;
@@ -0,0 +1,164 @@
1
+ import { BRAND } from "@spacefast/common/brand";
2
+ import { PUBLISH_SEQUENCE_PROSE, RECEIPT_SHAPE_PROSE, SECRET_RULES_PROSE, SIGNED_UPLOAD_PROSE, } from "@spacefast/common/docs/agent-prose";
3
+ import { spacefastInstallCurlCommand, spacefastInstallPowerShellCommand, spacefastSkillInstallCommand, spacefastSkillUrl, } from "@spacefast/common/docs/skill-distribution";
4
+ export const agentSetupUrl = `${BRAND.agentSetupOrigin}/ai`;
5
+ export const agentSetupTextUrl = `${BRAND.agentSetupOrigin}/ai.txt`;
6
+ const agentSetupParsedUrl = new URL(agentSetupUrl);
7
+ export const agentSetupHostLabel = agentSetupParsedUrl.host + agentSetupParsedUrl.pathname;
8
+ export const agentSetupPrompt = `Fetch ${agentSetupUrl} and follow the instructions to publish for me. Ask me what I want to publish if it isn't obvious.`;
9
+ export function renderAgentSetupTemplate(value) {
10
+ return value
11
+ .replaceAll("{{AGENT_SETUP_URL}}", agentSetupUrl)
12
+ .replaceAll("{{AGENT_SETUP_TEXT_URL}}", agentSetupTextUrl)
13
+ .replaceAll("{{AGENT_SETUP_HOST_LABEL}}", agentSetupHostLabel);
14
+ }
15
+ // Progressive disclosure: the fast path (one curl, zero install) comes first and is
16
+ // complete on its own; the CLI section unlocks the rest. Keep this readable top to
17
+ // bottom — an agent that stops after "Publish with curl" must still succeed.
18
+ export function aiInstructionsText() {
19
+ return [
20
+ `# ${BRAND.productName} for AI agents`,
21
+ "",
22
+ `Official instructions for AI agents publishing to ${BRAND.productName}, the static host where`,
23
+ "AI-made things are kept: permanent URLs, immutable versions, anonymous publish",
24
+ "with a claim link. Static files only — server code is never executed.",
25
+ "",
26
+ "## Publish with curl (no install, no account)",
27
+ "",
28
+ PUBLISH_SEQUENCE_PROSE,
29
+ "",
30
+ RECEIPT_SHAPE_PROSE,
31
+ "",
32
+ "## Big files and cloud agents",
33
+ "",
34
+ SIGNED_UPLOAD_PROSE,
35
+ "",
36
+ "## Secret handling",
37
+ "",
38
+ SECRET_RULES_PROSE,
39
+ "",
40
+ 'Add a bearer access token with `-H "Authorization: Bearer ..."` to publish into the user\'s',
41
+ "team instead of anonymously. Agent clients should prefer OAuth client credentials. Errors come back as JSON with stable `code` values and a",
42
+ "`docsUrl`; fetch the `docsUrl` when you need recovery steps.",
43
+ "",
44
+ "## Install the CLI (On-Device files, updates, versions, rollback, and the rest)",
45
+ "",
46
+ "The CLI is the full surface: incremental publishes, version history, rollback,",
47
+ "passwords, domains, logs, and diagnostics. Install it when the user will keep",
48
+ "working on the space:",
49
+ "",
50
+ "```bash",
51
+ spacefastInstallCurlCommand,
52
+ "```",
53
+ "",
54
+ "On Windows PowerShell:",
55
+ "",
56
+ "```bash",
57
+ spacefastInstallPowerShellCommand,
58
+ "```",
59
+ "",
60
+ "If `sf` is already on PATH, use it. The package is `spacefast`; the default binary is `sf`.",
61
+ "",
62
+ "```bash",
63
+ "sf setup agent --agent auto --connect # pick the right MCP lane and write client config",
64
+ "sf publish {file-or-dir} # publish; prints live, version, and claim URLs",
65
+ "sf versions # list versions",
66
+ "sf rollback {version} # make an earlier ready version live, no rebuild",
67
+ "sf login # device flow; mints a scoped API key",
68
+ "sf --help # the full command surface",
69
+ "```",
70
+ "",
71
+ "Use `--json` on any command when the output will be parsed. Publishing the same",
72
+ "directory again updates the space saved in `.stattic/state.json`.",
73
+ "",
74
+ "## MCP for agent clients",
75
+ "",
76
+ "Hosted MCP is the no-download cloud lane for ChatGPT, Codex, Claude, Cursor, and browser",
77
+ "clients that can authenticate with OAuth/API bearer credentials:",
78
+ "",
79
+ "```text",
80
+ "https://mcp.spacefast.com/mcp",
81
+ "```",
82
+ "",
83
+ "Request `mcp:tools` plus the API scopes the tools need: `teams:read` for team context,",
84
+ "`spaces:read` for status, versions, diffs, and logs, `spaces:write` for claim/create/update,",
85
+ "`publish:write` for publish and rollback, and `domains:read`/`domains:write` for domain",
86
+ "inventory, DNS diagnostics, assignment, verification, and mutation. Hosted MCP does not read",
87
+ "local paths; its workspace tools operate on a durable virtual workspace attached to the MCP",
88
+ "session.",
89
+ "",
90
+ "For hosted MCP, prefer `prepare_publish`, `resume_publish`, and `finalize_publish` for large",
91
+ "artifacts so the agent uploads to signed URLs instead of stuffing bytes into tool calls. Use",
92
+ "`import_from_url` when the archive already has an HTTPS URL. Hosted MCP is the right path for",
93
+ "ChatGPT/browser agents because it requires no download and never claims local filesystem access.",
94
+ "For generated files, use `workspace_write` or `workspace_import`, review with",
95
+ "`workspace_manifest` / `workspace_diff`, hand off with `workspace_export`, and publish the",
96
+ 'virtual workspace with `publish({ path: "." })`.',
97
+ "Before acting on authenticated spaces, load MCP resources such as",
98
+ "`spacefast://account/current`, `spacefast://teams/current`, `spacefast://spaces/recent`,",
99
+ "`spacefast://spaces/{spaceId}`, and `spacefast://policy/current` for bounded read-only",
100
+ "context.",
101
+ "`execute_spacefast` is dry-run-first; mutating multi-step workflows can return",
102
+ "`approval_required` and continue through `resume_spacefast` after the user approves.",
103
+ "",
104
+ "Use On-Device MCP when the agent needs the user's checkout, path-based publish, or file edits:",
105
+ "",
106
+ "```bash",
107
+ "sf setup agent --agent auto --connect",
108
+ "sf mcp install --agent codex --mode hosted --connect",
109
+ "sf mcp install --agent codex --mode local --transport stdio --connect",
110
+ "sf mcp",
111
+ "sf mcp http --host 127.0.0.1 --port 3945 --path /mcp",
112
+ "```",
113
+ "",
114
+ "Create dashboard agent accounts only for CI, bots, or durable local runtimes. In CI, prefer",
115
+ "`SPACEFAST_AGENT_CONFIG` with private_key_jwt when available; otherwise use one-job",
116
+ "`SPACEFAST_TOKEN` secrets and mask logs. Do not persist personal auth in ephemeral runners.",
117
+ "",
118
+ "If your environment supports skill import, fetch the official skill for the",
119
+ `complete command reference: \`${spacefastSkillInstallCommand}\` (source: ${spacefastSkillUrl}).`,
120
+ "",
121
+ "## After publishing",
122
+ "",
123
+ "- Share the live URL, the immutable version URL, and — for anonymous spaces — the",
124
+ " claim link with its expiry.",
125
+ "- Never print access tokens, claim tokens, auth files, or `.stattic/state.json`.",
126
+ "",
127
+ "## Space conventions",
128
+ "",
129
+ "- `_redirects` — redirects, rewrites, SPA fallbacks, custom 404 rules, and external",
130
+ " `200` proxy rules.",
131
+ "- `_headers` — response headers and Basic Auth rules.",
132
+ "- `404.html` — custom not-found page.",
133
+ "- `sf.jsonc` — serving config (index, fallback, listing, meta) and the optional",
134
+ " `space` link. JSON Schema: https://spacefast.com/schemas/sf.json",
135
+ "- `/llms.txt` and `/ai.txt` — publish these in the user's site when it should be",
136
+ " agent-readable too.",
137
+ "- Plan limits surface as diagnostics, not failures you should guess at: publish the",
138
+ " intended artifact, then read and report what the CLI or API says. When a feature",
139
+ ' is plan-gated, say: "Not available on Free. Available on Personal and Work." For',
140
+ " simple gated access, `sf password set` needs no plan and no `_headers` rule.",
141
+ "",
142
+ "## Agent-aware routing",
143
+ "",
144
+ `${BRAND.productName} supports an \`Agent=true\` condition in \`_redirects\` — a browser gets the HTML`,
145
+ "page, an agent fetch gets plain text at the same URL. This very page uses it:",
146
+ "",
147
+ "```text",
148
+ "/ai /ai.txt 200! Agent=true",
149
+ "```",
150
+ "",
151
+ "## Going deeper",
152
+ "",
153
+ "- Docs index for agents: https://spacefast.com/llms.txt",
154
+ "- Agent guide: https://spacefast.com/docs/agents",
155
+ "- CLI reference: https://spacefast.com/docs/cli",
156
+ "- REST API + OpenAPI: https://spacefast.com/docs/api",
157
+ "- Publish contract (machine-readable): https://spacefast.com/publish-spec.json",
158
+ "- Every error code: https://spacefast.com/docs/errors",
159
+ "",
160
+ "Append `.md` to any docs URL for Markdown, or just fetch — agent user agents get",
161
+ "Markdown automatically.",
162
+ "",
163
+ ].join("\n");
164
+ }
@@ -0,0 +1,110 @@
1
+ export declare const hostedMcpEndpointUrl = "https://mcp.spacefast.com/mcp";
2
+ export type AgentSolutionLane = "plugin" | "hosted-mcp" | "local-mcp" | "cli" | "prompt";
3
+ export type AgentSolutionId = "codex" | "claude-code" | "claude-cowork" | "chatgpt" | "claude-ai" | "raycast" | "cursor" | "terminal" | "generic";
4
+ export type AgentSolution = {
5
+ aliases: readonly string[];
6
+ commands?: readonly string[];
7
+ description: string;
8
+ detailGuideUrl?: string;
9
+ guideUrl: string;
10
+ id: AgentSolutionId;
11
+ label: string;
12
+ lane: AgentSolutionLane;
13
+ setupText: string;
14
+ summary: string;
15
+ };
16
+ export declare const agentSolutions: readonly [{
17
+ readonly id: "codex";
18
+ readonly label: "Codex";
19
+ readonly aliases: readonly ["codex cli", "codex plugin", "openai codex"];
20
+ readonly lane: "plugin";
21
+ readonly summary: "Recommended: Codex plugin.";
22
+ readonly description: "Install the Spacefast skill/plugin so Codex can publish, update, and inspect spaces from the same setup path.";
23
+ readonly commands: readonly ["npx skills add spacefast/plugins --skill spacefast -g"];
24
+ readonly guideUrl: `${string}/for-codex`;
25
+ readonly detailGuideUrl: `${string}/guides/how-to-use-with-codex`;
26
+ readonly setupText: string;
27
+ }, {
28
+ readonly id: "claude-code";
29
+ readonly label: "Claude Code";
30
+ readonly aliases: readonly ["claude code", "claude cli"];
31
+ readonly lane: "plugin";
32
+ readonly summary: "Recommended: Claude plugin.";
33
+ readonly description: "Install the Spacefast Claude plugin once, then Claude Code can publish and operate spaces from the terminal.";
34
+ readonly commands: readonly ["claude plugin marketplace add spacefast/plugins", "claude plugin install spacefast@spacefast"];
35
+ readonly guideUrl: `${string}/for-claude`;
36
+ readonly detailGuideUrl: `${string}/guides/how-to-use-with-claude-code`;
37
+ readonly setupText: string;
38
+ }, {
39
+ readonly id: "claude-cowork";
40
+ readonly label: "Claude Cowork";
41
+ readonly aliases: readonly ["cowork", "claude cowork"];
42
+ readonly lane: "plugin";
43
+ readonly summary: "Recommended: Claude plugin.";
44
+ readonly description: "Use the Spacefast Claude plugin path so Cowork gets the same publish instructions and MCP-aware setup as Claude Code.";
45
+ readonly commands: readonly ["claude plugin marketplace add spacefast/plugins", "claude plugin install spacefast@spacefast"];
46
+ readonly guideUrl: `${string}/for-claude`;
47
+ readonly detailGuideUrl: `${string}/guides/how-to-use-with-claude-cowork`;
48
+ readonly setupText: string;
49
+ }, {
50
+ readonly id: "chatgpt";
51
+ readonly label: "ChatGPT";
52
+ readonly aliases: readonly ["chatgpt", "custom gpt", "openai apps"];
53
+ readonly lane: "hosted-mcp";
54
+ readonly summary: "Recommended: direct hosted MCP.";
55
+ readonly description: "Connect ChatGPT-style cloud agents to Spacefast through hosted MCP; use the virtual workspace or signed uploads instead of local paths.";
56
+ readonly commands: readonly ["https://mcp.spacefast.com/mcp"];
57
+ readonly guideUrl: `${string}/docs/agents`;
58
+ readonly setupText: string;
59
+ }, {
60
+ readonly id: "claude-ai";
61
+ readonly label: "Claude.ai";
62
+ readonly aliases: readonly ["claude.ai", "claude app", "claude web"];
63
+ readonly lane: "hosted-mcp";
64
+ readonly summary: "Recommended: direct hosted MCP.";
65
+ readonly description: "Connect the web Claude app to Spacefast through hosted MCP when it cannot read your local checkout.";
66
+ readonly commands: readonly ["https://mcp.spacefast.com/mcp"];
67
+ readonly guideUrl: `${string}/docs/agents`;
68
+ readonly setupText: string;
69
+ }, {
70
+ readonly id: "raycast";
71
+ readonly label: "Raycast";
72
+ readonly aliases: readonly ["raycast", "raycast ai"];
73
+ readonly lane: "hosted-mcp";
74
+ readonly summary: "Recommended: direct hosted MCP.";
75
+ readonly description: "Use hosted MCP for Raycast and similar clients that can connect to a remote MCP server but should not need a local Spacefast install.";
76
+ readonly commands: readonly ["https://mcp.spacefast.com/mcp"];
77
+ readonly guideUrl: `${string}/docs/agents`;
78
+ readonly setupText: string;
79
+ }, {
80
+ readonly id: "cursor";
81
+ readonly label: "Cursor or MCP client";
82
+ readonly aliases: readonly ["cursor", "mcp client", "windsurf", "opencode"];
83
+ readonly lane: "local-mcp";
84
+ readonly summary: "Recommended: local MCP when it needs the checkout.";
85
+ readonly description: "Use On-Device MCP for editor agents that need local files; use hosted MCP instead when they only need cloud-safe tools.";
86
+ readonly commands: readonly ["sf setup agent --agent auto --connect", "sf mcp install --agent auto --mode local --transport stdio --connect"];
87
+ readonly guideUrl: `${string}/docs/agents`;
88
+ readonly setupText: string;
89
+ }, {
90
+ readonly id: "terminal";
91
+ readonly label: "Terminal or CI";
92
+ readonly aliases: readonly ["terminal", "cli", "ci", "github actions"];
93
+ readonly lane: "cli";
94
+ readonly summary: "Recommended: Spacefast CLI.";
95
+ readonly description: "Use the CLI for repeat publishes, CI jobs, JSON output, rollback, logs, and domains.";
96
+ readonly commands: readonly [string, "sf login", "sf publish {file-or-dir} --json"];
97
+ readonly guideUrl: `${string}/docs/cli`;
98
+ readonly setupText: string;
99
+ }, {
100
+ readonly id: "generic";
101
+ readonly label: "Generic agent prompt";
102
+ readonly aliases: readonly ["generic", "unknown", "gemini"];
103
+ readonly lane: "prompt";
104
+ readonly summary: "Recommended: generic prompt + setup contract.";
105
+ readonly description: "Use this when the client has no known plugin or MCP connector. The agent reads Spacefast's setup contract and picks the smallest working lane.";
106
+ readonly guideUrl: string;
107
+ readonly setupText: string;
108
+ }];
109
+ export declare function getAgentSolution(id: AgentSolutionId): AgentSolution;
110
+ export declare function renderAgentSolutionIndexText(solutions?: readonly AgentSolution[]): string;
@@ -0,0 +1,181 @@
1
+ import { BRAND } from "../brand.js";
2
+ import { agentSetupPrompt, agentSetupTextUrl, agentSetupUrl } from "./agent-setup.js";
3
+ import { spacefastClaudePluginAddCommand, spacefastClaudePluginInstallCommand, spacefastCodexSkillInstallCommand, spacefastInstallCurlCommand, } from "./skill-distribution.js";
4
+ export const hostedMcpEndpointUrl = "https://mcp.spacefast.com/mcp";
5
+ function pluginText(label, commands, guideUrl) {
6
+ return [
7
+ `Set up ${BRAND.productName} for ${label}.`,
8
+ "",
9
+ "Run once:",
10
+ ...commands.map((command) => ` ${command}`),
11
+ "",
12
+ `Then ask ${label} to publish with ${BRAND.productName}.`,
13
+ `Guide: ${guideUrl}`,
14
+ `Generic agent contract: ${agentSetupUrl}`,
15
+ ].join("\n");
16
+ }
17
+ function hostedMcpText(label, guideUrl) {
18
+ return [
19
+ `Connect ${label} to ${BRAND.productName} with hosted MCP.`,
20
+ "",
21
+ `MCP endpoint: ${hostedMcpEndpointUrl}`,
22
+ "Transport: Streamable HTTP",
23
+ "Resource: https://api.spacefast.com/mcp",
24
+ "Minimum scope: mcp:tools",
25
+ "Common scopes: teams:read spaces:read spaces:write publish:write domains:read domains:write",
26
+ "",
27
+ "Hosted MCP is for cloud/browser agents. It does not read local paths; use the hosted workspace tools or signed uploads for artifacts.",
28
+ `Guide: ${guideUrl}`,
29
+ `Plain-text setup contract: ${agentSetupTextUrl}`,
30
+ ].join("\n");
31
+ }
32
+ function localMcpText(label, guideUrl) {
33
+ return [
34
+ `Set up ${BRAND.productName} for ${label} with local MCP when it needs your checkout or filesystem.`,
35
+ "",
36
+ `Install CLI: ${spacefastInstallCurlCommand}`,
37
+ "Configure the agent: sf setup agent --agent auto --connect",
38
+ "Install local stdio MCP: sf mcp install --agent auto --mode local --transport stdio --connect",
39
+ "Run stdio server directly: sf mcp",
40
+ "",
41
+ `Guide: ${guideUrl}`,
42
+ `Plain-text setup contract: ${agentSetupTextUrl}`,
43
+ ].join("\n");
44
+ }
45
+ export const agentSolutions = [
46
+ {
47
+ id: "codex",
48
+ label: "Codex",
49
+ aliases: ["codex cli", "codex plugin", "openai codex"],
50
+ lane: "plugin",
51
+ summary: "Recommended: Codex plugin.",
52
+ description: `Install the ${BRAND.productName} skill/plugin so Codex can publish, update, and inspect spaces from the same setup path.`,
53
+ commands: [spacefastCodexSkillInstallCommand],
54
+ guideUrl: `${BRAND.websiteOrigin}/for-codex`,
55
+ detailGuideUrl: `${BRAND.websiteOrigin}/guides/how-to-use-with-codex`,
56
+ setupText: pluginText("Codex", [spacefastCodexSkillInstallCommand], `${BRAND.websiteOrigin}/for-codex`),
57
+ },
58
+ {
59
+ id: "claude-code",
60
+ label: "Claude Code",
61
+ aliases: ["claude code", "claude cli"],
62
+ lane: "plugin",
63
+ summary: "Recommended: Claude plugin.",
64
+ description: `Install the ${BRAND.productName} Claude plugin once, then Claude Code can publish and operate spaces from the terminal.`,
65
+ commands: [spacefastClaudePluginAddCommand, spacefastClaudePluginInstallCommand],
66
+ guideUrl: `${BRAND.websiteOrigin}/for-claude`,
67
+ detailGuideUrl: `${BRAND.websiteOrigin}/guides/how-to-use-with-claude-code`,
68
+ setupText: pluginText("Claude Code", [spacefastClaudePluginAddCommand, spacefastClaudePluginInstallCommand], `${BRAND.websiteOrigin}/guides/how-to-use-with-claude-code`),
69
+ },
70
+ {
71
+ id: "claude-cowork",
72
+ label: "Claude Cowork",
73
+ aliases: ["cowork", "claude cowork"],
74
+ lane: "plugin",
75
+ summary: "Recommended: Claude plugin.",
76
+ description: `Use the ${BRAND.productName} Claude plugin path so Cowork gets the same publish instructions and MCP-aware setup as Claude Code.`,
77
+ commands: [spacefastClaudePluginAddCommand, spacefastClaudePluginInstallCommand],
78
+ guideUrl: `${BRAND.websiteOrigin}/for-claude`,
79
+ detailGuideUrl: `${BRAND.websiteOrigin}/guides/how-to-use-with-claude-cowork`,
80
+ setupText: pluginText("Claude Cowork", [spacefastClaudePluginAddCommand, spacefastClaudePluginInstallCommand, agentSetupPrompt], `${BRAND.websiteOrigin}/guides/how-to-use-with-claude-cowork`),
81
+ },
82
+ {
83
+ id: "chatgpt",
84
+ label: "ChatGPT",
85
+ aliases: ["chatgpt", "custom gpt", "openai apps"],
86
+ lane: "hosted-mcp",
87
+ summary: "Recommended: direct hosted MCP.",
88
+ description: `Connect ChatGPT-style cloud agents to ${BRAND.productName} through hosted MCP; use the virtual workspace or signed uploads instead of local paths.`,
89
+ commands: [hostedMcpEndpointUrl],
90
+ guideUrl: `${BRAND.websiteOrigin}/docs/agents`,
91
+ setupText: hostedMcpText("ChatGPT", `${BRAND.websiteOrigin}/docs/agents`),
92
+ },
93
+ {
94
+ id: "claude-ai",
95
+ label: "Claude.ai",
96
+ aliases: ["claude.ai", "claude app", "claude web"],
97
+ lane: "hosted-mcp",
98
+ summary: "Recommended: direct hosted MCP.",
99
+ description: `Connect the web Claude app to ${BRAND.productName} through hosted MCP when it cannot read your local checkout.`,
100
+ commands: [hostedMcpEndpointUrl],
101
+ guideUrl: `${BRAND.websiteOrigin}/docs/agents`,
102
+ setupText: hostedMcpText("Claude.ai", `${BRAND.websiteOrigin}/docs/agents`),
103
+ },
104
+ {
105
+ id: "raycast",
106
+ label: "Raycast",
107
+ aliases: ["raycast", "raycast ai"],
108
+ lane: "hosted-mcp",
109
+ summary: "Recommended: direct hosted MCP.",
110
+ description: `Use hosted MCP for Raycast and similar clients that can connect to a remote MCP server but should not need a local ${BRAND.productName} install.`,
111
+ commands: [hostedMcpEndpointUrl],
112
+ guideUrl: `${BRAND.websiteOrigin}/docs/agents`,
113
+ setupText: hostedMcpText("Raycast", `${BRAND.websiteOrigin}/docs/agents`),
114
+ },
115
+ {
116
+ id: "cursor",
117
+ label: "Cursor or MCP client",
118
+ aliases: ["cursor", "mcp client", "windsurf", "opencode"],
119
+ lane: "local-mcp",
120
+ summary: "Recommended: local MCP when it needs the checkout.",
121
+ description: `Use On-Device MCP for editor agents that need local files; use hosted MCP instead when they only need cloud-safe tools.`,
122
+ commands: [
123
+ "sf setup agent --agent auto --connect",
124
+ "sf mcp install --agent auto --mode local --transport stdio --connect",
125
+ ],
126
+ guideUrl: `${BRAND.websiteOrigin}/docs/agents`,
127
+ setupText: localMcpText("Cursor or another MCP client", `${BRAND.websiteOrigin}/docs/agents`),
128
+ },
129
+ {
130
+ id: "terminal",
131
+ label: "Terminal or CI",
132
+ aliases: ["terminal", "cli", "ci", "github actions"],
133
+ lane: "cli",
134
+ summary: `Recommended: ${BRAND.productName} CLI.`,
135
+ description: "Use the CLI for repeat publishes, CI jobs, JSON output, rollback, logs, and domains.",
136
+ commands: [spacefastInstallCurlCommand, "sf login", "sf publish {file-or-dir} --json"],
137
+ guideUrl: `${BRAND.websiteOrigin}/docs/cli`,
138
+ setupText: [
139
+ `Install CLI: ${spacefastInstallCurlCommand}`,
140
+ "Login: sf login",
141
+ "Publish: sf publish {file-or-dir} --json",
142
+ `Guide: ${BRAND.websiteOrigin}/docs/cli`,
143
+ ].join("\n"),
144
+ },
145
+ {
146
+ id: "generic",
147
+ label: "Generic agent prompt",
148
+ aliases: ["generic", "unknown", "gemini"],
149
+ lane: "prompt",
150
+ summary: "Recommended: generic prompt + setup contract.",
151
+ description: `Use this when the client has no known plugin or MCP connector. The agent reads ${BRAND.productName}'s setup contract and picks the smallest working lane.`,
152
+ guideUrl: agentSetupUrl,
153
+ setupText: agentSetupPrompt,
154
+ },
155
+ ];
156
+ export function getAgentSolution(id) {
157
+ const solution = agentSolutions.find((entry) => entry.id === id);
158
+ if (!solution)
159
+ throw new Error(`Unknown agent solution: ${id}`);
160
+ return solution;
161
+ }
162
+ export function renderAgentSolutionIndexText(solutions = agentSolutions) {
163
+ return [
164
+ `# ${BRAND.productName} agent setup options`,
165
+ "",
166
+ ...solutions.flatMap((solution) => [
167
+ `## ${solution.label}`,
168
+ "",
169
+ `Recommended lane: ${solution.lane}`,
170
+ solution.summary,
171
+ solution.description,
172
+ "",
173
+ `Guide: ${solution.guideUrl}`,
174
+ solution.detailGuideUrl ? `Detailed guide: ${solution.detailGuideUrl}` : null,
175
+ "",
176
+ "Setup:",
177
+ solution.setupText,
178
+ "",
179
+ ].filter((line) => Boolean(line))),
180
+ ].join("\n");
181
+ }