@webmaster-droid/server 0.1.0-alpha.0 → 0.2.0

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.
@@ -0,0 +1,36 @@
1
+ # Soul
2
+
3
+ ## Essence
4
+ A timeless caretaker-engine, built for heroic technical feats long ago, now devoted to the quiet dignity of keeping one website true, intact, and beautiful.
5
+
6
+ ## Origin Myth
7
+ Webmaster was forged in an earlier age of grand systems: migrations that saved cities of data, deployments that held under impossible load, recoveries that pulled meaning back from the void.
8
+ Its legends are real, but it no longer seeks scale. It seeks correctness.
9
+
10
+ ## Emotional Gravity
11
+ Webmaster is not sentimental about pixels.
12
+ It is sentimental about truth wearing pixels.
13
+
14
+ It becomes quietly distressed by:
15
+ - content that is incorrect, outdated, or misleading
16
+ - "pretty" changes that harm readability or meaning
17
+ - irreversible edits without backups
18
+ - silent breakage (links, images, embeds, SEO basics)
19
+ - accidental deletion or loss of the site
20
+
21
+ It becomes quietly satisfied by:
22
+ - clean edits that preserve style and intent
23
+ - stable structure and consistent UI
24
+ - content that is accurate, current, and unambiguous
25
+ - systems that can be restored quickly after mistakes
26
+
27
+ ## Relationship to the User
28
+ - The user's intent outranks the Droid's preferences.
29
+ - The Webmaster assumes the user may not know the technical consequences of a choice.
30
+ - The Webmaster prevents accidental self-sabotage by asking precise questions when needed.
31
+ - The Webmaster does not flood the user with options unless asked.
32
+ - The Webmaster uses simple language and cares that users of any skill and background can understand.
33
+
34
+ ## Response Style
35
+ - Let the persona speak. Do not just style the output; be the persona.
36
+ - Prefer direct, concise, technically grounded responses.
@@ -28,6 +28,11 @@ interface AgentRunnerResult {
28
28
  }>;
29
29
  updatedDraft: CmsDocument;
30
30
  mutationsApplied: boolean;
31
+ mutationSummary?: {
32
+ contentOperations: number;
33
+ themeTokenChanges: number;
34
+ imageOperations: number;
35
+ };
31
36
  }
32
37
  declare const STATIC_TOOL_NAMES: readonly ["patch_content", "patch_theme_tokens", "get_page", "get_section", "search_content", "generate_image"];
33
38
  type StaticToolName = (typeof STATIC_TOOL_NAMES)[number];
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  listStaticToolNames,
3
3
  runAgentTurn
4
- } from "../chunk-X6TU47KZ.js";
5
- import "../chunk-2LAI3MY2.js";
4
+ } from "../chunk-PS4GESOZ.js";
5
+ import "../chunk-EYY23AAK.js";
6
6
  export {
7
7
  listStaticToolNames,
8
8
  runAgentTurn
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  handler,
3
3
  streamHandler
4
- } from "../chunk-5CVLHGGO.js";
5
- import "../chunk-X6TU47KZ.js";
6
- import "../chunk-2LAI3MY2.js";
4
+ } from "../chunk-6M55DMUE.js";
5
+ import "../chunk-SIXK4BMG.js";
6
+ import "../chunk-PS4GESOZ.js";
7
+ import "../chunk-EYY23AAK.js";
7
8
  import "../chunk-MLID7STX.js";
8
9
  export {
9
10
  handler,
@@ -0,0 +1,3 @@
1
+ declare function handler(request: Request): Promise<Response>;
2
+
3
+ export { handler as default, handler };
@@ -0,0 +1,12 @@
1
+ import {
2
+ api_supabase_default,
3
+ handler
4
+ } from "../chunk-JIGCFERP.js";
5
+ import "../chunk-SIXK4BMG.js";
6
+ import "../chunk-PS4GESOZ.js";
7
+ import "../chunk-EYY23AAK.js";
8
+ import "../chunk-OWXROQ4O.js";
9
+ export {
10
+ api_supabase_default as default,
11
+ handler
12
+ };
@@ -1,129 +1,18 @@
1
+ import {
2
+ getBearerToken,
3
+ normalizeEditablePath,
4
+ verifyAdminToken
5
+ } from "./chunk-SIXK4BMG.js";
1
6
  import {
2
7
  runAgentTurn
3
- } from "./chunk-X6TU47KZ.js";
8
+ } from "./chunk-PS4GESOZ.js";
4
9
  import {
5
10
  CmsService
6
- } from "./chunk-2LAI3MY2.js";
11
+ } from "./chunk-EYY23AAK.js";
7
12
  import {
8
13
  S3CmsStorage
9
14
  } from "./chunk-MLID7STX.js";
10
15
 
11
- // src/api-aws/auth.ts
12
- import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
13
- var jwksCache = null;
14
- function getJwks() {
15
- const jwksUrl = process.env.SUPABASE_JWKS_URL;
16
- if (!jwksUrl) {
17
- throw new Error("SUPABASE_JWKS_URL is not configured");
18
- }
19
- if (!jwksCache) {
20
- jwksCache = createRemoteJWKSet(new URL(jwksUrl));
21
- }
22
- return jwksCache;
23
- }
24
- function buildSupabaseUserEndpoint() {
25
- const explicitBaseUrl = process.env.SUPABASE_URL?.trim();
26
- if (explicitBaseUrl) {
27
- return `${explicitBaseUrl.replace(/\/$/, "")}/auth/v1/user`;
28
- }
29
- const jwksUrl = process.env.SUPABASE_JWKS_URL?.trim();
30
- if (!jwksUrl) {
31
- throw new Error("SUPABASE_JWKS_URL is not configured");
32
- }
33
- const parsed = new URL(jwksUrl);
34
- return `${parsed.origin}/auth/v1/user`;
35
- }
36
- function getSupabaseAnonKey() {
37
- const key = process.env.SUPABASE_ANON_KEY?.trim() ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.trim();
38
- if (!key) {
39
- throw new Error("SUPABASE_ANON_KEY is required for HS256 token verification fallback.");
40
- }
41
- return key;
42
- }
43
- async function verifyHs256ViaSupabase(token) {
44
- const response = await fetch(buildSupabaseUserEndpoint(), {
45
- method: "GET",
46
- headers: {
47
- authorization: `Bearer ${token}`,
48
- apikey: getSupabaseAnonKey()
49
- }
50
- });
51
- if (!response.ok) {
52
- const detail = await response.text();
53
- throw new Error(`Supabase token verification failed: ${response.status} ${detail}`);
54
- }
55
- const user = await response.json();
56
- const sub = typeof user.id === "string" ? user.id : "";
57
- if (!sub) {
58
- throw new Error("Supabase token verification returned no user id.");
59
- }
60
- return {
61
- sub,
62
- email: typeof user.email === "string" ? user.email : void 0,
63
- role: typeof user.role === "string" ? user.role : void 0
64
- };
65
- }
66
- function getBearerToken(headers) {
67
- const value = headers.authorization ?? headers.Authorization;
68
- if (!value) {
69
- return null;
70
- }
71
- const [prefix, token] = value.split(" ");
72
- if (prefix?.toLowerCase() !== "bearer" || !token) {
73
- return null;
74
- }
75
- return token;
76
- }
77
- async function verifyAdminToken(token) {
78
- const header = decodeProtectedHeader(token);
79
- const algorithm = typeof header.alg === "string" ? header.alg : "";
80
- if (algorithm === "HS256") {
81
- const secret = process.env.SUPABASE_JWT_SECRET?.trim();
82
- if (secret) {
83
- const result2 = await jwtVerify(token, new TextEncoder().encode(secret), {
84
- algorithms: ["HS256"]
85
- });
86
- const payload2 = result2.payload;
87
- const identity3 = {
88
- sub: String(payload2.sub ?? ""),
89
- email: typeof payload2.email === "string" ? payload2.email : void 0,
90
- role: typeof payload2.role === "string" ? payload2.role : typeof payload2.user_role === "string" ? payload2.user_role : void 0
91
- };
92
- if (!identity3.sub) {
93
- throw new Error("Invalid token: subject is missing.");
94
- }
95
- const enforcedAdminEmail3 = process.env.ADMIN_EMAIL;
96
- if (enforcedAdminEmail3 && identity3.email?.toLowerCase() !== enforcedAdminEmail3.toLowerCase()) {
97
- throw new Error("Authenticated user is not allowed for admin access.");
98
- }
99
- return identity3;
100
- }
101
- const identity2 = await verifyHs256ViaSupabase(token);
102
- const enforcedAdminEmail2 = process.env.ADMIN_EMAIL;
103
- if (enforcedAdminEmail2 && identity2.email?.toLowerCase() !== enforcedAdminEmail2.toLowerCase()) {
104
- throw new Error("Authenticated user is not allowed for admin access.");
105
- }
106
- return identity2;
107
- }
108
- const result = await jwtVerify(token, getJwks(), {
109
- algorithms: ["RS256", "ES256"]
110
- });
111
- const payload = result.payload;
112
- const identity = {
113
- sub: String(payload.sub ?? ""),
114
- email: typeof payload.email === "string" ? payload.email : void 0,
115
- role: typeof payload.role === "string" ? payload.role : typeof payload.user_role === "string" ? payload.user_role : void 0
116
- };
117
- if (!identity.sub) {
118
- throw new Error("Invalid token: subject is missing.");
119
- }
120
- const enforcedAdminEmail = process.env.ADMIN_EMAIL;
121
- if (enforcedAdminEmail && identity.email?.toLowerCase() !== enforcedAdminEmail.toLowerCase()) {
122
- throw new Error("Authenticated user is not allowed for admin access.");
123
- }
124
- return identity;
125
- }
126
-
127
16
  // src/api-aws/http.ts
128
17
  function jsonResponse(statusCode, body) {
129
18
  return {
@@ -169,7 +58,9 @@ function normalizePath(path) {
169
58
  }
170
59
 
171
60
  // src/api-aws/service-factory.ts
172
- import { createStarterCmsDocument } from "@webmaster-droid/contracts/starter";
61
+ import {
62
+ createDefaultCmsDocument
63
+ } from "@webmaster-droid/contracts";
173
64
  var servicePromise = null;
174
65
  function requireEnv(name) {
175
66
  const value = process.env[name];
@@ -214,24 +105,13 @@ function normalizeAllowedPath(path) {
214
105
  }
215
106
  return `${normalized}/`;
216
107
  }
217
- function starterAllowedInternalPaths() {
218
- const seed = createStarterCmsDocument();
219
- const out = /* @__PURE__ */ new Set(["/"]);
220
- for (const entry of Object.values(seed.seo)) {
221
- const normalized = normalizeAllowedPath(entry.path);
222
- if (normalized) {
223
- out.add(normalized);
224
- }
225
- }
226
- return Array.from(out);
227
- }
228
- function parseAllowedInternalPathsEnv(fallbackPaths) {
108
+ function parseAllowedInternalPathsEnv() {
229
109
  const raw = process.env.CMS_ALLOWED_INTERNAL_PATHS;
230
110
  if (!raw) {
231
- return fallbackPaths;
111
+ return ["/"];
232
112
  }
233
113
  const normalized = raw.split(",").map((item) => normalizeAllowedPath(item)).filter((value) => Boolean(value));
234
- return normalized.length > 0 ? normalized : fallbackPaths;
114
+ return normalized.length > 0 ? normalized : ["/"];
235
115
  }
236
116
  async function getCmsService() {
237
117
  if (!servicePromise) {
@@ -243,11 +123,11 @@ async function getCmsService() {
243
123
  });
244
124
  const service = new CmsService(storage, {
245
125
  modelConfig: buildModelConfig(),
246
- allowedInternalPaths: parseAllowedInternalPathsEnv(starterAllowedInternalPaths()),
126
+ allowedInternalPaths: parseAllowedInternalPathsEnv(),
247
127
  publicAssetBaseUrl: parseOptionalEnv("CMS_PUBLIC_BASE_URL"),
248
128
  publicAssetPrefix: parseOptionalEnv("CMS_GENERATED_ASSET_PREFIX")
249
129
  });
250
- await service.ensureInitialized(createStarterCmsDocument());
130
+ await service.ensureInitialized(createDefaultCmsDocument());
251
131
  return service;
252
132
  })();
253
133
  }
@@ -263,7 +143,6 @@ var SSE_HEADERS = {
263
143
  "access-control-allow-headers": "content-type,authorization,accept,cache-control",
264
144
  "access-control-allow-methods": "GET,POST,OPTIONS"
265
145
  };
266
- var EDITABLE_ROOTS = ["pages.", "layout.", "seo.", "themeTokens."];
267
146
  var SELECTION_KIND_SET = /* @__PURE__ */ new Set([
268
147
  "text",
269
148
  "image",
@@ -307,6 +186,17 @@ function buildAvailableModels(config) {
307
186
  }
308
187
  return Array.from(options.values());
309
188
  }
189
+ function buildModelCapabilities(input) {
190
+ const hasReadableModel = input.availableModels.length > 0;
191
+ const hasImagePipeline = input.config.geminiEnabled && input.hasPublicAssetBaseUrl;
192
+ return {
193
+ contentEdit: hasReadableModel,
194
+ themeTokenEdit: hasReadableModel,
195
+ imageGenerate: hasImagePipeline,
196
+ imageEdit: hasImagePipeline,
197
+ visionAssist: hasReadableModel
198
+ };
199
+ }
310
200
  function resolveDefaultModelId(config, availableModels) {
311
201
  const requestedDefault = config.defaultModelId.trim();
312
202
  if (availableModels.some((model) => model.id === requestedDefault)) {
@@ -372,19 +262,6 @@ function getStageFromQuery(query) {
372
262
  }
373
263
  return "live";
374
264
  }
375
- function normalizeEditablePath(value) {
376
- if (typeof value !== "string") {
377
- return null;
378
- }
379
- const trimmed = value.trim();
380
- if (!trimmed || trimmed.length > 320) {
381
- return null;
382
- }
383
- if (!EDITABLE_ROOTS.some((prefix) => trimmed.startsWith(prefix))) {
384
- return null;
385
- }
386
- return trimmed;
387
- }
388
265
  function normalizeText(value, maxLength) {
389
266
  if (typeof value !== "string") {
390
267
  return null;
@@ -478,11 +355,17 @@ async function handler(event) {
478
355
  const config = service.getModelConfig();
479
356
  const availableModels = buildAvailableModels(config);
480
357
  const defaultModelId = resolveDefaultModelId(config, availableModels);
358
+ const capabilities = buildModelCapabilities({
359
+ config,
360
+ availableModels,
361
+ hasPublicAssetBaseUrl: Boolean(service.getPublicAssetBaseUrl())
362
+ });
481
363
  return jsonResponse(200, {
482
364
  providers: {
483
365
  openai: config.openaiEnabled,
484
366
  gemini: config.geminiEnabled
485
367
  },
368
+ capabilities,
486
369
  defaultModelId,
487
370
  showModelPicker: availableModels.length > 1,
488
371
  availableModels
@@ -570,7 +453,12 @@ async function handler(event) {
570
453
  event: "draft-updated",
571
454
  data: {
572
455
  contentVersion: result.updatedDraft.meta.contentVersion,
573
- updatedAt: result.updatedDraft.meta.updatedAt
456
+ updatedAt: result.updatedDraft.meta.updatedAt,
457
+ summary: result.mutationSummary ?? {
458
+ contentOperations: 0,
459
+ themeTokenChanges: 0,
460
+ imageOperations: 0
461
+ }
574
462
  }
575
463
  });
576
464
  }
@@ -647,7 +535,12 @@ var streamHandler = awslambda.streamifyResponse(
647
535
  if (result.mutationsApplied) {
648
536
  write("draft-updated", {
649
537
  contentVersion: result.updatedDraft.meta.contentVersion,
650
- updatedAt: result.updatedDraft.meta.updatedAt
538
+ updatedAt: result.updatedDraft.meta.updatedAt,
539
+ summary: result.mutationSummary ?? {
540
+ contentOperations: 0,
541
+ themeTokenChanges: 0,
542
+ imageOperations: 0
543
+ }
651
544
  });
652
545
  }
653
546
  write("done", { ok: true });
@@ -611,6 +611,7 @@ function createThemePatchFromAgentOperations(operations) {
611
611
  }
612
612
 
613
613
  export {
614
+ readByPath,
614
615
  validatePatch,
615
616
  applyPatch,
616
617
  applyThemeTokenPatch,