@spacefast/common 0.0.24 → 0.2.1

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 (320) hide show
  1. package/brand-assets/LICENSE-Haskoy.txt +93 -0
  2. package/brand-assets/haskoy-latin-variable.woff2 +0 -0
  3. package/brand-assets/spacefast-favicon.svg +4 -4
  4. package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
  5. package/brand-assets/spacefast-wordmark.svg +2 -4
  6. package/dist/agents/connect-targets.d.ts +6 -146
  7. package/dist/agents/connect-targets.js +63 -295
  8. package/dist/agents/private-key-oauth.d.ts +31 -2
  9. package/dist/agents/private-key-oauth.js +166 -92
  10. package/dist/agents/registry/core.d.ts +292 -0
  11. package/dist/agents/registry/core.js +1388 -0
  12. package/dist/agents/registry/deeplink-probes.d.ts +23 -0
  13. package/dist/agents/registry/deeplink-probes.js +18 -0
  14. package/dist/agents/registry/node.d.ts +56 -0
  15. package/dist/agents/registry/node.js +339 -0
  16. package/dist/brand-assets-build.js +3 -6
  17. package/dist/brand-assets.d.ts +5 -15
  18. package/dist/brand-assets.js +12 -14
  19. package/dist/brand.d.ts +17 -0
  20. package/dist/brand.js +24 -4
  21. package/dist/config/domains.d.ts +79 -12
  22. package/dist/config/domains.js +128 -29
  23. package/dist/config/index.js +1 -2
  24. package/dist/contracts/abuse.js +9 -24
  25. package/dist/contracts/access-profiles.d.ts +86 -0
  26. package/dist/contracts/access-profiles.js +89 -0
  27. package/dist/contracts/access.d.ts +24 -30
  28. package/dist/contracts/access.js +33 -64
  29. package/dist/contracts/activity.d.ts +18 -1
  30. package/dist/contracts/activity.js +72 -26
  31. package/dist/contracts/analytics.js +8 -10
  32. package/dist/contracts/api-keys.d.ts +77 -52
  33. package/dist/contracts/api-keys.js +44 -24
  34. package/dist/contracts/application-journal.d.ts +216 -0
  35. package/dist/contracts/application-journal.js +167 -0
  36. package/dist/contracts/auth.d.ts +90 -17
  37. package/dist/contracts/auth.js +86 -17
  38. package/dist/contracts/beta.d.ts +9 -4
  39. package/dist/contracts/beta.js +20 -16
  40. package/dist/contracts/billing.d.ts +2 -1
  41. package/dist/contracts/billing.js +37 -11
  42. package/dist/contracts/bootstrap.d.ts +791 -0
  43. package/dist/contracts/bootstrap.js +51 -0
  44. package/dist/contracts/builds.d.ts +209 -18
  45. package/dist/contracts/builds.js +93 -27
  46. package/dist/contracts/channels.d.ts +43 -0
  47. package/dist/contracts/channels.js +25 -9
  48. package/dist/contracts/cli.d.ts +7 -0
  49. package/dist/contracts/cli.js +6 -0
  50. package/dist/contracts/collab-public.d.ts +1 -1
  51. package/dist/contracts/collab-public.js +5 -8
  52. package/dist/contracts/comments.d.ts +44 -34
  53. package/dist/contracts/comments.js +107 -77
  54. package/dist/contracts/commerce.d.ts +165 -0
  55. package/dist/contracts/commerce.js +146 -0
  56. package/dist/contracts/common.d.ts +3 -3
  57. package/dist/contracts/common.js +20 -31
  58. package/dist/contracts/content-contract-verification.d.ts +521 -0
  59. package/dist/contracts/content-contract-verification.js +320 -0
  60. package/dist/contracts/content-platform-fixture.d.ts +1278 -0
  61. package/dist/contracts/content-platform-fixture.js +44 -0
  62. package/dist/contracts/content-program.d.ts +994 -0
  63. package/dist/contracts/content-program.js +658 -0
  64. package/dist/contracts/content-sync.d.ts +290 -0
  65. package/dist/contracts/content-sync.js +167 -0
  66. package/dist/contracts/content.d.ts +432 -0
  67. package/dist/contracts/content.js +303 -0
  68. package/dist/contracts/continuation.d.ts +17 -1
  69. package/dist/contracts/continuation.js +23 -23
  70. package/dist/contracts/countries.js +3 -4
  71. package/dist/contracts/crons.d.ts +52 -0
  72. package/dist/contracts/crons.js +60 -0
  73. package/dist/contracts/dashboard-prefs.d.ts +56 -0
  74. package/dist/contracts/dashboard-prefs.js +51 -0
  75. package/dist/contracts/device-auth.d.ts +234 -37
  76. package/dist/contracts/device-auth.js +267 -53
  77. package/dist/contracts/docs.d.ts +1 -5
  78. package/dist/contracts/docs.js +7 -15
  79. package/dist/contracts/domains.d.ts +501 -93
  80. package/dist/contracts/domains.js +193 -55
  81. package/dist/contracts/email-preferences.d.ts +3 -3
  82. package/dist/contracts/email-preferences.js +6 -7
  83. package/dist/contracts/enums.d.ts +27 -26
  84. package/dist/contracts/enums.js +68 -61
  85. package/dist/contracts/error-code-meta.d.ts +283 -7
  86. package/dist/contracts/error-code-meta.js +97 -5
  87. package/dist/contracts/error-codes.d.ts +5 -5
  88. package/dist/contracts/error-codes.js +82 -17
  89. package/dist/contracts/events.d.ts +100 -20
  90. package/dist/contracts/events.js +107 -20
  91. package/dist/contracts/execution.d.ts +48 -64
  92. package/dist/contracts/execution.js +52 -62
  93. package/dist/contracts/feature-lifecycle.d.ts +73 -27
  94. package/dist/contracts/feature-lifecycle.js +86 -26
  95. package/dist/contracts/features.d.ts +68 -19
  96. package/dist/contracts/features.js +42 -41
  97. package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
  98. package/dist/contracts/frame-session.d.ts +192 -0
  99. package/dist/contracts/frame-session.js +164 -0
  100. package/dist/contracts/functions.d.ts +338 -164
  101. package/dist/contracts/functions.js +367 -198
  102. package/dist/contracts/git.d.ts +27 -33
  103. package/dist/contracts/git.js +10 -11
  104. package/dist/contracts/grant-copy.d.ts +43 -0
  105. package/dist/contracts/grant-copy.js +44 -0
  106. package/dist/contracts/grants.d.ts +62 -54
  107. package/dist/contracts/grants.js +124 -23
  108. package/dist/contracts/ids.d.ts +4 -1
  109. package/dist/contracts/ids.js +6 -3
  110. package/dist/contracts/internal.d.ts +8 -18
  111. package/dist/contracts/internal.js +13 -14
  112. package/dist/contracts/mcp.d.ts +28 -0
  113. package/dist/contracts/mcp.js +28 -0
  114. package/dist/contracts/me.d.ts +6 -1
  115. package/dist/contracts/me.js +12 -6
  116. package/dist/contracts/notifications.d.ts +27 -29
  117. package/dist/contracts/notifications.js +9 -4
  118. package/dist/contracts/oauth-resources.d.ts +112 -10
  119. package/dist/contracts/oauth-resources.js +116 -11
  120. package/dist/contracts/oauth-scope-actions.d.ts +90 -0
  121. package/dist/contracts/oauth-scope-actions.js +126 -0
  122. package/dist/contracts/operations.d.ts +27 -4
  123. package/dist/contracts/operations.js +12 -6
  124. package/dist/contracts/pages.d.ts +31 -7
  125. package/dist/contracts/pages.js +22 -5
  126. package/dist/contracts/partner-notifications.d.ts +3 -0
  127. package/dist/contracts/partner-notifications.js +65 -0
  128. package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
  129. package/dist/contracts/{platform.js → partner.js} +27 -40
  130. package/dist/contracts/plan-policy.js +1 -1
  131. package/dist/contracts/principal-assertion.d.ts +43 -0
  132. package/dist/contracts/principal-assertion.js +67 -0
  133. package/dist/contracts/principals.d.ts +73 -0
  134. package/dist/contracts/principals.js +59 -0
  135. package/dist/contracts/privacy.d.ts +11 -0
  136. package/dist/contracts/privacy.js +17 -0
  137. package/dist/contracts/problem-document.js +3 -4
  138. package/dist/contracts/publish-archive.d.ts +269 -161
  139. package/dist/contracts/publish-archive.js +11 -1
  140. package/dist/contracts/push-new.d.ts +8 -8
  141. package/dist/contracts/quotas.js +5 -5
  142. package/dist/contracts/realtime.d.ts +52 -0
  143. package/dist/contracts/realtime.js +54 -0
  144. package/dist/contracts/repository-connections.d.ts +44 -31
  145. package/dist/contracts/repository-connections.js +17 -5
  146. package/dist/contracts/resources.d.ts +105 -14
  147. package/dist/contracts/resources.js +150 -26
  148. package/dist/contracts/route-inventory.d.ts +358 -0
  149. package/dist/contracts/route-inventory.js +141 -0
  150. package/dist/contracts/runtime-api.d.ts +206 -82
  151. package/dist/contracts/runtime-api.js +131 -70
  152. package/dist/contracts/runtime-app.d.ts +13 -23
  153. package/dist/contracts/runtime-app.js +39 -24
  154. package/dist/contracts/runtime-components.d.ts +143 -0
  155. package/dist/contracts/runtime-components.js +199 -0
  156. package/dist/contracts/runtime-db.d.ts +49 -23
  157. package/dist/contracts/runtime-db.js +87 -22
  158. package/dist/contracts/runtime-purge.d.ts +8 -0
  159. package/dist/contracts/runtime-purge.js +5 -0
  160. package/dist/contracts/runtime-services.d.ts +43 -75
  161. package/dist/contracts/runtime-services.js +29 -49
  162. package/dist/contracts/runtime-storage.d.ts +15 -14
  163. package/dist/contracts/runtime-storage.js +30 -14
  164. package/dist/contracts/sf-config-v1.d.ts +48 -14
  165. package/dist/contracts/slugs.js +13 -4
  166. package/dist/contracts/space-config.d.ts +283 -19
  167. package/dist/contracts/space-config.js +430 -80
  168. package/dist/contracts/spaces.d.ts +376 -290
  169. package/dist/contracts/spaces.js +200 -135
  170. package/dist/contracts/superadmin-activity.d.ts +3 -43
  171. package/dist/contracts/superadmin-activity.js +6 -30
  172. package/dist/contracts/superadmin-emails.d.ts +1 -0
  173. package/dist/contracts/superadmin-emails.js +17 -1
  174. package/dist/contracts/superadmin-runtime.d.ts +199 -0
  175. package/dist/contracts/superadmin-runtime.js +141 -0
  176. package/dist/contracts/superadmin-search.d.ts +22 -2
  177. package/dist/contracts/superadmin-search.js +14 -6
  178. package/dist/contracts/superadmin-spaces.d.ts +438 -189
  179. package/dist/contracts/superadmin-spaces.js +131 -25
  180. package/dist/contracts/superadmin-teams.d.ts +5 -2
  181. package/dist/contracts/superadmin-teams.js +1 -2
  182. package/dist/contracts/superadmin-tenants.d.ts +627 -12
  183. package/dist/contracts/superadmin-tenants.js +32 -13
  184. package/dist/contracts/superadmin.d.ts +232 -37
  185. package/dist/contracts/superadmin.js +106 -22
  186. package/dist/contracts/tags.d.ts +85 -58
  187. package/dist/contracts/tags.js +47 -37
  188. package/dist/contracts/teams.d.ts +86 -26
  189. package/dist/contracts/teams.js +90 -31
  190. package/dist/contracts/tenants.d.ts +135 -0
  191. package/dist/contracts/tenants.js +120 -0
  192. package/dist/contracts/test-triggers.d.ts +70 -0
  193. package/dist/contracts/test-triggers.js +116 -0
  194. package/dist/contracts/theme-json.js +3 -8
  195. package/dist/contracts/transfers.d.ts +1 -0
  196. package/dist/contracts/transfers.js +3 -2
  197. package/dist/contracts/usage.d.ts +209 -0
  198. package/dist/contracts/usage.js +124 -12
  199. package/dist/contracts/variables.d.ts +2 -2
  200. package/dist/contracts/variables.js +9 -13
  201. package/dist/contracts/webhooks.d.ts +13 -5
  202. package/dist/contracts/webhooks.js +3 -1
  203. package/dist/contracts/zero.d.ts +72 -81
  204. package/dist/contracts/zero.js +80 -78
  205. package/dist/dashboard-paths/index.d.ts +24 -21
  206. package/dist/dashboard-paths/index.js +35 -47
  207. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
  208. package/dist/dashboard-paths/route-reservations.generated.js +36 -0
  209. package/dist/docs/agent-arrival.d.ts +92 -0
  210. package/dist/docs/agent-arrival.js +227 -0
  211. package/dist/docs/agent-output-policy.d.ts +6 -0
  212. package/dist/docs/agent-output-policy.js +22 -0
  213. package/dist/docs/agent-prose.d.ts +12 -18
  214. package/dist/docs/agent-prose.js +73 -51
  215. package/dist/docs/agent-setup.d.ts +72 -41
  216. package/dist/docs/agent-setup.js +339 -436
  217. package/dist/docs/catalog.d.ts +153 -95
  218. package/dist/docs/catalog.js +163 -48
  219. package/dist/docs/error-docs.d.ts +2098 -1
  220. package/dist/docs/error-docs.js +421 -144
  221. package/dist/docs/index-build.d.ts +0 -1
  222. package/dist/docs/index-build.js +2 -3
  223. package/dist/docs/page-context-prompt.d.ts +77 -0
  224. package/dist/docs/page-context-prompt.js +131 -0
  225. package/dist/docs/reference-urls.d.ts +46 -0
  226. package/dist/docs/reference-urls.js +79 -0
  227. package/dist/docs/search.d.ts +6 -8
  228. package/dist/docs/search.js +10 -13
  229. package/dist/docs/skill-distribution.d.ts +4 -1
  230. package/dist/docs/skill-distribution.js +10 -5
  231. package/dist/docs/skills.d.ts +120 -120
  232. package/dist/docs/skills.js +121 -73
  233. package/dist/docs/start-prompts.d.ts +45 -0
  234. package/dist/docs/start-prompts.js +62 -0
  235. package/dist/domain-exploration.js +9 -13
  236. package/dist/og-template.d.ts +56 -0
  237. package/dist/og-template.js +56 -0
  238. package/dist/slug-policy/index.js +5 -11
  239. package/dist/test-helpers/fetch-stub.js +1 -0
  240. package/dist/utils/browser-credential.d.ts +13 -6
  241. package/dist/utils/browser-credential.js +14 -7
  242. package/dist/utils/build-settings.d.ts +21 -1
  243. package/dist/utils/build-settings.js +133 -15
  244. package/dist/utils/canonical-json.d.ts +3 -5
  245. package/dist/utils/canonical-json.js +4 -5
  246. package/dist/utils/cast-transport.d.ts +5 -17
  247. package/dist/utils/cast-transport.js +6 -19
  248. package/dist/utils/claim-token.js +1 -2
  249. package/dist/utils/comment-avatars.js +4 -6
  250. package/dist/utils/concurrency.js +3 -3
  251. package/dist/utils/content-type.d.ts +7 -6
  252. package/dist/utils/content-type.js +73 -51
  253. package/dist/utils/credential-policy.d.ts +50 -6
  254. package/dist/utils/credential-policy.js +126 -59
  255. package/dist/utils/dns-instructions.js +9 -9
  256. package/dist/utils/egress-policy.fixtures.json +1 -1
  257. package/dist/utils/error-code.js +1 -0
  258. package/dist/utils/generate-space-name.d.ts +3 -8
  259. package/dist/utils/generate-space-name.js +3 -8
  260. package/dist/utils/grant-decision.fixtures.json +2 -2
  261. package/dist/utils/grants.d.ts +0 -1
  262. package/dist/utils/grants.js +7 -8
  263. package/dist/utils/gravatar.d.ts +2 -4
  264. package/dist/utils/gravatar.js +6 -12
  265. package/dist/utils/id-hints.js +14 -23
  266. package/dist/utils/idempotency.d.ts +3 -3
  267. package/dist/utils/idempotency.js +6 -7
  268. package/dist/utils/local-space-state.d.ts +23 -26
  269. package/dist/utils/local-space-state.js +46 -46
  270. package/dist/utils/oauth-signed-query.d.ts +3 -5
  271. package/dist/utils/oauth-signed-query.js +6 -9
  272. package/dist/utils/one-shot-replay.d.ts +7 -6
  273. package/dist/utils/one-shot-replay.js +3 -5
  274. package/dist/utils/page-colors.d.ts +15 -19
  275. package/dist/utils/page-colors.js +23 -45
  276. package/dist/utils/page-fonts.d.ts +1 -1
  277. package/dist/utils/page-fonts.js +8 -20
  278. package/dist/utils/page-preview.d.ts +0 -1
  279. package/dist/utils/page-preview.js +0 -1
  280. package/dist/utils/pages.d.ts +33 -3
  281. package/dist/utils/pages.js +83 -30
  282. package/dist/utils/privacy-regions.d.ts +19 -0
  283. package/dist/utils/privacy-regions.js +91 -0
  284. package/dist/utils/publish-form-data.d.ts +4 -5
  285. package/dist/utils/publish-form-data.js +2 -3
  286. package/dist/utils/publish-policy.d.ts +11 -17
  287. package/dist/utils/publish-policy.fixtures.json +11 -5
  288. package/dist/utils/publish-policy.js +65 -110
  289. package/dist/utils/query-client.d.ts +2 -0
  290. package/dist/utils/query-client.js +10 -1
  291. package/dist/utils/runtime-paths.d.ts +4 -4
  292. package/dist/utils/runtime-paths.js +6 -8
  293. package/dist/utils/runtime-upload.d.ts +9 -0
  294. package/dist/utils/runtime-upload.js +12 -0
  295. package/dist/utils/secure-local-file.d.ts +3 -3
  296. package/dist/utils/secure-local-file.js +23 -40
  297. package/dist/utils/space-config.d.ts +12 -15
  298. package/dist/utils/space-config.js +13 -16
  299. package/dist/utils/static-runtime-policy.d.ts +10 -7
  300. package/dist/utils/static-runtime-policy.fixtures.json +38 -41
  301. package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
  302. package/dist/utils/static-runtime-policy.generated.js +70 -0
  303. package/dist/utils/static-runtime-policy.js +31 -132
  304. package/dist/utils/storage-policy.js +1 -2
  305. package/dist/utils/upload-session.d.ts +4 -1
  306. package/dist/utils/upload-session.js +41 -19
  307. package/dist/utils/version-path.js +11 -19
  308. package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
  309. package/dist/utils/wpcom-auth-redirects.js +1 -4
  310. package/dist/vocabulary.d.ts +13 -13
  311. package/dist/vocabulary.js +52 -27
  312. package/package.json +15 -18
  313. package/dist/agents/client-registry.d.ts +0 -22
  314. package/dist/agents/client-registry.js +0 -41
  315. package/dist/docs/agent-handoff-document.d.ts +0 -15
  316. package/dist/docs/agent-handoff-document.js +0 -180
  317. package/dist/docs/agent-solutions.d.ts +0 -110
  318. package/dist/docs/agent-solutions.js +0 -183
  319. package/dist/utils/query-keys.d.ts +0 -84
  320. package/dist/utils/query-keys.js +0 -108
@@ -0,0 +1,1388 @@
1
+ /**
2
+ * Canonical, browser-safe agent integration registry.
3
+ *
4
+ * Keep this module free of Node built-ins, filesystem access, and environment
5
+ * reads. Runtime-specific path and config mechanics live in ./node.ts.
6
+ */
7
+ import { serviceOrigin } from "../../config/domains.js";
8
+ import { DEEPLINK_PROBE_RESULTS } from "./deeplink-probes.js";
9
+ export const AGENT_IDS = [
10
+ "claude-code",
11
+ "claude-app",
12
+ "claude-desktop",
13
+ "cursor",
14
+ "codex",
15
+ "chatgpt",
16
+ "vscode",
17
+ "github-copilot",
18
+ "devin-desktop",
19
+ "devin-cloud",
20
+ "zed",
21
+ "gemini-cli",
22
+ "opencode",
23
+ "amp",
24
+ "warp",
25
+ "factory-droid",
26
+ "cline",
27
+ "continue",
28
+ "raycast",
29
+ "poke",
30
+ "pi",
31
+ "indent",
32
+ "hermes",
33
+ "openclaw",
34
+ ];
35
+ // Setup commands are durable instructions, not release snapshots. Let npx
36
+ // resolve the current installer instead of baking a stale version into every
37
+ // dashboard page, generated guide, and agent fixture.
38
+ export const SKILLS_CLI_PACKAGE_SPEC = "skills";
39
+ export const SPACEFAST_CLI_PACKAGE_SPEC = "spacefast";
40
+ export const PLUGINS_CLI_PACKAGE_SPEC = "plugins";
41
+ /**
42
+ * The complete `-t` target set the universal plugin installer implements.
43
+ * Anything outside this list has no install
44
+ * mechanics upstream: `npx plugins add … -t <id>` either errors or synthesizes a
45
+ * manifest, so `sf plugins` must refuse it rather than run a command that cannot
46
+ * work. Capability flags are NOT a substitute — several entries carry a `plugin`
47
+ * capability because a vendor-native plugin lane exists, not because this
48
+ * installer can drive it.
49
+ */
50
+ export const PLUGINS_CLI_INSTALLER_TARGETS = [
51
+ "claude-code",
52
+ "cursor",
53
+ "codex",
54
+ "github-copilot",
55
+ "vscode",
56
+ "grok",
57
+ "kimi",
58
+ ];
59
+ export const AGENT_DISTRIBUTION_PROFILE = {
60
+ author: { name: "Automattic", url: "https://spacefast.com" },
61
+ description: "Spacefast publishes files and folders to permanent web URLs with immutable versions, scoped access, and resumable MCP workflows.",
62
+ displayName: "Spacefast",
63
+ homepage: "https://spacefast.com",
64
+ keywords: ["spacefast", "publish-to-spacefast", "artifact-sharing", "hosting"],
65
+ license: "MIT",
66
+ name: "spacefast",
67
+ privacyPolicyUrl: "https://automattic.com/privacy/",
68
+ repository: "https://github.com/spacefast/plugins",
69
+ reviewerAccount: {
70
+ environment: "marketplace-reviewers",
71
+ secret: "SPACEFAST_REVIEWER_ACCOUNT",
72
+ },
73
+ shortDescription: "Publish files and folders to the web instantly.",
74
+ termsOfServiceUrl: "https://wordpress.com/tos/",
75
+ };
76
+ export const AGENTS_MD_SPACEFAST_START = "<!-- SPACEFAST START -->";
77
+ export const AGENTS_MD_SPACEFAST_END = "<!-- SPACEFAST END -->";
78
+ export const AGENTS_MD_SPACEFAST_SNIPPET = [
79
+ AGENTS_MD_SPACEFAST_START,
80
+ "Spacefast agent index: CHECK existing `.spacefast/space.json` or receipt before creating; PUBLISH only the intended files from the output root; SAVE `.spacefast/state.json` as private credential cache; UPDATE by reusing `spaceId` plus bearer auth; CONTINUE after claim by exchanging the saved claim token once.",
81
+ "",
82
+ "State contract: `.spacefast/space.json` is non-secret and can name the space; `.spacefast/state.json` can contain claim tokens or API keys and must not be printed, committed, archived, or published.",
83
+ "",
84
+ "Links ladder: error recovery uses the problem document's `type`; capability discovery uses `execute` with the OpenAPI catalog; Markdown docs use `.md` suffix; publish contract is https://spacefast.com/publish-spec.json; API is https://spacefast.com/openapi.json.",
85
+ AGENTS_MD_SPACEFAST_END,
86
+ ].join("\n");
87
+ export const SOURCE_LANES = [
88
+ {
89
+ id: "drop",
90
+ label: "Drop files",
91
+ description: "Drop a folder or archive and put it live now.",
92
+ href: "/drop",
93
+ icon: "upload",
94
+ },
95
+ {
96
+ id: "push",
97
+ label: "Push directly",
98
+ description: "Push a Git repository to Spacefast and publish every update.",
99
+ href: "/push",
100
+ icon: "git",
101
+ },
102
+ {
103
+ id: "github",
104
+ label: "Connect a GitHub repository",
105
+ description: "Connect GitHub and build and publish on every push.",
106
+ href: "https://spacefast.com/docs/builds",
107
+ icon: "github",
108
+ },
109
+ {
110
+ id: "terminal",
111
+ label: "Command line",
112
+ description: "Publish from the terminal with the Spacefast CLI.",
113
+ // The CLI setup sheet — install, sign in, publish. Not `/terminal`, which
114
+ // is the in-browser shell: a different surface that assumes you already
115
+ // have the CLI story.
116
+ href: "/start?step=terminal",
117
+ icon: "terminal",
118
+ },
119
+ {
120
+ id: "api",
121
+ label: "API",
122
+ description: "Publish or automate with the REST API and OpenAPI.",
123
+ href: "https://spacefast.com/docs/api",
124
+ icon: "api",
125
+ },
126
+ ];
127
+ const CHECKED_AT = "2026-08-23";
128
+ const SPACEFAST_PLUGINS_REPO = "https://github.com/spacefast/plugins";
129
+ const SPACEFAST_DOCS = "https://spacefast.com/docs/agents";
130
+ const SPACEFAST_SKILL = "https://spacefast.com/SKILL.md";
131
+ /**
132
+ * Interactive-primary ranking is the conversion ladder, stated once as data:
133
+ * a button beats a paste beats a terminal. An explicit full setup command may
134
+ * lead when it is the cleanest client path; raw client-specific MCP commands
135
+ * stay out of the interactive context.
136
+ */
137
+ const PRIMARY_RANK = {
138
+ deeplink: 10,
139
+ download: 15,
140
+ plugin: 20,
141
+ nativeFlow: 30,
142
+ /** Only where the skill IS the client's whole integration (Continue). */
143
+ skill: 35,
144
+ config: 50,
145
+ /** A listing that is not live yet: rendered as a "soon" chip, never leading. */
146
+ soon: 60,
147
+ };
148
+ /**
149
+ * The terminal lane, ordered by the npx priority rule: our own installer leads
150
+ * for clients the CLI drives as machine clients, the universal skills installer
151
+ * carries the long tail, and the client's own commands sit between them.
152
+ */
153
+ const TERMINAL_RANK = {
154
+ cliSetup: 5,
155
+ npxSetup: 7,
156
+ npxPlugin: 8,
157
+ plugin: 10,
158
+ command: 20,
159
+ config: 30,
160
+ skill: 40,
161
+ // Last of the terminal lanes: it publishes rather than sets anything up, so
162
+ // it is what a reader reaches after the lanes that make `sf` exist.
163
+ gitPush: 50,
164
+ };
165
+ function verification(sourceUrl, evidence) {
166
+ const result = {
167
+ checkedAt: CHECKED_AT,
168
+ sourceUrl,
169
+ status: "declared",
170
+ };
171
+ if (evidence !== undefined)
172
+ result.evidence = evidence;
173
+ return result;
174
+ }
175
+ function methodBase(input) {
176
+ const listing = {
177
+ checkedAt: CHECKED_AT,
178
+ status: input.listingStatus ?? "live",
179
+ };
180
+ if (input.listingUrl !== undefined)
181
+ listing.url = input.listingUrl;
182
+ const method = {
183
+ authLane: input.authLane,
184
+ availability: { clients: [input.agentId] },
185
+ docsUrl: input.docsUrl ?? SPACEFAST_DOCS,
186
+ id: input.id,
187
+ label: input.label,
188
+ listing,
189
+ rank: input.rank,
190
+ summary: input.summary,
191
+ verification: verification(input.sourceUrl),
192
+ };
193
+ if (input.tokenSlot !== undefined)
194
+ method.tokenSlot = input.tokenSlot;
195
+ if (input.vendorDocs !== undefined)
196
+ method.vendorDocs = input.vendorDocs;
197
+ return method;
198
+ }
199
+ function hostedConfig(agentId, path, language, build, sourceUrl) {
200
+ return {
201
+ ...methodBase({
202
+ agentId,
203
+ authLane: "oauth",
204
+ id: "hosted-mcp-config",
205
+ label: `Configure ${path}`,
206
+ rank: {
207
+ "interactive-primary": PRIMARY_RANK.config,
208
+ "machine-installer": 20,
209
+ "static-docs": 30,
210
+ "terminal-alternates": TERMINAL_RANK.config,
211
+ },
212
+ sourceUrl,
213
+ summary: "Merge the hosted Spacefast server into the client MCP configuration.",
214
+ }),
215
+ build,
216
+ kind: "config",
217
+ language,
218
+ path,
219
+ };
220
+ }
221
+ /**
222
+ * A client whose only first-party lane is its own settings form (Raycast,
223
+ * Indent). The value is the endpoint the user pastes; `path` names where. It
224
+ * outranks the generic prompt because it is the best method that actually
225
+ * completes setup for that client, and it sits below deeplinks and plugins.
226
+ */
227
+ function hostedFormConfig(agentId, path, sourceUrl, summary) {
228
+ return {
229
+ ...methodBase({
230
+ agentId,
231
+ authLane: "oauth",
232
+ id: "hosted-mcp-form",
233
+ label: "Add the hosted MCP server",
234
+ rank: {
235
+ "interactive-primary": PRIMARY_RANK.nativeFlow,
236
+ "static-docs": 12,
237
+ },
238
+ sourceUrl,
239
+ summary: summary ?? `Paste the Spacefast endpoint into ${path} and approve OAuth in the browser.`,
240
+ }),
241
+ build: (context) => context.mcpEndpoint,
242
+ kind: "config",
243
+ language: "text",
244
+ path,
245
+ };
246
+ }
247
+ /**
248
+ * A client's own plugin install, in that client's own commands.
249
+ *
250
+ * BOOTSTRAP RULE: nothing rendered here may assume `sf` is already installed.
251
+ * A page that answers "how do I connect this?" with `sf plugins …` has answered
252
+ * a different question — how do I connect this, given I already installed the
253
+ * thing I am trying to install. So the shell commands are the vendor's
254
+ * (`claude plugin …`, `codex plugin …`, `droid plugin …`) or a pinned `npx`
255
+ * invocation. `installerTargets` is separate metadata: it is the `sf plugins`
256
+ * gate, not the rendered command.
257
+ */
258
+ function plugin(input) {
259
+ const method = {
260
+ ...methodBase({
261
+ agentId: input.agentId,
262
+ authLane: "oauth",
263
+ id: "plugin",
264
+ label: "Install the Spacefast plugin",
265
+ rank: {
266
+ "interactive-primary": PRIMARY_RANK.plugin,
267
+ "machine-installer": 30,
268
+ "static-docs": 10,
269
+ "terminal-alternates": TERMINAL_RANK.plugin,
270
+ },
271
+ sourceUrl: input.sourceUrl,
272
+ summary: input.summary,
273
+ vendorDocs: input.vendorDocs,
274
+ }),
275
+ authPolicy: "on-install",
276
+ commands: { inSession: [], shell: input.shell },
277
+ kind: "plugin",
278
+ marketplace: { id: "spacefast", url: input.marketplaceUrl ?? SPACEFAST_PLUGINS_REPO },
279
+ };
280
+ return input.installerTargets === undefined
281
+ ? method
282
+ : { ...method, installerTargets: input.installerTargets };
283
+ }
284
+ /** The universal plugin installer, for the targets it actually implements. */
285
+ function npxPluginInstall(agentId, sourceUrl) {
286
+ return {
287
+ ...methodBase({
288
+ agentId,
289
+ authLane: "oauth",
290
+ id: "npx-plugin-install",
291
+ label: "Install with npx plugins",
292
+ rank: { "terminal-alternates": TERMINAL_RANK.npxPlugin },
293
+ sourceUrl,
294
+ summary: "Install the plugin with the universal installer — nothing to install first.",
295
+ }),
296
+ authPolicy: "on-install",
297
+ commands: {
298
+ inSession: [],
299
+ shell: [`npx -y ${PLUGINS_CLI_PACKAGE_SPEC} add spacefast/plugins -t ${agentId} -y`],
300
+ },
301
+ installerTargets: [agentId],
302
+ kind: "plugin",
303
+ marketplace: { id: "spacefast", url: SPACEFAST_PLUGINS_REPO },
304
+ };
305
+ }
306
+ function cliSetup(agentId, options = {}) {
307
+ const rank = {
308
+ "machine-installer": 5,
309
+ "terminal-alternates": TERMINAL_RANK.cliSetup,
310
+ };
311
+ return {
312
+ ...methodBase({
313
+ agentId,
314
+ authLane: "oauth",
315
+ id: "cli-setup",
316
+ label: "Set up with the Spacefast CLI",
317
+ rank: options.interactive
318
+ ? { ...rank, "interactive-primary": PRIMARY_RANK.nativeFlow }
319
+ : rank,
320
+ sourceUrl: "https://spacefast.com/docs/cli",
321
+ summary: options.skillOnly
322
+ ? "Install the CLI once, then let it install the Spacefast skill for this agent."
323
+ : "Install the CLI once, then let it write MCP config, install the skill, and sign you in.",
324
+ }),
325
+ build: () => ["npm install -g spacefast", `sf setup agent --agent ${agentId}`],
326
+ kind: "command",
327
+ };
328
+ }
329
+ /** The same setup with nothing installed globally. */
330
+ function npxSetup(agentId, options = {}) {
331
+ return {
332
+ ...methodBase({
333
+ agentId,
334
+ authLane: "oauth",
335
+ id: "npx-setup",
336
+ label: "Set up without installing",
337
+ rank: {
338
+ "machine-installer": 7,
339
+ "terminal-alternates": TERMINAL_RANK.npxSetup,
340
+ },
341
+ sourceUrl: "https://spacefast.com/docs/cli",
342
+ summary: options.skillOnly
343
+ ? "Install the same Spacefast skill in one shot, with nothing left behind."
344
+ : "One-shot setup with no global install — same work, nothing left behind.",
345
+ }),
346
+ build: (context) => [
347
+ `npx -y ${context.packageSpecs?.spacefastCli ?? SPACEFAST_CLI_PACKAGE_SPEC} setup agent --agent ${agentId} -y`,
348
+ ],
349
+ kind: "command",
350
+ };
351
+ }
352
+ /**
353
+ * Push to deploy: the space's git remote is a real remote, and a push to it
354
+ * publishes.
355
+ *
356
+ * This is the one terminal lane that is not a setup step — nothing is
357
+ * installed and nothing is configured. It belongs here because for an agent
358
+ * already working in a git checkout it is the shortest path to a live URL, and
359
+ * because the push's own output carries the receipt back (live URL, version
360
+ * URL, and for an anonymous space the claim link), so the agent does not have
361
+ * to make a second call to learn where the site went.
362
+ *
363
+ * The remote is credential-free by design; the token rides in the HTTP Basic
364
+ * password slot as user `t`, which is why this method declares a token slot.
365
+ * The exact URL for a given space is on every publish receipt and on the Space
366
+ * resource as `git.remoteUrl` — this rendering shows its shape.
367
+ */
368
+ function gitPushDeploy(agentId) {
369
+ return {
370
+ ...methodBase({
371
+ agentId,
372
+ authLane: "token-slot",
373
+ id: "git-push",
374
+ label: "Push to deploy",
375
+ // Terminal lane only. This method connects nothing and installs nothing,
376
+ // so it must not compete in `machine-installer` (which picks what `sf
377
+ // setup agent` runs) or in `static-docs` (whose per-client table answers
378
+ // "connect with what?" — an answer this is not). The machine artifacts
379
+ // carry push-to-deploy as its own prose section instead.
380
+ rank: { "terminal-alternates": TERMINAL_RANK.gitPush },
381
+ sourceUrl: "https://spacefast.com/docs/git-push",
382
+ summary: "Push to deploy — output returns your live and claim links.",
383
+ tokenSlot: { accepts: ["api-key"], location: "command", placeholder: "{{token}}" },
384
+ }),
385
+ build: (context) => [
386
+ `git remote add spacefast ${pushRemoteTemplate(context)}`,
387
+ "git push spacefast main",
388
+ ],
389
+ kind: "command",
390
+ };
391
+ }
392
+ /**
393
+ * The shape of a space's push remote. `{{space}}` is the space slug and
394
+ * `{{token}}` a space key or API key; a publish receipt hands back the resolved
395
+ * URL in `git.remoteUrl`, so nothing has to reconstruct this by hand.
396
+ */
397
+ function pushRemoteTemplate(context) {
398
+ const origin = context.pushOrigin ?? serviceOrigin("push", "prod");
399
+ return `${origin.replace(/\/$/, "")}/{{space}}.git`.replace("://", "://t:{{token}}@");
400
+ }
401
+ /** Every terminal lane a machine client supports, ordered by the npx priority rule. */
402
+ function terminalLanes(agentId, options = {}) {
403
+ return [cliSetup(agentId, options), npxSetup(agentId, options), gitPushDeploy(agentId)];
404
+ }
405
+ /**
406
+ * A store listing that is not live yet. It carries no commands on purpose — it
407
+ * presents as a disabled "soon" affordance and is ranked below every working
408
+ * method, so a page always leads with something that works (spec §8.2).
409
+ */
410
+ function pluginMarketplace(agentId, sourceUrl, listingStatus) {
411
+ return {
412
+ ...methodBase({
413
+ agentId,
414
+ authLane: "oauth",
415
+ id: "plugin-marketplace",
416
+ label: "Install from the plugin marketplace",
417
+ listingStatus,
418
+ listingUrl: sourceUrl,
419
+ rank: { "interactive-primary": PRIMARY_RANK.soon, "static-docs": 15 },
420
+ sourceUrl,
421
+ summary: "The public marketplace listing is not live yet.",
422
+ }),
423
+ authPolicy: "on-install",
424
+ commands: { inSession: [], shell: [] },
425
+ kind: "plugin",
426
+ marketplace: { id: "spacefast", url: sourceUrl },
427
+ };
428
+ }
429
+ // TODO(skill-ids): spec §5.9 wants verb-noun skill ids for skills.sh ranking
430
+ // (`publish-to-spacefast`, mirroring `deploy-to-vercel`). The shipped skill is
431
+ // named `spacefast` — frontmatter `name:`, `plugins/*/skills/spacefast/`, and
432
+ // every install command — so this reference names what we actually ship. Renaming
433
+ // the skill is a coordinated change across the generators and both plugin trees.
434
+ const SHIPPED_SKILL_ID = "spacefast";
435
+ function skill(agentId, options = {}) {
436
+ // Continue reads `.claude/skills` natively and offers nothing else — for it
437
+ // the skill is not an alternate, it is the integration.
438
+ const rank = {
439
+ "machine-installer": 40,
440
+ "static-docs": 40,
441
+ "terminal-alternates": TERMINAL_RANK.skill,
442
+ };
443
+ const presentedRank = options.interactive
444
+ ? { ...rank, "interactive-primary": PRIMARY_RANK.skill }
445
+ : rank;
446
+ return {
447
+ ...methodBase({
448
+ agentId,
449
+ authLane: "anonymous",
450
+ id: "skill",
451
+ label: "Install the Spacefast skill",
452
+ rank: presentedRank,
453
+ sourceUrl: options.sourceUrl ?? SPACEFAST_SKILL,
454
+ summary: "Install publish and hosting guidance using the Agent Skills standard.",
455
+ }),
456
+ kind: "skill",
457
+ lane: "cli",
458
+ source: { id: SHIPPED_SKILL_ID, url: SPACEFAST_SKILL },
459
+ };
460
+ }
461
+ function universalPrompt(agentId, options = {}) {
462
+ const rank = { "static-docs": 50 };
463
+ const presentedRank = options.interactive
464
+ ? { ...rank, "interactive-primary": PRIMARY_RANK.nativeFlow }
465
+ : rank;
466
+ return {
467
+ ...methodBase({
468
+ agentId,
469
+ authLane: "anonymous",
470
+ id: "universal-prompt",
471
+ label: "Copy setup prompt",
472
+ // Usually the universal dock owns this one-paste affordance. Skill-native
473
+ // clients with no direct Spacefast integration use it as their registry
474
+ // primary too; the setup page leaves prompt primaries to the dock.
475
+ rank: presentedRank,
476
+ docsUrl: "https://spacefast.com/setup.md",
477
+ sourceUrl: "https://spacefast.com/setup.md",
478
+ summary: "Give the agent one prompt that lets it choose and complete the best setup lane.",
479
+ }),
480
+ build: (context) => `Fetch ${context.wwwOrigin}/setup.md`,
481
+ kind: "prompt",
482
+ };
483
+ }
484
+ function deeplink(input) {
485
+ const baseInput = {
486
+ agentId: input.agentId,
487
+ authLane: "oauth",
488
+ id: "deeplink",
489
+ label: input.label,
490
+ rank: { "interactive-primary": PRIMARY_RANK.deeplink, "static-docs": 15 },
491
+ sourceUrl: input.sourceUrl,
492
+ summary: input.summary,
493
+ };
494
+ if (input.listingStatus !== undefined)
495
+ baseInput.listingStatus = input.listingStatus;
496
+ return {
497
+ ...methodBase(baseInput),
498
+ build: input.build,
499
+ // The kind is a placeholder: entry() resolves it against the sibling method
500
+ // that `fallbackId` names, and throws when nothing resolves.
501
+ fallback: { id: input.fallbackId, kind: "deeplink" },
502
+ kind: "deeplink",
503
+ };
504
+ }
505
+ /**
506
+ * The generic hosted-MCP entry, for any client whose config is an `mcpServers`
507
+ * map. `type: "http"` is load-bearing and not decoration: Claude Code rejects a
508
+ * bare `{ url }` outright and VS Code reads one as a stdio command, so a bare
509
+ * url is not a smaller version of this — it is a broken version of it.
510
+ */
511
+ export function hostedMcpServersConfig(context) {
512
+ return jsonHostedConfig("mcpServers")(context);
513
+ }
514
+ function jsonHostedConfig(root) {
515
+ return (context) => JSON.stringify({
516
+ [root]: {
517
+ spacefast: root === "mcp"
518
+ ? { enabled: true, type: "remote", url: context.mcpEndpoint }
519
+ : root === "context_servers"
520
+ ? { settings: {}, source: "custom", url: context.mcpEndpoint }
521
+ : { type: "http", url: context.mcpEndpoint },
522
+ },
523
+ }, null, 2);
524
+ }
525
+ /**
526
+ * Applies the two invariants a hand-written method array cannot hold on its own:
527
+ * every deeplink's declared fallback resolves to a sibling method (and takes that
528
+ * sibling's real kind), and any probe result recorded for a deeplink overrides
529
+ * its `declared` verification status.
530
+ */
531
+ function resolveMethods(agentId, methods) {
532
+ return methods.map((method) => {
533
+ if (method.kind !== "deeplink")
534
+ return method;
535
+ const target = methods.find((candidate) => candidate.id === method.fallback.id);
536
+ if (!target || target === method) {
537
+ throw new TypeError(`Agent ${agentId} deeplink "${method.id}" declares fallback "${method.fallback.id}", which is not one of its methods.`);
538
+ }
539
+ const probe = DEEPLINK_PROBE_RESULTS[`${agentId}:${method.id}`];
540
+ const probed = { ...method.verification };
541
+ if (probe) {
542
+ probed.checkedAt = probe.checkedAt;
543
+ probed.status = probe.status;
544
+ if (probe.evidence !== undefined)
545
+ probed.evidence = probe.evidence;
546
+ }
547
+ return {
548
+ ...method,
549
+ fallback: { id: target.id, kind: target.kind },
550
+ verification: probed,
551
+ };
552
+ });
553
+ }
554
+ function entry(input) {
555
+ return {
556
+ ...input,
557
+ icon: input.icon ?? input.id,
558
+ legacyResultAliases: input.legacyResultAliases ?? [],
559
+ methods: resolveMethods(input.id, input.methods),
560
+ monogram: input.monogram ?? input.name.slice(0, 2),
561
+ verification: verification(input.docs.vendor),
562
+ };
563
+ }
564
+ const claudeCodeSource = "https://docs.anthropic.com/en/docs/claude-code/mcp";
565
+ const claudeAppSource = "https://claude.com/docs/connectors/building/directory-vs-custom";
566
+ const cursorSource = "https://docs.cursor.com/context/model-context-protocol";
567
+ const codexSource = "https://developers.openai.com/codex/mcp";
568
+ const chatgptSource = "https://platform.openai.com/docs/guides/developer-mode";
569
+ const vscodeSource = "https://code.visualstudio.com/docs/copilot/chat/mcp-servers";
570
+ export const AGENT_REGISTRY = [
571
+ entry({
572
+ id: "claude-code",
573
+ aliases: ["claude", "claude cli", "claude-code-cli"],
574
+ name: "Claude Code",
575
+ vendor: "Anthropic",
576
+ description: "Publish, host, and operate Spacefast artifacts from Claude Code.",
577
+ detectionHints: ["CLAUDECODE", ".claude"],
578
+ kind: "coding-agent",
579
+ tier: "featured",
580
+ wizardGroup: "claude",
581
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
582
+ skills: { agentsDirs: true, claudeDirs: true },
583
+ agentsMd: "native",
584
+ docs: { spacefast: "https://spacefast.com/setup/claude-code", vendor: claudeCodeSource },
585
+ machineClient: true,
586
+ redeemableClientTargetId: "claude-code",
587
+ legacyResultAliases: ["claude-code", "claude"],
588
+ methods: [
589
+ plugin({
590
+ agentId: "claude-code",
591
+ installerTargets: ["claude-code"],
592
+ shell: [
593
+ "claude plugin marketplace add spacefast/plugins",
594
+ "claude plugin install spacefast@spacefast",
595
+ ],
596
+ sourceUrl: "https://docs.anthropic.com/en/docs/claude-code/plugin-marketplaces",
597
+ summary: "Add the Spacefast marketplace and install the plugin with Claude Code itself.",
598
+ vendorDocs: {
599
+ href: "https://docs.anthropic.com/en/docs/claude-code/plugin-marketplaces",
600
+ label: "Claude's official docs",
601
+ },
602
+ }),
603
+ npxPluginInstall("claude-code", "https://docs.anthropic.com/en/docs/claude-code/plugin-marketplaces"),
604
+ ...terminalLanes("claude-code"),
605
+ skill("claude-code"),
606
+ universalPrompt("claude-code"),
607
+ ],
608
+ }),
609
+ entry({
610
+ id: "claude-app",
611
+ aliases: ["claude ai", "claude.ai", "claude web", "claude cowork"],
612
+ name: "Claude",
613
+ vendor: "Anthropic",
614
+ description: "Connect Spacefast to Claude on the web, mobile, and Cowork with one click.",
615
+ detectionHints: [],
616
+ kind: "chat-app",
617
+ tier: "featured",
618
+ wizardGroup: "claude",
619
+ capabilities: ["mcp-oauth", "deeplink-only", "plugin"],
620
+ skills: { agentsDirs: false, claudeDirs: false },
621
+ agentsMd: "none",
622
+ docs: { spacefast: "https://spacefast.com/setup/claude-app", vendor: claudeAppSource },
623
+ legacyResultAliases: ["claude-ai", "claude-cowork"],
624
+ methods: [
625
+ deeplink({
626
+ agentId: "claude-app",
627
+ build: (context) => `https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Spacefast&connectorUrl=${encodeURIComponent(context.mcpEndpoint)}`,
628
+ fallbackId: "universal-prompt",
629
+ label: "Add to Claude",
630
+ sourceUrl: claudeAppSource,
631
+ summary: "Open Claude's connector form with Spacefast filled in.",
632
+ }),
633
+ universalPrompt("claude-app"),
634
+ ],
635
+ }),
636
+ entry({
637
+ id: "claude-desktop",
638
+ aliases: ["claude desktop", "claude for mac", "claude for windows"],
639
+ name: "Claude Desktop",
640
+ vendor: "Anthropic",
641
+ description: "Install Spacefast in Claude Desktop as a signed local extension.",
642
+ detectionHints: [],
643
+ kind: "chat-app",
644
+ tier: "featured",
645
+ wizardGroup: "claude",
646
+ capabilities: ["mcp-oauth", "mcp-local"],
647
+ skills: { agentsDirs: false, claudeDirs: false },
648
+ agentsMd: "none",
649
+ docs: {
650
+ spacefast: "https://spacefast.com/setup/claude-desktop",
651
+ vendor: "https://github.com/modelcontextprotocol/mcpb",
652
+ },
653
+ notes: [
654
+ "Connectors added on claude.ai sync down to Desktop; the bundle is the desktop-only lane.",
655
+ ],
656
+ methods: [
657
+ {
658
+ ...methodBase({
659
+ agentId: "claude-desktop",
660
+ authLane: "oauth",
661
+ id: "desktop-extension",
662
+ label: "Download desktop extension",
663
+ rank: {
664
+ "interactive-primary": PRIMARY_RANK.download,
665
+ "static-docs": 10,
666
+ "terminal-alternates": 15,
667
+ },
668
+ sourceUrl: "https://github.com/modelcontextprotocol/mcpb",
669
+ summary: "Install the signed Spacefast MCP bundle — double-click, nothing to configure.",
670
+ }),
671
+ kind: "download",
672
+ url: () => "https://github.com/spacefast/plugins/releases/latest/download/claude-desktop.mcpb",
673
+ },
674
+ hostedConfig("claude-desktop", "claude_desktop_config.json", "json", jsonHostedConfig("mcpServers"), "https://support.claude.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop"),
675
+ universalPrompt("claude-desktop"),
676
+ ],
677
+ }),
678
+ entry({
679
+ id: "cursor",
680
+ aliases: ["cursor editor"],
681
+ name: "Cursor",
682
+ vendor: "Cursor",
683
+ description: "Publish and host from Cursor with its plugin, skills, and hosted MCP.",
684
+ detectionHints: ["CURSOR_TRACE_ID", ".cursor"],
685
+ kind: "editor",
686
+ tier: "featured",
687
+ wizardGroup: "coding-agents",
688
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
689
+ skills: { agentsDirs: true, claudeDirs: true },
690
+ agentsMd: "native",
691
+ docs: { spacefast: "https://spacefast.com/setup/cursor", vendor: cursorSource },
692
+ machineClient: true,
693
+ redeemableClientTargetId: "cursor",
694
+ legacyResultAliases: ["cursor"],
695
+ methods: [
696
+ plugin({
697
+ agentId: "cursor",
698
+ installerTargets: ["cursor"],
699
+ shell: [`npx -y ${PLUGINS_CLI_PACKAGE_SPEC} add spacefast/plugins -t cursor -y`],
700
+ sourceUrl: "https://docs.cursor.com/plugins",
701
+ summary: "Install the plugin, its rules, and MCP into Cursor with nothing installed first.",
702
+ }),
703
+ ...terminalLanes("cursor"),
704
+ pluginMarketplace("cursor", "https://docs.cursor.com/plugins", "planned"),
705
+ deeplink({
706
+ agentId: "cursor",
707
+ build: (context) => `https://cursor.com/install-mcp?name=spacefast&config=${encodeURIComponent(base64Utf8(JSON.stringify({ url: context.mcpEndpoint })))}`,
708
+ fallbackId: "hosted-mcp-config",
709
+ label: "Add to Cursor",
710
+ sourceUrl: cursorSource,
711
+ summary: "Add Spacefast hosted MCP to Cursor in one click.",
712
+ }),
713
+ hostedConfig("cursor", "~/.cursor/mcp.json", "json", jsonHostedConfig("mcpServers"), cursorSource),
714
+ skill("cursor"),
715
+ universalPrompt("cursor"),
716
+ ],
717
+ }),
718
+ entry({
719
+ id: "codex",
720
+ aliases: ["codex cli", "openai codex"],
721
+ name: "Codex",
722
+ vendor: "OpenAI",
723
+ description: "Publish and host from Codex locally or in the cloud.",
724
+ detectionHints: ["CODEX_HOME", ".codex"],
725
+ kind: "coding-agent",
726
+ tier: "featured",
727
+ wizardGroup: "openai",
728
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
729
+ skills: { agentsDirs: true, claudeDirs: false },
730
+ agentsMd: "native",
731
+ docs: { spacefast: "https://spacefast.com/setup/codex", vendor: codexSource },
732
+ machineClient: true,
733
+ redeemableClientTargetId: "codex",
734
+ legacyResultAliases: ["codex"],
735
+ methods: [
736
+ plugin({
737
+ agentId: "codex",
738
+ installerTargets: ["codex"],
739
+ shell: [
740
+ "codex plugin marketplace add spacefast/plugins",
741
+ "codex plugin add spacefast@spacefast",
742
+ ],
743
+ sourceUrl: "https://developers.openai.com/codex/plugins",
744
+ summary: "Add the Spacefast marketplace and install the plugin with Codex itself.",
745
+ }),
746
+ npxPluginInstall("codex", "https://developers.openai.com/codex/plugins"),
747
+ ...terminalLanes("codex"),
748
+ hostedConfig("codex", "~/.codex/config.toml", "toml", (context) => `[mcp_servers.spacefast]\nurl = ${JSON.stringify(context.mcpEndpoint)}`, codexSource),
749
+ skill("codex"),
750
+ universalPrompt("codex"),
751
+ ],
752
+ }),
753
+ entry({
754
+ id: "chatgpt",
755
+ aliases: ["chat gpt", "chatgpt work", "chatgpt-work", "custom gpt", "openai apps"],
756
+ name: "ChatGPT",
757
+ vendor: "OpenAI",
758
+ description: "Use Spacefast from ChatGPT and ChatGPT Work through the app directory.",
759
+ detectionHints: [],
760
+ kind: "chat-app",
761
+ tier: "featured",
762
+ wizardGroup: "openai",
763
+ capabilities: ["mcp-oauth", "deeplink-only", "plugin"],
764
+ skills: { agentsDirs: false, claudeDirs: false },
765
+ agentsMd: "none",
766
+ docs: { spacefast: "https://spacefast.com/setup/chatgpt", vendor: chatgptSource },
767
+ methods: [
768
+ deeplink({
769
+ agentId: "chatgpt",
770
+ build: () => "https://chatgpt.com/plugins",
771
+ fallbackId: "hosted-mcp-form",
772
+ label: "Find Spacefast in ChatGPT",
773
+ listingStatus: "submitted",
774
+ sourceUrl: "https://platform.openai.com/plugins",
775
+ summary: "Install from the ChatGPT and Codex plugin directory — the only lane that reaches consumer accounts with write tools.",
776
+ }),
777
+ // The dev-mode connector is a settings form, not a config file: ChatGPT has
778
+ // no `mcpServers` file to merge into. The workspace restriction lives on
779
+ // THIS method, not on the directory listing above — a published app is
780
+ // exactly what escapes it, so putting the caveat on the listing inverts it.
781
+ hostedFormConfig("chatgpt", "Settings → Connectors → Advanced → Developer mode → Add custom connector", chatgptSource, "Add Spacefast as a custom connector and approve OAuth in the browser. Web only; write tools need a Business or Enterprise workspace, and consumer Pro is read-only."),
782
+ universalPrompt("chatgpt"),
783
+ ],
784
+ }),
785
+ entry({
786
+ id: "vscode",
787
+ aliases: ["visual studio code", "copilot chat"],
788
+ name: "VS Code",
789
+ vendor: "Microsoft",
790
+ description: "Add Spacefast MCP to VS Code and Copilot agent mode.",
791
+ detectionHints: ["VSCODE_PID", ".vscode"],
792
+ kind: "editor",
793
+ tier: "featured",
794
+ wizardGroup: "coding-agents",
795
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "deeplink-only"],
796
+ skills: { agentsDirs: true, claudeDirs: false },
797
+ agentsMd: "native",
798
+ docs: { spacefast: "https://spacefast.com/setup/vscode", vendor: vscodeSource },
799
+ machineClient: true,
800
+ redeemableClientTargetId: "vscode",
801
+ legacyResultAliases: ["vscode", "copilot"],
802
+ methods: [
803
+ deeplink({
804
+ agentId: "vscode",
805
+ build: (context) => `https://vscode.dev/redirect/mcp/install?name=spacefast&config=${encodeURIComponent(JSON.stringify({ type: "http", url: context.mcpEndpoint }))}`,
806
+ fallbackId: "hosted-mcp-config",
807
+ label: "Add to VS Code",
808
+ sourceUrl: vscodeSource,
809
+ summary: "Add Spacefast hosted MCP to VS Code in one click.",
810
+ }),
811
+ plugin({
812
+ agentId: "vscode",
813
+ installerTargets: ["vscode"],
814
+ shell: [`npx -y ${PLUGINS_CLI_PACKAGE_SPEC} add spacefast/plugins -t vscode -y`],
815
+ sourceUrl: vscodeSource,
816
+ summary: "Install the plugin and its MCP server into VS Code with nothing installed first.",
817
+ }),
818
+ ...terminalLanes("vscode"),
819
+ hostedConfig("vscode", ".vscode/mcp.json", "jsonc", jsonHostedConfig("servers"), vscodeSource),
820
+ skill("vscode"),
821
+ universalPrompt("vscode"),
822
+ ],
823
+ }),
824
+ entry({
825
+ id: "github-copilot",
826
+ aliases: ["copilot", "github copilot", "copilot cli"],
827
+ name: "GitHub Copilot",
828
+ vendor: "GitHub",
829
+ description: "Publish from Copilot CLI and coding agent with native plugin and MCP support.",
830
+ detectionHints: [".copilot"],
831
+ kind: "coding-agent",
832
+ tier: "listed",
833
+ wizardGroup: "coding-agents",
834
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
835
+ skills: { agentsDirs: true, claudeDirs: true },
836
+ agentsMd: "native",
837
+ docs: {
838
+ spacefast: "https://spacefast.com/setup/github-copilot",
839
+ vendor: "https://docs.github.com/copilot/customizing-copilot/extending-copilot-chat-with-mcp",
840
+ },
841
+ machineClient: true,
842
+ methods: [
843
+ plugin({
844
+ agentId: "github-copilot",
845
+ installerTargets: ["github-copilot"],
846
+ shell: [`npx -y ${PLUGINS_CLI_PACKAGE_SPEC} add spacefast/plugins -t github-copilot -y`],
847
+ sourceUrl: "https://github.com/github/awesome-copilot",
848
+ summary: "Install the plugin's skills and MCP into Copilot with nothing installed first.",
849
+ }),
850
+ ...terminalLanes("github-copilot"),
851
+ pluginMarketplace("github-copilot", "https://github.com/github/awesome-copilot", "planned"),
852
+ skill("github-copilot"),
853
+ universalPrompt("github-copilot"),
854
+ ],
855
+ }),
856
+ entry({
857
+ id: "devin-desktop",
858
+ aliases: ["windsurf", "devin cli", "devin-desktop"],
859
+ name: "Devin Desktop",
860
+ vendor: "Cognition",
861
+ description: "Connect the local Devin desktop and CLI configuration plane.",
862
+ detectionHints: [".devin", ".codeium/windsurf"],
863
+ kind: "editor",
864
+ tier: "listed",
865
+ wizardGroup: "coding-agents",
866
+ capabilities: ["skills", "mcp-local", "mcp-oauth"],
867
+ skills: { agentsDirs: true, claudeDirs: false },
868
+ agentsMd: "native",
869
+ docs: {
870
+ spacefast: "https://spacefast.com/setup/devin-desktop",
871
+ vendor: "https://docs.devin.ai/work-with-devin/devin-desktop/mcp",
872
+ },
873
+ machineClient: true,
874
+ methods: [
875
+ hostedConfig("devin-desktop", "~/.devin/mcp_config.json", "json", jsonHostedConfig("mcpServers"), "https://docs.devin.ai/work-with-devin/devin-desktop/mcp"),
876
+ ...terminalLanes("devin-desktop"),
877
+ skill("devin-desktop"),
878
+ universalPrompt("devin-desktop"),
879
+ ],
880
+ }),
881
+ entry({
882
+ id: "devin-cloud",
883
+ aliases: ["devin", "devin ai", "app.devin.ai"],
884
+ name: "Devin Cloud",
885
+ vendor: "Cognition",
886
+ description: "Connect Spacefast in Devin's cloud-only integrations plane.",
887
+ detectionHints: [],
888
+ kind: "chat-app",
889
+ tier: "listed",
890
+ wizardGroup: "mcp-apps",
891
+ capabilities: ["skills", "mcp-oauth"],
892
+ skills: { agentsDirs: true, claudeDirs: false },
893
+ agentsMd: "native",
894
+ docs: {
895
+ spacefast: "https://spacefast.com/setup/devin-cloud",
896
+ vendor: "https://docs.devin.ai/integrations/mcp",
897
+ },
898
+ methods: [
899
+ hostedFormConfig("devin-cloud", "Settings \u2192 Connections \u2192 MCP servers \u2192 Add custom MCP", "https://docs.devin.ai/integrations/mcp"),
900
+ skill("devin-cloud"),
901
+ universalPrompt("devin-cloud"),
902
+ ],
903
+ }),
904
+ entry({
905
+ id: "zed",
906
+ aliases: ["zed editor"],
907
+ name: "Zed",
908
+ vendor: "Zed Industries",
909
+ description: "Connect Spacefast as a custom context server in Zed.",
910
+ detectionHints: [".config/zed"],
911
+ kind: "editor",
912
+ tier: "listed",
913
+ wizardGroup: "coding-agents",
914
+ capabilities: ["skills", "mcp-oauth"],
915
+ skills: { agentsDirs: true, claudeDirs: false },
916
+ agentsMd: "opt-in",
917
+ docs: {
918
+ spacefast: "https://spacefast.com/setup/zed",
919
+ vendor: "https://zed.dev/docs/assistant/model-context-protocol",
920
+ },
921
+ machineClient: true,
922
+ methods: [
923
+ hostedConfig("zed", "~/.config/zed/settings.json", "jsonc", jsonHostedConfig("context_servers"), "https://zed.dev/docs/assistant/model-context-protocol"),
924
+ ...terminalLanes("zed"),
925
+ skill("zed"),
926
+ universalPrompt("zed"),
927
+ ],
928
+ }),
929
+ entry({
930
+ id: "gemini-cli",
931
+ aliases: ["gemini", "google gemini cli"],
932
+ name: "Gemini CLI",
933
+ vendor: "Google",
934
+ description: "Use the Spacefast extension, skills, and OAuth MCP from Gemini CLI.",
935
+ detectionHints: [".gemini"],
936
+ kind: "coding-agent",
937
+ tier: "listed",
938
+ wizardGroup: "coding-agents",
939
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
940
+ skills: { agentsDirs: true, claudeDirs: false },
941
+ agentsMd: "opt-in",
942
+ docs: {
943
+ spacefast: "https://spacefast.com/setup/gemini-cli",
944
+ vendor: "https://geminicli.com/docs/tools/mcp-server",
945
+ },
946
+ machineClient: true,
947
+ methods: [
948
+ pluginMarketplace("gemini-cli", "https://geminicli.com/docs/extensions", "planned"),
949
+ hostedConfig("gemini-cli", "~/.gemini/settings.json", "json", (context) => JSON.stringify({
950
+ mcpServers: { spacefast: { httpUrl: context.mcpEndpoint, oauth: { enabled: true } } },
951
+ }, null, 2), "https://geminicli.com/docs/tools/mcp-server"),
952
+ ...terminalLanes("gemini-cli"),
953
+ skill("gemini-cli"),
954
+ universalPrompt("gemini-cli"),
955
+ ],
956
+ }),
957
+ entry({
958
+ id: "opencode",
959
+ aliases: ["open code"],
960
+ name: "OpenCode",
961
+ vendor: "Anomaly",
962
+ description: "Connect Spacefast to OpenCode with native OAuth MCP and skills.",
963
+ detectionHints: [".config/opencode"],
964
+ kind: "coding-agent",
965
+ tier: "listed",
966
+ wizardGroup: "coding-agents",
967
+ capabilities: ["skills", "mcp-local", "mcp-oauth"],
968
+ skills: { agentsDirs: true, claudeDirs: true },
969
+ agentsMd: "native",
970
+ docs: {
971
+ spacefast: "https://spacefast.com/setup/opencode",
972
+ vendor: "https://opencode.ai/docs/mcp-servers",
973
+ },
974
+ machineClient: true,
975
+ methods: [
976
+ hostedConfig("opencode", "~/.config/opencode/opencode.json", "jsonc", jsonHostedConfig("mcp"), "https://opencode.ai/docs/mcp-servers"),
977
+ ...terminalLanes("opencode"),
978
+ skill("opencode"),
979
+ universalPrompt("opencode"),
980
+ ],
981
+ }),
982
+ entry({
983
+ id: "amp",
984
+ aliases: ["sourcegraph amp"],
985
+ name: "Amp",
986
+ vendor: "Amp",
987
+ description: "Install Spacefast skills with bundled MCP support in Amp.",
988
+ detectionHints: [".config/amp"],
989
+ kind: "coding-agent",
990
+ tier: "listed",
991
+ wizardGroup: "coding-agents",
992
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
993
+ skills: { agentsDirs: true, claudeDirs: true },
994
+ agentsMd: "native",
995
+ docs: {
996
+ spacefast: "https://spacefast.com/setup/amp",
997
+ vendor: "https://ampcode.com/manual#mcp",
998
+ },
999
+ machineClient: true,
1000
+ methods: [
1001
+ hostedConfig("amp", "~/.config/amp/mcp.json", "json", jsonHostedConfig("mcpServers"), "https://ampcode.com/manual#mcp"),
1002
+ ...terminalLanes("amp", { interactive: true }),
1003
+ skill("amp"),
1004
+ universalPrompt("amp"),
1005
+ ],
1006
+ }),
1007
+ entry({
1008
+ id: "warp",
1009
+ aliases: ["warp terminal"],
1010
+ name: "Warp",
1011
+ vendor: "Warp",
1012
+ description: "Use Spacefast skills and MCP in Warp agents.",
1013
+ detectionHints: [".warp"],
1014
+ kind: "coding-agent",
1015
+ tier: "listed",
1016
+ wizardGroup: "coding-agents",
1017
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "deeplink-only"],
1018
+ skills: { agentsDirs: true, claudeDirs: false },
1019
+ agentsMd: "opt-in",
1020
+ docs: {
1021
+ spacefast: "https://spacefast.com/setup/warp",
1022
+ vendor: "https://docs.warp.dev/knowledge-and-collaboration/mcp",
1023
+ },
1024
+ machineClient: true,
1025
+ methods: [
1026
+ deeplink({
1027
+ agentId: "warp",
1028
+ build: () => "warp://settings/mcp",
1029
+ fallbackId: "hosted-mcp-config",
1030
+ label: "Open Warp MCP settings",
1031
+ sourceUrl: "https://docs.warp.dev/knowledge-and-collaboration/mcp",
1032
+ summary: "Open Warp's MCP settings and add Spacefast.",
1033
+ }),
1034
+ hostedConfig("warp", "~/.warp/mcp.json", "json", jsonHostedConfig("mcpServers"), "https://docs.warp.dev/knowledge-and-collaboration/mcp"),
1035
+ ...terminalLanes("warp"),
1036
+ skill("warp"),
1037
+ universalPrompt("warp"),
1038
+ ],
1039
+ }),
1040
+ entry({
1041
+ id: "factory-droid",
1042
+ aliases: ["droid", "factory"],
1043
+ name: "Factory Droid",
1044
+ vendor: "Factory",
1045
+ description: "Install Spacefast as a Droid plugin or native OAuth MCP server.",
1046
+ detectionHints: [".factory"],
1047
+ kind: "coding-agent",
1048
+ tier: "listed",
1049
+ wizardGroup: "coding-agents",
1050
+ capabilities: ["skills", "mcp-local", "mcp-oauth", "plugin"],
1051
+ skills: { agentsDirs: true, claudeDirs: true },
1052
+ agentsMd: "native",
1053
+ docs: {
1054
+ spacefast: "https://spacefast.com/setup/factory-droid",
1055
+ vendor: "https://docs.factory.ai/cli/configuration/mcp",
1056
+ },
1057
+ machineClient: true,
1058
+ methods: [
1059
+ plugin({
1060
+ agentId: "factory-droid",
1061
+ shell: [
1062
+ "droid plugin marketplace add spacefast/plugins",
1063
+ "droid plugin install spacefast@spacefast",
1064
+ ],
1065
+ sourceUrl: "https://docs.factory.ai/cli/configuration/plugins",
1066
+ summary: "Add the Spacefast marketplace to Droid and install the plugin.",
1067
+ }),
1068
+ ...terminalLanes("factory-droid"),
1069
+ skill("factory-droid"),
1070
+ universalPrompt("factory-droid"),
1071
+ ],
1072
+ }),
1073
+ entry({
1074
+ id: "cline",
1075
+ aliases: ["cline vscode"],
1076
+ name: "Cline",
1077
+ vendor: "Cline",
1078
+ description: "Install Spacefast from the Cline marketplace or connect OAuth MCP.",
1079
+ detectionHints: [".cline"],
1080
+ kind: "editor",
1081
+ tier: "listed",
1082
+ wizardGroup: "coding-agents",
1083
+ capabilities: ["skills", "mcp-oauth", "plugin"],
1084
+ skills: { agentsDirs: true, claudeDirs: true },
1085
+ agentsMd: "native",
1086
+ docs: { spacefast: "https://spacefast.com/setup/cline", vendor: "https://docs.cline.bot/mcp" },
1087
+ machineClient: true,
1088
+ methods: [
1089
+ pluginMarketplace("cline", "https://github.com/cline/marketplace", "planned"),
1090
+ hostedConfig("cline", "~/.cline/data/settings/cline_mcp_settings.json", "json", jsonHostedConfig("mcpServers"), "https://docs.cline.bot/mcp"),
1091
+ ...terminalLanes("cline"),
1092
+ skill("cline"),
1093
+ universalPrompt("cline"),
1094
+ ],
1095
+ }),
1096
+ entry({
1097
+ id: "continue",
1098
+ aliases: ["continue dev"],
1099
+ name: "Continue",
1100
+ vendor: "Continue",
1101
+ description: "Use the Spacefast skill in discontinued Continue installations.",
1102
+ detectionHints: [".continue"],
1103
+ kind: "editor",
1104
+ tier: "listed",
1105
+ wizardGroup: "prompt-first",
1106
+ capabilities: ["skills"],
1107
+ skills: { agentsDirs: false, claudeDirs: true },
1108
+ agentsMd: "none",
1109
+ docs: {
1110
+ spacefast: "https://spacefast.com/setup/continue",
1111
+ vendor: "https://github.com/continuedev/continue",
1112
+ },
1113
+ notes: ["Continue is discontinued; active MCP support is not advertised."],
1114
+ machineClient: true,
1115
+ methods: [
1116
+ skill("continue", { interactive: true }),
1117
+ ...terminalLanes("continue"),
1118
+ universalPrompt("continue"),
1119
+ ],
1120
+ }),
1121
+ entry({
1122
+ id: "raycast",
1123
+ aliases: ["raycast ai"],
1124
+ name: "Raycast",
1125
+ vendor: "Raycast",
1126
+ description: "Connect Spacefast to Raycast AI using its native OAuth MCP form.",
1127
+ detectionHints: [],
1128
+ kind: "mcp-app",
1129
+ tier: "listed",
1130
+ wizardGroup: "mcp-apps",
1131
+ capabilities: ["mcp-oauth", "deeplink-only"],
1132
+ skills: { agentsDirs: false, claudeDirs: false },
1133
+ agentsMd: "none",
1134
+ docs: {
1135
+ spacefast: "https://spacefast.com/setup/raycast",
1136
+ vendor: "https://manual.raycast.com/model-context-protocol",
1137
+ },
1138
+ methods: [
1139
+ hostedFormConfig("raycast", "Raycast Settings \u2192 AI \u2192 MCP Servers \u2192 Install MCP Server", "https://manual.raycast.com/model-context-protocol"),
1140
+ hostedConfig("raycast", "an MCP client configuration", "json", jsonHostedConfig("mcpServers"), "https://manual.raycast.com/model-context-protocol"),
1141
+ universalPrompt("raycast"),
1142
+ ],
1143
+ }),
1144
+ entry({
1145
+ id: "poke",
1146
+ aliases: ["poke ai"],
1147
+ name: "Poke",
1148
+ vendor: "Interaction",
1149
+ description: "Prefill a Spacefast integration in Poke and authorize with OAuth.",
1150
+ detectionHints: [],
1151
+ kind: "mcp-app",
1152
+ // Poke connects over MCP, but people reach for it the way they reach for
1153
+ // Hermes or OpenClaw — as their own assistant, not as an app they are
1154
+ // configuring. `kind` keeps the wire truth; the wizard groups it by use.
1155
+ tier: "listed",
1156
+ wizardGroup: "personal-agents",
1157
+ capabilities: ["mcp-oauth", "deeplink-only"],
1158
+ skills: { agentsDirs: false, claudeDirs: false },
1159
+ agentsMd: "none",
1160
+ docs: {
1161
+ spacefast: "https://spacefast.com/setup/poke",
1162
+ vendor: "https://poke.com/docs/mcp-servers",
1163
+ },
1164
+ methods: [
1165
+ deeplink({
1166
+ agentId: "poke",
1167
+ build: (context) => `https://poke.com/integrations/new?name=Spacefast&url=${encodeURIComponent(context.mcpEndpoint)}`,
1168
+ fallbackId: "universal-prompt",
1169
+ label: "Add to Poke",
1170
+ sourceUrl: "https://poke.com/docs/mcp-servers",
1171
+ summary: "Open Poke's editable integration form with Spacefast filled in.",
1172
+ }),
1173
+ universalPrompt("poke"),
1174
+ ],
1175
+ }),
1176
+ entry({
1177
+ id: "pi",
1178
+ aliases: ["pi.dev", "earendil"],
1179
+ name: "Pi",
1180
+ vendor: "Pi",
1181
+ description: "Install the third-party Pi MCP extension for Spacefast.",
1182
+ detectionHints: [".pi"],
1183
+ kind: "coding-agent",
1184
+ tier: "listed",
1185
+ wizardGroup: "prompt-first",
1186
+ capabilities: ["skills"],
1187
+ skills: { agentsDirs: true, claudeDirs: false },
1188
+ agentsMd: "none",
1189
+ docs: { spacefast: "https://spacefast.com/setup/pi", vendor: "https://pi.dev" },
1190
+ machineClient: true,
1191
+ methods: [
1192
+ hostedConfig("pi", "~/.pi/agent/mcp.json", "json", (context) => JSON.stringify({
1193
+ mcpServers: { spacefast: { transport: "streamable-http", url: context.mcpEndpoint } },
1194
+ }, null, 2), "https://pi.dev"),
1195
+ ...terminalLanes("pi"),
1196
+ skill("pi"),
1197
+ universalPrompt("pi"),
1198
+ ],
1199
+ }),
1200
+ entry({
1201
+ id: "indent",
1202
+ aliases: ["indent ai"],
1203
+ name: "Indent",
1204
+ vendor: "Indent",
1205
+ description: "Connect Spacefast through Indent's native OAuth MCP integration.",
1206
+ detectionHints: [],
1207
+ kind: "mcp-app",
1208
+ tier: "listed",
1209
+ wizardGroup: "mcp-apps",
1210
+ capabilities: ["mcp-oauth", "deeplink-only"],
1211
+ skills: { agentsDirs: false, claudeDirs: false },
1212
+ agentsMd: "none",
1213
+ docs: {
1214
+ spacefast: "https://spacefast.com/setup/indent",
1215
+ vendor: "https://indent.com/docs/integrations/mcp",
1216
+ },
1217
+ methods: [
1218
+ hostedFormConfig("indent", "Indent Settings \u2192 Integrations \u2192 Add custom MCP", "https://indent.com/docs/integrations/mcp"),
1219
+ hostedConfig("indent", "an MCP client configuration", "json", jsonHostedConfig("mcpServers"), "https://indent.com/docs/integrations/mcp"),
1220
+ universalPrompt("indent"),
1221
+ ],
1222
+ }),
1223
+ entry({
1224
+ id: "hermes",
1225
+ aliases: ["hermes agent", "nous hermes"],
1226
+ name: "Hermes",
1227
+ vendor: "Nous Research",
1228
+ description: "Give Hermes the Spacefast setup guide, or install its skill with the CLI.",
1229
+ detectionHints: [".hermes"],
1230
+ kind: "coding-agent",
1231
+ tier: "listed",
1232
+ wizardGroup: "personal-agents",
1233
+ capabilities: ["skills"],
1234
+ skills: { agentsDirs: true, claudeDirs: false },
1235
+ agentsMd: "native",
1236
+ docs: {
1237
+ spacefast: "https://spacefast.com/setup.md",
1238
+ vendor: "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/guides/work-with-skills.md",
1239
+ },
1240
+ machineClient: true,
1241
+ methods: [
1242
+ ...terminalLanes("hermes", { skillOnly: true }),
1243
+ universalPrompt("hermes", { interactive: true }),
1244
+ ],
1245
+ }),
1246
+ entry({
1247
+ id: "openclaw",
1248
+ aliases: ["open claw", "clawhub"],
1249
+ name: "OpenClaw",
1250
+ vendor: "OpenClaw",
1251
+ description: "Give OpenClaw the Spacefast setup guide, or install its skill with the CLI.",
1252
+ detectionHints: [".openclaw"],
1253
+ kind: "coding-agent",
1254
+ tier: "listed",
1255
+ wizardGroup: "personal-agents",
1256
+ capabilities: ["skills"],
1257
+ skills: { agentsDirs: true, claudeDirs: false },
1258
+ agentsMd: "none",
1259
+ docs: {
1260
+ spacefast: "https://spacefast.com/setup.md",
1261
+ vendor: "https://docs.openclaw.ai/tools/skills",
1262
+ },
1263
+ machineClient: true,
1264
+ methods: [
1265
+ ...terminalLanes("openclaw", { skillOnly: true }),
1266
+ universalPrompt("openclaw", { interactive: true }),
1267
+ ],
1268
+ }),
1269
+ ];
1270
+ const registryById = new Map(AGENT_REGISTRY.map((agent) => [agent.id, agent]));
1271
+ const idByAlias = new Map();
1272
+ for (const agent of AGENT_REGISTRY) {
1273
+ idByAlias.set(agent.id, agent.id);
1274
+ for (const alias of agent.aliases)
1275
+ idByAlias.set(normalizeLookup(alias), agent.id);
1276
+ }
1277
+ export function getAgentEntry(id) {
1278
+ const agent = registryById.get(id);
1279
+ if (!agent)
1280
+ throw new TypeError(`Unknown agent: ${id}`);
1281
+ return agent;
1282
+ }
1283
+ export function resolveAgentId(value) {
1284
+ return idByAlias.get(normalizeLookup(value)) ?? null;
1285
+ }
1286
+ /**
1287
+ * The ranking rule, as a pure function so the demote path is testable without a
1288
+ * registry entry: a deeplink whose last probe failed is dropped from the
1289
+ * interactive lane, and everything else sorts by its per-context rank.
1290
+ */
1291
+ export function methodsForContext(methods, context) {
1292
+ return methods
1293
+ .filter((method) => method.rank[context] !== undefined)
1294
+ .filter((method) => method.kind !== "deeplink" ||
1295
+ method.verification.status !== "failed" ||
1296
+ context !== "interactive-primary")
1297
+ .toSorted((left, right) => (left.rank[context] ?? 0) - (right.rank[context] ?? 0));
1298
+ }
1299
+ export function agentMethodsForContext(agentId, context) {
1300
+ return methodsForContext(getAgentEntry(agentId).methods, context);
1301
+ }
1302
+ /**
1303
+ * The `sf plugins` gate. Returns the universal-installer targets this agent can
1304
+ * actually be installed into — empty for every agent whose plugin lane is
1305
+ * vendor-native or not live, including entries that carry a `plugin` capability.
1306
+ */
1307
+ export function pluginInstallerTargetsFor(agentId) {
1308
+ return [
1309
+ ...new Set(getAgentEntry(agentId).methods.flatMap((method) => method.kind === "plugin" && method.listing.status === "live"
1310
+ ? [...(method.installerTargets ?? [])]
1311
+ : [])),
1312
+ ];
1313
+ }
1314
+ export function presentAgentEntry(agentId, context) {
1315
+ const agent = getAgentEntry(agentId);
1316
+ return {
1317
+ ...agent,
1318
+ methods: agentMethodsForContext(agent.id, "interactive-primary").map((method) => presentMethod(method, context, "interactive-primary")),
1319
+ staticDocs: agentMethodsForContext(agent.id, "static-docs").map((method) => presentMethod(method, context, "static-docs")),
1320
+ terminalAlternates: agentMethodsForContext(agent.id, "terminal-alternates").map((method) => presentMethod(method, context, "terminal-alternates")),
1321
+ };
1322
+ }
1323
+ export function base64Utf8(value) {
1324
+ const bytes = new TextEncoder().encode(value);
1325
+ let binary = "";
1326
+ for (const byte of bytes)
1327
+ binary += String.fromCharCode(byte);
1328
+ return btoa(binary);
1329
+ }
1330
+ function presentMethod(method, context, presentation) {
1331
+ const action = methodAction(method, context);
1332
+ const presented = {
1333
+ action,
1334
+ docsUrl: method.docsUrl,
1335
+ id: method.id,
1336
+ kind: method.kind,
1337
+ label: method.label,
1338
+ language: methodLanguage(method),
1339
+ listing: method.listing,
1340
+ rank: method.rank[presentation] ?? 0,
1341
+ summary: method.summary,
1342
+ };
1343
+ if (method.kind === "deeplink")
1344
+ presented.fallbackMethodId = method.fallback.id;
1345
+ if (method.kind === "config")
1346
+ presented.path = method.path;
1347
+ if (method.vendorDocs !== undefined)
1348
+ presented.vendorDocs = method.vendorDocs;
1349
+ return presented;
1350
+ }
1351
+ function methodLanguage(method) {
1352
+ if (method.kind === "config")
1353
+ return method.language;
1354
+ if (method.kind === "command" || method.kind === "plugin" || method.kind === "skill") {
1355
+ return "bash";
1356
+ }
1357
+ return "text";
1358
+ }
1359
+ function methodAction(method, context) {
1360
+ if (method.kind === "deeplink") {
1361
+ return { kind: "link", label: method.label, value: method.build(context) };
1362
+ }
1363
+ if (method.kind === "download") {
1364
+ return { kind: "download", label: method.label, value: method.url(context) };
1365
+ }
1366
+ if (method.kind === "plugin") {
1367
+ // A listing with no install command yet is disabled AT THE REGISTRY, so no
1368
+ // consumer has to discover emptiness by inspecting the copy string.
1369
+ if (method.commands.shell.length === 0) {
1370
+ return { kind: "disabled", label: method.label, value: "" };
1371
+ }
1372
+ return { kind: "copy", label: method.label, value: method.commands.shell.join(" && ") };
1373
+ }
1374
+ if (method.kind === "command") {
1375
+ return { kind: "copy", label: method.label, value: method.build(context).join(" && ") };
1376
+ }
1377
+ if (method.kind === "config" || method.kind === "prompt") {
1378
+ return { kind: "copy", label: method.label, value: method.build(context) };
1379
+ }
1380
+ return {
1381
+ kind: "copy",
1382
+ label: method.label,
1383
+ value: `npx -y ${context.packageSpecs?.skillsCli ?? SKILLS_CLI_PACKAGE_SPEC} add ${method.source.url} -y`,
1384
+ };
1385
+ }
1386
+ function normalizeLookup(value) {
1387
+ return value.trim().toLowerCase().replaceAll("_", "-").replace(/\s+/g, " ");
1388
+ }