@tryvitris/website-harness 0.1.0-beta.8 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +5 -1
  2. package/agent/refs/admin.md +11 -0
  3. package/agent/refs/launch.md +13 -11
  4. package/bin/api/agent/evaluate.js +1 -1
  5. package/bin/api/agent/grade.js +1 -1
  6. package/bin/api/cli/api.js +9 -9
  7. package/bin/api/cli.js +11 -11
  8. package/bin/api/engine.js +2 -2
  9. package/bin/api/hosting.js +3 -3
  10. package/bin/chunks/{chunk-QQOMS2DL.js → chunk-445FZZEC.js} +125 -19
  11. package/bin/chunks/{chunk-PYCRUMCT.js → chunk-54QPYUM2.js} +1 -1
  12. package/bin/chunks/{chunk-R2WRQDVE.js → chunk-65LRZIBP.js} +1 -1
  13. package/bin/chunks/{chunk-FGSQE3DU.js → chunk-72Y4YMTV.js} +1 -1
  14. package/bin/chunks/{chunk-XY73ZLHB.js → chunk-GQPUILFR.js} +33 -5
  15. package/bin/chunks/{chunk-A2XMAPVW.js → chunk-JBSGJR7X.js} +1 -1
  16. package/bin/chunks/{chunk-WNE543KF.js → chunk-KIWEFRNG.js} +5 -5
  17. package/bin/chunks/{chunk-DYKLM4I3.js → chunk-M72KKZIB.js} +24 -6
  18. package/bin/chunks/{chunk-FEFLSJM5.js → chunk-RE4GOHSU.js} +3 -3
  19. package/bin/chunks/{chunk-WMJVLMU2.js → chunk-UR4NWYVW.js} +2 -2
  20. package/bin/chunks/{chunk-3OKR5AZJ.js → chunk-VKU5TY5C.js} +1 -1
  21. package/bin/chunks/{design-context-store-W3VTZ4HA.js → design-context-store-ETVS4N6W.js} +1 -1
  22. package/bin/chunks/{packets-LBMDIFPO.js → packets-XFXKHSEY.js} +1 -1
  23. package/bin/chunks/{preparation-AV4PDQY6.js → preparation-3OR7LVPQ.js} +1 -1
  24. package/bin/cloudflare/backend.js +143 -61
  25. package/bin/types/src/admin/connection.d.ts +3 -0
  26. package/bin/types/src/agent/refine-document-checks.d.ts +5 -0
  27. package/bin/types/src/cli/admin.d.ts +21 -0
  28. package/bin/types/src/cli/site-create.d.ts +2 -0
  29. package/bin/vitris-art.js +1 -1
  30. package/bin/vitris-create-site.js +5 -3
  31. package/bin/vitris-cursor-hook.js +1 -1
  32. package/bin/vitris-eval.js +3 -3
  33. package/bin/vitris-go.js +3 -3
  34. package/bin/vitris-harvest.js +2 -2
  35. package/bin/vitris-knowledge.js +2 -2
  36. package/bin/vitris-launch-preflight.js +1 -1
  37. package/bin/vitris-motion-view.js +1 -1
  38. package/bin/vitris-refresh.js +2 -2
  39. package/bin/vitris-stock.js +1 -1
  40. package/bin/vitris-themes.js +1 -1
  41. package/bin/vitris-upgrade.js +2 -2
  42. package/bin/vitris-verify-browser.js +1 -1
  43. package/bin/vitris.js +11 -11
  44. package/docs/ADMIN.md +85 -0
  45. package/docs/CLI.md +11 -5
  46. package/docs/CLOUDFLARE.md +24 -2
  47. package/docs/DISTRIBUTION.md +5 -3
  48. package/docs/PRESERVE.md +1 -1
  49. package/docs/SECURITY.md +2 -2
  50. package/package.json +28 -23
  51. package/templates/site/AGENTS.md +3 -1
  52. package/templates/site/README.md +5 -3
  53. package/templates/site/deployment/README.md +24 -0
  54. package/templates/site/{.github/workflows/deploy.yml → deployment/github-actions.yml.example} +5 -6
  55. package/templates/site/gitignore +3 -0
  56. package/templates/site/package.json +2 -1
@@ -7,12 +7,12 @@ import {
7
7
  import {
8
8
  gatherBlankOnboard,
9
9
  persistOnboard
10
- } from "./chunk-PYCRUMCT.js";
10
+ } from "./chunk-54QPYUM2.js";
11
11
  import {
12
12
  persistSiteInputs,
13
13
  recordManagedKit,
14
14
  resolveSiteInputsLive
15
- } from "./chunk-XY73ZLHB.js";
15
+ } from "./chunk-GQPUILFR.js";
16
16
  import {
17
17
  initialState,
18
18
  parseGraphYaml
@@ -151,7 +151,9 @@ Preserved custom website from ${fromArg}.
151
151
  2. npx playwright install chromium
152
152
  3. npm run go
153
153
  4. npm run preview
154
- 5. npm run deploy (temporary Cloudflare preview)
154
+ 5. npm run deploy (build and verify locally, then upload a temporary Cloudflare preview)
155
+
156
+ Local builds and direct uploads are the default. GitHub stores source; no deployment workflow runs automatically. Keep dist/ and release bundles out of Git.
155
157
 
156
158
  Custom source: preserved/. Verification: agent/reviews/preserve.json. See AGENTS.md and the installed harness docs/PRESERVE.md. Run vitris forms inspect and vitris forms setup to register and connect contact forms; backendOrigin alone does not enable submission or notifications. See docs/CONTACT-FORMS.md.
157
159
  `);
@@ -161,10 +163,14 @@ Run npm run go to capture and verify the existing client-facing site. Keep its a
161
163
 
162
164
  Repair source in preserved/ when agent/reviews/preserve.json reports a failure; run npm run go again. Never change reference screenshots or hashes to make a check pass. Do not overwrite source by recapturing. onboarding/preserve.json records the source and captured routes. Contact forms require explicit registration and public launch.json forms bindings. backendOrigin alone does not enable delivery. Default previews show a notice without submitting. See docs/CONTACT-FORMS.md.
163
165
 
164
- Use npm run deploy for a temporary Cloudflare preview after verification. Production and customer domains are outside this mode. Scaffold a later redesign separately from the same source URL.
166
+ Use npm run deploy to build and verify locally and directly upload a temporary Cloudflare preview. Keep dist/ and release bundles out of Git. Hosted automation is opt-in; deployment/github-actions.yml.example is inactive by default. Production and customer domains are outside this mode. Scaffold a later redesign separately from the same source URL.
165
167
  `);
166
168
  }
167
- const workflow = join(dest, ".github", "workflows", "deploy.yml");
169
+ if (mode === "preserve") {
170
+ const instructions = join(dest, "AGENTS.md");
171
+ writeFileSync(instructions, readFileSync(instructions, "utf8") + "\nFor a requested sibling business admin, read agent/refs/admin.md and the installed harness docs/ADMIN.md. Use vitris admin setup --plan then reviewed --apply. Preserve current forms and backend identity; cloud provisioning, authentication and deployment remain separate.\n");
172
+ }
173
+ const workflow = join(dest, "deployment", "github-actions.yml.example");
168
174
  if (existsSync(workflow)) {
169
175
  if (mode === "preserve") {
170
176
  writeFileSync(workflow, `name: Preserve preview
@@ -198,6 +204,18 @@ jobs:
198
204
  copyMarkdown: gathered.copyMarkdown
199
205
  });
200
206
  persistSiteInputs(dest, inputs);
207
+ if (opts.admin) {
208
+ writeFileSync(join(dest, "ADMIN-SETUP.md"), `# Optional business admin
209
+
210
+ Read node_modules/@tryvitris/website-harness/docs/ADMIN.md and agent/refs/admin.md.
211
+
212
+ Prepare a separate sibling installation after confirming the exact website origin, existing tenant ID and backend origin:
213
+
214
+ vitris admin setup --site . --dir ../${slug}-admin --domain admin.CUSTOMER_DOMAIN --website https://www.CUSTOMER_DOMAIN --tenant EXISTING_TENANT_ID --backend-origin https://BACKEND --plan
215
+
216
+ Review artifacts/admin/setup-plan.json, then repeat with --apply and a dedicated VITRIS_ADMIN_API_TOKEN in the operator environment. This opt-in has created instructions only. No backend, grant, account, collection, authentication, DNS or cloud resources have been changed.
217
+ `);
218
+ }
201
219
  const graphPath = join(dest, "agent", "graph.yaml");
202
220
  if (existsSync(graphPath)) {
203
221
  const graph = parseGraphYaml(readFileSync(graphPath, "utf8"));
@@ -210,7 +228,7 @@ jobs:
210
228
  return {
211
229
  ok: true,
212
230
  exitCode: EXIT_CODES.SUCCESS,
213
- summary: `Created site ${slug} at ${dest}`,
231
+ summary: `Created site ${slug} at ${dest}${opts.admin ? "; optional admin next steps: ADMIN-SETUP.md" : ""}`,
214
232
  data: { dest, slug, name }
215
233
  };
216
234
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  capturePreservedSite,
3
3
  verifyPreservedSite
4
- } from "./chunk-R2WRQDVE.js";
4
+ } from "./chunk-65LRZIBP.js";
5
5
  import {
6
6
  AGENT_RUNTIME_HISTORY_ENTRIES
7
7
  } from "./chunk-34H54KEY.js";
@@ -68,7 +68,7 @@ import {
68
68
  verifyAllowedWrites,
69
69
  withMutationLock,
70
70
  writeVisualProbe
71
- } from "./chunk-XY73ZLHB.js";
71
+ } from "./chunk-GQPUILFR.js";
72
72
  import {
73
73
  QUESTION_VERSION,
74
74
  questionSemanticsHash
@@ -253,7 +253,7 @@ function scaffoldNestedSite(opts) {
253
253
  for (const [key, value] of Object.entries(pkg.scripts)) pkg.scripts[key] = value.replaceAll("__SLUG__", opts.slug);
254
254
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
255
255
  `);
256
- for (const file2 of ["site.json", "README.md", "AGENTS.md"]) {
256
+ for (const file2 of ["site.json", "README.md", "AGENTS.md", "deployment/github-actions.yml.example"]) {
257
257
  const p = join2(dest, file2);
258
258
  if (!existsSync2(p)) continue;
259
259
  if (file2 === "site.json") {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2NW7KSPA.js";
4
4
  import {
5
5
  verifyPreservedSite
6
- } from "./chunk-R2WRQDVE.js";
6
+ } from "./chunk-65LRZIBP.js";
7
7
  import {
8
8
  EXIT_CODES
9
9
  } from "./chunk-JCRGE6AL.js";
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  gradeAll,
15
15
  sourceRevision
16
- } from "./chunk-XY73ZLHB.js";
16
+ } from "./chunk-GQPUILFR.js";
17
17
  import {
18
18
  parseReleaseRecord
19
19
  } from "./chunk-M4LNHYYR.js";
@@ -6,7 +6,7 @@ import {
6
6
  selectStageKnowledge,
7
7
  signalsFromSite,
8
8
  siteWidgets
9
- } from "./chunk-XY73ZLHB.js";
9
+ } from "./chunk-GQPUILFR.js";
10
10
  import {
11
11
  coverageReport,
12
12
  loadKnowledgeV2
@@ -3,7 +3,7 @@ import {
3
3
  loadPersistedDesignContextPacket,
4
4
  persistDesignContextArtifacts,
5
5
  resolveDesignContextExpansion
6
- } from "./chunk-XY73ZLHB.js";
6
+ } from "./chunk-GQPUILFR.js";
7
7
  import "./chunk-KVMZSQ3D.js";
8
8
  import "./chunk-WOORR67N.js";
9
9
  import "./chunk-PRLL7B63.js";
@@ -10,7 +10,7 @@ import {
10
10
  partitionByObligation,
11
11
  rankEvidenceRelevance,
12
12
  selectDiverseEvidence
13
- } from "./chunk-XY73ZLHB.js";
13
+ } from "./chunk-GQPUILFR.js";
14
14
  import "./chunk-KVMZSQ3D.js";
15
15
  import "./chunk-WOORR67N.js";
16
16
  import "./chunk-PRLL7B63.js";
@@ -30,7 +30,7 @@ import {
30
30
  withMutationLock,
31
31
  withMutationLockAsync,
32
32
  writeCommitJournal
33
- } from "./chunk-XY73ZLHB.js";
33
+ } from "./chunk-GQPUILFR.js";
34
34
  import "./chunk-KVMZSQ3D.js";
35
35
  import "./chunk-WOORR67N.js";
36
36
  import "./chunk-PRLL7B63.js";
@@ -881,67 +881,6 @@ async function handleInboundEmail(message, env) {
881
881
  });
882
882
  }
883
883
 
884
- // src/html.ts
885
- function escapeHtml(value) {
886
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
887
- }
888
- function escapeAttr(value) {
889
- return escapeHtml(value);
890
- }
891
-
892
- // src/types.ts
893
- var defaultTheme = {
894
- colorBg: "#F7F4EF",
895
- colorText: "#1A1714",
896
- colorMuted: "#5C574F",
897
- colorAccent: "#B44A2C",
898
- colorAccentText: "#FFFFFF",
899
- colorSurface: "#FFFFFF",
900
- colorSurface2: "#EFEAE3",
901
- colorBorder: "#E4DDD2",
902
- colorFocus: "#B44A2C",
903
- fontBody: 'ui-sans-serif, system-ui, "Segoe UI", sans-serif',
904
- fontDisplay: "ui-serif, Georgia, 'Times New Roman', serif",
905
- radius: "8px",
906
- radiusLg: "16px",
907
- radiusPill: "999px",
908
- maxWidth: "1120px"
909
- };
910
-
911
- // src/worker/tenant.ts
912
- async function tenantBySlug(env, slug) {
913
- return env.DB.prepare("SELECT * FROM tenants WHERE slug = ?").bind(slug).first();
914
- }
915
- async function tenantById(env, id) {
916
- return env.DB.prepare("SELECT * FROM tenants WHERE id = ?").bind(id).first();
917
- }
918
- async function tenantByHostname(env, hostname) {
919
- const cached = await env.KV.get(`host:${hostname}`);
920
- if (cached) return tenantById(env, cached);
921
- const row = await env.DB.prepare(
922
- `SELECT tenants.* FROM tenants JOIN hostnames ON hostnames.tenant_id = tenants.id WHERE hostnames.hostname = ?`
923
- ).bind(hostname.toLowerCase()).first();
924
- if (row) await env.KV.put(`host:${hostname}`, row.id, { expirationTtl: 3600 });
925
- return row;
926
- }
927
- async function loadSiteSnapshot(env, tenantId) {
928
- const siteRow = await env.DB.prepare("SELECT json FROM site_config WHERE tenant_id = ?").bind(tenantId).first();
929
- const contentRow = await env.DB.prepare("SELECT json FROM content WHERE tenant_id = ?").bind(tenantId).first();
930
- const themeRow = await env.DB.prepare("SELECT json FROM theme WHERE tenant_id = ?").bind(tenantId).first();
931
- if (!siteRow || !contentRow || !themeRow) return null;
932
- return {
933
- site: JSON.parse(siteRow.json),
934
- content: JSON.parse(contentRow.json),
935
- theme: { ...defaultTheme, ...JSON.parse(themeRow.json) }
936
- };
937
- }
938
- async function bumpVersion(env, tenantId) {
939
- const row = await env.DB.prepare(
940
- "UPDATE tenants SET content_version = content_version + 1 WHERE id = ? RETURNING content_version"
941
- ).bind(tenantId).first();
942
- return row?.content_version ?? 1;
943
- }
944
-
945
884
  // src/forms/config.ts
946
885
  var safeId = (s) => typeof s === "string" && /^[a-zA-Z0-9_-]{1,80}$/.test(s);
947
886
  var safeEmail = (s) => {
@@ -977,6 +916,14 @@ function validateFormConfig(value) {
977
916
  return c2;
978
917
  }
979
918
 
919
+ // src/html.ts
920
+ function escapeHtml(value) {
921
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
922
+ }
923
+ function escapeAttr(value) {
924
+ return escapeHtml(value);
925
+ }
926
+
980
927
  // src/worker/forms.ts
981
928
  var MAX_BODY = 32768;
982
929
  function sandboxMailReadiness(env) {
@@ -1220,6 +1167,140 @@ async function handleFormsAdmin(env, request) {
1220
1167
  }
1221
1168
  }
1222
1169
 
1170
+ // src/worker/admin.ts
1171
+ var scopes = ["leads:read", "analytics:read", "leads:write"];
1172
+ var json2 = (data, status = 200) => Response.json(data, { status, headers: { "cache-control": "no-store", "x-content-type-options": "nosniff" } });
1173
+ async function handleAdmin(env, request) {
1174
+ const url = new URL(request.url);
1175
+ const match = url.pathname.match(/^\/api\/admin\/v1\/([a-zA-Z0-9_-]{1,100})\/(status|leads|analytics|leads\/status)$/);
1176
+ if (!match) return json2({ error: "Not found" }, 404);
1177
+ const [, tenant, resource] = match;
1178
+ if (request.headers.has("origin")) return json2({ error: "Server credential required" }, 403);
1179
+ const installationId = request.headers.get("x-vitris-installation") || "";
1180
+ const token = request.headers.get("authorization")?.match(/^Bearer ([a-zA-Z0-9_-]{32,200})$/)?.[1];
1181
+ let grant;
1182
+ try {
1183
+ const map = JSON.parse(env.ADMIN_CONNECTIONS || "{}");
1184
+ if (map && typeof map === "object" && !Array.isArray(map) && Object.hasOwn(map, installationId)) grant = map[installationId];
1185
+ } catch {
1186
+ }
1187
+ if (!grant || !token || !/^[a-f0-9]{64}$/.test(grant.tokenHash) || await digest(token) !== grant.tokenHash) return json2({ error: "Unauthorized" }, 401);
1188
+ if (grant.tenantId !== tenant || grant.siteOrigin !== request.headers.get("x-vitris-site-origin") || !Array.isArray(grant.scopes) || grant.scopes.some((s) => !scopes.includes(s))) return json2({ error: "Forbidden" }, 403);
1189
+ if (!await env.DB.prepare("SELECT id FROM tenants WHERE id=?").bind(tenant).first()) return json2({ error: "Forbidden" }, 403);
1190
+ const identity = { schemaVersion: 1, installationId, tenantId: tenant, siteOrigin: grant.siteOrigin };
1191
+ const required = resource === "analytics" ? "analytics:read" : resource === "leads/status" ? "leads:write" : resource === "leads" ? "leads:read" : void 0;
1192
+ if (required && !grant.scopes.includes(required)) return json2({ error: "Forbidden" }, 403);
1193
+ if (request.method !== (resource === "leads/status" ? "POST" : "GET")) return json2({ error: "Method not allowed" }, 405);
1194
+ if (resource === "status") return json2({ ...identity, scopes: grant.scopes, capabilities: { leads: grant.scopes.includes("leads:read"), testSubmissions: grant.scopes.includes("leads:read"), analytics: grant.scopes.includes("analytics:read"), leadStatus: grant.scopes.includes("leads:write") } });
1195
+ if (resource === "leads/status") {
1196
+ if (url.searchParams.has("mode")) return json2({ error: "Test submissions are read-only" }, 400);
1197
+ if (!request.headers.get("content-type")?.startsWith("application/json")) return json2({ error: "JSON required" }, 400);
1198
+ const reader = request.body?.getReader();
1199
+ let body = "";
1200
+ let size = 0;
1201
+ if (reader) for (; ; ) {
1202
+ const { done, value } = await reader.read();
1203
+ if (done) break;
1204
+ size += value.length;
1205
+ if (size > 2048) {
1206
+ await reader.cancel();
1207
+ return json2({ error: "Body too large" }, 413);
1208
+ }
1209
+ body += new TextDecoder().decode(value);
1210
+ }
1211
+ let data;
1212
+ try {
1213
+ data = JSON.parse(body);
1214
+ } catch {
1215
+ return json2({ error: "Invalid JSON" }, 400);
1216
+ }
1217
+ if (!data || typeof data.id !== "string" || data.id.length > 200 || !["new", "contacted", "closed"].includes(data.status)) return json2({ error: "Invalid lead status" }, 400);
1218
+ const result = await env.DB.prepare("UPDATE leads SET status=? WHERE id=? AND tenant_id=? RETURNING id").bind(data.status, data.id, tenant).first();
1219
+ return result ? json2({ ok: true }) : json2({ error: "Lead not found" }, 404);
1220
+ }
1221
+ if (resource === "leads") {
1222
+ const mode = url.searchParams.get("mode") || "live";
1223
+ if (!["live", "test"].includes(mode)) return json2({ error: "Invalid mode" }, 400);
1224
+ const sql = mode === "test" ? `SELECT s.id,s.name,s.email,s.phone,s.message,s.page_path,s.created_at,s.fields_json,'test' AS status,s.form_id,'test' AS mode,o.status AS notification_status,o.last_error AS notification_error,o.test_output
1225
+ FROM form_submissions s LEFT JOIN form_outbox o ON o.id=s.id AND o.tenant_id=s.tenant_id WHERE s.tenant_id=? AND s.mode='test' ORDER BY s.created_at DESC,s.id DESC LIMIT 200` : `SELECT l.id,l.name,l.email,l.phone,l.message,l.page_path,l.status,l.created_at,s.fields_json,o.status AS notification_status,o.last_error AS notification_error
1226
+ FROM leads l LEFT JOIN form_submissions s ON s.id=l.id AND s.tenant_id=l.tenant_id AND s.mode='live'
1227
+ LEFT JOIN form_outbox o ON o.id=s.id AND o.tenant_id=l.tenant_id WHERE l.tenant_id=? ORDER BY l.created_at DESC,l.id DESC LIMIT 200`;
1228
+ const stored = (await env.DB.prepare(sql).bind(tenant).all()).results;
1229
+ const rows2 = [];
1230
+ let size = 0;
1231
+ for (const row of stored) {
1232
+ size += new TextEncoder().encode(JSON.stringify(row)).length;
1233
+ if (size > 35e5) break;
1234
+ rows2.push(row);
1235
+ }
1236
+ return json2({ ...identity, source: "connected", mode: mode === "live" ? "production" : "test", writable: mode !== "test" && grant.scopes.includes("leads:write"), truncated: rows2.length < stored.length || stored.length === 200, rows: rows2 });
1237
+ }
1238
+ const days = Number(url.searchParams.get("days") || "30");
1239
+ if (!Number.isInteger(days) || days < 1 || days > 90) return json2({ error: "days must be 1\u201390" }, 400);
1240
+ const rows = (await env.DB.prepare(`WITH days AS (
1241
+ SELECT day FROM analytics_events WHERE tenant_id=? AND (src IS NULL OR src='' OR src='live')
1242
+ UNION SELECT substr(created_at,1,10) FROM leads WHERE tenant_id=?
1243
+ ) SELECT day,
1244
+ (SELECT COUNT(*) FROM analytics_events e WHERE e.tenant_id=? AND e.day=days.day AND e.kind='pageview' AND (e.src IS NULL OR e.src='' OR e.src='live')) AS views,
1245
+ (SELECT COUNT(DISTINCT vid) FROM analytics_events e WHERE e.tenant_id=? AND e.day=days.day AND e.kind='pageview' AND (e.src IS NULL OR e.src='' OR e.src='live')) AS visitors,
1246
+ (SELECT COUNT(*) FROM leads l WHERE l.tenant_id=? AND substr(l.created_at,1,10)=days.day) AS enquiries
1247
+ FROM days WHERE day>=date('now',?) AND day<=date('now') ORDER BY day`).bind(tenant, tenant, tenant, tenant, tenant, `-${days - 1} days`).all()).results;
1248
+ return json2({ ...identity, source: "connected", rows });
1249
+ }
1250
+
1251
+ // src/types.ts
1252
+ var defaultTheme = {
1253
+ colorBg: "#F7F4EF",
1254
+ colorText: "#1A1714",
1255
+ colorMuted: "#5C574F",
1256
+ colorAccent: "#B44A2C",
1257
+ colorAccentText: "#FFFFFF",
1258
+ colorSurface: "#FFFFFF",
1259
+ colorSurface2: "#EFEAE3",
1260
+ colorBorder: "#E4DDD2",
1261
+ colorFocus: "#B44A2C",
1262
+ fontBody: 'ui-sans-serif, system-ui, "Segoe UI", sans-serif',
1263
+ fontDisplay: "ui-serif, Georgia, 'Times New Roman', serif",
1264
+ radius: "8px",
1265
+ radiusLg: "16px",
1266
+ radiusPill: "999px",
1267
+ maxWidth: "1120px"
1268
+ };
1269
+
1270
+ // src/worker/tenant.ts
1271
+ async function tenantBySlug(env, slug) {
1272
+ return env.DB.prepare("SELECT * FROM tenants WHERE slug = ?").bind(slug).first();
1273
+ }
1274
+ async function tenantById(env, id) {
1275
+ return env.DB.prepare("SELECT * FROM tenants WHERE id = ?").bind(id).first();
1276
+ }
1277
+ async function tenantByHostname(env, hostname) {
1278
+ const cached = await env.KV.get(`host:${hostname}`);
1279
+ if (cached) return tenantById(env, cached);
1280
+ const row = await env.DB.prepare(
1281
+ `SELECT tenants.* FROM tenants JOIN hostnames ON hostnames.tenant_id = tenants.id WHERE hostnames.hostname = ?`
1282
+ ).bind(hostname.toLowerCase()).first();
1283
+ if (row) await env.KV.put(`host:${hostname}`, row.id, { expirationTtl: 3600 });
1284
+ return row;
1285
+ }
1286
+ async function loadSiteSnapshot(env, tenantId) {
1287
+ const siteRow = await env.DB.prepare("SELECT json FROM site_config WHERE tenant_id = ?").bind(tenantId).first();
1288
+ const contentRow = await env.DB.prepare("SELECT json FROM content WHERE tenant_id = ?").bind(tenantId).first();
1289
+ const themeRow = await env.DB.prepare("SELECT json FROM theme WHERE tenant_id = ?").bind(tenantId).first();
1290
+ if (!siteRow || !contentRow || !themeRow) return null;
1291
+ return {
1292
+ site: JSON.parse(siteRow.json),
1293
+ content: JSON.parse(contentRow.json),
1294
+ theme: { ...defaultTheme, ...JSON.parse(themeRow.json) }
1295
+ };
1296
+ }
1297
+ async function bumpVersion(env, tenantId) {
1298
+ const row = await env.DB.prepare(
1299
+ "UPDATE tenants SET content_version = content_version + 1 WHERE id = ? RETURNING content_version"
1300
+ ).bind(tenantId).first();
1301
+ return row?.content_version ?? 1;
1302
+ }
1303
+
1223
1304
  // src/worker/surfaces/api.ts
1224
1305
  var KINDS = /* @__PURE__ */ new Set(["pageview", "leave", "convert", "engage", "ping"]);
1225
1306
  var GOALS = /* @__PURE__ */ new Set(["form", "tel", "mailto", "maps"]);
@@ -23687,6 +23768,7 @@ async function route(env, request) {
23687
23768
  const path = url.pathname;
23688
23769
  if (isPreviewHost(env, request)) return handlePreview(env, request);
23689
23770
  if (path.startsWith("/preview/")) return previewHandoff(env, request);
23771
+ if (path.startsWith("/api/admin/")) return handleAdmin(env, request);
23690
23772
  const approve = await handleApproveReject(env, request);
23691
23773
  if (approve) return approve;
23692
23774
  const updatesApi = await handleUpdatesApi(env, request);
@@ -0,0 +1,3 @@
1
+ import type { AdminManifest } from '@tryvitris/website-admin-contracts';
2
+ /** Identity and response requirements of the published alpha adapter. */
3
+ export declare function readConnection(manifest: AdminManifest, token: string, resource: 'status' | 'leads' | 'analytics', test?: boolean, transport?: typeof fetch): Promise<any>;
@@ -0,0 +1,5 @@
1
+ export declare function hasRefinementPlaceholder(html: string): boolean;
2
+ /** Load laid-out lazy images in the isolated verification page before judging them. */
3
+ export declare function settleRefinementImages(page: {
4
+ evaluate: (expression: string) => Promise<unknown>;
5
+ }, timeoutMs?: number): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { type CommandResult } from './types.ts';
2
+ export declare const ADMIN_VERSION = "0.1.0-alpha.1";
3
+ export type AdminOptions = {
4
+ site?: string;
5
+ dir?: string;
6
+ domain?: string;
7
+ website?: string;
8
+ tenant?: string;
9
+ backendOrigin?: string;
10
+ plan?: boolean;
11
+ apply?: boolean;
12
+ leadWrite?: boolean;
13
+ dryRun?: boolean;
14
+ grants?: string;
15
+ out?: string;
16
+ };
17
+ /** Merge the operator's COMPLETE map, never replace unrelated installations. */
18
+ export declare function mergeAdminGrant(existing: unknown, addition: unknown): {
19
+ [x: string]: unknown;
20
+ };
21
+ export declare function adminCommand(command: string, options: AdminOptions): Promise<CommandResult>;
@@ -4,6 +4,8 @@ export type CreateSiteOptions = SiteInputOptions & {
4
4
  dir: string;
5
5
  slug: string;
6
6
  name?: string;
7
+ /** Opt-in instructions only; never provision during site creation. */
8
+ admin?: boolean;
7
9
  from?: string;
8
10
  mode?: "redesign" | "preserve";
9
11
  blank?: boolean;
package/bin/vitris-art.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  retireProducedAsset,
13
13
  validateAssetProduction,
14
14
  withMutationLock
15
- } from "./chunks/chunk-XY73ZLHB.js";
15
+ } from "./chunks/chunk-GQPUILFR.js";
16
16
  import "./chunks/chunk-KVMZSQ3D.js";
17
17
  import "./chunks/chunk-WOORR67N.js";
18
18
  import "./chunks/chunk-PRLL7B63.js";
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createSite
4
- } from "./chunks/chunk-DYKLM4I3.js";
4
+ } from "./chunks/chunk-M72KKZIB.js";
5
5
  import "./chunks/chunk-34H54KEY.js";
6
6
  import "./chunks/chunk-JCRGE6AL.js";
7
7
  import {
8
8
  promptSourceUrl
9
- } from "./chunks/chunk-PYCRUMCT.js";
9
+ } from "./chunks/chunk-54QPYUM2.js";
10
10
  import "./chunks/chunk-XGTHGWPW.js";
11
11
  import {
12
12
  siteInputsFromArgv
13
- } from "./chunks/chunk-XY73ZLHB.js";
13
+ } from "./chunks/chunk-GQPUILFR.js";
14
14
  import "./chunks/chunk-KVMZSQ3D.js";
15
15
  import "./chunks/chunk-WOORR67N.js";
16
16
  import "./chunks/chunk-PRLL7B63.js";
@@ -41,6 +41,7 @@ var arg = (flag) => {
41
41
  };
42
42
  var usage = `Usage: vitris-create-site --dir ../acme --slug acme [--name "Acme Shop"] [--from URL | --blank]
43
43
  --owner EMAIL Owner login email
44
+ --admin Prepare optional sibling admin instructions (no cloud writes)
44
45
  --template ID Catalog ID, name, or hosted template URL; continue automatically
45
46
  --template-release R Optional catalog revision
46
47
  --content TEXT Additional business content or instructions
@@ -67,6 +68,7 @@ async function main() {
67
68
  blank,
68
69
  ...siteInputsFromArgv(process.argv),
69
70
  mode: arg("--mode"),
71
+ admin: process.argv.includes("--admin"),
70
72
  owner: arg("--owner"),
71
73
  backendOrigin: arg("--backend-origin") || arg("--kernel-origin"),
72
74
  harnessSpec: arg("--harness") || arg("--kernel")
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleCursorHook
4
- } from "./chunks/chunk-XY73ZLHB.js";
4
+ } from "./chunks/chunk-GQPUILFR.js";
5
5
  import "./chunks/chunk-KVMZSQ3D.js";
6
6
  import "./chunks/chunk-WOORR67N.js";
7
7
  import "./chunks/chunk-PRLL7B63.js";
@@ -3,7 +3,7 @@ import {
3
3
  gradeAll,
4
4
  gradeNode,
5
5
  reviewSubject
6
- } from "./chunks/chunk-XY73ZLHB.js";
6
+ } from "./chunks/chunk-GQPUILFR.js";
7
7
  import "./chunks/chunk-KVMZSQ3D.js";
8
8
  import "./chunks/chunk-WOORR67N.js";
9
9
  import "./chunks/chunk-PRLL7B63.js";
@@ -44,7 +44,7 @@ if (paths.length) {
44
44
  } else if (process.argv.includes("--expand-packet")) {
45
45
  const expId = arg("--expand-packet");
46
46
  const manifestHash = arg("--manifest-hash");
47
- const { expandPacketItem } = await import("./chunks/packets-LBMDIFPO.js");
47
+ const { expandPacketItem } = await import("./chunks/packets-XFXKHSEY.js");
48
48
  console.log(expandPacketItem(root, node || "business-review", expId, manifestHash));
49
49
  } else if (process.argv.includes("--expand-design-context")) {
50
50
  const preparationId = arg("--preparation");
@@ -52,7 +52,7 @@ if (paths.length) {
52
52
  if (!preparationId || !expansionId || expansionId.startsWith("--")) {
53
53
  throw new Error("Use --expand-design-context <ID> --preparation <PREP_ID> [--site PATH]");
54
54
  }
55
- const { expandDesignContextForPreparation } = await import("./chunks/design-context-store-W3VTZ4HA.js");
55
+ const { expandDesignContextForPreparation } = await import("./chunks/design-context-store-ETVS4N6W.js");
56
56
  const result = expandDesignContextForPreparation(root, preparationId, expansionId);
57
57
  if (!result.ok) {
58
58
  console.error(result.reason);
package/bin/vitris-go.js CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  runCreativeProfile,
8
8
  runSite,
9
9
  statusSite
10
- } from "./chunks/chunk-FEFLSJM5.js";
11
- import "./chunks/chunk-R2WRQDVE.js";
10
+ } from "./chunks/chunk-RE4GOHSU.js";
11
+ import "./chunks/chunk-65LRZIBP.js";
12
12
  import "./chunks/chunk-34H54KEY.js";
13
13
  import "./chunks/chunk-JCRGE6AL.js";
14
14
  import "./chunks/chunk-XL2DRAYI.js";
@@ -19,7 +19,7 @@ import {
19
19
  resolveSiteInputsLive,
20
20
  runWorkflow,
21
21
  siteInputsFromArgv
22
- } from "./chunks/chunk-XY73ZLHB.js";
22
+ } from "./chunks/chunk-GQPUILFR.js";
23
23
  import "./chunks/chunk-KVMZSQ3D.js";
24
24
  import "./chunks/chunk-WOORR67N.js";
25
25
  import "./chunks/chunk-PRLL7B63.js";
@@ -3,13 +3,13 @@ import {
3
3
  gatherOnboard,
4
4
  normalizeSourceUrl,
5
5
  persistOnboard
6
- } from "./chunks/chunk-PYCRUMCT.js";
6
+ } from "./chunks/chunk-54QPYUM2.js";
7
7
  import {
8
8
  loadDevVars
9
9
  } from "./chunks/chunk-XGTHGWPW.js";
10
10
  import {
11
11
  gradeHarvest
12
- } from "./chunks/chunk-XY73ZLHB.js";
12
+ } from "./chunks/chunk-GQPUILFR.js";
13
13
  import "./chunks/chunk-KVMZSQ3D.js";
14
14
  import "./chunks/chunk-WOORR67N.js";
15
15
  import "./chunks/chunk-PRLL7B63.js";
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  explainStage
4
- } from "./chunks/chunk-3OKR5AZJ.js";
5
- import "./chunks/chunk-XY73ZLHB.js";
4
+ } from "./chunks/chunk-VKU5TY5C.js";
5
+ import "./chunks/chunk-GQPUILFR.js";
6
6
  import "./chunks/chunk-KVMZSQ3D.js";
7
7
  import "./chunks/chunk-WOORR67N.js";
8
8
  import "./chunks/chunk-PRLL7B63.js";
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunks/chunk-XL2DRAYI.js";
11
11
  import {
12
12
  templateAdaptationFailures
13
- } from "./chunks/chunk-XY73ZLHB.js";
13
+ } from "./chunks/chunk-GQPUILFR.js";
14
14
  import "./chunks/chunk-KVMZSQ3D.js";
15
15
  import "./chunks/chunk-WOORR67N.js";
16
16
  import "./chunks/chunk-PRLL7B63.js";
@@ -3,7 +3,7 @@ import {
3
3
  TEMPLATE_MOTION_PATH,
4
4
  currentSelectionHash,
5
5
  templateMotionHash
6
- } from "./chunks/chunk-XY73ZLHB.js";
6
+ } from "./chunks/chunk-GQPUILFR.js";
7
7
  import "./chunks/chunk-KVMZSQ3D.js";
8
8
  import "./chunks/chunk-WOORR67N.js";
9
9
  import "./chunks/chunk-PRLL7B63.js";
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  upgradeSite
4
- } from "./chunks/chunk-A2XMAPVW.js";
4
+ } from "./chunks/chunk-JBSGJR7X.js";
5
5
  import "./chunks/chunk-JCRGE6AL.js";
6
- import "./chunks/chunk-XY73ZLHB.js";
6
+ import "./chunks/chunk-GQPUILFR.js";
7
7
  import "./chunks/chunk-KVMZSQ3D.js";
8
8
  import "./chunks/chunk-WOORR67N.js";
9
9
  import "./chunks/chunk-PRLL7B63.js";
@@ -33,7 +33,7 @@ if (!command || command === "--help") {
33
33
  if (!site) throw new Error("--site required");
34
34
  const item = readStockCatalog(catalogPath).items.find((i) => i.id === id);
35
35
  if (!item) throw new Error("Unknown stock asset");
36
- const { findActivePreparation } = await import("./chunks/preparation-AV4PDQY6.js");
36
+ const { findActivePreparation } = await import("./chunks/preparation-3OR7LVPQ.js");
37
37
  const current = existsSync(join(site, "agent/PREPARED.json"));
38
38
  if (current && !findActivePreparation(site, "media") && !findActivePreparation(site, "asset-production")) throw new Error("Import stock during media or asset-production; route later additions through the declared owner");
39
39
  const launch = existsSync(join(site, "launch.json")) ? json(join(site, "launch.json")) : {};
@@ -19,7 +19,7 @@ import {
19
19
  thumbnailService,
20
20
  validateAdaptationPreferences,
21
21
  writeThemePicker
22
- } from "./chunks/chunk-XY73ZLHB.js";
22
+ } from "./chunks/chunk-GQPUILFR.js";
23
23
  import "./chunks/chunk-KVMZSQ3D.js";
24
24
  import "./chunks/chunk-WOORR67N.js";
25
25
  import "./chunks/chunk-PRLL7B63.js";
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  upgradeSite
4
- } from "./chunks/chunk-A2XMAPVW.js";
4
+ } from "./chunks/chunk-JBSGJR7X.js";
5
5
  import "./chunks/chunk-JCRGE6AL.js";
6
- import "./chunks/chunk-XY73ZLHB.js";
6
+ import "./chunks/chunk-GQPUILFR.js";
7
7
  import "./chunks/chunk-KVMZSQ3D.js";
8
8
  import "./chunks/chunk-WOORR67N.js";
9
9
  import "./chunks/chunk-PRLL7B63.js";
@@ -11,7 +11,7 @@ import {
11
11
  observeMotion,
12
12
  templateMotionHash,
13
13
  validateTemplateMotionPlan
14
- } from "./chunks/chunk-XY73ZLHB.js";
14
+ } from "./chunks/chunk-GQPUILFR.js";
15
15
  import "./chunks/chunk-KVMZSQ3D.js";
16
16
  import "./chunks/chunk-WOORR67N.js";
17
17
  import "./chunks/chunk-PRLL7B63.js";