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.
- package/README.md +29 -3
- package/package.json +3 -2
- package/r/annotate.json +3 -3
- package/r/approval-request.json +2 -2
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +2 -2
- package/r/chat.json +2 -2
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/contact-section.json +2 -2
- package/r/csv-import.json +3 -3
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/flow-canvas.json +2 -2
- package/r/footer.json +2 -2
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/map.json +2 -2
- package/r/menubar.json +2 -2
- package/r/mind-map.json +3 -3
- package/r/navigation-menu.json +2 -2
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +3 -3
- package/r/page-builder.json +2 -2
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +2 -2
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-notes.json +4 -4
- package/r/registry.json +121 -121
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +2 -2
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +2 -2
- package/r/spreadsheet.json +2 -2
- package/r/stat-cards.json +3 -3
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/transaction-list.json +2 -2
- package/r/whiteboard.json +3 -3
- package/src/cli.mjs +35 -6
- package/src/install.mjs +101 -4
- package/src/lockfile.mjs +468 -0
- package/src/pm.mjs +473 -0
- package/src/registry.mjs +48 -1
- package/templates/next/files/README.md +20 -15
- package/templates/next/files/app/claw-alive/route.js +2 -2
- package/templates/next/files/app/frame.js +6 -5
- package/templates/next/files/app/layout.jsx +2 -2
- package/templates/next/files/check/run.mjs +125 -15
- package/templates/next/files/lib/actions.js +1 -1
- package/templates/next/files/lib/data.js +1 -1
- package/templates/next/files/next.config.mjs +50 -8
- package/templates/next/files/package.json +3 -3
- package/templates/next/files/{middleware.js → proxy.js} +13 -6
- package/templates/next/pnpm-lock.yaml +5870 -0
- 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
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// (
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// preview
|
|
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:
|
|
50
|
-
//
|
|
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
|
-
/^
|
|
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.
|
|
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:
|
|
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 (
|
|
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
|
|
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
|
|
28
|
-
//
|
|
29
|
-
//
|
|
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
|
-
|
|
37
|
-
|
|
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": "^
|
|
13
|
+
"next": "^16.3.7",
|
|
14
14
|
"pg": "^8.16.0",
|
|
15
|
-
"react": "^19.
|
|
16
|
-
"react-dom": "^19.
|
|
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
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
|
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
|
|
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.", {
|