makersclaw 0.5.1 → 0.6.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.
Files changed (73) hide show
  1. package/README.md +29 -3
  2. package/package.json +3 -2
  3. package/r/annotate.json +3 -3
  4. package/r/approval-request.json +2 -2
  5. package/r/audio-player.json +2 -2
  6. package/r/auth-form.json +2 -2
  7. package/r/board.json +2 -2
  8. package/r/calendar-view.json +3 -3
  9. package/r/cart.json +2 -2
  10. package/r/chart.json +2 -2
  11. package/r/chat.json +2 -2
  12. package/r/code-editor.json +2 -2
  13. package/r/comment-box.json +3 -3
  14. package/r/contact-section.json +2 -2
  15. package/r/csv-import.json +3 -3
  16. package/r/data-table.json +2 -2
  17. package/r/diagram.json +2 -2
  18. package/r/doc-editor.json +2 -2
  19. package/r/doc-page.json +3 -3
  20. package/r/email-render.json +2 -2
  21. package/r/faq.json +2 -2
  22. package/r/file-upload.json +2 -2
  23. package/r/flow-canvas.json +2 -2
  24. package/r/footer.json +2 -2
  25. package/r/gantt.json +3 -3
  26. package/r/job-panel.json +2 -2
  27. package/r/json-editor.json +3 -3
  28. package/r/map.json +2 -2
  29. package/r/menubar.json +2 -2
  30. package/r/mind-map.json +3 -3
  31. package/r/navigation-menu.json +2 -2
  32. package/r/order-detail.json +2 -2
  33. package/r/org-chart.json +3 -3
  34. package/r/page-builder.json +2 -2
  35. package/r/payment-methods.json +2 -2
  36. package/r/pdf-document.json +2 -2
  37. package/r/pdf-viewer.json +2 -2
  38. package/r/product-detail.json +2 -2
  39. package/r/product-grid.json +3 -3
  40. package/r/record-form.json +2 -2
  41. package/r/record-notes.json +4 -4
  42. package/r/registry.json +121 -121
  43. package/r/search-dialog.json +2 -2
  44. package/r/settings-list.json +2 -2
  45. package/r/setup-checklist.json +2 -2
  46. package/r/share-dialog.json +2 -2
  47. package/r/sidebar.json +2 -2
  48. package/r/signature-pad.json +2 -2
  49. package/r/sketch-canvas.json +2 -2
  50. package/r/spreadsheet.json +2 -2
  51. package/r/stat-cards.json +3 -3
  52. package/r/task-list.json +3 -3
  53. package/r/task-model.json +2 -2
  54. package/r/testimonials.json +2 -2
  55. package/r/transaction-list.json +2 -2
  56. package/r/whiteboard.json +3 -3
  57. package/src/cli.mjs +35 -6
  58. package/src/install.mjs +101 -4
  59. package/src/lockfile.mjs +468 -0
  60. package/src/pm.mjs +473 -0
  61. package/src/registry.mjs +48 -1
  62. package/templates/next/files/README.md +20 -15
  63. package/templates/next/files/app/claw-alive/route.js +2 -2
  64. package/templates/next/files/app/frame.js +6 -5
  65. package/templates/next/files/app/layout.jsx +2 -2
  66. package/templates/next/files/check/run.mjs +125 -15
  67. package/templates/next/files/lib/actions.js +1 -1
  68. package/templates/next/files/lib/data.js +1 -1
  69. package/templates/next/files/next.config.mjs +50 -8
  70. package/templates/next/files/package.json +3 -3
  71. package/templates/next/files/{middleware.js → proxy.js} +13 -6
  72. package/templates/next/pnpm-lock.yaml +5870 -0
  73. package/templates/next/template.json +5 -4
@@ -10,29 +10,44 @@
10
10
  // example INSERTed into a shared table, a manifest entry whose file was
11
11
  // never written, an icon name that rendered nothing, a tweet card painted
12
12
  // in the workspace's tokens (unrecognisable as a tweet — the one thing
13
- // the app was for), a kit file "improved" per app.
13
+ // the app was for), a kit file "improved" per app, a button whose server
14
+ // action the app's own agent had no way to know about.
14
15
  //
15
16
  // npm run check static rules, then smoke
16
17
  // npm run check -- --static static rules only
17
18
  //
18
19
  // The smoke test builds for itself, and only when a source file is newer
19
- // than its last build. Where: in the sandbox the app folder is
20
- // `ubuntu:claw 2755` — the terminal user can read it, not write in it —
21
- // so the sources are copied to a folder of its own (`~/.claw-check/<app>`,
22
- // node_modules symlinked back) and built there. On a writable checkout
23
- // (local dev) it builds in place, into `.next-check` (CLAW_DIST_DIR,
24
- // next.config.mjs). Never into `.next`: that is the dev server's, and a
25
- // second build there dies with EACCES on .next/trace. The founder's live
26
- // preview keeps running, untouched, while the check runs beside it.
20
+ // than its last build. Where: in place when the app folder is writable,
21
+ // into `.next-check` (CLAW_DIST_DIR, next.config.mjs). Where it is not (a
22
+ // sandbox tree made `2755` before claw opened app trees to its `claw`
23
+ // group, tree-access.ts), the sources are copied to a folder of its own
24
+ // (`~/.claw-check/<app>`, node_modules symlinked back) and built there.
25
+ //
26
+ // Never into `.next`: it is not the check's. In a working copy it is the
27
+ // preview dev server's; in a live app it holds the production build the
28
+ // platform made and serves, and whose .next/BUILD_ID it reads as proof of
29
+ // a build. The founder's live preview keeps running, untouched, while the
30
+ // check runs beside it. What Next 16 changed (16.3.7, 2026-09-29): `next
31
+ // dev` keeps its own files in `.next/dev`, and a build by the same user
32
+ // beside a running dev server works and leaves `dev/` alone. Under claw's
33
+ // umask 002 a `.next` the dev server makes is 775, so the EACCES on
34
+ // .next/trace that Next 15 hit across users should not happen in today's
35
+ // sandbox (not tried with two Linux users). It comes back wherever the
36
+ // builder cannot write `.next` (`chmod 555` on it: EACCES on .next/trace),
37
+ // which is where one Linux user per app, claw's planned walls, leads.
38
+ //
39
+ // A copy's node_modules is a symlink back to the app's, and Turbopack
40
+ // reads nothing outside its root, so the copy's build is told a root
41
+ // that holds both (CLAW_TURBOPACK_ROOT, commonRoot below).
27
42
  //
28
43
  // Output is meant to be read by the platform agent in one glance: keep
29
44
  // it under twenty lines, name file:line, say what to do instead.
30
45
 
31
- import { accessSync, constants, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync } from "node:fs";
46
+ import { accessSync, constants, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync } from "node:fs";
32
47
  import { createHash } from "node:crypto";
33
48
  import { spawn, spawnSync } from "node:child_process";
34
49
  import { createServer } from "node:net";
35
- import { basename, dirname, join, relative } from "node:path";
50
+ import { basename, dirname, join, relative, sep } from "node:path";
36
51
  import { homedir, tmpdir } from "node:os";
37
52
  import { fileURLToPath } from "node:url";
38
53
 
@@ -46,8 +61,11 @@ const STATIC_ONLY = process.argv.includes("--static");
46
61
  // `kit` rule refuses it (a shared piece goes in components/, a picture in
47
62
  // components/depictions/). The list is the template's; `makersclaw
48
63
  // update` lays both folders again.
49
- // Exact names AND extensions: Next resolves .js before .jsx, so a
50
- // components/ui/depiction.js beside depiction.jsx would win unseen.
64
+ // Exact names AND extensions: a file of the same name with an extension
65
+ // the bundler tries first wins unseen. Next 15's webpack tried .js before
66
+ // .jsx (a components/ui/depiction.js beside depiction.jsx won); Next 16's
67
+ // Turbopack tries .jsx before .js (seen on 16.3.7), so the kit's own file
68
+ // wins there, but the rule holds for every bundler.
51
69
  const KIT = /^components\/(?:ui\/(?:cx|index)\.js|ui\/(?:radio|toggle|button-group|avatar|breadcrumb|pagination|button|collapsible|item|table|depiction|feedback|stat|distribution|swatch|steps|separator|scroll-area|field|icon|menu|context-menu|popover|calendar|date-picker|tooltip|notice|sheet|tabs|slider|input-group|image|file-input|input-otp|command|combobox|rating|toast|carousel|resizable)\.jsx|parts\/(?:index|sample)\.js|parts\/(?:fixture|record-switcher|live-work)\.jsx)$/;
52
70
 
53
71
  // The pins: sha256 of every fixed file, written by `makersclaw init` and
@@ -103,7 +121,7 @@ const SKIP = [
103
121
  /^app\/frame\.js$/,
104
122
  /^app\/claw-alive\/route\.js$/,
105
123
  /^app\/s\/\[key\]\/page\.jsx$/,
106
- /^middleware\.js$/,
124
+ /^proxy\.js$/,
107
125
  /^next\.config\.mjs$/,
108
126
  /^postcss\.config\.mjs$/,
109
127
  /^public\//,
@@ -343,6 +361,13 @@ const surfaceKeys = [];
343
361
  if (e.isFile() && /^(?:ui|parts|depictions|blocks)\.[^.]+$/.test(e.name)) L.hit(`components/${e.name}`, "shadow", `a file beside the components/${e.name.replace(/\..*$/, "")}/ folder wins module resolution over it; delete it`);
344
362
  }
345
363
  }
364
+ // Next 16 runs proxy.js (fixed) before every request. A middleware file
365
+ // beside it, the name Next 15 used, fails the build; alone it would run
366
+ // in the token check's place. `makersclaw update` removes the template's
367
+ // own from 0.5.x.
368
+ for (const e of existsSync(ROOT) ? readdirSync(ROOT) : []) {
369
+ if (/^(?:middleware|proxy)\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/.test(e) && e !== "proxy.js") L.hit(e, "proxy", `Next 16 runs proxy.js, the template's fixed token check; delete ${e}`);
370
+ }
346
371
  // check/ is this script and its pins; a module hidden there is bundled
347
372
  // by Next and read by no rule.
348
373
  if (existsSync(join(ROOT, "check"))) {
@@ -556,6 +581,74 @@ const surfaceKeys = [];
556
581
  else ok7(`${pictures} picture${pictures === 1 ? "" : "s"} of elsewhere, framed in ${hosts} file${hosts === 1 ? "" : "s"}: ${[...rendered.keys()].join(", ")}`);
557
582
  }
558
583
 
584
+ // 8. The agent runs the app. Every server action that writes is named, by
585
+ // its function name in backticks, in the agent's brief beside the app
586
+ // (../AGENTS.md) or its references (../references/*.md), with the sql
587
+ // that does the same: anything a person can do on the screens, the
588
+ // app's agent can do. Born of Whiteboard 0.3.0 (2026-09-28): asked in
589
+ // chat for a board, its agent asked the member to make one, since
590
+ // nothing told it how. An app with no brief beside it (a copy on its
591
+ // own) is not checked here.
592
+ {
593
+ const home = dirname(ROOT);
594
+ if (!existsSync(join(home, "AGENTS.md"))) ok("agent", "no AGENTS.md beside the app: its actions are not checked here");
595
+ else {
596
+ const refs = join(home, "references");
597
+ const brief = [join(home, "AGENTS.md"), ...(existsSync(refs) ? readdirSync(refs).filter((n) => n.endsWith(".md")).map((n) => join(refs, n)) : [])]
598
+ .map((p) => readFileSync(p, "utf8"))
599
+ .join("\n");
600
+ const WRITES =
601
+ /\b(insertRow|updateRow|deleteRow|moveRow|saveSetting)\s*\(|\binsert\s+into\b|\bupdate\s+(?:only\s+)?(?:public\.)?[a-z_][a-z0-9_]*(?:\s+(?:as\s+)?[a-z_][a-z0-9_]*)?\s+set\b|\bdelete\s+from\b/i;
602
+ // Every top-level function of a server file, bare or wrapped in a call
603
+ // (`export const save = safe(async (…) => …)`), exported by name or in
604
+ // an `export { … }` list. One writes when its body writes, or when it
605
+ // calls or hands on a function of its own file, or another action, that
606
+ // writes: a scan of direct SQL alone missed 67 of 669 in the store's
607
+ // apps (2026-09-28).
608
+ const HEAD =
609
+ /^(export\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)\s*\(|^(export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:[A-Za-z_$][\w$.]*\s*\(\s*)?(?:async\s*)?(?:function\b|\((?:[^()]|\([^()]*\))*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/gm;
610
+ const mentions = (body, name) => new RegExp(`(^|[^\\w$.])${name.replace(/\$/g, "\\$")}(?![\\w$])`).test(body);
611
+ const fns = [];
612
+ for (const f of appFiles) {
613
+ if (!/^\s*(["'])use server\1/m.test(f.text.split("\n").slice(0, 8).join("\n"))) continue;
614
+ const listed = new Map();
615
+ for (const m of f.code.matchAll(/^export\s*\{([^}]*)\}/gm))
616
+ for (const part of m[1].split(",")) {
617
+ const [local, as] = part.trim().split(/\s+as\s+/);
618
+ if (local) listed.set(local.trim(), (as ?? local).trim());
619
+ }
620
+ const heads = [...f.code.matchAll(HEAD)];
621
+ heads.forEach((m, i) => {
622
+ const name = m[2] ?? m[4];
623
+ const body = f.code.slice(m.index, heads[i + 1]?.index ?? f.code.length);
624
+ fns.push({ file: f.path, name, exported: m[1] || m[3] ? name : (listed.get(name) ?? null), body, writes: WRITES.test(body), where: `${f.path}:${lineOf(f.code, m.index)}` });
625
+ });
626
+ }
627
+ for (let changed = true; changed; ) {
628
+ changed = false;
629
+ const actions = fns.filter((g) => g.writes && g.exported).map((g) => g.exported);
630
+ for (const f of fns) {
631
+ if (f.writes) continue;
632
+ const local = fns.filter((g) => g.writes && g.file === f.file && g !== f).map((g) => g.name);
633
+ const others = actions.filter((n) => n !== f.exported && n !== f.name);
634
+ if ([...local, ...others].some((n) => mentions(f.body.slice(f.body.indexOf("(")), n))) f.writes = changed = true;
635
+ }
636
+ }
637
+ const writes = fns.filter((f) => f.writes && f.exported).map((f) => ({ where: f.where, name: f.exported }));
638
+ const missing = writes.filter((w) => !brief.includes(`\`${w.name}\``));
639
+ if (!writes.length) ok("agent", "no server action writes");
640
+ else if (missing.length)
641
+ fail(
642
+ "agent",
643
+ missing[0].where,
644
+ `${missing.length} of ${writes.length} server actions that write are not in the agent's brief: ` +
645
+ `${missing.slice(0, 6).map((w) => w.name).join(", ")}${missing.length > 6 ? ", …" : ""}. Name each in ` +
646
+ `../references/actions.md, in backticks, with the sql that does the same, so the app's agent can do what the screen does`,
647
+ );
648
+ else ok("agent", `all ${writes.length} server actions that write are in the agent's brief`);
649
+ }
650
+ }
651
+
559
652
  // 6. Smoke: the app, built and started, answers every surface.
560
653
 
561
654
  /** Newest mtime among the app's own sources (what a build reads): walk
@@ -587,6 +680,18 @@ function buildSite() {
587
680
  return { dir: join(home, ".claw-check", name), dist: ".next", copied: true };
588
681
  }
589
682
 
683
+ /** The nearest folder holding both paths, by their real paths ("/" at
684
+ * worst). A copy's build reads node_modules through a symlink back to the
685
+ * app, and Turbopack reads nothing outside its root: the check hands it
686
+ * this folder as the root (CLAW_TURBOPACK_ROOT, next.config.mjs). */
687
+ function commonRoot(a, b) {
688
+ const x = realpathSync(a).split(sep);
689
+ const y = realpathSync(b).split(sep);
690
+ let i = 0;
691
+ while (i < x.length && i < y.length && x[i] === y[i]) i++;
692
+ return x.slice(0, i).join(sep) || sep;
693
+ }
694
+
590
695
  /** Copy the app's sources (never node_modules or a build) to `dir`, and
591
696
  * point its node_modules back at the app's. */
592
697
  function copySources(dir) {
@@ -624,7 +729,12 @@ function buildForCheck(site, env) {
624
729
  async function smoke() {
625
730
  const site = buildSite();
626
731
  const base = { ...process.env, ...dotenv(join(ROOT, ".env")), NODE_ENV: "production", HOSTNAME: "127.0.0.1", CLAW_DIST_DIR: site.dist };
627
- delete base.CLAW_PRODUCT_TOKEN; // the check is not the proxy; the middleware would 401 it
732
+ delete base.CLAW_TURBOPACK_ROOT;
733
+ if (site.copied) {
734
+ mkdirSync(site.dir, { recursive: true });
735
+ base.CLAW_TURBOPACK_ROOT = commonRoot(site.dir, ROOT);
736
+ }
737
+ delete base.CLAW_PRODUCT_TOKEN; // the check is not the product proxy; proxy.js would 401 it
628
738
  const built = buildForCheck(site, base);
629
739
  if (!built.ok) {
630
740
  fail("smoke", "build", `next build failed:\n${built.tail}`);
@@ -10,7 +10,7 @@ import { ident, logActivity, query } from "./db";
10
10
  // are quoted through ident(), which refuses anything that is not a plain
11
11
  // identifier. Returns { ok } or { ok: false, error }; never throws.
12
12
  //
13
- // `surface` is optional: middleware.js stamps the current surface key on
13
+ // `surface` is optional: proxy.js stamps the current surface key on
14
14
  // the request, so a surface rarely needs to pass it.
15
15
  //
16
16
  // `detail` is the founder's sentence for the row. The OS Activity tab
@@ -160,7 +160,7 @@ export async function loadActivity({ limit = 100, surface, since } = {}) {
160
160
  * code keeps one import. */
161
161
  export { markSample };
162
162
 
163
- /** The surface key of the current request (middleware.js sets it from
163
+ /** The surface key of the current request (proxy.js sets it from
164
164
  * /s/<key>), unless the caller names one — a string, or the page's
165
165
  * `surface` object. */
166
166
  export async function currentSurface(explicit) {
@@ -7,8 +7,8 @@
7
7
  // attribute app/layout.jsx renders is the runtime copy and is read first, so
8
8
  // a start under a different prefix than the build still resolves.
9
9
  //
10
- // allowedOrigins is NOT optional here, and it is not yours to edit. Next 15
11
- // refuses a Server Action whose `Origin` header disagrees with
10
+ // allowedOrigins is NOT optional here, and it is not yours to edit. Next
11
+ // (15 and 16) refuses a Server Action whose `Origin` header disagrees with
12
12
  // `x-forwarded-host` — and in this stack they always disagree, because the
13
13
  // sandbox ingress rewrites `x-forwarded-host` to its own vercel.run name
14
14
  // while the browser is on the product host. Without this the app loads
@@ -23,19 +23,61 @@ const allowedOrigins = (process.env.CLAW_ALLOWED_ORIGINS || "")
23
23
  .map((o) => o.trim())
24
24
  .filter(Boolean);
25
25
 
26
+ // The same hosts, for `next dev` alone. Next 16 refuses a request for a
27
+ // dev resource (/_next/*, /__nextjs*: the HMR socket, the error overlay's
28
+ // lookups) whose Origin host is not localhost or listed here; Next 15 only
29
+ // warned. The preview's browser is on the product host, so without this
30
+ // every such request answers 403. Hostnames only: Next compares no port.
31
+ const allowedDevOrigins = [
32
+ ...new Set(
33
+ allowedOrigins
34
+ .map((o) => o.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/\/.*$/, "").replace(/:\d+$/, "").toLowerCase())
35
+ .filter(Boolean),
36
+ ),
37
+ ];
38
+
26
39
  // `npm run check` builds into its own folder (CLAW_DIST_DIR=.next-check,
27
- // check/run.mjs) so it never touches `.next`: in the sandbox the running
28
- // dev server owns that folder as another user, and a second `next build`
29
- // there dies with EACCES on .next/trace before it starts.
40
+ // check/run.mjs) so it never touches `.next`, which is not the check's:
41
+ // in a working copy it is the dev server's, and in a live app it holds
42
+ // the production build the platform made, serves and reads as its proof
43
+ // (.next/BUILD_ID). check/run.mjs says more.
44
+ //
45
+ // CLAW_TURBOPACK_ROOT is set by `npm run check` alone, when it builds a
46
+ // copy of an app folder it cannot write (check/run.mjs buildSite). The
47
+ // copy's node_modules is a symlink back to the app's, and Turbopack (Next
48
+ // 16's bundler for build as well as dev) refuses a symlink that leaves its
49
+ // root: "Symlink [project]/node_modules is invalid, it points out of the
50
+ // filesystem root". The check names the nearest folder holding both.
51
+ // Unset, Turbopack finds the root itself.
52
+ const turbopackRoot = process.env.CLAW_TURBOPACK_ROOT || "";
53
+
54
+ // agentRules: false. Next 16's `next dev` writes AGENTS.md and CLAUDE.md
55
+ // into the app folder when it sees an AI agent in its environment
56
+ // (AI_AGENT, CLAUDECODE…), telling the agent to read Next's own docs.
57
+ // This app's agent brief is the platform's, beside the app, and the app
58
+ // folder holds only what the template and the app's own screens put there.
59
+ //
60
+ // mcpServer: false. Next 16's `next dev` also serves an MCP endpoint at
61
+ // /_next/mcp (the app's routes, logs and errors, the errors of every
62
+ // browser on the page, a route compile) and answers it before proxy.js
63
+ // runs, so no product token guards it. Off: the token check is meant to
64
+ // be the preview's only door. One more answers there that no setting
65
+ // turns off, the dev overlay's /__nextjs_attach-nodejs-inspector (it
66
+ // opens a Node debugger on loopback); only the platform can wall that
67
+ // (CHANGELOG, 0.6.0).
30
68
  const nextConfig = {
31
69
  basePath: process.env.CLAW_BASE_PATH || "",
32
70
  distDir: process.env.CLAW_DIST_DIR || ".next",
33
71
  env: {
34
72
  CLAW_BASE_PATH: process.env.CLAW_BASE_PATH || "",
35
73
  },
36
- ...(allowedOrigins.length > 0
37
- ? { experimental: { serverActions: { allowedOrigins } } }
38
- : {}),
74
+ agentRules: false,
75
+ ...(allowedDevOrigins.length > 0 ? { allowedDevOrigins } : {}),
76
+ ...(turbopackRoot ? { turbopack: { root: turbopackRoot }, outputFileTracingRoot: turbopackRoot } : {}),
77
+ experimental: {
78
+ mcpServer: false,
79
+ ...(allowedOrigins.length > 0 ? { serverActions: { allowedOrigins } } : {}),
80
+ },
39
81
  };
40
82
 
41
83
  export default nextConfig;
@@ -10,10 +10,10 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@tailwindcss/postcss": "^4",
13
- "next": "^15.4.0",
13
+ "next": "^16.3.7",
14
14
  "pg": "^8.16.0",
15
- "react": "^19.1.0",
16
- "react-dom": "^19.1.0",
15
+ "react": "^19.3.0",
16
+ "react-dom": "^19.3.0",
17
17
  "tailwindcss": "^4"
18
18
  }
19
19
  }
@@ -1,8 +1,15 @@
1
1
  import { NextResponse } from "next/server";
2
2
 
3
- // The only door is the Makersclaw dashboard proxy, which stamps this token
4
- // on every request. The sandbox port itself is public, so without this
5
- // check anyone with the URL could read the founder's data.
3
+ // Next runs this before every request. Until 0.6.0 it was middleware.js
4
+ // (Next 15's name for it); Next 16 renamed the file and its export to
5
+ // `proxy`, and runs it on Node, never the edge runtime. A middleware.js
6
+ // beside this file fails the build, so `makersclaw update` removes the old
7
+ // one. Not to be confused with the product proxy, the Makersclaw dashboard
8
+ // door that sits in front of the whole app.
9
+ //
10
+ // The only door is that product proxy, which stamps this token on every
11
+ // request. The sandbox port itself is public, so without this check
12
+ // anyone with the URL could read the founder's data.
6
13
  //
7
14
  // It also tells the server which surface a request is for: /s/<key> becomes
8
15
  // the x-claw-surface request header, so lib/data.js and lib/actions.js can
@@ -13,12 +20,12 @@ import { NextResponse } from "next/server";
13
20
  // the desk, and the product proxy carries it as the x-claw-theme header
14
21
  // on every later request (the browser's cookies never reach the app).
15
22
  // ?theme= on any URL wins for that request and sets the claw_theme cookie
16
- // too, so the app keeps its theme when run without the proxy (local
17
- // `next dev`). app/layout.jsx reads header, then cookie, onto
23
+ // too, so the app keeps its theme when run without the product proxy
24
+ // (local `next dev`). app/layout.jsx reads header, then cookie, onto
18
25
  // <html data-theme>.
19
26
  const THEMES = new Set(["light", "dark"]);
20
27
 
21
- export function middleware(request) {
28
+ export function proxy(request) {
22
29
  const expected = process.env.CLAW_PRODUCT_TOKEN;
23
30
  if (expected && request.headers.get("x-claw-product-token") !== expected) {
24
31
  return new NextResponse("Open this app from your Makersclaw dashboard.", {