@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.
- package/dist/agents/client-registry.d.ts +22 -0
- package/dist/agents/client-registry.js +41 -0
- package/dist/agents/connect-targets.d.ts +51 -6
- package/dist/agents/connect-targets.js +93 -28
- package/dist/brand.d.ts +0 -11
- package/dist/brand.js +0 -20
- package/dist/config/domains.d.ts +15 -6
- package/dist/config/domains.js +24 -10
- package/dist/config/postgres-budget.d.ts +3 -0
- package/dist/config/postgres-budget.js +6 -1
- package/dist/config/postgres-budget.json +2 -1
- package/dist/contracts/abuse.js +1 -1
- package/dist/contracts/access.d.ts +486 -1196
- package/dist/contracts/access.js +323 -1054
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +11 -15
- package/dist/contracts/api-keys.d.ts +16 -0
- package/dist/contracts/api-keys.js +11 -9
- package/dist/contracts/archives.d.ts +1 -2
- package/dist/contracts/archives.js +15 -17
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/builds.js +14 -14
- package/dist/contracts/channels.js +2 -2
- package/dist/contracts/comments.d.ts +143 -99
- package/dist/contracts/comments.js +194 -79
- package/dist/contracts/common.d.ts +12 -12
- package/dist/contracts/common.js +33 -30
- package/dist/contracts/continuation.js +5 -5
- package/dist/contracts/countries.js +2 -7
- package/dist/contracts/data-source-routes.d.ts +20 -0
- package/dist/contracts/data-source-routes.js +15 -0
- package/dist/contracts/device-auth.d.ts +13 -9
- package/dist/contracts/device-auth.js +12 -12
- package/dist/contracts/docs.d.ts +10 -10
- package/dist/contracts/domains.js +39 -33
- package/dist/contracts/enums.d.ts +4 -14
- package/dist/contracts/enums.js +3 -10
- package/dist/contracts/error-code-meta.d.ts +122 -56
- package/dist/contracts/error-code-meta.js +43 -19
- package/dist/contracts/error-codes.d.ts +7 -4
- package/dist/contracts/error-codes.js +48 -24
- package/dist/contracts/events.d.ts +22 -15
- package/dist/contracts/events.js +33 -5
- package/dist/contracts/execution.d.ts +238 -0
- package/dist/contracts/execution.js +309 -0
- package/dist/contracts/feature-lifecycle.d.ts +39 -188
- package/dist/contracts/feature-lifecycle.js +52 -196
- package/dist/contracts/features.d.ts +13 -115
- package/dist/contracts/features.js +5 -35
- package/dist/contracts/functions.d.ts +597 -0
- package/dist/contracts/functions.js +475 -0
- package/dist/contracts/grants.d.ts +1175 -0
- package/dist/contracts/grants.js +783 -0
- package/dist/contracts/ids.d.ts +7 -5
- package/dist/contracts/ids.js +7 -7
- package/dist/contracts/internal.js +2 -2
- package/dist/contracts/mcp.d.ts +17 -556
- package/dist/contracts/mcp.js +9 -103
- package/dist/contracts/me.js +3 -4
- package/dist/contracts/notifications.d.ts +108 -0
- package/dist/contracts/notifications.js +54 -0
- package/dist/contracts/oauth-resources.d.ts +22 -5
- package/dist/contracts/oauth-resources.js +23 -2
- package/dist/contracts/operations.d.ts +0 -48
- package/dist/contracts/operations.js +0 -19
- package/dist/contracts/pages.d.ts +10 -14
- package/dist/contracts/pages.js +17 -21
- package/dist/contracts/plan-policy.d.ts +1 -8
- package/dist/contracts/plan-policy.js +1 -4
- package/dist/contracts/platform.d.ts +78 -30
- package/dist/contracts/platform.js +69 -20
- package/dist/contracts/problem-document.d.ts +14 -0
- package/dist/contracts/problem-document.js +35 -0
- package/dist/contracts/push-new.d.ts +41 -2
- package/dist/contracts/push-new.js +4 -0
- package/dist/contracts/quotas.d.ts +0 -1
- package/dist/contracts/quotas.js +0 -5
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/resources.d.ts +16 -71
- package/dist/contracts/resources.js +13 -16
- package/dist/contracts/runtime-api.d.ts +666 -303
- package/dist/contracts/runtime-api.js +272 -113
- package/dist/contracts/runtime-app.d.ts +168 -0
- package/dist/contracts/runtime-app.js +42 -0
- package/dist/contracts/runtime-db.d.ts +424 -0
- package/dist/contracts/runtime-db.js +108 -0
- package/dist/contracts/runtime-storage.d.ts +120 -0
- package/dist/contracts/runtime-storage.js +40 -0
- package/dist/contracts/sf-config-v1.d.ts +54 -1
- package/dist/contracts/sf-config-v1.js +2 -2
- package/dist/contracts/sites.d.ts +6 -0
- package/dist/contracts/sites.js +30 -11
- package/dist/contracts/space-config.d.ts +91 -121
- package/dist/contracts/space-config.js +464 -42
- package/dist/contracts/spaces.d.ts +300 -171
- package/dist/contracts/spaces.js +128 -98
- package/dist/contracts/superadmin-emails.d.ts +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +26 -130
- package/dist/contracts/superadmin-spaces.js +22 -0
- package/dist/contracts/superadmin-tenants.js +4 -4
- package/dist/contracts/superadmin.d.ts +4 -0
- package/dist/contracts/superadmin.js +14 -1
- package/dist/contracts/tags.d.ts +6 -6
- package/dist/contracts/tags.js +1 -1
- package/dist/contracts/teams.d.ts +9 -9
- package/dist/contracts/teams.js +25 -16
- package/dist/contracts/theme-json.js +1 -1
- package/dist/contracts/transfers.d.ts +0 -1
- package/dist/contracts/transfers.js +4 -4
- package/dist/contracts/variables.js +3 -3
- package/dist/contracts/zero.d.ts +384 -596
- package/dist/contracts/zero.js +167 -299
- package/dist/dashboard-paths/index.d.ts +18 -3
- package/dist/dashboard-paths/index.js +19 -0
- package/dist/docs/agent-handoff-document.d.ts +2 -2
- package/dist/docs/agent-handoff-document.js +8 -7
- package/dist/docs/agent-prose.d.ts +22 -3
- package/dist/docs/agent-prose.js +227 -55
- package/dist/docs/agent-setup.d.ts +14 -11
- package/dist/docs/agent-setup.js +289 -61
- package/dist/docs/agent-solutions.d.ts +6 -6
- package/dist/docs/agent-solutions.js +24 -22
- package/dist/docs/catalog.d.ts +53 -98
- package/dist/docs/catalog.js +90 -117
- package/dist/docs/error-docs.js +304 -236
- package/dist/docs/skill-distribution.d.ts +2 -0
- package/dist/docs/skill-distribution.js +7 -4
- package/dist/docs/skills.d.ts +546 -0
- package/dist/docs/skills.js +340 -0
- package/dist/utils/auth-redirect.d.ts +1 -3
- package/dist/utils/auth-redirect.js +1 -21
- package/dist/utils/browser-credential.d.ts +19 -0
- package/dist/utils/browser-credential.js +62 -0
- package/dist/utils/build-settings.d.ts +2 -0
- package/dist/utils/build-settings.js +61 -9
- package/dist/utils/comment-avatars.d.ts +7 -0
- package/dist/utils/comment-avatars.js +42 -0
- package/dist/utils/content-type.d.ts +3 -3
- package/dist/utils/content-type.js +38 -61
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -0
- package/dist/utils/domain-diagnostics.d.ts +5 -5
- package/dist/utils/domain-diagnostics.js +21 -10
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/grants.d.ts +53 -0
- package/dist/utils/grants.js +160 -0
- package/dist/utils/local-space-state.d.ts +78 -4
- package/dist/utils/local-space-state.js +384 -82
- package/dist/utils/one-shot-replay.d.ts +14 -0
- package/dist/utils/one-shot-replay.js +21 -0
- package/dist/utils/page-preview.d.ts +7 -0
- package/dist/utils/page-preview.js +9 -0
- package/dist/utils/pages.d.ts +2 -5
- package/dist/utils/pages.js +64 -33
- package/dist/utils/publish-form-data.d.ts +22 -0
- package/dist/utils/publish-form-data.js +26 -0
- package/dist/utils/publish-policy.d.ts +34 -4
- package/dist/utils/publish-policy.js +149 -62
- package/dist/utils/query-keys.d.ts +8 -10
- package/dist/utils/query-keys.js +14 -10
- package/dist/utils/runtime-paths.d.ts +3 -0
- package/dist/utils/runtime-paths.js +7 -0
- package/dist/utils/secure-local-file.d.ts +73 -0
- package/dist/utils/secure-local-file.js +1373 -0
- package/dist/utils/space-config.d.ts +17 -4
- package/dist/utils/space-config.js +151 -8
- package/dist/utils/static-runtime-policy.d.ts +5 -0
- package/dist/utils/static-runtime-policy.js +54 -10
- package/dist/utils/storage-policy.d.ts +20 -0
- package/dist/utils/storage-policy.fixtures.json +174 -0
- package/dist/utils/storage-policy.js +193 -0
- package/dist/utils/upload-session.d.ts +15 -1
- package/dist/utils/upload-session.js +68 -2
- package/dist/utils/version-file-browser.d.ts +13 -0
- package/dist/utils/version-file-browser.js +59 -0
- package/package.json +20 -1
- package/dist/contracts/mounts.d.ts +0 -142
- package/dist/contracts/mounts.js +0 -56
- package/dist/utils/access-grant.d.ts +0 -14
- package/dist/utils/access-grant.js +0 -88
- package/dist/utils/access-match.d.ts +0 -46
- package/dist/utils/access-match.fixtures.json +0 -513
- package/dist/utils/access-match.js +0 -312
- package/dist/utils/access-verdict.fixtures.json +0 -421
- package/dist/utils/asset-fingerprint.d.ts +0 -53
- package/dist/utils/asset-fingerprint.js +0 -574
- package/dist/utils/sf-config-v1.d.ts +0 -2
- package/dist/utils/sf-config-v1.fixtures.json +0 -9
- 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/
|
|
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
|
|
11
|
-
*
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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,
|
|
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
|
-
|
|
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
|
|
159
|
-
*
|
|
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:
|
|
37
|
-
* zip for a folder. This is the load-bearing rung 0 — an
|
|
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;
|
package/dist/docs/agent-prose.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
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
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
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.
|
|
130
|
-
"
|
|
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
|
-
"
|
|
134
|
-
"
|
|
135
|
-
|
|
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
|
-
"
|
|
142
|
-
"
|
|
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
|
-
" -
|
|
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
|
|
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:
|
|
188
|
-
* zip for a folder. This is the load-bearing rung 0 — an
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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,
|
|
209
|
-
"directory or remove
|
|
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.
|
|
220
|
-
" It is
|
|
221
|
-
"
|
|
222
|
-
"
|
|
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
|
-
"- `
|
|
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
|
};
|