@spacefast/common 0.0.12 → 0.0.17

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 (189) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +486 -1196
  14. package/dist/contracts/access.js +323 -1054
  15. package/dist/contracts/activity.d.ts +1 -1
  16. package/dist/contracts/activity.js +11 -15
  17. package/dist/contracts/api-keys.d.ts +16 -0
  18. package/dist/contracts/api-keys.js +11 -9
  19. package/dist/contracts/archives.d.ts +1 -2
  20. package/dist/contracts/archives.js +15 -17
  21. package/dist/contracts/beta.js +1 -1
  22. package/dist/contracts/builds.js +14 -14
  23. package/dist/contracts/channels.js +2 -2
  24. package/dist/contracts/comments.d.ts +143 -99
  25. package/dist/contracts/comments.js +194 -79
  26. package/dist/contracts/common.d.ts +12 -12
  27. package/dist/contracts/common.js +33 -30
  28. package/dist/contracts/continuation.js +5 -5
  29. package/dist/contracts/countries.js +2 -7
  30. package/dist/contracts/data-source-routes.d.ts +20 -0
  31. package/dist/contracts/data-source-routes.js +15 -0
  32. package/dist/contracts/device-auth.d.ts +13 -9
  33. package/dist/contracts/device-auth.js +12 -12
  34. package/dist/contracts/docs.d.ts +10 -10
  35. package/dist/contracts/domains.js +39 -33
  36. package/dist/contracts/enums.d.ts +4 -14
  37. package/dist/contracts/enums.js +3 -10
  38. package/dist/contracts/error-code-meta.d.ts +122 -56
  39. package/dist/contracts/error-code-meta.js +43 -19
  40. package/dist/contracts/error-codes.d.ts +7 -4
  41. package/dist/contracts/error-codes.js +48 -24
  42. package/dist/contracts/events.d.ts +22 -15
  43. package/dist/contracts/events.js +33 -5
  44. package/dist/contracts/execution.d.ts +238 -0
  45. package/dist/contracts/execution.js +309 -0
  46. package/dist/contracts/feature-lifecycle.d.ts +39 -188
  47. package/dist/contracts/feature-lifecycle.js +52 -196
  48. package/dist/contracts/features.d.ts +13 -115
  49. package/dist/contracts/features.js +5 -35
  50. package/dist/contracts/functions.d.ts +597 -0
  51. package/dist/contracts/functions.js +475 -0
  52. package/dist/contracts/grants.d.ts +1175 -0
  53. package/dist/contracts/grants.js +783 -0
  54. package/dist/contracts/ids.d.ts +7 -5
  55. package/dist/contracts/ids.js +7 -7
  56. package/dist/contracts/internal.js +2 -2
  57. package/dist/contracts/mcp.d.ts +17 -556
  58. package/dist/contracts/mcp.js +9 -103
  59. package/dist/contracts/me.js +3 -4
  60. package/dist/contracts/notifications.d.ts +108 -0
  61. package/dist/contracts/notifications.js +54 -0
  62. package/dist/contracts/oauth-resources.d.ts +22 -5
  63. package/dist/contracts/oauth-resources.js +23 -2
  64. package/dist/contracts/operations.d.ts +0 -48
  65. package/dist/contracts/operations.js +0 -19
  66. package/dist/contracts/pages.d.ts +10 -14
  67. package/dist/contracts/pages.js +17 -21
  68. package/dist/contracts/plan-policy.d.ts +1 -8
  69. package/dist/contracts/plan-policy.js +1 -4
  70. package/dist/contracts/platform.d.ts +78 -30
  71. package/dist/contracts/platform.js +69 -20
  72. package/dist/contracts/problem-document.d.ts +14 -0
  73. package/dist/contracts/problem-document.js +35 -0
  74. package/dist/contracts/push-new.d.ts +41 -2
  75. package/dist/contracts/push-new.js +4 -0
  76. package/dist/contracts/quotas.d.ts +0 -1
  77. package/dist/contracts/quotas.js +0 -5
  78. package/dist/contracts/repository-connections.d.ts +1 -1
  79. package/dist/contracts/resources.d.ts +16 -71
  80. package/dist/contracts/resources.js +13 -16
  81. package/dist/contracts/runtime-api.d.ts +666 -303
  82. package/dist/contracts/runtime-api.js +272 -113
  83. package/dist/contracts/runtime-app.d.ts +168 -0
  84. package/dist/contracts/runtime-app.js +42 -0
  85. package/dist/contracts/runtime-db.d.ts +424 -0
  86. package/dist/contracts/runtime-db.js +108 -0
  87. package/dist/contracts/runtime-storage.d.ts +120 -0
  88. package/dist/contracts/runtime-storage.js +40 -0
  89. package/dist/contracts/sf-config-v1.d.ts +54 -1
  90. package/dist/contracts/sf-config-v1.js +2 -2
  91. package/dist/contracts/sites.d.ts +6 -0
  92. package/dist/contracts/sites.js +30 -11
  93. package/dist/contracts/space-config.d.ts +91 -121
  94. package/dist/contracts/space-config.js +464 -42
  95. package/dist/contracts/spaces.d.ts +300 -171
  96. package/dist/contracts/spaces.js +128 -98
  97. package/dist/contracts/superadmin-emails.d.ts +2 -2
  98. package/dist/contracts/superadmin-spaces.d.ts +26 -130
  99. package/dist/contracts/superadmin-spaces.js +22 -0
  100. package/dist/contracts/superadmin-tenants.js +4 -4
  101. package/dist/contracts/superadmin.d.ts +4 -0
  102. package/dist/contracts/superadmin.js +14 -1
  103. package/dist/contracts/tags.d.ts +6 -6
  104. package/dist/contracts/tags.js +1 -1
  105. package/dist/contracts/teams.d.ts +9 -9
  106. package/dist/contracts/teams.js +25 -16
  107. package/dist/contracts/theme-json.js +1 -1
  108. package/dist/contracts/transfers.d.ts +0 -1
  109. package/dist/contracts/transfers.js +4 -4
  110. package/dist/contracts/variables.js +3 -3
  111. package/dist/contracts/zero.d.ts +384 -596
  112. package/dist/contracts/zero.js +167 -299
  113. package/dist/dashboard-paths/index.d.ts +18 -3
  114. package/dist/dashboard-paths/index.js +19 -0
  115. package/dist/docs/agent-handoff-document.d.ts +2 -2
  116. package/dist/docs/agent-handoff-document.js +8 -7
  117. package/dist/docs/agent-prose.d.ts +22 -3
  118. package/dist/docs/agent-prose.js +227 -55
  119. package/dist/docs/agent-setup.d.ts +14 -11
  120. package/dist/docs/agent-setup.js +289 -61
  121. package/dist/docs/agent-solutions.d.ts +6 -6
  122. package/dist/docs/agent-solutions.js +24 -22
  123. package/dist/docs/catalog.d.ts +53 -98
  124. package/dist/docs/catalog.js +90 -117
  125. package/dist/docs/error-docs.js +304 -236
  126. package/dist/docs/skill-distribution.d.ts +2 -0
  127. package/dist/docs/skill-distribution.js +7 -4
  128. package/dist/docs/skills.d.ts +546 -0
  129. package/dist/docs/skills.js +340 -0
  130. package/dist/utils/auth-redirect.d.ts +1 -3
  131. package/dist/utils/auth-redirect.js +1 -21
  132. package/dist/utils/browser-credential.d.ts +19 -0
  133. package/dist/utils/browser-credential.js +62 -0
  134. package/dist/utils/build-settings.d.ts +2 -0
  135. package/dist/utils/build-settings.js +61 -9
  136. package/dist/utils/comment-avatars.d.ts +7 -0
  137. package/dist/utils/comment-avatars.js +42 -0
  138. package/dist/utils/content-type.d.ts +3 -3
  139. package/dist/utils/content-type.js +38 -61
  140. package/dist/utils/credential-policy.d.ts +1 -1
  141. package/dist/utils/credential-policy.js +4 -0
  142. package/dist/utils/domain-diagnostics.d.ts +5 -5
  143. package/dist/utils/domain-diagnostics.js +21 -10
  144. package/dist/utils/egress-policy.fixtures.json +1 -1
  145. package/dist/utils/grants.d.ts +53 -0
  146. package/dist/utils/grants.js +160 -0
  147. package/dist/utils/local-space-state.d.ts +78 -4
  148. package/dist/utils/local-space-state.js +384 -82
  149. package/dist/utils/one-shot-replay.d.ts +14 -0
  150. package/dist/utils/one-shot-replay.js +21 -0
  151. package/dist/utils/page-preview.d.ts +7 -0
  152. package/dist/utils/page-preview.js +9 -0
  153. package/dist/utils/pages.d.ts +2 -5
  154. package/dist/utils/pages.js +64 -33
  155. package/dist/utils/publish-form-data.d.ts +22 -0
  156. package/dist/utils/publish-form-data.js +26 -0
  157. package/dist/utils/publish-policy.d.ts +34 -4
  158. package/dist/utils/publish-policy.js +149 -62
  159. package/dist/utils/query-keys.d.ts +8 -10
  160. package/dist/utils/query-keys.js +14 -10
  161. package/dist/utils/runtime-paths.d.ts +3 -0
  162. package/dist/utils/runtime-paths.js +7 -0
  163. package/dist/utils/secure-local-file.d.ts +73 -0
  164. package/dist/utils/secure-local-file.js +1373 -0
  165. package/dist/utils/space-config.d.ts +17 -4
  166. package/dist/utils/space-config.js +151 -8
  167. package/dist/utils/static-runtime-policy.d.ts +5 -0
  168. package/dist/utils/static-runtime-policy.js +54 -10
  169. package/dist/utils/storage-policy.d.ts +20 -0
  170. package/dist/utils/storage-policy.fixtures.json +174 -0
  171. package/dist/utils/storage-policy.js +193 -0
  172. package/dist/utils/upload-session.d.ts +15 -1
  173. package/dist/utils/upload-session.js +68 -2
  174. package/dist/utils/version-file-browser.d.ts +13 -0
  175. package/dist/utils/version-file-browser.js +59 -0
  176. package/package.json +20 -1
  177. package/dist/contracts/mounts.d.ts +0 -142
  178. package/dist/contracts/mounts.js +0 -56
  179. package/dist/utils/access-grant.d.ts +0 -14
  180. package/dist/utils/access-grant.js +0 -88
  181. package/dist/utils/access-match.d.ts +0 -46
  182. package/dist/utils/access-match.fixtures.json +0 -513
  183. package/dist/utils/access-match.js +0 -312
  184. package/dist/utils/access-verdict.fixtures.json +0 -421
  185. package/dist/utils/asset-fingerprint.d.ts +0 -53
  186. package/dist/utils/asset-fingerprint.js +0 -574
  187. package/dist/utils/sf-config-v1.d.ts +0 -2
  188. package/dist/utils/sf-config-v1.fixtures.json +0 -9
  189. package/dist/utils/sf-config-v1.js +0 -551
@@ -4,7 +4,7 @@
4
4
  * Append-only: add a new top-level static dashboard route's first segment here
5
5
  * before its route module lands. A repository structural test enforces this.
6
6
  */
7
- export declare const TEAM_SLUG_ROUTE_RESERVATIONS: readonly ["accept-beta-invite", "accept-invitation", "account", "agent", "claim", "device", "drop", "new-team", "oauth", "sign-in", "start", "terminal", "two-factor"];
7
+ export declare const TEAM_SLUG_ROUTE_RESERVATIONS: readonly ["accept-beta-invite", "accept-invitation", "account", "access", "agent", "claim", "device", "drop", "new-team", "oauth", "sign-in", "start", "terminal", "two-factor"];
8
8
  /**
9
9
  * Dashboard route first-segments that cannot be used as space slugs.
10
10
  *
@@ -12,18 +12,33 @@ export declare const TEAM_SLUG_ROUTE_RESERVATIONS: readonly ["accept-beta-invite
12
12
  * here before its route module lands. A repository structural test enforces
13
13
  * this.
14
14
  */
15
- export declare const SPACE_SLUG_ROUTE_RESERVATIONS: readonly ["billing", "connect", "domains", "drop", "members", "new", "settings"];
15
+ export declare const SPACE_SLUG_ROUTE_RESERVATIONS: readonly ["billing", "connect", "domains", "drop", "members", "new", "resume", "settings"];
16
16
  export type TeamSlugRouteReservation = (typeof TEAM_SLUG_ROUTE_RESERVATIONS)[number];
17
17
  export type SpaceSlugRouteReservation = (typeof SPACE_SLUG_ROUTE_RESERVATIONS)[number];
18
18
  export declare const TEAM_SLUG_ROUTE_RESERVATION_SET: ReadonlySet<string>;
19
19
  export declare const SPACE_SLUG_ROUTE_RESERVATION_SET: ReadonlySet<string>;
20
+ /**
21
+ * The Runtime page's sections. They are search params rather than path
22
+ * segments, so `runtime` stays a single route (and a single ledger row) and a
23
+ * space slugged "runtime" keeps its own Runtime page at
24
+ * `/<team>/runtime/runtime`.
25
+ */
26
+ export declare const SPACE_RUNTIME_SECTIONS: readonly ["overview", "database", "storage", "logs"];
27
+ export type SpaceRuntimeSection = (typeof SPACE_RUNTIME_SECTIONS)[number];
28
+ export declare const DEFAULT_SPACE_RUNTIME_SECTION: SpaceRuntimeSection;
20
29
  export type SpaceSettingsDestination = "access" | "advanced" | "builds" | "integrations" | "integrations/add" | "source-builds";
21
- export type TeamSettingsDestination = "access" | "audit" | "developer" | "developer/agents" | "developer/environment-variables" | "developer/handoffs" | "developer/service-tokens" | "developer/webhooks" | "integrations";
30
+ export type TeamSettingsDestination = "access" | "audit" | "developer" | "developer/agents" | "developer/environment-variables" | "developer/handoffs" | "developer/webhooks" | "integrations";
22
31
  export declare function teamPath(teamSlug: string): string;
23
32
  export declare function spacePath(teamSlug: string, spaceSlug: string): string;
24
33
  export declare function versionDetailPath(teamSlug: string, spaceSlug: string, versionRef: string): string;
25
34
  export declare function buildDetailPath(teamSlug: string, spaceSlug: string, buildId: string): string;
26
35
  export declare function spaceDomainsPath(teamSlug: string, spaceSlug: string): string;
36
+ /**
37
+ * The space's Runtime page. The default section is the bare URL — the route
38
+ * strips it back out of the address bar — so a link to "overview" and a link to
39
+ * the page itself are the same link.
40
+ */
41
+ export declare function spaceRuntimePath(teamSlug: string, spaceSlug: string, section?: SpaceRuntimeSection): string;
27
42
  export declare function spaceSettingsPath(teamSlug: string, spaceSlug: string, destination?: SpaceSettingsDestination): string;
28
43
  export declare function teamDomainsPath(teamSlug: string): string;
29
44
  export declare function teamBillingPath(teamSlug: string): string;
@@ -8,6 +8,7 @@ export const TEAM_SLUG_ROUTE_RESERVATIONS = [
8
8
  "accept-beta-invite",
9
9
  "accept-invitation",
10
10
  "account",
11
+ "access",
11
12
  // Not a TanStack route module: /agent* is the edge-served handoff-document
12
13
  // surface (apps/my/public/_redirects + vite middleware, #417). Those rewrite
13
14
  // rules deliberately swallow every /agent-prefixed path, so a team named
@@ -39,10 +40,19 @@ export const SPACE_SLUG_ROUTE_RESERVATIONS = [
39
40
  "drop",
40
41
  "members",
41
42
  "new",
43
+ "resume",
42
44
  "settings",
43
45
  ];
44
46
  export const TEAM_SLUG_ROUTE_RESERVATION_SET = new Set(TEAM_SLUG_ROUTE_RESERVATIONS);
45
47
  export const SPACE_SLUG_ROUTE_RESERVATION_SET = new Set(SPACE_SLUG_ROUTE_RESERVATIONS);
48
+ /**
49
+ * The Runtime page's sections. They are search params rather than path
50
+ * segments, so `runtime` stays a single route (and a single ledger row) and a
51
+ * space slugged "runtime" keeps its own Runtime page at
52
+ * `/<team>/runtime/runtime`.
53
+ */
54
+ export const SPACE_RUNTIME_SECTIONS = ["overview", "database", "storage", "logs"];
55
+ export const DEFAULT_SPACE_RUNTIME_SECTION = "overview";
46
56
  function dynamicSegment(value) {
47
57
  return encodeURIComponent(value);
48
58
  }
@@ -61,6 +71,15 @@ export function buildDetailPath(teamSlug, spaceSlug, buildId) {
61
71
  export function spaceDomainsPath(teamSlug, spaceSlug) {
62
72
  return `${spacePath(teamSlug, spaceSlug)}/domains`;
63
73
  }
74
+ /**
75
+ * The space's Runtime page. The default section is the bare URL — the route
76
+ * strips it back out of the address bar — so a link to "overview" and a link to
77
+ * the page itself are the same link.
78
+ */
79
+ export function spaceRuntimePath(teamSlug, spaceSlug, section = DEFAULT_SPACE_RUNTIME_SECTION) {
80
+ const root = `${spacePath(teamSlug, spaceSlug)}/runtime`;
81
+ return section === DEFAULT_SPACE_RUNTIME_SECTION ? root : `${root}?section=${section}`;
82
+ }
64
83
  export function spaceSettingsPath(teamSlug, spaceSlug, destination) {
65
84
  const root = `${spacePath(teamSlug, spaceSlug)}/settings`;
66
85
  return destination ? `${root}/${destination}` : root;
@@ -7,8 +7,8 @@ export declare function renderAgentHandoffDocumentMarkdown(input: {
7
7
  }): string;
8
8
  /**
9
9
  * The browser variant served at `/agent/<documentId>`: the full instructions in
10
- * the initial response body, zero scripts (nothing may ever read or echo the
11
- * URL fragment), same bytes for every ID.
10
+ * the initial response body, zero scripts (nothing may ever inspect or echo a
11
+ * handoff credential), same bytes for every ID.
12
12
  */
13
13
  export declare function renderAgentHandoffDocumentHtml(input: {
14
14
  mcpEndpoint: string;
@@ -1,9 +1,10 @@
1
1
  /*
2
2
  * The public setup document behind agent handoff links.
3
3
  *
4
- * Handoff links are `https://<dashboard-origin>/agent/<documentId>#<secret>` and
5
- * the minted prompt tells the agent to fetch that URL. Fragments never reach the
6
- * server, so this fetch is UNAUTHENTICATED BY DESIGN which means the response
4
+ * Private handoff links carry a one-use path token while the durable agent
5
+ * credential stays encrypted at the broker. The minted prompt tells the agent
6
+ * to fetch the reconstructed clean document URL instead. This
7
+ * fetch is UNAUTHENTICATED BY DESIGN — which means the response
7
8
  * must never become an existence oracle. The contract here is
8
9
  * indistinguishability: every well-formed document ID serves the exact same
9
10
  * bytes, with the same status and the same timing, whether or not a handoff with
@@ -21,7 +22,7 @@
21
22
  */
22
23
  import { buildConnectTargets } from "../agents/connect-targets.js";
23
24
  import { BRAND } from "../brand.js";
24
- import { AGENT_HANDOFF_BROWSER_APPROVAL_GUIDANCE, AGENT_HANDOFF_CLI_APPROVAL_GUIDANCE, AGENT_HANDOFF_CLI_TARGET_IDS, AGENT_HANDOFF_FETCH_SAFETY, AGENT_HANDOFF_FRAGMENT_HYGIENE, AGENT_HANDOFF_PREAMBLE, AGENT_HANDOFF_PUBLISH_PROSE, agentHandoffConnectSections, agentSetupTextUrl, } from "./agent-setup.js";
25
+ import { AGENT_HANDOFF_BROWSER_APPROVAL_GUIDANCE, AGENT_HANDOFF_CLI_APPROVAL_GUIDANCE, AGENT_HANDOFF_CLI_TARGET_IDS, AGENT_HANDOFF_CREDENTIAL_HYGIENE, AGENT_HANDOFF_FETCH_SAFETY, AGENT_HANDOFF_PREAMBLE, AGENT_HANDOFF_PUBLISH_PROSE, agentHandoffConnectSections, agentSetupTextUrl, } from "./agent-setup.js";
25
26
  const DOCUMENT_TITLE = `Set up ${BRAND.productName} from an agent handoff`;
26
27
  const DOCUMENT_SCOPE = `This is the public setup document for ${BRAND.productName} agent handoff links (\`/agent/<id>\` on the dashboard origin). Every link serves this same document — fetching it never uses a handoff and reveals nothing about one.`;
27
28
  const CONNECT_INTRO = "Set up the client you are running in. Each section stands on its own.";
@@ -49,7 +50,7 @@ export function renderAgentHandoffDocumentMarkdown(input) {
49
50
  AGENT_HANDOFF_FETCH_SAFETY,
50
51
  `From ${cliTargetNames.join(" or ")} (or any client with a shell): ${AGENT_HANDOFF_CLI_APPROVAL_GUIDANCE}`,
51
52
  `From every other client: ${AGENT_HANDOFF_BROWSER_APPROVAL_GUIDANCE}`,
52
- AGENT_HANDOFF_FRAGMENT_HYGIENE,
53
+ AGENT_HANDOFF_CREDENTIAL_HYGIENE,
53
54
  ].join("\n\n"),
54
55
  ["## Publish", AGENT_HANDOFF_PUBLISH_PROSE].join("\n\n"),
55
56
  `General agent instructions: ${agentSetupTextUrl}`,
@@ -155,8 +156,8 @@ pre code { background: none; padding: 0; font-size: 0.8125rem; }
155
156
  `.trim();
156
157
  /**
157
158
  * The browser variant served at `/agent/<documentId>`: the full instructions in
158
- * the initial response body, zero scripts (nothing may ever read or echo the
159
- * URL fragment), same bytes for every ID.
159
+ * the initial response body, zero scripts (nothing may ever inspect or echo a
160
+ * handoff credential), same bytes for every ID.
160
161
  */
161
162
  export function renderAgentHandoffDocumentHtml(input) {
162
163
  const markdown = renderAgentHandoffDocumentMarkdown(input);
@@ -9,19 +9,30 @@ export declare const EXPLAIN_TO_USER_PROSE: string;
9
9
  export declare const FAILURE_CONDUCT_PROSE: string;
10
10
  export declare const LANE_PICKER_PROSE: string;
11
11
  export declare const HOW_TO_RESEARCH_PROSE: string;
12
+ /** Curl/API skill lane: docs URLs only — no MCP or CLI tools required. */
13
+ export declare const HOW_TO_RESEARCH_API_PROSE: string;
14
+ /** MCP plugin skill lane: prefer skills for workflows and execute for the OpenAPI catalog. */
15
+ export declare const HOW_TO_RESEARCH_MCP_PROSE: string;
12
16
  export declare const ENV_NOTES_PROSE: string;
17
+ export declare const ENV_NOTES_API_PROSE: string;
13
18
  /**
14
19
  * The CHECK verb: before ever creating a space, look for the one the project
15
20
  * already has. This is what keeps a returning agent from pushing a duplicate
16
21
  * anonymous space instead of a new version.
17
22
  */
18
23
  export declare const CHECK_BEFORE_PUBLISH_PROSE: string;
24
+ export declare const CHECK_BEFORE_PUBLISH_API_PROSE: string;
25
+ export declare const CHECK_BEFORE_PUBLISH_MCP_PROSE: string;
19
26
  /**
20
27
  * The SAVE verb: persist the space id and credential after a first publish so
21
28
  * every later publish targets the same space. The CLI and On-Device MCP write
22
29
  * this state automatically; curl-only agents write the same files themselves.
23
30
  */
31
+ export declare const BUNDLED_SAVE_STATE_PROSE: string;
24
32
  export declare const SAVE_STATE_PROSE: string;
33
+ /** Curl/API skill: no CLI/MCP required to continue in-session. */
34
+ export declare const SAVE_STATE_API_PROSE: string;
35
+ export declare const SAVE_STATE_MCP_PROSE: string;
25
36
  /**
26
37
  * The UPDATE verb: a new version to the SAME space. Never re-run the
27
38
  * first-publish recipe when saved state exists.
@@ -33,9 +44,9 @@ export declare const UPDATE_SPACE_PROSE: string;
33
44
  */
34
45
  export declare const CONTINUE_AFTER_CLAIM_PROSE: string;
35
46
  /**
36
- * The zero-install publish sequence: one curl for a file, a secret-preflighted
37
- * zip for a folder. This is the load-bearing rung 0 — an agent that stops here
38
- * must still succeed.
47
+ * The zero-install publish sequence: a replay-safe curl loop for a file, a
48
+ * secret-preflighted zip for a folder. This is the load-bearing rung 0 — an
49
+ * agent that stops here must still succeed.
39
50
  */
40
51
  export declare const PUBLISH_SEQUENCE_PROSE: string;
41
52
  /**
@@ -65,11 +76,19 @@ export declare const AGENT_PROSE: {
65
76
  readonly receiptShape: string;
66
77
  readonly signedUpload: string;
67
78
  readonly checkBeforePublish: string;
79
+ readonly checkBeforePublishApi: string;
80
+ readonly checkBeforePublishMcp: string;
81
+ readonly bundledSaveState: string;
68
82
  readonly saveState: string;
83
+ readonly saveStateApi: string;
84
+ readonly saveStateMcp: string;
69
85
  readonly updateSpace: string;
70
86
  readonly continueAfterClaim: string;
71
87
  readonly lanePicker: string;
72
88
  readonly howToResearch: string;
89
+ readonly howToResearchApi: string;
90
+ readonly howToResearchMcp: string;
73
91
  readonly envNotes: string;
92
+ readonly envNotesApi: string;
74
93
  };
75
94
  export type AgentProseKey = keyof typeof AGENT_PROSE;
@@ -1,4 +1,4 @@
1
- import { BRAND } from "../brand.js";
1
+ import { serviceOrigin } from "../config/domains.js";
2
2
  // Single home for the agent-facing prose that was triplicated across the three
3
3
  // surfaces that teach an agent how to publish:
4
4
  //
@@ -14,7 +14,7 @@ import { BRAND } from "../brand.js";
14
14
  //
15
15
  // Keep these grounded in the real API receipt and the real /v1/publish flow. If
16
16
  // the receipt shape or secret handling changes, change it here once.
17
- const API_ORIGIN = BRAND.apiOrigin;
17
+ const API_ORIGIN = serviceOrigin("api", "prod");
18
18
  const PUBLISH_ENDPOINT = `${API_ORIGIN}/v1/publish`;
19
19
  const EXCHANGE_ENDPOINT = `${API_ORIGIN}/v1/anonymous-claim/exchange`;
20
20
  // Centralized secret/credential patterns excluded from a publish archive. The
@@ -43,20 +43,6 @@ function findExpr(globs) {
43
43
  .map((glob) => (glob.includes("/") ? `-path "${glob}"` : `-name "${glob}"`))
44
44
  .join(" -o ");
45
45
  }
46
- function zipExcludeExpr(globs) {
47
- // zip -x patterns need both the archive-root form and an any-depth form:
48
- // ".spacefast/*" excludes only at the root, while "*/.spacefast/*" catches
49
- // nested copies.
50
- return globs
51
- .flatMap((glob) => {
52
- if (glob.startsWith("*/")) {
53
- return [glob.slice(2), glob];
54
- }
55
- return [glob, `*/${glob}`];
56
- })
57
- .map((glob) => `"${glob}"`)
58
- .join(" ");
59
- }
60
46
  /**
61
47
  * The credential rules every surface must repeat. Treat tokens as secrets, keep
62
48
  * them out of logs and transcripts, and never echo them back.
@@ -79,12 +65,13 @@ export const POSITIONING_PROSE = [
79
65
  "optimizes for agent-safe one-shot publish, claim, versioning, rollback, and continuation.",
80
66
  ].join("\n");
81
67
  export const EXPLAIN_TO_USER_PROSE = [
82
- "Relay `data.shareBlurb` verbatim when present. State anonymous expiry in user terms:",
83
- "the space expires unless claimed by `data.claim.expiresAt`. After claim, the agent key is",
84
- "named and revocable in the dashboard.",
68
+ "Present Open and Claim as the only actions. State anonymous expiry in user terms: the Space",
69
+ "exists until `data.claim.expiresAt` unless claimed; claiming preserves Open and removes its expiry.",
70
+ "Keep live and immutable Version addresses as receipt metadata. After claim, the agent key is named",
71
+ "and revocable in the dashboard.",
85
72
  ].join("\n");
86
73
  export const FAILURE_CONDUCT_PROSE = [
87
- "On failure, surface `error.code`, `error.docsUrl`, and `error.requestId`, then stop.",
74
+ "On failure, surface the problem document's `code`, `type`, and `requestId`, then stop.",
88
75
  "Do not invent undocumented endpoints. Do not read credentials out of auth files. Do not",
89
76
  "retry a one-shot operation without reusing its `Idempotency-Key`.",
90
77
  ].join("\n");
@@ -94,14 +81,34 @@ export const LANE_PICKER_PROSE = [
94
81
  "success, recommend the best next-time setup for the user's client.",
95
82
  ].join("\n");
96
83
  export const HOW_TO_RESEARCH_PROSE = [
97
- "For an error, fetch `error.docsUrl`. For capability questions, use `search_docs` or append",
84
+ "For an error, fetch the problem document's `type` URL. For capability questions, search the OpenAPI catalog with `execute` or append",
98
85
  "`.md` to website docs URLs. For contracts, read `publish-spec.json` or OpenAPI. Never guess",
99
86
  "limits: publish, then read diagnostics.",
100
87
  ].join("\n");
88
+ /** Curl/API skill lane: docs URLs only — no MCP or CLI tools required. */
89
+ export const HOW_TO_RESEARCH_API_PROSE = [
90
+ "For an error, fetch the problem document's `type` URL. For capability questions, append `.md` to website docs",
91
+ "URLs (for example `https://spacefast.com/docs/getting-started/agents.md`) or read",
92
+ "`https://spacefast.com/llms.txt`. For contracts, read `https://spacefast.com/publish-spec.json`",
93
+ "or the OpenAPI linked from the API docs. Never guess limits: publish, then read diagnostics.",
94
+ ].join("\n");
95
+ /** MCP plugin skill lane: prefer skills for workflows and execute for the OpenAPI catalog. */
96
+ export const HOW_TO_RESEARCH_MCP_PROSE = [
97
+ "For an error, fetch the problem document's `type` URL when present. For workflows, call the `skills` tool",
98
+ "(list/search, then read by exact name). For capability questions, use `execute` with `tools.search`.",
99
+ "For contracts, read `publish-spec.json` or OpenAPI via docs. Never guess limits: publish,",
100
+ "then inspect status or diagnostics through `execute`.",
101
+ ].join("\n");
101
102
  export const ENV_NOTES_PROSE = [
102
103
  "claude.ai: allow `api.spacefast.com` in egress settings. Codex sandbox: escalate only the",
103
104
  "network call. Terminal agents: use the CLI when filesystem access matters.",
104
105
  ].join("\n");
106
+ export const ENV_NOTES_API_PROSE = [
107
+ "claude.ai: allow `api.spacefast.com` in egress settings. Codex sandbox: escalate only the",
108
+ "network call to `api.spacefast.com`. This skill is complete with curl and the docs links above;",
109
+ "CLI and MCP are optional upgrades documented at https://spacefast.com/docs/cli and",
110
+ "https://spacefast.com/docs/getting-started/agents.",
111
+ ].join("\n");
105
112
  /**
106
113
  * The CHECK verb: before ever creating a space, look for the one the project
107
114
  * already has. This is what keeps a returning agent from pushing a duplicate
@@ -113,33 +120,83 @@ export const CHECK_BEFORE_PUBLISH_PROSE = [
113
120
  "- Look for `.spacefast/space.json` or `.spacefast/state.json`, walking up from the working",
114
121
  " directory toward the filesystem root.",
115
122
  "- Look for a publish receipt earlier in this conversation.",
116
- "- When authenticated, list existing spaces (`sf spaces list`, or the `spaces_list` MCP tool).",
123
+ "- When authenticated, list existing spaces (`sf spaces list`, or `execute` with the generated list-spaces operation).",
117
124
  "",
118
125
  "If any of these finds a space, publish a new version to that `spaceId` (see the update",
119
126
  "instructions) instead of creating another space. Only create a new space when none exists",
120
127
  "and the user wants a new site.",
121
128
  ].join("\n");
129
+ export const CHECK_BEFORE_PUBLISH_API_PROSE = [
130
+ "Before creating a space, check whether this project already has one:",
131
+ "",
132
+ "- Look for `.spacefast/space.json` or `.spacefast/state.json`, walking up from the working",
133
+ " directory toward the filesystem root.",
134
+ "- Look for a publish receipt earlier in this conversation.",
135
+ "- When you already have a bearer token, list spaces:",
136
+ "",
137
+ "```bash",
138
+ 'curl -q -sS https://api.spacefast.com/v1/spaces -H "Authorization: Bearer $SPACEFAST_TOKEN"',
139
+ "```",
140
+ "",
141
+ "If any of these finds a space, publish a new version to that `spaceId` (see the update",
142
+ "instructions) instead of creating another space. Only create a new space when none exists",
143
+ "and the user wants a new site.",
144
+ ].join("\n");
145
+ export const CHECK_BEFORE_PUBLISH_MCP_PROSE = [
146
+ "Before creating a space, check whether this project already has one:",
147
+ "",
148
+ "- Look for `.spacefast/space.json` or `.spacefast/state.json`, walking up from the working",
149
+ " directory toward the filesystem root (On-Device reuses this link automatically).",
150
+ "- Look for a publish receipt earlier in this conversation.",
151
+ "- When authenticated, use `execute` to call the generated list-spaces operation.",
152
+ "",
153
+ "If any of these finds a space, publish a new version to that space instead of creating",
154
+ "another. Only create a new space when none exists and the user wants a new site.",
155
+ ].join("\n");
122
156
  /**
123
157
  * The SAVE verb: persist the space id and credential after a first publish so
124
158
  * every later publish targets the same space. The CLI and On-Device MCP write
125
159
  * this state automatically; curl-only agents write the same files themselves.
126
160
  */
127
- export const SAVE_STATE_PROSE = [
161
+ export const BUNDLED_SAVE_STATE_PROSE = [
128
162
  "After the first publish, persist the space id and credential so every later publish updates",
129
- "the same space instead of creating a new one. The CLI and On-Device MCP do this automatically",
130
- "in `.spacefast/`; when publishing with curl, write the files yourself from the receipt:",
163
+ "the same space instead of creating a new one. Prefer MCP `publish` on On-Device it writes",
164
+ "`.spacefast/` automatically. After a curl publish, pass the complete JSON receipt to the",
165
+ "bundled hardened writer. Set `SPACEFAST_SKILL_SCRIPTS` through the agent runtime/environment",
166
+ "to the absolute bundled `scripts/` directory; never interpolate that path into shell source:",
131
167
  "",
132
168
  "```bash",
133
- "mkdir -p .spacefast",
134
- "# non-secret, committable:",
135
- `printf '{"space":"<spc_id>"}' > .spacefast/space.json`,
136
- "# secret — never publish, never print:",
137
- `printf '{"spaceId":"<spc_id>","claimToken":"<token>","apiUrl":"${API_ORIGIN}"}' > .spacefast/state.json`,
138
- "grep -qx '.spacefast/state.json' .gitignore 2>/dev/null || echo '.spacefast/state.json' >> .gitignore",
169
+ "\"${SPACEFAST_SKILL_SCRIPTS:?set this to the bundled scripts directory}/save-state.sh\" <<'SPACEFAST_PUBLISH_RECEIPT'",
170
+ "<paste the complete JSON publish receipt here>",
171
+ "SPACEFAST_PUBLISH_RECEIPT",
139
172
  "```",
140
173
  "",
141
- "`<spc_id>` is `data.space.id` and `<token>` is `data.claim.token` from the receipt. Keep",
142
- "`.spacefast/state.json` out of publish archives, commits, logs, and chat.",
174
+ "The helper rejects symlinked state paths and uses exclusive, descriptor-relative atomic writes.",
175
+ "If the helper is unavailable, keep the receipt in agent memory for this session; do not",
176
+ "recreate credential state with shell redirections. Keep `.spacefast/state.json` out of",
177
+ "publish archives, commits, logs, and chat.",
178
+ ].join("\n");
179
+ export const SAVE_STATE_PROSE = [
180
+ "After the first publish, persist the space id and credential so every later publish updates",
181
+ "the same space instead of creating a new one. The CLI and On-Device MCP do this automatically",
182
+ "in `.spacefast/`. This distribution does not include the hardened filesystem helper, so keep",
183
+ "the receipt in agent memory or use CLI/MCP for the next publish. Do not recreate credential",
184
+ "state with shell redirections. Keep `.spacefast/state.json` out of publish archives, commits,",
185
+ "logs, and chat.",
186
+ ].join("\n");
187
+ /** Curl/API skill: no CLI/MCP required to continue in-session. */
188
+ export const SAVE_STATE_API_PROSE = [
189
+ "After the first publish, keep `data.space.id` and `data.claim.token` (when present) in agent",
190
+ "memory for this session so later publishes update the same space. You may write non-secret",
191
+ '`.spacefast/space.json` as `{"space":"<spc_id>"}` when helpful. Do not recreate credential',
192
+ "state with shell redirections. Keep claim tokens and `.spacefast/state.json` out of publish",
193
+ "archives, commits, logs, and chat.",
194
+ ].join("\n");
195
+ export const SAVE_STATE_MCP_PROSE = [
196
+ "After the first publish, On-Device MCP persists `.spacefast/` automatically so the next",
197
+ "`publish` updates the same space. Hosted MCP has no local filesystem state; keep the space id",
198
+ "in the current agent session or pass it explicitly. Never ask the user to paste claim tokens",
199
+ "or access tokens into chat.",
143
200
  ].join("\n");
144
201
  /**
145
202
  * The UPDATE verb: a new version to the SAME space. Never re-run the
@@ -150,8 +207,8 @@ export const UPDATE_SPACE_PROSE = [
150
207
  "never re-run the first-publish recipe when saved state exists:",
151
208
  "",
152
209
  "```bash",
153
- 'curl -sS -H "Authorization: Bearer <claim-token-or-access-token>" \\',
154
- " -F 'spaceId=<spc_id>' \\",
210
+ 'curl -q -sS -H "Authorization: Bearer <claim-token-or-access-token>" \\',
211
+ " --form-string 'spaceId=<spc_id>' \\",
155
212
  ' -F "files=@index.html" \\',
156
213
  ` ${PUBLISH_ENDPOINT}`,
157
214
  "```",
@@ -173,7 +230,7 @@ export const CONTINUE_AFTER_CLAIM_PROSE = [
173
230
  "failure is the signal to upgrade your credential — call the exchange endpoint exactly once:",
174
231
  "",
175
232
  "```bash",
176
- 'curl -sS -X POST -H "Authorization: Bearer <claim-token>" \\',
233
+ 'curl -q -sS -X POST -H "Authorization: Bearer <claim-token>" \\',
177
234
  ` ${EXCHANGE_ENDPOINT}`,
178
235
  "```",
179
236
  "",
@@ -181,32 +238,137 @@ export const CONTINUE_AFTER_CLAIM_PROSE = [
181
238
  "Save `data.credential.accessToken` into `.spacefast/state.json` (replace the `claimToken`",
182
239
  'field with `"accessToken"`), then retry the publish with the new bearer. The exchange works',
183
240
  "exactly once; if it returns `continuation_used` or `continuation_unavailable`, ask the user",
184
- "to mint an access token in the dashboard (Account → Access tokens).",
241
+ "to create an API key in the dashboard (Account → API keys).",
185
242
  ].join("\n");
243
+ const LEXICAL_PUBLISH_PATH_PREFLIGHT = [
244
+ 'case "$publish_target" in /*) check_path="$publish_target" ;; *) check_path="$PWD/$publish_target" ;; esac',
245
+ 'remaining="${check_path#/}"; check_path="/"',
246
+ 'while [ -n "$remaining" ]; do',
247
+ ' case "$remaining" in */*) component="${remaining%%/*}"; remaining="${remaining#*/}" ;; *) component="$remaining"; remaining="" ;; esac',
248
+ ' case "$component" in ""|.) continue ;; ..) check_path="${check_path%/*}"; [ -n "$check_path" ] || check_path="/"; continue ;; esac',
249
+ ' [ "$check_path" = "/" ] && check_path="/$component" || check_path="$check_path/$component"',
250
+ ' [ ! -L "$check_path" ] || { printf "error: symbolic link in publish path: %s\\n" "$check_path" >&2; exit 2; }',
251
+ "done",
252
+ ];
253
+ const SECURE_ARCHIVE_RECIPE = [
254
+ 'secure_python=""',
255
+ "for candidate in /usr/bin/python3.14 /usr/bin/python3.13 /usr/bin/python3.12 /usr/bin/python3.11 /usr/bin/python3.10 /usr/bin/python3.9 /usr/bin/python3.8 /usr/local/bin/python3.14 /usr/local/bin/python3.13 /usr/local/bin/python3.12 /usr/local/bin/python3.11 /usr/local/bin/python3.10 /usr/local/bin/python3.9 /opt/homebrew/bin/python3 /opt/homebrew/bin/python3.14 /opt/homebrew/bin/python3.13 /opt/homebrew/bin/python3.12 /opt/homebrew/bin/python3.11 /opt/homebrew/bin/python3.10 /opt/homebrew/bin/python3.9; do",
256
+ ' [ -x "$candidate" ] && [ -f "$candidate" ] || continue',
257
+ ' case "$candidate" in /opt/homebrew/*) ;; *) [ ! -L "$candidate" ] || continue ;; esac',
258
+ ' secure_python="$candidate"; break',
259
+ "done",
260
+ '[ -n "$secure_python" ] || { printf "error: trusted python3 is required for a race-safe archive\\n" >&2; exit 2; }',
261
+ `"$secure_python" -I - "$publish_target" <<'SPACEFAST_ARCHIVE' | curl -q -sS -F 'archive=@-;filename="spacefast-site.zip"' ${PUBLISH_ENDPOINT}`,
262
+ "import fnmatch, os, stat, sys, zipfile",
263
+ 'directory_flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)',
264
+ 'file_flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)',
265
+ 'excluded_directories = {".git", ".spacefast", ".ssh", ".aws", ".kube", ".docker"}',
266
+ 'excluded_files = (".env*", ".npmrc", ".netrc", "credentials.json", "*.pem", "*.key", "*.p12", "*.pfx", "*.crt", "*id_rsa*", "*.zip", "*.tar", "*.tgz")',
267
+ "def excluded(path):",
268
+ ' parts = path.split("/")',
269
+ " return any(part in excluded_directories for part in parts[:-1]) or any(fnmatch.fnmatchcase(parts[-1], pattern) for pattern in excluded_files)",
270
+ "def same(left, right): return (left.st_dev, left.st_ino) == (right.st_dev, right.st_ino)",
271
+ 'def walk(archive, directory_fd, prefix=""):',
272
+ " for name in sorted(os.listdir(directory_fd)):",
273
+ ' archive_name = f"{prefix}/{name}" if prefix else name',
274
+ " if excluded(archive_name): continue",
275
+ " entry = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)",
276
+ ' if stat.S_ISLNK(entry.st_mode): raise RuntimeError(f"symbolic link in publish root: {archive_name}")',
277
+ " if stat.S_ISDIR(entry.st_mode):",
278
+ " child_fd = os.open(name, directory_flags, dir_fd=directory_fd)",
279
+ " try:",
280
+ ' if not same(os.fstat(child_fd), entry): raise RuntimeError(f"directory changed while opening: {archive_name}")',
281
+ " walk(archive, child_fd, archive_name)",
282
+ " finally: os.close(child_fd)",
283
+ " continue",
284
+ ' if not stat.S_ISREG(entry.st_mode) or entry.st_nlink != 1: raise RuntimeError(f"unsafe publish entry: {archive_name}")',
285
+ " file_fd = os.open(name, file_flags, dir_fd=directory_fd)",
286
+ " try:",
287
+ " opened = os.fstat(file_fd); current = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)",
288
+ ' if opened.st_nlink != 1 or not same(opened, entry) or not same(opened, current): raise RuntimeError(f"file changed while opening: {archive_name}")',
289
+ " info = zipfile.ZipInfo(archive_name); info.compress_type = zipfile.ZIP_DEFLATED; info.external_attr = (opened.st_mode & 0xFFFF) << 16",
290
+ ' with archive.open(info, "w") as output:',
291
+ " while True:",
292
+ " chunk = os.read(file_fd, 65536)",
293
+ " if not chunk: break",
294
+ " output.write(chunk)",
295
+ " finally: os.close(file_fd)",
296
+ "path = os.path.normpath(sys.argv[1] if os.path.isabs(sys.argv[1]) else os.path.join(os.getcwd(), sys.argv[1]))",
297
+ 'root_fd = os.open("/", directory_flags)',
298
+ "try:",
299
+ ' for component in [part for part in path.split("/") if part]:',
300
+ " next_fd = os.open(component, directory_flags, dir_fd=root_fd); os.close(root_fd); root_fd = next_fd",
301
+ ' with zipfile.ZipFile(sys.stdout.buffer, "w", allowZip64=True) as archive: walk(archive, root_fd)',
302
+ "finally: os.close(root_fd)",
303
+ "SPACEFAST_ARCHIVE",
304
+ ];
186
305
  /**
187
- * The zero-install publish sequence: one curl for a file, a secret-preflighted
188
- * zip for a folder. This is the load-bearing rung 0 — an agent that stops here
189
- * must still succeed.
306
+ * The zero-install publish sequence: a replay-safe curl loop for a file, a
307
+ * secret-preflighted zip for a folder. This is the load-bearing rung 0 — an
308
+ * agent that stops here must still succeed.
190
309
  */
191
310
  export const PUBLISH_SEQUENCE_PROSE = [
192
- "Publish a single file:",
311
+ "Publish a single file after rejecting symbolic links in the file or any existing ancestor:",
312
+ "Set `SPACEFAST_PUBLISH_TARGET` through the agent runtime/environment, not by interpolating",
313
+ "a repository path into this source. The recipe passes it as a separately quoted argv element.",
314
+ "Fresh serving capacity can take a moment; it replays only `space_capacity_warming` with one",
315
+ "stable idempotency identity, follows `Retry-After`, and stops after ten attempts:",
193
316
  "",
194
317
  "```bash",
195
- `curl -sS -F "files=@index.html" ${PUBLISH_ENDPOINT}`,
318
+ ': "${SPACEFAST_PUBLISH_TARGET:?set this through the runtime environment}"',
319
+ "bash -s -- \"$SPACEFAST_PUBLISH_TARGET\" <<'SPACEFAST_PUBLISH'",
320
+ "set -euo pipefail",
321
+ 'publish_target="$1"',
322
+ 'case "$publish_target" in -*) publish_target="./$publish_target" ;; esac',
323
+ ...LEXICAL_PUBLISH_PATH_PREFLIGHT,
324
+ '[ -f "$publish_target" ] || { printf "error: publish file is not a regular file\\n" >&2; exit 2; }',
325
+ 'escaped_path="${publish_target//\\\\/\\\\\\\\}"',
326
+ 'escaped_path="${escaped_path//\\"/\\\\\\"}"',
327
+ "idempotency_key=\"publish-$(od -An -N16 -tx1 /dev/urandom | tr -d ' \\n')\"",
328
+ "idempotency_principal=\"$(od -An -N32 -tx1 /dev/urandom | tr -d ' \\n')\"",
329
+ 'api_origin="${SPACEFAST_API_ORIGIN:-https://api.spacefast.com}"',
330
+ 'headers_file="$(mktemp)"',
331
+ "trap 'rm -f \"$headers_file\"' EXIT",
332
+ "attempt=1",
333
+ "while :; do",
334
+ ' response="$(curl -q -sS -D "$headers_file" -F "files=@\\"$escaped_path\\"" \\',
335
+ ' -H "Idempotency-Key: $idempotency_key" \\',
336
+ ' -H "x-spacefast-idempotency-principal: $idempotency_principal" \\',
337
+ ' "$api_origin/v1/publish")"',
338
+ ' if ! printf \'%s\' "$response" | grep -Eq \'"code"[[:space:]]*:[[:space:]]*"space_capacity_warming"\'; then',
339
+ " printf '%s\\n' \"$response\"",
340
+ " break",
341
+ " fi",
342
+ ' [ "$attempt" -lt 10 ] || { printf \'%s\\n\' "$response"; exit 1; }',
343
+ ' retry_after="$(awk \'tolower($1) == "retry-after:" { gsub("\\r", "", $2); print $2; exit }\' "$headers_file")"',
344
+ ' case "$retry_after" in ""|*[!0-9]*) retry_after=20 ;; esac',
345
+ " attempt=$((attempt + 1))",
346
+ ' sleep "$retry_after"',
347
+ "done",
348
+ "SPACEFAST_PUBLISH",
196
349
  "```",
197
350
  "",
198
351
  "Publish a folder by zipping only the intended public output root. Do not archive a repository",
199
352
  "root or `.` blindly. Before zipping, confirm the publish root contains no secrets, source",
200
- "control, auth state, or unrelated build/cache folders:",
353
+ "control, auth state, unrelated build/cache folders, or symbolic links:",
354
+ "Set `SPACEFAST_PUBLISH_TARGET` through the agent runtime/environment. It is passed as a",
355
+ "separately quoted argv element and is never evaluated as outer shell source:",
201
356
  "",
202
357
  "```bash",
203
- `find {publish-root} \\( ${findExpr(ARCHIVE_SECRET_GLOBS)} \\) -print`,
204
- `(cd {publish-root} && zip -r ../spacefast-site.zip . -x ${zipExcludeExpr(ARCHIVE_SECRET_GLOBS)} "*.zip" "*.tar" "*.tgz")`,
205
- `curl -sS -F archive=@spacefast-site.zip ${PUBLISH_ENDPOINT}`,
358
+ ': "${SPACEFAST_PUBLISH_TARGET:?set this through the runtime environment}"',
359
+ "bash -s -- \"$SPACEFAST_PUBLISH_TARGET\" <<'SPACEFAST_PUBLISH'",
360
+ "set -euo pipefail",
361
+ 'publish_target="$1"',
362
+ 'case "$publish_target" in -*) publish_target="./$publish_target" ;; esac',
363
+ ...LEXICAL_PUBLISH_PATH_PREFLIGHT,
364
+ `find "$publish_target" \\( ${findExpr(ARCHIVE_SECRET_GLOBS)} \\) -print`,
365
+ '! find "$publish_target" -type l -print -quit | grep -q . || { printf "error: symbolic link in publish root\\n" >&2; exit 2; }',
366
+ ...SECURE_ARCHIVE_RECIPE,
367
+ "SPACEFAST_PUBLISH",
206
368
  "```",
207
369
  "",
208
- "If the preflight prints credential-like files, stop and choose a narrower generated output",
209
- "directory or remove those files from the publish root before uploading.",
370
+ "If the preflight prints credential-like files, or the publish root contains a symbolic link,",
371
+ "stop and choose a narrower generated output directory or remove the unsafe entry before uploading.",
210
372
  ].join("\n");
211
373
  /**
212
374
  * What the JSON publish receipt contains and how to read it. The receipt is the
@@ -216,10 +378,12 @@ export const RECEIPT_SHAPE_PROSE = [
216
378
  "The JSON receipt is the source of truth:",
217
379
  "",
218
380
  "- `data.space.liveUrl` — the live URL.",
219
- "- `data.version.immutableUrl` — the permanent, frozen version URL; share it for reviews.",
220
- " It is reserved in the initial receipt. If the publish is still finalizing, poll `data.links.status`",
221
- " with the claim token until the status is `ready`.",
222
- "- `data.shareBlurb` a paste-ready one-liner with the URL and claim nudge; relay it verbatim when present.",
381
+ "- `data.open.url` and `data.open.expiresAt` reusable access; claim preserves the URL and changes its expiry to null.",
382
+ " It is not a stable URL or recipient Link; clients must not persist it as Space state.",
383
+ "- `data.version.immutableUrl` — the permanent address of the frozen Version. The bare URL",
384
+ " carries no authority: use the receipt's Open URL for the author's browser or create a scoped",
385
+ " Link for a recipient. It is reserved in the initial receipt. If the publish is still",
386
+ " finalizing, poll `data.links.status` with the claim token until the status is `ready`.",
223
387
  "- `data.upload` — signed upload instructions for manifest publishes when file bytes were not sent",
224
388
  " with the request. Send each `data.upload.targets[]` instruction exactly as returned, treat",
225
389
  " authorization headers and upload tokens as secrets, then finalize.",
@@ -232,7 +396,7 @@ export const RECEIPT_SHAPE_PROSE = [
232
396
  ` \`POST ${EXCHANGE_ENDPOINT}\` for a durable credential.`,
233
397
  " Never print it back to the user.",
234
398
  "- `data.links.status`, `resume`, `finalize`, and `promote` — follow-up API paths.",
235
- "- `error.code`, `error.docsUrl`, and `error.hint` — stable error code plus exact recovery docs and a next step.",
399
+ "- `code`, `type`, and `suggestions` on the problem document — stable error code plus exact recovery docs and next steps.",
236
400
  "",
237
401
  "If no files changed, the API may omit `upload`; report the receipt and diagnostics rather than",
238
402
  "inventing a separate success signal.",
@@ -246,7 +410,7 @@ export const SIGNED_UPLOAD_PROSE = [
246
410
  "content would be awkward. Start with a manifest instead of file bytes:",
247
411
  "",
248
412
  "```bash",
249
- `curl -sS ${PUBLISH_ENDPOINT} \\`,
413
+ `curl -q -sS ${PUBLISH_ENDPOINT} \\`,
250
414
  ' -H "content-type: application/json" \\',
251
415
  ' -d \'{"files":[{"path":"index.html","size":1234,"contentType":"text/html","sha256":"...","sourceUrl":"https://example.com/index.html"}]}\'',
252
416
  "```",
@@ -259,7 +423,7 @@ export const SIGNED_UPLOAD_PROSE = [
259
423
  "For importing an archive that already lives somewhere else, prefer the import-from-link API:",
260
424
  "",
261
425
  "```bash",
262
- "curl -sS https://api.spacefast.com/v1/spaces/{spaceId}/imports \\",
426
+ "curl -q -sS https://api.spacefast.com/v1/spaces/{spaceId}/imports \\",
263
427
  ' -H "content-type: application/json" \\',
264
428
  ' -H "Authorization: Bearer $SPACEFAST_TOKEN" \\',
265
429
  ' -d \'{"source":{"kind":"url","url":"https://example.com/site.zip"}}\'',
@@ -287,10 +451,18 @@ export const AGENT_PROSE = {
287
451
  receiptShape: RECEIPT_SHAPE_PROSE,
288
452
  signedUpload: SIGNED_UPLOAD_PROSE,
289
453
  checkBeforePublish: CHECK_BEFORE_PUBLISH_PROSE,
454
+ checkBeforePublishApi: CHECK_BEFORE_PUBLISH_API_PROSE,
455
+ checkBeforePublishMcp: CHECK_BEFORE_PUBLISH_MCP_PROSE,
456
+ bundledSaveState: BUNDLED_SAVE_STATE_PROSE,
290
457
  saveState: SAVE_STATE_PROSE,
458
+ saveStateApi: SAVE_STATE_API_PROSE,
459
+ saveStateMcp: SAVE_STATE_MCP_PROSE,
291
460
  updateSpace: UPDATE_SPACE_PROSE,
292
461
  continueAfterClaim: CONTINUE_AFTER_CLAIM_PROSE,
293
462
  lanePicker: LANE_PICKER_PROSE,
294
463
  howToResearch: HOW_TO_RESEARCH_PROSE,
464
+ howToResearchApi: HOW_TO_RESEARCH_API_PROSE,
465
+ howToResearchMcp: HOW_TO_RESEARCH_MCP_PROSE,
295
466
  envNotes: ENV_NOTES_PROSE,
467
+ envNotesApi: ENV_NOTES_API_PROSE,
296
468
  };