@tokenoftrust/storefront-runner 2.0.3 → 2.0.4

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 (138) hide show
  1. package/apps/storefront/.astro/content.d.ts +159 -0
  2. package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
  3. package/apps/storefront/.astro/types.d.ts +3 -0
  4. package/apps/storefront/node_modules/.bin/acorn +43 -0
  5. package/apps/storefront/node_modules/.bin/astro +43 -0
  6. package/apps/storefront/node_modules/.bin/astro-check +43 -0
  7. package/apps/storefront/node_modules/.bin/browserslist +43 -0
  8. package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
  9. package/apps/storefront/node_modules/.bin/depcruise +43 -0
  10. package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
  11. package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
  12. package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
  13. package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
  14. package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
  15. package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
  16. package/apps/storefront/node_modules/.bin/esbuild +43 -0
  17. package/apps/storefront/node_modules/.bin/jiti +43 -0
  18. package/apps/storefront/node_modules/.bin/playwright +43 -0
  19. package/apps/storefront/node_modules/.bin/playwright-core +43 -0
  20. package/apps/storefront/node_modules/.bin/prettier +43 -0
  21. package/apps/storefront/node_modules/.bin/rollup +43 -0
  22. package/apps/storefront/node_modules/.bin/tsc +43 -0
  23. package/apps/storefront/node_modules/.bin/tsserver +43 -0
  24. package/apps/storefront/node_modules/.bin/tsx +43 -0
  25. package/apps/storefront/node_modules/.bin/vite +43 -0
  26. package/apps/storefront/node_modules/.bin/vitest +43 -0
  27. package/apps/storefront/node_modules/.bin/workerd +43 -0
  28. package/apps/storefront/node_modules/.bin/wrangler +43 -0
  29. package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
  30. package/apps/storefront/node_modules/.bin/yaml +43 -0
  31. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
  32. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
  33. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
  34. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
  35. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
  36. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
  37. package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
  38. package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
  39. package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
  40. package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  41. package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  42. package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
  43. package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  44. package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  45. package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  46. package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  47. package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  48. package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
  49. package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
  50. package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
  51. package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
  52. package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
  53. package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
  54. package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
  55. package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
  56. package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
  57. package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
  58. package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
  59. package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
  60. package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
  61. package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
  62. package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
  63. package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
  64. package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
  65. package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
  66. package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
  67. package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
  68. package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
  69. package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
  70. package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
  71. package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
  72. package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
  73. package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
  74. package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
  75. package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
  76. package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
  77. package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
  78. package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
  79. package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
  80. package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
  81. package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
  82. package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
  83. package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
  84. package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
  85. package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
  86. package/apps/storefront/src/layouts/Layout.astro +1 -1
  87. package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
  88. package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
  89. package/apps/storefront/src/lib/shared/capability.ts +68 -0
  90. package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
  91. package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
  92. package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
  93. package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
  94. package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
  95. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
  96. package/apps/storefront/src/pages/index.astro +1 -1
  97. package/apps/storefront/src/pages/products/[handle].astro +1 -1
  98. package/package.json +1 -1
  99. package/packages/public-runtime/node_modules/.bin/tsc +43 -0
  100. package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
  101. package/packages/public-runtime/node_modules/.bin/vitest +43 -0
  102. package/pnpm-workspace.yaml +3 -0
  103. package/apps/storefront/src/lib/apps/adminService.ts +0 -116
  104. package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
  105. package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
  106. package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
  107. package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
  108. package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
  109. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
  110. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
  111. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
  112. package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
  113. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
  114. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
  115. package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
  116. package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
  117. package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
  118. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
  119. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
  120. package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
  121. package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
  122. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
  123. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
  124. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
  125. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
  126. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
  127. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
  128. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
  129. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
  130. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
  131. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
  132. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
  133. package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
  134. package/apps/storefront/src/lib/content-edit/client.ts +0 -286
  135. package/apps/storefront/src/lib/content-edit/index.ts +0 -14
  136. package/apps/storefront/src/lib/content-edit/route.ts +0 -25
  137. package/apps/storefront/src/lib/content-edit/types.ts +0 -19
  138. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
@@ -1,286 +0,0 @@
1
- /**
2
- * JsonRpcContentEditClient — the storefront's server-to-server caller for
3
- * tot-mcp's `content_edit` tool (the inline-AI edit loop, WS5 G1). Mirrors
4
- * {@link JsonRpcChangeClient} exactly (same transport, same session-validation,
5
- * same tool-result unwrap, same typed errors) — reusing the forge seam's
6
- * exported transport + error types so there is ONE wire contract, not two.
7
- *
8
- * AUTH SHAPE (Option A, `content-edit-machine-identity` — mirrors change/B3):
9
- * the storefront backend calls `content_edit` with the SAME machine-identity
10
- * APP token the change/forge clients use — a `client_credentials` +
11
- * `private_key_jwt` token minted against the MCP's OAuth token endpoint via
12
- * {@link AppTokenProvider}, attached as Bearer at `initialize` and on every
13
- * request by {@link FetchTransport} — NOT the retired `credential_validate`
14
- * handshake and NOT a human `TOT_ACCESS_TOKEN`. Developer-level authorization
15
- * is, and remains, the caller's job: `/dev`'s own `readViewerSession()` +
16
- * capability check (BEFORE this client is ever constructed) derives the
17
- * tenant from the signed-in developer's OWN session, never from a
18
- * caller-supplied value — tenant isolation is enforced there, unaffected by
19
- * this change. This client only proves "a legitimate storefront backend
20
- * (machine identity) is asking", the same trust boundary
21
- * `/api/changes/accept` already relies on post-B3.
22
- */
23
- import {
24
- FetchTransport,
25
- ForgeAuthError,
26
- ForgeConfigError,
27
- ForgeTransportError,
28
- SESSION_HEADER,
29
- AppTokenProvider,
30
- deriveMcpOauthEndpoints,
31
- type McpTransport,
32
- type JsonRpcRequest,
33
- type TotCredentials,
34
- type TransportResponse,
35
- type BearerProvider,
36
- } from "@/lib/forge";
37
- import { mapForgeError } from "@/lib/forge/errors.js";
38
- import { readEnv } from "@/lib/env";
39
- import type { ContentEditFile, ContentEditResult } from "./types.js";
40
-
41
- export const CONTENT_EDIT_TOOL = {
42
- /**
43
- * Session-validation tool for the NON-OAuth (api-key/secret) construction —
44
- * kept ONLY for backward-compatible test-transport injection (no
45
- * bearerProvider). The deployed path never calls this — see the module
46
- * header.
47
- */
48
- validate: "credential_validate",
49
- edit: "content_edit",
50
- } as const;
51
-
52
- export { SESSION_HEADER } from "@/lib/forge";
53
-
54
- // --- MCP tool-result envelope (mirrors the change client) -------------------
55
-
56
- interface McpToolResult {
57
- content?: Array<{ type?: string; text?: string }>;
58
- structuredContent?: unknown;
59
- isError?: boolean;
60
- }
61
-
62
- function parseTextContent(content: McpToolResult["content"]): unknown {
63
- const text = content?.find((c) => c.type === "text" || c.text != null)?.text;
64
- if (text == null) return undefined;
65
- try {
66
- return JSON.parse(text);
67
- } catch {
68
- return text;
69
- }
70
- }
71
-
72
- function unwrapToolResult<T>(result: unknown): T {
73
- const env = (result ?? {}) as McpToolResult;
74
- const payload =
75
- env.structuredContent !== undefined
76
- ? env.structuredContent
77
- : parseTextContent(env.content);
78
-
79
- if (env.isError) {
80
- const p = (payload ?? {}) as Record<string, unknown>;
81
- throw mapForgeError({
82
- code: typeof p.code === "number" ? p.code : undefined,
83
- message: typeof p.message === "string" ? p.message : "content_edit tool error",
84
- data: (p.data as Record<string, unknown>) ?? p,
85
- });
86
- }
87
- return payload as T;
88
- }
89
-
90
- // --- Config -------------------------------------------------------------
91
-
92
- export interface JsonRpcContentEditClientConfig {
93
- /** MCP base URL (the `POST /mcp` endpoint). */
94
- baseUrl: string;
95
- /** Storefront's ToT credentials for the legacy `credential_validate` path. */
96
- credentials: TotCredentials;
97
- /** Transport override (defaults to fetch). Injected by tests. */
98
- transport?: McpTransport;
99
- /**
100
- * Machine-identity app-token source (mirrors forge's B2 / change's B3,
101
- * binding `c0-forge-auth-supersede`). When present, the client
102
- * authenticates PURELY via a `client_credentials` + `private_key_jwt` app
103
- * token — attached as Bearer at `initialize` and on every request by
104
- * {@link FetchTransport}, re-minted on expiry/401 — and NEVER calls the
105
- * retired `credential_validate` handshake. Built by
106
- * {@link createContentEditClient} from `MCP_BASE_URL`; tests may inject
107
- * their own {@link BearerProvider}.
108
- */
109
- bearerProvider?: BearerProvider;
110
- }
111
-
112
- // --- The client -----------------------------------------------------------
113
-
114
- export class JsonRpcContentEditClient {
115
- private readonly baseUrl: string;
116
- private readonly credentials: TotCredentials;
117
- private readonly transport: McpTransport;
118
- private nextId = 1;
119
- /** Cached session validation — validate once, reuse (lazily). */
120
- private sessionPromise?: Promise<string | undefined>;
121
- /** True when a machine-identity app token authenticates the session (mirrors forge B2 / change B3). */
122
- private readonly usesServiceAuth: boolean;
123
-
124
- constructor(config: JsonRpcContentEditClientConfig) {
125
- if (!config.baseUrl) {
126
- throw new ForgeConfigError("content-edit: MCP_BASE_URL is not configured");
127
- }
128
- if (!config.credentials?.apiKey) {
129
- throw new ForgeConfigError(
130
- "content-edit: storefront ToT credentials are not configured",
131
- );
132
- }
133
- this.baseUrl = config.baseUrl;
134
- this.credentials = config.credentials;
135
- this.usesServiceAuth = config.bearerProvider != null;
136
- this.transport =
137
- config.transport ?? new FetchTransport(config.baseUrl, config.bearerProvider);
138
- }
139
-
140
- async editContent(
141
- prompt: string,
142
- files: readonly ContentEditFile[],
143
- ): Promise<ContentEditResult> {
144
- const sessionId = await this.ensureSession();
145
- const result = await this.rpc(
146
- "tools/call",
147
- { name: CONTENT_EDIT_TOOL.edit, arguments: { prompt, files } },
148
- sessionId,
149
- );
150
- return unwrapToolResult<ContentEditResult>(result);
151
- }
152
-
153
- // --- internals (mirror JsonRpcChangeClient) --------------------------------
154
-
155
- private ensureSession(): Promise<string | undefined> {
156
- // Service-auth (mirrors forge B2 / change B3 / binding
157
- // `c0-forge-auth-supersede`): the app token is attached as Bearer at the
158
- // transport layer (initialize + every request), so the legacy
159
- // `credential_validate` round-trip is skipped entirely — it was retired
160
- // MCP-side and would now be rejected. FetchTransport still threads the
161
- // real `Mcp-Session-Id` the Bearer-authed handshake minted, so a session
162
- // id is never needed from here. Without a bearerProvider (e.g. an
163
- // injected mock transport in unit tests) the legacy validate path is kept.
164
- this.sessionPromise ??= this.usesServiceAuth
165
- ? Promise.resolve(undefined)
166
- : this.validateSession();
167
- return this.sessionPromise;
168
- }
169
-
170
- private async validateSession(): Promise<string | undefined> {
171
- const args = this.credentialArgs();
172
- let res: TransportResponse;
173
- try {
174
- res = await this.rpcRaw(
175
- "tools/call",
176
- { name: CONTENT_EDIT_TOOL.validate, arguments: args },
177
- undefined,
178
- );
179
- } catch (err) {
180
- this.sessionPromise = undefined; // let a later call retry
181
- throw err;
182
- }
183
- if (res.body.error) {
184
- this.sessionPromise = undefined;
185
- throw new ForgeAuthError(
186
- res.body.error.message ?? "content-edit: credential validation failed",
187
- );
188
- }
189
- try {
190
- const validated = unwrapToolResult<{ session?: string; sessionId?: string }>(
191
- res.body.result,
192
- );
193
- return (
194
- res.sessionId ?? validated?.session ?? validated?.sessionId ?? undefined
195
- );
196
- } catch (err) {
197
- this.sessionPromise = undefined;
198
- throw err instanceof ForgeAuthError
199
- ? err
200
- : new ForgeAuthError("content-edit: credential validation was rejected", {
201
- cause: err,
202
- });
203
- }
204
- }
205
-
206
- /** Build the credential payload; omits absent secrets. Never logged. */
207
- private credentialArgs(): Record<string, unknown> {
208
- const c = this.credentials;
209
- const out: Record<string, unknown> = { apiKey: c.apiKey };
210
- if (c.secretKey) out.secretKey = c.secretKey;
211
- if (c.accessToken) out.accessToken = c.accessToken;
212
- return out;
213
- }
214
-
215
- private async rpc(
216
- method: string,
217
- params: unknown,
218
- sessionId: string | undefined,
219
- ): Promise<unknown> {
220
- const res = await this.rpcRaw(method, params, sessionId);
221
- if (res.body.error) throw mapForgeError(res.body.error);
222
- return res.body.result;
223
- }
224
-
225
- private async rpcRaw(
226
- method: string,
227
- params: unknown,
228
- sessionId: string | undefined,
229
- ): Promise<TransportResponse> {
230
- const headers: Record<string, string> = {};
231
- if (sessionId) headers[SESSION_HEADER] = sessionId;
232
- const body: JsonRpcRequest = {
233
- jsonrpc: "2.0",
234
- id: this.nextId++,
235
- method,
236
- params,
237
- };
238
- try {
239
- return await this.transport.post({ body, headers });
240
- } catch (err) {
241
- if (err instanceof ForgeTransportError) throw err;
242
- throw new ForgeTransportError(
243
- `content-edit: transport failure calling ${method}`,
244
- { cause: err },
245
- );
246
- }
247
- }
248
- }
249
-
250
- // --- Factory ----------------------------------------------------------------
251
-
252
- /**
253
- * Build a {@link JsonRpcContentEditClient} from the runtime env — the SAME env
254
- * the change client reads:
255
- * - `MCP_BASE_URL` — the `POST /mcp` endpoint (its ORIGIN also hosts the OAuth
256
- * token endpoint + resource the app token binds to; already `…/storefront`
257
- * per B4, so the app token's resource + this RPC endpoint match).
258
- * - `TOT_API_KEY` (+ optional `TOT_SECRET_KEY`) — storefront's OWN ToT
259
- * credentials (retained for legacy/local paths; the deployed content-edit
260
- * session no longer uses them for auth).
261
- *
262
- * Machine identity (mirrors forge B2 / change B3 / binding
263
- * `c0-forge-auth-supersede`): the content-edit session authenticates with a
264
- * `client_credentials` + `private_key_jwt` APP token minted against the MCP's
265
- * OAuth token endpoint via {@link AppTokenProvider} — NOT a human
266
- * `TOT_ACCESS_TOKEN` and NOT a shared secret. `TOT_ACCESS_TOKEN` is
267
- * deliberately NOT read here.
268
- *
269
- * Throws {@link ForgeConfigError} when the base URL or api key is missing.
270
- * Secrets are read via {@link readEnv} and never logged.
271
- */
272
- export async function createContentEditClient(
273
- overrides: Partial<JsonRpcContentEditClientConfig> = {},
274
- ): Promise<JsonRpcContentEditClient> {
275
- const baseUrl = overrides.baseUrl ?? (await readEnv("MCP_BASE_URL")) ?? "";
276
- const credentials =
277
- overrides.credentials ??
278
- ({
279
- apiKey: (await readEnv("TOT_API_KEY")) ?? "",
280
- secretKey: await readEnv("TOT_SECRET_KEY"),
281
- } satisfies TotCredentials);
282
- const bearerProvider =
283
- overrides.bearerProvider ??
284
- (baseUrl ? new AppTokenProvider(deriveMcpOauthEndpoints(baseUrl)) : undefined);
285
- return new JsonRpcContentEditClient({ ...overrides, baseUrl, credentials, bearerProvider });
286
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * content_edit integration — public barrel. Import the typed client and its
3
- * route helper from here (`@/lib/content-edit`) so call sites never reach into
4
- * individual files.
5
- */
6
- export type { ContentEditFile, ContentEditFileDiff, ContentEditResult } from "./types.js";
7
- export {
8
- JsonRpcContentEditClient,
9
- createContentEditClient,
10
- CONTENT_EDIT_TOOL,
11
- SESSION_HEADER,
12
- } from "./client.js";
13
- export type { JsonRpcContentEditClientConfig } from "./client.js";
14
- export { getContentEditClient } from "./route.js";
@@ -1,25 +0,0 @@
1
- /**
2
- * Route helper for `/api/content-edit/ai-edit` — mirrors `@/lib/change/route/route.ts`'s
3
- * `getChangeClient`: build the env-configured client, or a 503 Response when
4
- * the MCP integration isn't configured (missing `MCP_BASE_URL`/credentials).
5
- */
6
- import { ForgeError } from "@/lib/forge";
7
- import { createContentEditClient } from "./client.js";
8
- import { json } from "@/lib/grants/route.js";
9
-
10
- /**
11
- * Build a content-edit client over the real MCP from env, or return a 503
12
- * Response when the integration is not configured. Never leaks secret values.
13
- */
14
- export async function getContentEditClient(): Promise<
15
- Awaited<ReturnType<typeof createContentEditClient>> | Response
16
- > {
17
- try {
18
- return await createContentEditClient();
19
- } catch (err) {
20
- if (err instanceof ForgeError && err.kind === "config") {
21
- return json({ error: "content_edit integration not configured" }, 503);
22
- }
23
- throw err;
24
- }
25
- }
@@ -1,19 +0,0 @@
1
- /** Wire shape for tot-mcp's `content_edit` tool — mirrors tot-mcp's own
2
- * ContentFile/ContentFileDiff/ContentEditResult (src/services/content-editor.ts). */
3
-
4
- export interface ContentEditFile {
5
- /** e.g. "content/home.html" or "theme.json" — content_edit's own allowlist. */
6
- path: string;
7
- content: string;
8
- }
9
-
10
- export interface ContentEditFileDiff {
11
- path: string;
12
- /** Unified diff (`--- a/<path>` / `+++ b/<path>`) against the given content. */
13
- diff: string;
14
- }
15
-
16
- export interface ContentEditResult {
17
- diffs: ContentEditFileDiff[];
18
- summary: string;
19
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * POST /api/content-edit/ai-edit — the `/dev` page's "Build with Claude" live loop
3
- * (WS5 G4 + decision draft-overlay-same-origin). A prompt becomes a reviewable
4
- * per-file diff via tot-mcp's `content_edit` tool, applied to the signed-in
5
- * developer's OWN tenant content — and materialized into their private DRAFT
6
- * overlay so the /dev live preview shows the change instantly (visible AND
7
- * interactive), while live/test channels and every other viewer are untouched.
8
- *
9
- * AUTH: `/dev`'s own signed-in-viewer session (`readViewerSession`) is the
10
- * developer-level gate; the MCP call authenticates as the storefront's own
11
- * TENANT-OPERATOR credential (`getContentEditClient` — the same
12
- * credential_validate path `/api/changes/accept` uses). The tenant edited is the
13
- * SESSION'S OWN first membership — never a caller-supplied tenant.
14
- *
15
- * DRAFT MATERIALIZATION: content_edit returns unified DIFFS, not contents, so we
16
- * re-apply each diff to the exact content we sent (applyUnifiedDiff, strict +
17
- * fail-closed) and write the results to `dev-draft:` KV keys with a TTL, minting
18
- * the signed tot_dev_draft cookie the middleware's overlay verifies. When KV or
19
- * the signing secret is absent (e.g. bare `astro dev`), the edit still runs and
20
- * returns diffs — `applied:false` tells the page the preview won't change.
21
- */
22
- import type { APIContext } from "astro";
23
- import { readViewerSession } from "@/lib/auth/route";
24
- import { getContentEditClient } from "@/lib/content-edit";
25
- import { applyUnifiedDiff, DiffApplyError } from "@/lib/content-edit/applyDiff";
26
- import { json } from "@/lib/grants/route.js";
27
- import { readEnv, readKv } from "@/lib/env";
28
- // Arbitrary-tenant lookup lives behind the config-layer provider so this handler
29
- // doesn't import the concrete resolver/static-map directly (tenancy-resolver-fence).
30
- import { resolvePreviewTenant } from "@/config/previewTenantLookup";
31
- import { createContentProvider } from "@/lib/storyblok/provider";
32
- import { readPublishedArtifact, themeArtifactPath } from "@tot/public-runtime";
33
- import {
34
- DEV_DRAFT_COOKIE,
35
- DEV_DRAFT_TTL_SECONDS,
36
- devDraftKey,
37
- signDevDraftToken,
38
- verifyDevDraftToken,
39
- } from "@/lib/dev/devDraft";
40
- import type { ContentEditFile } from "@/lib/content-edit/types";
41
-
42
- export const prerender = false;
43
-
44
- export async function POST(context: APIContext): Promise<Response> {
45
- const session = await readViewerSession({ cookies: context.cookies });
46
- if (!session) return json({ error: "sign in required" }, 401);
47
- if (!session.capability || session.capability === "none") {
48
- return json({ error: "no developer capability on this account" }, 403);
49
- }
50
-
51
- let body: { prompt?: unknown };
52
- try {
53
- body = (await context.request.json()) as typeof body;
54
- } catch {
55
- return json({ error: "invalid JSON body" }, 400);
56
- }
57
- const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
58
- if (!prompt) return json({ error: "prompt is required" }, 400);
59
-
60
- // The tenant is the session's OWN store (first membership, else the session's
61
- // primary resource) — the same resolution /dev renders with. No caller-supplied
62
- // tenant is ever honored here.
63
- const wanted = (session.tenants?.[0]?.resource ?? session.resource ?? "").toLowerCase();
64
- const tenant = wanted ? await resolvePreviewTenant(wanted) : null;
65
- if (!tenant) return json({ error: "no store linked to this account" }, 403);
66
-
67
- const kv = await readKv("TENANT_CACHE");
68
- const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
69
- const secret = (await readEnv("CUSTOMIZATION_PREVIEW_SECRET")) ?? null;
70
- const nowSeconds = Math.floor(Date.now() / 1000);
71
-
72
- // Reuse the caller's existing draft (so successive edits stack) or start a new one.
73
- const priorCookie = context.cookies.get(DEV_DRAFT_COOKIE)?.value ?? null;
74
- const priorClaims =
75
- priorCookie && secret ? await verifyDevDraftToken(secret, priorCookie, nowSeconds) : null;
76
- const draftId =
77
- priorClaims?.tenantId === tenant.tenant_id ? priorClaims.draftId : crypto.randomUUID();
78
-
79
- // Current content = the developer's draft when present, else what actually serves
80
- // (published overlay → build-time), so edits stack and always start from reality.
81
- const provider = createContentProvider(tenant, kv ? { get: kvGet, env: "live" } : undefined);
82
- const draftHome = await kvGet(devDraftKey(tenant.tenant_id, draftId, "content/home.html"));
83
- const homeHtml = draftHome ?? (await provider.getHomeHtml());
84
- const draftTheme = await kvGet(devDraftKey(tenant.tenant_id, draftId, "theme.json"));
85
- const themeJson =
86
- draftTheme ??
87
- (await readPublishedArtifact(kvGet, tenant.tenant_id, "live", themeArtifactPath(tenant.tenant_id))) ??
88
- JSON.stringify(tenant.theme_tokens ?? {}, null, 2);
89
-
90
- // content_edit's allowlist: home document + theme. A block-based tenant with no raw
91
- // home.html still gets theme edits (themeStyle drives its pages).
92
- const files: ContentEditFile[] = [];
93
- if (homeHtml) files.push({ path: "content/home.html", content: homeHtml });
94
- files.push({ path: "theme.json", content: themeJson });
95
-
96
- const client = await getContentEditClient();
97
- if (client instanceof Response) return client;
98
-
99
- let result;
100
- try {
101
- result = await client.editContent(prompt, files);
102
- } catch (err) {
103
- const message = err instanceof Error ? err.message : String(err);
104
- return json({ ok: false, error: message }, 502);
105
- }
106
-
107
- // Materialize the draft: re-apply each returned diff to the exact content we sent.
108
- // Strict + fail-closed per file — a bad diff writes nothing for that file. Theme
109
- // must still parse as JSON before it may land (a broken draft theme would degrade
110
- // the whole preview).
111
- const sent = new Map(files.map((f) => [f.path, f.content]));
112
- const appliedPaths: string[] = [];
113
- const canApply = !!kv && !!secret;
114
- if (canApply) {
115
- for (const d of result.diffs ?? []) {
116
- const base = sent.get(d.path);
117
- if (base == null) continue; // never write a path we didn't offer
118
- let next: string;
119
- try {
120
- next = applyUnifiedDiff(base, d.diff);
121
- } catch (err) {
122
- if (err instanceof DiffApplyError) continue;
123
- throw err;
124
- }
125
- if (next === base) continue;
126
- if (d.path === "theme.json") {
127
- try {
128
- JSON.parse(next);
129
- } catch {
130
- continue;
131
- }
132
- }
133
- await kv!.put(devDraftKey(tenant.tenant_id, draftId, d.path), next, {
134
- expirationTtl: DEV_DRAFT_TTL_SECONDS,
135
- });
136
- appliedPaths.push(d.path);
137
- }
138
- if (appliedPaths.length > 0) {
139
- // (Re)issue the signed draft cookie so the middleware overlay serves this draft.
140
- const token = await signDevDraftToken(
141
- secret!,
142
- tenant.tenant_id,
143
- draftId,
144
- DEV_DRAFT_TTL_SECONDS,
145
- nowSeconds,
146
- );
147
- context.cookies.set(DEV_DRAFT_COOKIE, token, {
148
- httpOnly: true,
149
- secure: true,
150
- sameSite: "lax",
151
- path: "/",
152
- maxAge: DEV_DRAFT_TTL_SECONDS,
153
- });
154
- }
155
- }
156
-
157
- return json({
158
- ok: true,
159
- ...result,
160
- // True when the draft landed and the live preview will show it on reload.
161
- applied: appliedPaths.length > 0,
162
- appliedPaths,
163
- });
164
- }