@spacefast/common 0.0.13 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +149 -44
  14. package/dist/contracts/access.js +94 -8
  15. package/dist/contracts/activity.js +5 -1
  16. package/dist/contracts/api-keys.js +9 -9
  17. package/dist/contracts/archives.js +11 -11
  18. package/dist/contracts/beta.js +1 -1
  19. package/dist/contracts/builds.js +14 -14
  20. package/dist/contracts/channels.js +2 -2
  21. package/dist/contracts/comments.d.ts +86 -15
  22. package/dist/contracts/comments.js +116 -20
  23. package/dist/contracts/common.d.ts +12 -12
  24. package/dist/contracts/common.js +33 -30
  25. package/dist/contracts/continuation.js +5 -5
  26. package/dist/contracts/data-source-routes.js +1 -1
  27. package/dist/contracts/device-auth.d.ts +6 -6
  28. package/dist/contracts/device-auth.js +12 -12
  29. package/dist/contracts/docs.d.ts +10 -10
  30. package/dist/contracts/domains.js +39 -33
  31. package/dist/contracts/enums.d.ts +2 -10
  32. package/dist/contracts/enums.js +0 -2
  33. package/dist/contracts/error-code-meta.d.ts +116 -47
  34. package/dist/contracts/error-code-meta.js +37 -16
  35. package/dist/contracts/error-codes.d.ts +7 -4
  36. package/dist/contracts/error-codes.js +46 -21
  37. package/dist/contracts/events.d.ts +10 -3
  38. package/dist/contracts/events.js +33 -5
  39. package/dist/contracts/execution.d.ts +26 -0
  40. package/dist/contracts/execution.js +21 -0
  41. package/dist/contracts/feature-lifecycle.d.ts +21 -24
  42. package/dist/contracts/feature-lifecycle.js +28 -26
  43. package/dist/contracts/features.js +4 -4
  44. package/dist/contracts/functions.d.ts +8 -140
  45. package/dist/contracts/functions.js +9 -19
  46. package/dist/contracts/git.d.ts +1 -1
  47. package/dist/contracts/grants.d.ts +96 -79
  48. package/dist/contracts/grants.js +130 -43
  49. package/dist/contracts/ids.d.ts +2 -2
  50. package/dist/contracts/ids.js +2 -4
  51. package/dist/contracts/internal.js +2 -2
  52. package/dist/contracts/mcp.d.ts +17 -556
  53. package/dist/contracts/mcp.js +9 -103
  54. package/dist/contracts/me.js +3 -4
  55. package/dist/contracts/notifications.d.ts +108 -0
  56. package/dist/contracts/notifications.js +54 -0
  57. package/dist/contracts/oauth-resources.d.ts +18 -1
  58. package/dist/contracts/oauth-resources.js +22 -2
  59. package/dist/contracts/operations.d.ts +2 -50
  60. package/dist/contracts/operations.js +0 -19
  61. package/dist/contracts/pages.d.ts +8 -4
  62. package/dist/contracts/pages.js +16 -3
  63. package/dist/contracts/platform.js +10 -8
  64. package/dist/contracts/problem-document.d.ts +14 -0
  65. package/dist/contracts/problem-document.js +35 -0
  66. package/dist/contracts/push-new.d.ts +21 -6
  67. package/dist/contracts/push-new.js +3 -7
  68. package/dist/contracts/resources.d.ts +8 -7
  69. package/dist/contracts/resources.js +9 -6
  70. package/dist/contracts/runtime-api.d.ts +162 -92
  71. package/dist/contracts/runtime-api.js +99 -27
  72. package/dist/contracts/runtime-app.d.ts +41 -11
  73. package/dist/contracts/runtime-app.js +22 -1
  74. package/dist/contracts/runtime-db.d.ts +424 -0
  75. package/dist/contracts/runtime-db.js +108 -0
  76. package/dist/contracts/runtime-storage.d.ts +120 -0
  77. package/dist/contracts/runtime-storage.js +40 -0
  78. package/dist/contracts/sf-config-v1.d.ts +49 -1
  79. package/dist/contracts/sf-config-v1.js +2 -2
  80. package/dist/contracts/sites.d.ts +1 -0
  81. package/dist/contracts/sites.js +20 -12
  82. package/dist/contracts/space-config.d.ts +62 -22
  83. package/dist/contracts/space-config.js +173 -47
  84. package/dist/contracts/spaces.d.ts +79 -45
  85. package/dist/contracts/spaces.js +66 -50
  86. package/dist/contracts/superadmin-spaces.d.ts +13 -2
  87. package/dist/contracts/superadmin-spaces.js +15 -0
  88. package/dist/contracts/superadmin-tenants.js +4 -4
  89. package/dist/contracts/superadmin.d.ts +4 -0
  90. package/dist/contracts/superadmin.js +14 -1
  91. package/dist/contracts/tags.d.ts +17 -17
  92. package/dist/contracts/tags.js +1 -1
  93. package/dist/contracts/teams.js +25 -16
  94. package/dist/contracts/theme-json.js +1 -1
  95. package/dist/contracts/transfers.js +4 -4
  96. package/dist/contracts/variables.js +3 -3
  97. package/dist/contracts/zero.d.ts +204 -338
  98. package/dist/contracts/zero.js +94 -98
  99. package/dist/dashboard-paths/index.d.ts +16 -1
  100. package/dist/dashboard-paths/index.js +18 -0
  101. package/dist/docs/agent-handoff-document.d.ts +2 -2
  102. package/dist/docs/agent-handoff-document.js +8 -7
  103. package/dist/docs/agent-prose.d.ts +22 -3
  104. package/dist/docs/agent-prose.js +221 -51
  105. package/dist/docs/agent-setup.d.ts +13 -10
  106. package/dist/docs/agent-setup.js +279 -53
  107. package/dist/docs/agent-solutions.d.ts +6 -6
  108. package/dist/docs/agent-solutions.js +23 -21
  109. package/dist/docs/catalog.d.ts +29 -74
  110. package/dist/docs/catalog.js +65 -92
  111. package/dist/docs/error-docs.js +293 -221
  112. package/dist/docs/skill-distribution.d.ts +2 -0
  113. package/dist/docs/skill-distribution.js +7 -4
  114. package/dist/docs/skills.d.ts +546 -0
  115. package/dist/docs/skills.js +340 -0
  116. package/dist/utils/browser-credential.d.ts +19 -0
  117. package/dist/utils/browser-credential.js +62 -0
  118. package/dist/utils/comment-avatars.d.ts +7 -0
  119. package/dist/utils/comment-avatars.js +42 -0
  120. package/dist/utils/content-type.d.ts +3 -3
  121. package/dist/utils/content-type.js +38 -61
  122. package/dist/utils/domain-diagnostics.d.ts +5 -5
  123. package/dist/utils/domain-diagnostics.js +21 -10
  124. package/dist/utils/local-space-state.d.ts +78 -4
  125. package/dist/utils/local-space-state.js +383 -82
  126. package/dist/utils/one-shot-replay.d.ts +14 -0
  127. package/dist/utils/one-shot-replay.js +21 -0
  128. package/dist/utils/page-preview.d.ts +7 -0
  129. package/dist/utils/page-preview.js +9 -0
  130. package/dist/utils/pages.d.ts +2 -11
  131. package/dist/utils/pages.js +58 -14
  132. package/dist/utils/publish-form-data.d.ts +22 -0
  133. package/dist/utils/publish-form-data.js +26 -0
  134. package/dist/utils/publish-policy.js +20 -4
  135. package/dist/utils/query-keys.d.ts +7 -4
  136. package/dist/utils/query-keys.js +13 -4
  137. package/dist/utils/runtime-paths.d.ts +3 -0
  138. package/dist/utils/runtime-paths.js +7 -0
  139. package/dist/utils/secure-local-file.d.ts +73 -0
  140. package/dist/utils/secure-local-file.js +1373 -0
  141. package/dist/utils/space-config.d.ts +13 -4
  142. package/dist/utils/space-config.js +123 -15
  143. package/dist/utils/storage-policy.d.ts +20 -0
  144. package/dist/utils/storage-policy.fixtures.json +174 -0
  145. package/dist/utils/storage-policy.js +193 -0
  146. package/dist/utils/upload-session.d.ts +12 -1
  147. package/dist/utils/upload-session.js +37 -2
  148. package/dist/utils/version-file-browser.d.ts +13 -0
  149. package/dist/utils/version-file-browser.js +59 -0
  150. package/package.json +19 -1
  151. package/dist/contracts/mounts.d.ts +0 -142
  152. package/dist/contracts/mounts.js +0 -56
  153. package/dist/utils/asset-fingerprint.d.ts +0 -64
  154. package/dist/utils/asset-fingerprint.js +0 -620
@@ -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
- "Report the bare Live URL, repeatable expiring private Open URL, immutable Version URL, and Claim link",
83
- "as separate actions. State anonymous expiry in user terms: the space expires unless claimed by",
84
- "`data.claim.expiresAt`. After claim, the agent key is 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
  "",
@@ -183,30 +240,135 @@ export const CONTINUE_AFTER_CLAIM_PROSE = [
183
240
  "exactly once; if it returns `continuation_used` or `continuation_unavailable`, ask the user",
184
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,7 +378,7 @@ 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.open.url` and `data.open.expiresAt` — a repeatable private author Link until expiry, claim, or revocation.",
381
+ "- `data.open.url` and `data.open.expiresAt` — reusable access; claim preserves the URL and changes its expiry to null.",
220
382
  " It is not a stable URL or recipient Link; clients must not persist it as Space state.",
221
383
  "- `data.version.immutableUrl` — the permanent address of the frozen Version. The bare URL",
222
384
  " carries no authority: use the receipt's Open URL for the author's browser or create a scoped",
@@ -234,7 +396,7 @@ export const RECEIPT_SHAPE_PROSE = [
234
396
  ` \`POST ${EXCHANGE_ENDPOINT}\` for a durable credential.`,
235
397
  " Never print it back to the user.",
236
398
  "- `data.links.status`, `resume`, `finalize`, and `promote` — follow-up API paths.",
237
- "- `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.",
238
400
  "",
239
401
  "If no files changed, the API may omit `upload`; report the receipt and diagnostics rather than",
240
402
  "inventing a separate success signal.",
@@ -248,7 +410,7 @@ export const SIGNED_UPLOAD_PROSE = [
248
410
  "content would be awkward. Start with a manifest instead of file bytes:",
249
411
  "",
250
412
  "```bash",
251
- `curl -sS ${PUBLISH_ENDPOINT} \\`,
413
+ `curl -q -sS ${PUBLISH_ENDPOINT} \\`,
252
414
  ' -H "content-type: application/json" \\',
253
415
  ' -d \'{"files":[{"path":"index.html","size":1234,"contentType":"text/html","sha256":"...","sourceUrl":"https://example.com/index.html"}]}\'',
254
416
  "```",
@@ -261,7 +423,7 @@ export const SIGNED_UPLOAD_PROSE = [
261
423
  "For importing an archive that already lives somewhere else, prefer the import-from-link API:",
262
424
  "",
263
425
  "```bash",
264
- "curl -sS https://api.spacefast.com/v1/spaces/{spaceId}/imports \\",
426
+ "curl -q -sS https://api.spacefast.com/v1/spaces/{spaceId}/imports \\",
265
427
  ' -H "content-type: application/json" \\',
266
428
  ' -H "Authorization: Bearer $SPACEFAST_TOKEN" \\',
267
429
  ' -d \'{"source":{"kind":"url","url":"https://example.com/site.zip"}}\'',
@@ -289,10 +451,18 @@ export const AGENT_PROSE = {
289
451
  receiptShape: RECEIPT_SHAPE_PROSE,
290
452
  signedUpload: SIGNED_UPLOAD_PROSE,
291
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,
292
457
  saveState: SAVE_STATE_PROSE,
458
+ saveStateApi: SAVE_STATE_API_PROSE,
459
+ saveStateMcp: SAVE_STATE_MCP_PROSE,
293
460
  updateSpace: UPDATE_SPACE_PROSE,
294
461
  continueAfterClaim: CONTINUE_AFTER_CLAIM_PROSE,
295
462
  lanePicker: LANE_PICKER_PROSE,
296
463
  howToResearch: HOW_TO_RESEARCH_PROSE,
464
+ howToResearchApi: HOW_TO_RESEARCH_API_PROSE,
465
+ howToResearchMcp: HOW_TO_RESEARCH_MCP_PROSE,
297
466
  envNotes: ENV_NOTES_PROSE,
467
+ envNotesApi: ENV_NOTES_API_PROSE,
298
468
  };
@@ -3,6 +3,7 @@ export declare const agentSetupUrl: string;
3
3
  export declare const agentSetupTextUrl: string;
4
4
  export declare const agentSetupHostLabel: string;
5
5
  export declare const agentSetupPrompt: string;
6
+ export declare const anonymousPublishCurlCommand: string;
6
7
  /**
7
8
  * Typed, safe decline states for the app-private redemption exchange.
8
9
  * `invalid_handoff` is the single answer for both an unknown document ID and a wrong secret, so a
@@ -31,40 +32,42 @@ export type AgentHandoff = Readonly<{
31
32
  }>;
32
33
  /**
33
34
  * The single shared grammar for handoff links:
34
- * `<app-origin>/agent/<documentId>#<secret>`. The document ID must match
35
- * `[A-Za-z0-9_-]{1,512}` and the secret `[A-Za-z0-9_-]{16,512}` (the minimum keeps secrets
36
- * high-entropy and makes the public-projection leak check meaningful); the two must differ;
37
- * queries and embedded credentials are forbidden. The URL must sit exactly on the caller's
38
- * configured HTTP(S) app origin; that origin pin enforces scheme, hostname, and port without a
39
- * second host allowlist that would reject legitimate dev or container origins.
35
+ * `<access-origin>/agent/<documentId>/<oneUseToken>`. The document ID must match
36
+ * `[A-Za-z0-9_-]{1,512}` and the token `[A-Za-z0-9_-]{16,512}`; the two must differ;
37
+ * queries, fragments, and URL userinfo are forbidden. The URL must sit exactly on the caller's
38
+ * configured HTTP(S) access origin; the clean static document may live on a separate explicitly
39
+ * supplied dashboard origin.
40
40
  * Normalization is part of the contract: the input is WHATWG-URL-normalized first (backslashes,
41
41
  * dot segments, stray tab/CR/LF, empty userinfo, case, default ports), the grammar is enforced on
42
42
  * the normalized URL, and only the reconstructed canonical URL ever leaves this function.
43
43
  * Dashboard minting, CLI ingestion, and control-plane validation must all use this parser so the
44
44
  * grammar can never fork.
45
45
  */
46
- export declare function parseAgentHandoffUrl(value: string, expectedOrigin: string): Readonly<{
46
+ export declare function parseAgentHandoffUrl(value: string, expectedOrigin: string, documentOrigin?: string): Readonly<{
47
47
  documentId: string;
48
48
  documentUrl: string;
49
+ handoffUrl: string;
49
50
  secret: string;
50
51
  }>;
51
52
  /** Connect targets whose handoff continues through the CLI stdin exchange. */
52
53
  export declare const AGENT_HANDOFF_CLI_TARGET_IDS: readonly ConnectTargetId[];
53
54
  export declare const AGENT_HANDOFF_PREAMBLE = "Talk to the user before installing tools or publishing. Confirm the file or folder they want to publish.";
54
55
  /** The fetch-safety contract: reading the document never consumes or leaks a handoff. */
55
- export declare const AGENT_HANDOFF_FETCH_SAFETY = "Keep the original handoff link private. Fetching this document did not use the link or send its fragment secret; the secret is spent only by one successful exchange. An expired, used, or revoked link fails safely, so ask the user for a fresh one from the dashboard.";
56
- export declare const AGENT_HANDOFF_FRAGMENT_HYGIENE = "Keep its fragment out of commands, request URLs, logs, and replies.";
56
+ export declare const AGENT_HANDOFF_FETCH_SAFETY = "Keep the original handoff link private. Fetching this clean document URL did not use or send its credential; the credential is spent only by one successful exchange. An expired, used, or revoked link fails safely, so ask the user for a fresh one from the dashboard.";
57
+ export declare const AGENT_HANDOFF_CREDENTIAL_HYGIENE = "Keep the private handoff URL out of commands, request URLs, logs, and replies.";
57
58
  export declare const AGENT_HANDOFF_CLI_APPROVAL_GUIDANCE: string;
58
59
  export declare const AGENT_HANDOFF_BROWSER_APPROVAL_GUIDANCE = "When the client opens browser approval, ask the user to approve there before publishing.";
59
- export declare const AGENT_HANDOFF_PUBLISH_PROSE = "After approval, confirm the selected Space and permissions, then publish only the files the user approved. Report the bare Live and immutable Version addresses separately from the private Open URL; create a scoped Link when a recipient needs access.";
60
+ export declare const AGENT_HANDOFF_PUBLISH_PROSE = "After approval, confirm the selected Space and permissions, then publish only the files the user approved. Report the bare Live and immutable Version addresses separately from the private Access URL; create a scoped Link when a recipient needs access.";
60
61
  /** One target's Connect prose: blurb, copyable setup blocks, deeplink/download lines. */
61
62
  export declare function agentHandoffConnectSections(target: ConnectTarget): string[];
62
63
  /** Builds every copy-ready handoff artifact from one validated URL and target catalog row. */
63
64
  export declare function buildAgentHandoff(input: {
64
65
  expectedOrigin: string;
66
+ documentOrigin?: string;
65
67
  mcpEndpoint: string;
66
68
  targetId: ConnectTargetId;
67
69
  url: string;
68
70
  }): AgentHandoff;
69
71
  export declare function renderAgentSetupTemplate(value: string): string;
70
72
  export declare function aiInstructionsText(): string;
73
+ export declare function agentSetupMarkdown(): string;